# Using suiren 1 suiren 1 forecasts each country's total hourly electricity demand for the next day or so, with a 5% to 95% band. It covers 33 European countries and runs on an ordinary computer. This guide shows how to run it, what it needs, how accurate it was, and how an energy supplier or a trader would use it. The model card (`README.md`) has the full results and the method. Every number here is from a backtest. The weights and code are for non-commercial use (CC BY-NC 4.0); see [Licence](#licence). ## Quick start ```bash hf download kitfunso/suiren-1 --local-dir suiren-1 cd suiren-1 pip install -r requirements.txt python -m eval.forecast --zone de > de.csv ``` `de.csv` holds one row per hour, in UTC, from the first hour with no published demand yet through the end of tomorrow: ``` hour_utc,p05_mw,p50_mw,p95_mw ``` `p50_mw` is the median forecast in MW. `p05_mw` and `p95_mw` are the 5% to 95% band. Options: - `--seeds 0,1,2` averages the three weight files. The card scored each file on its own, so averaging is cheap and usually helps, but it was not scored. - `--hours 38` forecasts a set number of hours instead of running to the end of tomorrow. - `--device cpu` stays off the GPU. One 24-hour forecast took about two minutes on a desktop CPU; a GPU is faster. The script needs internet access. Progress and the count of missing history hours go to stderr. ## Use it from an agent A daily job publishes a forecast for 32 countries at 09:45 London time. An agent can read it three ways. - **Raw JSON.** `https://huggingface.co/datasets/kitfunso/suiren-1-forecasts/resolve/main/latest.json` needs no sign-in and never sleeps. Start here. - **MCP server.** Run `claude mcp add --transport http suiren https://suiren-anatomy.pages.dev/mcp`. The agent gets two tools, `get_forecast(zone)` and `list_zones()`. A zone with no forecast returns an error with the reason. Claude Code's `.mcp.json` takes this entry; other clients use their own key for the same URL. ```json {"mcpServers": {"suiren": {"type": "http", "url": "https://suiren-anatomy.pages.dev/mcp"}}} ``` - **Command line.** `python -m eval.forecast --zone de --json` prints one forecast record instead of a CSV. This one is shortened to two rows, and its numbers are made up to show the shape: ```json {"model": "suiren-1", "zone": "de", "issued_at": "2026-10-26T09:45:12Z", "first_hour_utc": "2026-10-26T08:00Z", "unit": "MW", "seeds": [0], "missing_history_hours": 0, "rows": [{"hour_utc": "2026-10-26T08:00Z", "p05_mw": 58210, "p50_mw": 60140, "p95_mw": 62390}, {"hour_utc": "2026-10-26T09:00Z", "p05_mw": 59480, "p50_mw": 61530, "p95_mw": 63810}]} ``` The record holds one row per hour, as in the CSV. `missing_history_hours` counts the hours of demand the script could not fetch. The raw JSON file keeps the last good record for each country under `zones`. The MCP tool adds `age_hours` and `stale` (true after 26 hours) to the record it returns, and `failed_this_run` (the error and its time) when the last run failed for that country and the record is an older one. ## What it forecasts - **Whole countries, hourly.** The ids are `at ba be bg ch cz de dk ee es fi fr gb ge gr hr hu ie it lt lu lv md nl no pl pt ro rs se si sk xk`. The script serves all of them except `gb` (see below). - `de` is Germany alone. The German price zone, DE-LU, also includes Luxembourg (`lu`), so add the two. - `dk`, `no`, `se` and `it` are whole-country totals. Price areas inside them (DK1, NO2, SE3, Italy's zones) are not forecast one by one. - The model also knows 25 upper-case ids such as `DE-LU` and `DK1`. Those were price series, and suiren 1 never trained on price, so do not use them. - Demand only. No price, wind or solar forecast. - Hourly only. The European day-ahead auction has traded 15-minute products since 1 October 2025, so a desk needs its own way to split each hour into quarters. ## What the script fetches `eval/forecast.py` builds the same inputs the model trained on. 1. **Demand.** The last 2,048 hours (about 12 weeks) of the country's demand from the [Energy-Charts](https://api.energy-charts.info/) `public_power` endpoint, series `Load`. Quarter-hours are averaged to hours, and an hour missing any quarter is left as a gap, as in training. This series matches ENTSO-E's published actual total load (article 6.1.A). 2. **Weather.** Temperature at 2 m, wind speed at 10 m, shortwave radiation and cloud cover from the [Open-Meteo](https://open-meteo.com/) historical forecast API, at the point for that country in `weather_points.json`. The script reads the `_previous_day1` columns: the forecast issued the day before each hour. For tomorrow's hours that is today's forecast. 3. **Holidays.** National public holidays from [Nager.Date](https://date.nager.at/), only days typed `Public` and flagged `global`. Nager.Date does not serve Kosovo, so `xk` has no holidays, as in training. 4. **Calendar.** Hour, weekday and month, all in UTC. `gb` is not in the script. Its training target was Elexon's demand outturn, which Energy-Charts does not serve. To forecast `gb`, fetch Elexon's demand yourself, average it to hours, and call the model directly as below. ## Calling the model directly Use this when you have your own data feed. The model card's "Making a forecast" section has a full example. ```python from eval.suiren import Suiren model = Suiren.from_checkpoint("weights/v31_final_stepC_seed0.pt") bands = model.predict(history, horizon=24, levels=(0.05, 0.5, 0.95), covariates=covariates, zone="de") ``` - `history`: 2,048 hourly loads in MW, oldest first, NaN for a missing hour. - `covariates`: one row per hour for the history and the hours ahead (2,048 + horizon rows), eight columns: hour, weekday (0 is Monday), month, holiday flag, then the four weather columns. NaN for anything you do not have. - Each covariate column is standardised over the window, so units do not matter. Keep one source and one unit per column across the whole window. - Use the `v31_final_*` files for new forecasts. They trained on every hour before 2026-09-01. The `v31_backtest_*` files trained on hours before 2025-09-01 and are the ones the card scores. ## Timing for a day-ahead bid The European day-ahead auction closes at 12:00 CET on the day before delivery and covers 00:00 to 24:00 of the delivery day. A desk bidding into it runs its forecast on the morning before, about 38 hours before the last hour it bids for. - The card's headline numbers start at midnight UTC on the delivery day and run 24 hours. That is after the auction has closed, so they describe an intraday forecast. - The comparison with the grid operators started each forecast when the operator's own forecast appeared, a median of 12 hours before the delivery day (Spain 2 hours, Italy 19), and ran to the end of that day. That is about 36 hours ahead, close to what a bidder needs. - Nobody has scored suiren at exactly the bidder's timing. Example: on Wednesday at 10:00 in Berlin, a run for Thursday's auction starts at 08:00 UTC and ends at 21:00 UTC on Thursday, the last hour of Thursday in Berlin. That is 38 hours: `python -m eval.forecast --zone de --hours 38`, then keep the last 24 rows. ## How accurate it was MAPE is the average error as a percent of actual demand. An error of 3% on a 50 GW hour is 1.5 GW. | Test | suiren 1 | Compared with | |---|---|---| | 33 countries, midnight UTC start, 24 hours, Sep 2025 to Jun 2026 | 2.98% | Chronos-2 given the same weather and calendar 3.11%; Chronos-2 on demand history alone 3.62%; repeating last week 7.21% | | 26 countries, operator timing, mid-Nov 2025 to Jun 2026 | 3.02% | The grid operators' own day-ahead forecasts 3.76%; more accurate in 15 of 26 countries | | 23 countries, operator forecast corrected for its own repeat errors, with suiren added | 5.7% lower error (median) | The corrected operator forecast alone; lower in 20 of 23 countries | The 5% to 95% band held 88% of actual hours where 90% was the aim, so it runs a little narrow. ### Country by country The two operator columns cover different days and a longer horizon than the first two, so compare down a column. Blank cells are countries left out of the operator tests. `gb` has no operator forecast in the ENTSO-E data used. In `ba`, `nl`, `md`, `lt`, `ee` and `ie`, the operator's forecast and the published actual measure slightly different things at midday. `be`, `dk` and `gr` had under 30 usable days for the correction test. | Country | suiren, midnight start (MAPE %) | Chronos-2 with weather (MAPE %) | suiren at operator timing (MAPE %) | Operator (MAPE %) | Error cut from adding suiren to the corrected operator forecast | Median hours before the day | |---|---:|---:|---:|---:|---:|---:| | `at` | 2.36 | 2.72 | 2.78 | 5.25 | +39.3% | 13 | | `ba` | 9.08 | 9.49 | | | | | | `be` | 2.14 | 2.45 | 1.77 | 4.63 | | 18 | | `bg` | 2.91 | 2.78 | 3.78 | 4.61 | +11.3% | 16 | | `ch` | 4.07 | 4.21 | 4.80 | 6.55 | +1.8% | 16 | | `cz` | 1.43 | 1.65 | 2.27 | 1.28 | +0.6% | 15 | | `de` | 2.12 | 2.36 | 2.78 | 4.23 | +20.4% | 15 | | `dk` | 2.99 | 2.89 | 3.56 | 10.69 | | 5 | | `ee` | 5.66 | 5.67 | | | | | | `es` | 1.89 | 2.26 | 2.22 | 1.61 | +1.0% | 2 | | `fi` | 2.80 | 2.61 | 3.20 | 3.04 | -0.4% | 3 | | `fr` | 1.73 | 1.96 | 2.69 | 1.85 | +3.4% | 10 | | `gb` | 3.98 | 3.93 | | | | | | `ge` | 2.51 | 2.40 | 3.41 | 3.31 | +5.6% | 13 | | `gr` | 2.35 | 2.41 | 2.14 | 5.02 | | 9 | | `hr` | 2.69 | 2.88 | 3.07 | 2.74 | +14.2% | 9 | | `hu` | 2.91 | 2.99 | 3.46 | 4.45 | +5.7% | 12 | | `ie` | 2.01 | 2.08 | | | | | | `it` | 1.96 | 2.59 | 2.90 | 2.34 | +7.0% | 19 | | `lt` | 5.29 | 5.33 | | | | | | `lu` | 2.24 | 2.69 | 2.99 | 3.26 | +7.0% | 10 | | `lv` | 1.94 | 2.13 | 2.59 | 3.79 | +24.1% | 11 | | `md` | 7.17 | 6.68 | | | | | | `nl` | 1.92 | 2.03 | | | | | | `no` | 1.50 | 1.36 | 2.25 | 2.29 | +4.5% | 16 | | `pl` | 1.93 | 2.18 | 2.56 | 2.28 | +9.4% | 16 | | `pt` | 2.30 | 2.63 | 2.89 | 1.95 | +0.2% | 9 | | `ro` | 2.88 | 3.21 | 3.43 | 2.27 | -1.2% | 12 | | `rs` | 2.05 | 2.34 | 2.57 | 3.16 | +19.9% | 17 | | `se` | 2.91 | 3.04 | 4.15 | 3.36 | -1.6% | 16 | | `si` | 3.30 | 3.23 | 3.69 | 4.29 | +17.4% | 11.5 | | `sk` | 1.84 | 2.18 | 2.17 | 2.29 | +19.2% | 11 | | `xk` | 3.38 | 3.34 | 4.39 | 7.24 | +4.8% | 12 | Sources: `eval/results/backtest_2025_v31.json`, `backtest_2025_chronos2x.json`, `backtest_2025_tso_v31_backtest_stepC.json` and `lane_o_v31.json`. ## For an energy supplier A retail supplier estimates its customers' demand for tomorrow and buys it in the day-ahead auction. It trades the difference in the intraday market and pays the imbalance price on whatever is left. suiren forecasts the whole country, not one supplier's customers, so it goes in as an input. 1. **National demand as a driver.** Run suiren over the past year, one forecast per day at the time you would have run it. At about two minutes a forecast, that is at least 12 hours of CPU per country. Fit your customers' demand on suiren's forecast plus your own terms (customer count, tariff mix, large sites). Each morning, put the new forecast into the fit. Fit on suiren's past forecasts rather than on actual national demand, so the fit learns how suiren's errors show up in your book. 2. **Sizing what to leave for intraday.** The band shows how uncertain each hour is. If your customers use about 2% of national demand and suiren's band for 18:00 runs from 48 to 52 GW, your own band is roughly 0.96 to 1.04 GW. Keep the wide hours flexible. Widen the band a touch, since it runs narrow. 3. **A second opinion before the gate closes.** Put suiren next to the operator's forecast and your current one. When they disagree by more than usual, look for the reason before noon: a holiday, a change in the weather run, a data gap. 4. **An experiment.** Feed your customers' own demand series to `model.predict` with the country's id. The model scales each window by its own average, so it sees the shape of a series and not its size, and it trained on generation series as well as demand. Nobody has scored this, so backtest it against your current forecast first. It does not do hedging months or years ahead, customer-level forecasts or settlement periods shorter than an hour. ## For an energy trader Price follows residual load: demand minus wind and solar. suiren gives the demand half. 1. **A price view.** Subtract your own wind and solar forecasts from suiren's demand, and feed the residual load into your price model. suiren 1 trained on generation series too, but only demand was scored. 2. **Trading the disagreement.** Each operator's day-ahead demand forecast is public on ENTSO-E (article 6.1.B), so the market sees it. The backtest built a three-step forecast a desk can rebuild: store each operator forecast with its publish time; correct the operator's own repeat errors with a model fitted only on past days; add a fitted weight times the gap between suiren and the operator. The last step cut the error of the first two by a median 5.7% across 23 countries, by 39% in Austria and about 20% in Germany. A large gap in a country where suiren adds most is where your demand view differs from the public one. 3. **When to lean which way.** In Denmark, Belgium, Greece, Austria, Kosovo and Germany, suiren on its own was far more accurate than the operator. In Czechia, Spain, France, Portugal and Romania the operator was better, and adding suiren helped little. 4. **Intraday.** Re-run as fresh demand comes in and look at the hours where the median moves. How much a re-run gains was not measured. 5. **Scenarios.** The 5th and 95th percentiles give a low and a high case per hour. They are worked out hour by hour, so adding up each hour's 95th percentile does not give the 95th percentile of the day's total. `predict` draws 100 sampled futures inside; returning them is a small code change. Nothing here measured profit. A lower demand error turns into money only if the market price has not already taken in the same information. The comparison covered the operators' public forecasts only, not commercial forecast vendors. Keep a paper ledger for a few months before trading on it: log each day's view and the trade it implies before the gate closes, then score the error and the paper profit and loss. ## Limits 1. **Backtest only.** The 2025-26 year had been read before the recipe was chosen. A clean forward test runs from 15 September to 1 December 2026. 2. **Holidays are the weak spot.** On public holidays the earlier model's error was 1.7 times its normal-day error, and the operator was better in most countries. suiren 1 trained with a fixed holiday table, but its holiday error was not measured on its own. On a holiday, trust the operator. 3. **No autumn in the operator tests.** They cover mid-November to June. 4. **One weather point per country.** Operators use weather across their whole area. 5. **Late data.** The backtests read demand as stored later, so every hour before the start was present. Live, the last hours may be missing; the script starts at the first unpublished hour. It stops with a message when a country's load is more than 48 hours late. 6. In `ba`, `md` and `nl` the published actual is net of something at midday from some date. suiren forecasts the published series as it is. ## Licence - **Weights and code:** CC BY-NC 4.0 (`LICENSE`). Research and personal use are fine. Commercial use needs a separate licence from the author; ask on this repository's Community tab. - **Weather:** the script calls Open-Meteo's free API, which is for non-commercial use. A commercial user needs a paid Open-Meteo plan or another feed with the same four variables at the same points. - **Demand data:** Energy-Charts data is CC BY 4.0, with attribution to Fraunhofer ISE. Elexon's BMRS data comes under its own licence, also with attribution. ## More - `anatomy.html` is an interactive 3D tour of the model's parts, live at [suiren-anatomy.pages.dev](https://suiren-anatomy.pages.dev). - `README.md` is the model card: method, every result and every limit.