> ## Documentation Index
> Fetch the complete documentation index at: https://docs.energy.nlead.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Forecast Generation

> When the supplier has only master data and metered data, the bridge builds the consumption forecast itself

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

```mermaid theme={null}
flowchart TD
    M[Market location<br/>in delivery on day D] --> Q{metering type?}
    Q -->|profiled| P[Rolled-out profile of the MGA<br/>× yearly consumption forecast]
    Q -->|quarter-hourly| H{metered history<br/>available?}
    H -->|yes| V[Vergleichstagsprognose:<br/>mean of recent comparable days]
    H -->|no| P
    P --> A[Aggregate per control area]
    V --> A
    A --> F[Forecast input for the pipeline]
```

**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:

| Endpoint                | Mirrors                                                                                | Source process                              |
| ----------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------- |
| `PUT /v1/master-data`   | MaLo master data: control area, MGA, metering type, delivery period, profile code, JVP | GPKE/UTILMD (Anmeldung, Stammdatenänderung) |
| `POST /v1/metered-data` | Quarter-hourly meter readings per MaLo-day (kWh/MWh/MW)                                | MSCONS (MaBiS)                              |
| `POST /v1/profiles`     | Rolled-out profile days per MGA and profile code                                       | NB→LF profile communication                 |

<Note>
  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.
</Note>

## 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`):

| Incoming                  | Effect on an open-ended stored slice                    |
| ------------------------- | ------------------------------------------------------- |
| `valid_from` only         | Stored slice truncated at that date, new slice appended |
| `valid_from` + `valid_to` | Stored slice split; the middle window replaced          |
| `active: false`           | Same, but the window becomes a supply gap               |

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.

<Note>
  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?"
</Note>

## 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.
