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 carriesvalid_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.
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)indomain/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.