diff --git a/dao/DOCS.md b/dao/DOCS.md index cb1be64..b3c3fd6 100644 --- a/dao/DOCS.md +++ b/dao/DOCS.md @@ -234,7 +234,9 @@ Ook het ophalen van dynamische uurprijzen (day ahead prices) stel je in via Daar is alles al ingevuld, maar je kunt het aanpassen aan jouw situatie:
De belangrijkste onderdelen zijn:
* source day ahead: waar haal je de data vandaan: nordpool is een goede - eerste keuze
+ eerste keuze. Je kunt ook `energypriceforecast` gebruiken als forecast-fallback voor markten waar je voor publicatie van de officiele day-ahead prijs al een prijsreeks wilt gebruiken.
+ * energypriceforecast-api-url: optionele override voor de DAO-price feed van Energy Price Forecast EU
+ * energypriceforecast-country: optionele marktcode zoals `nl`, `de`, `dk1` of `no3`
* energy taxes consumption: energiebelasting (euro/kWh, ex BTW) bij afname
* energy taxes production: energiebelasting bij teruglevering (euro/kWh, ex BTW)
* cost supplier consumption: kosten leverancier voor levering (euro/kWh, ex BTW)
@@ -652,8 +654,10 @@ Het is allemaal optioneel. | **meteoserver-key** | | string | | | | **meteoserver-model** | | string | harmonie | keuze uit harmonie of gfs | | **meteoserver-attempts** | | getal | 2 | aantal ophaal pogingen | -| **prices** | source day ahead | string | nordpool | keuze uit: nordpool / entsoe / easyenergy / tibber | +| **prices** | source day ahead | string | nordpool | keuze uit: nordpool / entsoe / easyenergy / tibber / energypriceforecast | | | entsoe-api-key | string | | alleen bij entsoe als source | +| | energypriceforecast-api-url | string, url | https://api.energypriceforecast.eu/api/v1/dao/prices | optioneel, alleen bij energypriceforecast | +| | energypriceforecast-country | string | | optioneel, alleen bij energypriceforecast | | | regular high | getal | | | | | regular low | getal | | | | | switch to low | integer | 23 | | @@ -923,11 +927,19 @@ De meteodata worden opgehaald bij meteoserver. Ook hiervoor heb je een key nodig ### **prices**
* source day ahead, default "nordpool" - Hier bepaal je waar je je day ahead prijzen vandaan wilt halen. Je hebt de keuze uit drie bronnen: + Hier bepaal je waar je je day ahead prijzen vandaan wilt halen. Je hebt de keuze uit vijf bronnen: * nordpool * entsoe * easyenergy - * tibber
+ * tibber + * energypriceforecast
+ + Kies je voor **energypriceforecast**, dan gebruikt DAO de aparte DAO-feed van Energy Price Forecast EU. Die feed combineert officiele day-ahead prijzen met forecast-slots voor uren die nog niet officieel gepubliceerd zijn. + * energypriceforecast-api-url: + Optionele override voor de DAO-price feed. Standaard: + `https://api.energypriceforecast.eu/api/v1/dao/prices` + * energypriceforecast-country: + Optionele expliciete marktcode voor de feed, bijvoorbeeld `nl`, `de`, `dk1` of `no3`. Laat je dit leeg, dan probeert DAO te mappen vanuit de ingestelde landcode. Als je kiest voor **entsoe** dan moet je hieronder een api key invullen. * entsoe-api-key: diff --git a/dao/data/options_example.json b/dao/data/options_example.json index 8043d5f..120e073 100644 --- a/dao/data/options_example.json +++ b/dao/data/options_example.json @@ -13,6 +13,8 @@ "prices": { "source day ahead": "nordpool", "entsoe-api-key": "!secret entsoe-api-key", + "energypriceforecast-api-url": "https://api.energypriceforecast.eu/api/v1/dao/prices", + "energypriceforecast-country": "nl", "regular high": 0.5, "regular low": 0.4, "switch to low": 23, @@ -590,4 +592,4 @@ } ] } -} \ No newline at end of file +} diff --git a/dao/data/options_start.json b/dao/data/options_start.json index 359b22b..e683dc2 100644 --- a/dao/data/options_start.json +++ b/dao/data/options_start.json @@ -12,6 +12,8 @@ "meteoserver-key": "!secret meteoserver-key", "prices": { "source day ahead": "nordpool", + "energypriceforecast-api-url": "https://api.energypriceforecast.eu/api/v1/dao/prices", + "energypriceforecast-country": "nl", "regular high": 0.50, "regular low": 0.40, "switch to low": 23, diff --git a/dao/lib/da_prices.py b/dao/lib/da_prices.py index 55cedce..76b9e5b 100644 --- a/dao/lib/da_prices.py +++ b/dao/lib/da_prices.py @@ -20,6 +20,27 @@ class DaPrices: self.interval = str(config.interval or "1hour").lower() self.country = country if country is not None else "NL" + def _resolve_energypriceforecast_country(self): + configured = getattr(self.config.prices, "energypriceforecast_country", None) + if configured: + return str(configured).strip().lower() + mapping = { + "NL": "nl", + "BE": "be", + "DE": "de", + "FR": "fr", + "AT": "at", + "CZ": "cz", + "DK1": "dk1", + "DK2": "dk2", + "NO1": "no1", + "NO2": "no2", + "NO3": "no3", + "NO4": "no4", + "NO5": "no5", + } + return mapping.get(str(self.country or "").upper(), "nl") + def get_prices( self, source, _start: datetime.datetime = None, _end: datetime.datetime = None ): @@ -192,6 +213,44 @@ class DaPrices: ) self.db_da.savedata(df_db) + if source.lower() == "energypriceforecast": + now_ts = datetime.datetime.now(datetime.timezone.utc).timestamp() + end_ts = end.timestamp() if hasattr(end, "timestamp") else now_ts + 48 * 3600 + hours = max(1, min(168, math.ceil((end_ts - now_ts) / 3600))) + api_url = ( + getattr(self.config.prices, "energypriceforecast_api_url", None) + or "https://api.energypriceforecast.eu/api/v1/dao/prices" + ) + country = self._resolve_energypriceforecast_country() + url = f"{api_url}?country={country}&hours={hours}" + resp = get(url, timeout=15) + resp.raise_for_status() + payload = json.loads(resp.text) + entries = payload.get("entries") or [] + logging.info( + f"Day ahead prijzen van Energy Price Forecast EU ({country}): \n" + f"{pp.pformat(entries[:24], indent=2)}" + ) + df_db = pd.DataFrame(columns=["time", "code", "value"]) + for entry in entries: + start_raw = str(entry.get("start") or "") + value = entry.get("value") + if not start_raw: + continue + try: + dt = datetime.datetime.fromisoformat(start_raw.replace("Z", "+00:00")) + time_stamp = int(dt.timestamp()) + value = float(value) + except (TypeError, ValueError): + continue + df_db.loc[df_db.shape[0]] = [str(time_stamp), "da", value] + logging.debug( + f"Day ahead prijzen (source: energypriceforecast, db-records): \n " + f"{df_db.to_string(index=False)}" + ) + self.db_da.savedata(df_db) + return + if source.lower() == "tibber": now_ts = datetime.datetime.now().timestamp() get_ts = start.timestamp() diff --git a/dao/prog/config/models/pricing.py b/dao/prog/config/models/pricing.py index e63d9ba..06c3060 100644 --- a/dao/prog/config/models/pricing.py +++ b/dao/prog/config/models/pricing.py @@ -11,12 +11,12 @@ from datetime import date class PricingConfig(BaseModel): """Day-ahead pricing and tariff configuration.""" - source_day_ahead: Literal['nordpool', 'entsoe', 'tibber'] = Field( + source_day_ahead: Literal['nordpool', 'entsoe', 'tibber', 'energypriceforecast'] = Field( default='nordpool', alias="source day ahead", description="Source for day-ahead prices", json_schema_extra={ - "x-help": "Data source for day-ahead electricity market prices. 'nordpool' for Nordic/Baltic, 'entsoe' for European markets, 'tibber' if using Tibber integration.", + "x-help": "Data source for day-ahead electricity market prices. 'nordpool' for Nordic/Baltic, 'entsoe' for European markets, 'tibber' if using Tibber integration, 'energypriceforecast' for forecast fallback from Energy Price Forecast EU.", "x-ui-section": "Prices" } ) @@ -40,6 +40,42 @@ class PricingConfig(BaseModel): } } ) + energypriceforecast_api_url: Optional[str] = Field( + default="https://api.energypriceforecast.eu/api/v1/dao/prices", + alias="energypriceforecast-api-url", + description="Energy Price Forecast EU DAO API URL", + json_schema_extra={ + "x-help": "Custom API endpoint for Energy Price Forecast EU DAO prices. Expected response: format=dao-prices with entries[].", + "x-ui-section": "Prices", + "x-ui-rules": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/source_day_ahead", + "schema": { + "const": "energypriceforecast" + } + } + } + } + ) + energypriceforecast_country: Optional[str] = Field( + default=None, + alias="energypriceforecast-country", + description="Override country code for Energy Price Forecast EU", + json_schema_extra={ + "x-help": "Optional explicit country/market code for Energy Price Forecast EU, for example 'nl', 'de', 'dk1' or 'no3'. Leave empty to map from DAO country automatically.", + "x-ui-section": "Prices", + "x-ui-rules": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/source_day_ahead", + "schema": { + "const": "energypriceforecast" + } + } + } + } + ) # Date-based tariff configurations (date string -> value) energy_taxes_consumption: dict[str, float] = Field( @@ -167,7 +203,7 @@ Configure electricity market prices and tariff components for accurate cost opti ## Price Components Total electricity cost consists of: -1. **Market price**: Day-ahead spot price (nordpool/entsoe/tibber) +1. **Market price**: Day-ahead spot price (nordpool/entsoe/tibber/energypriceforecast) 2. **Energy taxes**: Government energy taxes 3. **Supplier costs**: Your supplier's markup/fees 4. **VAT**: Value-added tax on sum of above @@ -192,6 +228,7 @@ System uses tariff active on optimization date. - **nordpool**: Nord Pool (Nordic/Baltic markets) - **entsoe**: ENTSO-E Transparency Platform (all European markets) - **tibber**: Tibber API (if using Tibber as supplier) +- **energypriceforecast**: Energy Price Forecast EU DAO endpoint (forecast fallback before official publication) ## Tips