Skip to main content
When the supplier cannot deliver a portfolio forecast, the bridge generates one — at least for consumption — from three mirrored data sets: market location master data, quarter-hourly metered history, and the DSO’s/MGA’s rolled-out load profiles.

Forecast method per market location

Profiled — the DSO’s rolled-out day profile (already dynamized per calendar day) is scaled by the MaLo’s yearly consumption forecast (Jahresverbrauchsprognose). The normalization basis is explicit per profile (normalization_kwh_per_year): 1,000 kWh/a for classic SLP, 1,000,000 kWh/a for TLP procedures. Vergleichstagsprognose — for quarter-hourly metered locations with history: the mean of the most recent comparable days (same day class: workday / Saturday / Sunday; VERGLEICHSTAG_COUNT, default 3, up to 120 days back). A metered location without history falls back to the profiled mechanism (requires profile code + JVP in its master data). Locations that cannot be forecast (missing JVP, missing profile day, wrong interval count) are returned in skipped with a reason — never silently treated as zero.

Getting the data: mirroring GPKE/MaBiS

The bridge does not (yet) speak EDIFACT. The v1 channel is JSON mirror endpoints (scope: external) fed by whoever operates the supplier’s market communication — their MaKo service provider, or a forwarding rule:
A native EDIFACT/AS4 adapter (UTILMD/MSCONS parsing, full or partial stream) is a planned communication channel — the mirror endpoints define the internal contract it will map onto, so it can be added behind the same stores without touching the forecasting logic.

Temporal master data

Master data is stored per market location as a sequence of non-overlapping time slices (Zeitscheiben). Each slice carries valid_from (inclusive), valid_to (exclusive, null = open-ended), an active flag and the attributes: control area, MGA, metering type, profile code, annual consumption forecast. Generation resolves the slice valid on the delivery day (MarketLocation.slice_on(day)); no slice or an inactive one means the location is not supplied that day and is silently excluded (not “skipped” — it is simply not part of the portfolio then).

Upsert is a temporal merge

MasterDataStore.upsert does not replace a location’s history. Each incoming slice wins for its own window and overlapping stored slices are truncated or split around it (merge_slices in domain/forecasting.py): Adjacent slices that are identical apart from their boundary are coalesced, so repeated equivalent submissions do not fragment the history. The merge is idempotent, which means both sender styles work: incremental change messages (the normal MaKo case) and full-history resubmissions.
Inspect a location’s stored history with GET /v1/process/market-locations/{malo} — the fastest way to answer “which data was valid on that day, and why was this MaLo not in the forecast?”

Triggering generation

  • Manually: POST /v1/process/delivery-days/{day}/generate-forecast (scope: process) — builds, persists as forecast input, responds with method counts, skipped MaLos and the daily total.
  • Automatically: with AUTO_GENERATE_FORECAST=true (config/console), each orchestrator tick generates tomorrow’s forecast if the customer has not posted one — the pipeline then runs on it as usual.
A generated forecast is ordinary pipeline input with source: generated in the day’s state; if the customer later posts a real forecast, the input hash changes and the orchestrator re-runs everything on the better data.

Simplifications to revisit

  • Holidays are classified by weekday only; German practice treats most holidays like Sundays and needs the MGA’s federal-state holiday calendar. TODO(confirm) in domain/forecasting.py.
  • DST mismatches between a comparable day and the target day are resolved by truncating/extending at the end of the series (affects only the 02:00–03:00 night hour).
  • Production forecasting (PV/wind in the portfolio) is out of scope here — generated forecasts currently cover consumption, as required.