mako-redispatch 0.20.0

Redispatch 2.0 process engine for German grid congestion management (§§ 13, 13a, 14 EnWG)
Documentation
# mako-redispatch

Event-sourced process engine for **Redispatch 2.0** congestion-management
workflows under §§ 13, 13a, 14 EnWG. Part of the `mako` workspace.

## Format boundary

This crate is format-agnostic: workflows and the type router know only the
domain enum `RedispatchDocumentKind` — the same layering as
`mako-gpke`/`mako-wim`/`mako-mabis`, which never depend on `edi-energy`.
The **canonical** `DocumentType → RedispatchDocumentKind` mapping lives at
the transport boundary in `makod` (`redispatch_xml_ingest::document_kind`),
the only crate that depends on both halves. The mapping is exhaustive, so a
tenth XML document type fails compilation instead of silently never
routing; makod's `tests/redispatch_xml_pipeline.rs` proves the full wire
path (raw ActivationDocument XML → namespace-checked parse → kind → router
→ `redispatch-aktivierung`) and asserts a routing decision for all nine
document types (the AcknowledgementDocument is correlation-routed by
design).

## Regulatory scope

Redispatch 2.0 is mandatory for all German grid operators (ÜNB and VNB) and
their connected asset operators (ANB), effective 2021-10-01. Suppliers (LF) and
metering operators (MSB) are out of scope.

**BK6-23-241 (Beschluss 07.05.2026) consolidated it.** Its Anlage „Bilanzieller
Ausgleich von Redispatch-Maßnahmen (BilAReM)" is the basis this crate cites, and
it repeals the earlier decisions:

| Repealed | By | With effect from |
|---|---|---|
| BK6-20-059 Tenorziffer 1 | Tenorziffer 1 | end of 30.06.2026 |
| BK6-20-060 (Netzbetreiberkoordinierung) | Tenorziffer 4 | 07.05.2026 |
| BK6-20-061 (Informationsbereitstellung) | Tenorziffer 3 | 07.05.2026 |
| BK6-20-059 Tenorziffer 2 · Anlage zur BilAReM | Tenorziffer 8 | first day the new EDI@Energy documents apply |
| MaBiS Anlage 1 Kap. 17 | Tenorziffer 5 | end of 30.09.2026 |

**Market roles in scope:** ANB (Anschlussnetzbetreiber), BTR (Betreiber der
technischen Ressource), EIV (Einsatzverantwortlicher), VNB, ÜNB, LF, BKV.

## Three-crate architecture

| Crate | Responsibility |
|---|---|
| `edi-energy` | IFTSTA status messages (EDIFACT, PIDs 21037/21038) |
| `redispatch-xml` | XML/XSD format parsing (ActivationDocument, Stammdaten, …) |
| `mako-redispatch` | Process engine — workflows, routing, deadlines |

## Workflows

| Workflow name | Document type | Direction |
|---|---|---|
| `redispatch-aktivierung` | `ActivationDocument` (ACO/ACR/AAR) | ÜNB → VNB → ANB |
| `redispatch-stammdaten` | `Stammdaten` | ANB → VNB → ÜNB |
| `redispatch-planungsdaten` | `PlannedResourceScheduleDocument` | ÜNB → VNB → ANB |
| `redispatch-verfuegbarkeit` | `UnavailabilityMarketDocument` | ANB → VNB |
| `redispatch-netzengpass` | `NetworkConstraintDocument` | ÜNB ↔ VNB |
| `redispatch-kaskade` | `Kaskade` (§ 13 Abs. 2 EnWG) | ÜNB → VNB → ANB |
| `redispatch-statusanfrage` | `StatusRequest_MarketDocument` | bidirectional |
| `redispatch-kostenblatt` | `Kostenblatt` | VNB → ÜNB |

## Deadlines

Four Redispatch deadlines are widely quoted — a 6-hour acknowledgement, a
24-hour Statusanfrage answer, a 5-minute activation response, and a Kostenblatt
due on the 15th. **Three of them no longer have a published source, and the
fourth was never 6 hours.** `mako_redispatch::fristen` splits them accordingly.

### Sourced

| Obligation | Value | Source |
|---|---|---|
| `AcknowledgementDocument` | **3 minutes**, unverzüglich | AcknowledgementDocument FB 1.0g |
| Vorab-Information, Prognosemodell | 30 minutes before validity | BilAReM Kap. 6.3.1 |
| Ausfallarbeit final or Dissens established | end of the **3rd** following month, no restart after | BilAReM Kap. 6.4.3 |
| Wetterdaten of the Anlagenbetreiber | 4th Werktag of the following month | BilAReM Kap. 3.2.1 |
| Stammdaten `gueltig_ab` | ≥ 5 or ≥ 10 Werktage ahead, ≤ 2 years | Stammdaten AWT 1.4b Fn. 27/31/32/33 |
| Überführung ins Planwertmodell | ≥ 6 months' notice, only on 01.01./04./07./10. | BilAReM Kap. 2.3.2 |
| Zuordnung einer neuen SR | ≥ 5 WT before IBN if informed ≥ 10 WT ahead, else ≤ 5 WT after | BilAReM Kap. 2.3.2 |

> **The acknowledgement is three minutes, not six hours.** The
> AcknowledgementDocument Formatbeschreibung 1.0g states „unverzüglich, jedoch
> spätestens **3 Minuten** nach Erhalt der Übertragungsdatei". Six hours is a
> batch job; three minutes has to be answered by the ingest path.

The ACK carries four protocol rules worth stating separately (same source):
exactly one per Übertragungsdatei, confirming or rejecting the file as a whole;
a **missing** ACK means the message was not processed; an ACK is never itself
acknowledged; and a **late** ACK „darf nicht zu einer Fristverletzung des
eigentlichen Geschäftsvorfalles führen".

### Operator-configured

BK6-23-241 Tenorziffer 7 obliges the ÜNB to develop bundesweit einheitliche
Prozessbeschreibungen with the industry and submit them to the Beschlusskammer,
which then publishes them. Until that happens these windows are the operator's
own — `fristen::Betreiberfristen` holds them, with the historical BK6-20-05x
figure as a documented default:

| Obligation | Historical default | Was |
|---|---|---|
| Activation (ACO) response | 5 minutes | BK6-20-060 §6.3 |
| Kostenblatt submission | 15th of the following month | BK6-20-061 §7 |
| Stammdaten forward (VNB→ÜNB) | 1 Werktag | BK6-20-060 §3.2 |

> **`StatusRequest_MarketDocument` is not a request/response pair.** Its `type`
> codes are `A60` (status request for a position independently from a specific
> process) and `Z15` Erreichbarkeitsinformation, and its `status` carries `A03`
> Deactivated / `A04` Reactivated / `A13` Withdrawn — a communication-
> availability notification about a Marktpartner. There is no answer document
> and no 24-hour window.

> **Real-time scheduling:** whatever the configured ACR/AAR window, it stays a
> real-time constraint, and the 3-minute ACK is stricter still. The `makod`
> Redispatch deadline scheduler must poll well inside it — the standard
> Werktage-based scheduler used for GPKE/WiM is not sufficient.

## EDIFACT PIDs

The complete EDIFACT half of Redispatch 2.0, from the BDEW *Anwendungsübersicht
Prüfidentifikatoren 4.0* (01.04.2026) — every row whose Prozessbeschreibung is
„Kommunikationsprozesse Redispatch":

| PID | Nachricht | Inhalt | Von → An | EBD |
|----:|-----------|--------|----------|-----|
| 13021 | MSCONS | meteorologische Daten (Ex-post) | BTR → ANB · ANB → anfNB ||
| 13022 | MSCONS | Einzelzeitreihe Ausfallarbeit | BTR ↔ NB · anfNB → ANB ||
| 17209 | ORDERS | Anforderung der Ausfallarbeit | anfNB → ANB ||
| 21037 | IFTSTA | Ansicht NB | NB → BTR | `E_0902` |
| 21038 | IFTSTA | Ansicht BTR | BTR → NB ||

The two Redispatch Entscheidungsbäume — `E_0902` (Ausfallarbeit unter
Einbeziehung Fahrplananteil plausibilisieren) and `E_0901` (Gegenvorschlag
prüfen) — both name the **NB** as prüfende Rolle and live in
`mako_pruefung::mabis::ausfallarbeit`. The BTR's own view publishes none.

There is **no ORDRSP in this family**: the ANB answers ORDERS 17209 with MSCONS
13022 (Prozessschritt 2).

### Seven neighbouring PIDs this crate does not own

| PID | Belongs to |
|----:|------------|
| 13020 | `mako-mabis` `mabis-billing` — Ausfallarbeitsüberführungszeitreihe |
| 13023 | `mako-mabis` `mabis-billing` — Lieferantenausfallarbeitssummenzeitreihe |
| 13026 | Geschäftsprozesse für EEG-Überführungszeitreihen |
| 17210 | `mako-mabis` `mabis-anforderung` — Anforderung LF-AACL |
| 17211 | `mako-mabis` `mabis-profile` — Reklamation Profile bzw. Profilscharen (`E_0100`) |
| 19204 | `mako-mabis` `mabis-anforderung` — Ablehnung Ab-/Bestellung der Aggregationsebene |
| 19301 / 19302 | Herkunftsnachweisregister (NB ↔ RB HKN-R), `S_0092` / `S_0093` |

13020 and 13023 are MaBiS Summenzeitreihen with a full Prüfmitteilung/
Datenstatus cycle, so routing them here would leave them no settlement stream to
live in and the obligation they carry nowhere to be recorded. 19301/19302 belong
to a different market exchange entirely.

## Routing

Unlike GPKE/WiM/GeLi Gas (EDIFACT `RFF+Z13` Prüfidentifikatoren), Redispatch
2.0 XML documents are routed by `RedispatchRouter` based on XML document type,
not EDIFACT PID. The `makod` AS4 ingest sniffs XML payloads (first
non-whitespace byte `<`), parses them with
`redispatch_xml::parse_and_validate`, and maps the document type to a
`RedispatchDocumentKind` before routing.

## BilAReM

`mako_redispatch::bilarem` and `mako_redispatch::ausfallarbeit` implement the
Anlage to BK6-23-241:

- **Two Bilanzierungsmodelle.** The NB-side Ausgleich happens „ausschließlich im
  Planwertmodell" (Kap. 2.1) via korrespondierende Fahrpläne against the NB's
  single Redispatch-Bilanzkreis. In the Prognosemodell the NB does not settle at
  all until 31.12.2031 (§14 Abs. 1 S. 3 EnWG); the BKV keeps the imbalance and
  receives Aufwendungsersatz.
- **Migration is one-way**, effective only on 01.01./01.04./01.07./01.10. with
  ≥ 6 months' notice. `Zuordnungsmitteilung::validate` refuses the rest.
- **The Ausfallarbeit window is a hard stop.** Kap. 6.4.3: by the end of the
  third following month the figure stands or the Dissens is formally
  established — „Danach dürfen die Prozesse … **nicht erneut gestartet**
  werden." `bilarem::abstimmung_zulaessig` enforces it.
- **Kap. 3 Ausfallarbeit**`P_lim` per Aufforderungs-/Duldungsfall and
  Referenzprofil, Wind Spitz- and vereinfachte Spitzabrechnung, the Wind-Bin
  method for WEA auf See, Solar Spitz and Pauschal, the nicht-fluktuierende
  variants, the Kap.-3.4 Überbauungs-cap and the §24 Abs. 3 S. 2 EEG 2023
  MaLo→TR split.
- **The Wind-KF Vergleichszeitraum is selected, not assumed.**
  `ausfallarbeit::vergleichszeitraum` picks the four quarter-hours Kap. 3.2.2.1
  admits — contiguous, fully measured, unrestricted, each at least 10 % of the
  Nennleistung, nearest to the Maßnahme with ties to the side before it, and
  never from the Folgemonat. „Nearest" has two anchors, which the text is
  explicit about („vor oder nach der Viertelstunde, in der die Maßnahme beginnt
  **bzw. endet**"): a run before the Maßnahme is measured to its beginning, one
  after it to its end. Each criterion changes the Korrekturfaktor, and
  through it every kWh of Ausfallarbeit the measure is settled at, so the
  selection is part of the engine rather than a caller's convention.
- **Solar has its own Vergleichszeitraum, and it is a calendar day.**
  `ausfallarbeit::solar_vergleichstag` implements Kap. 3.2.4.1: the nearest day
  before or after the Maßnahme on which no Maßnahme was directed at the SR, ties
  to the day before, never from another month, and within it only the
  quarter-hours reaching 10 % of the Nennleistung with no Nichtbeanspruchbarkeit
  or marktbedingte Anpassung. A day too dark to qualify is stepped over rather
  than ending the search. Applying the wind rule to a Solaranlage would change
  `P_VZ,ist / G_VZ` and with it every kWh.

---

## Engine module

`RedispatchModule` implements `EngineModule` and is registered in `makod` when
`DeploymentRoles` contains at least one of `Marktrolle::Nb`, `Marktrolle::Unb`,
or `Marktrolle::Anb`:

```rust,ignore
if roles.contains_any(&[Marktrolle::Nb, Marktrolle::Unb, Marktrolle::Anb]) {
    builder.register(Box::new(RedispatchModule));
}
```

`RedispatchModule::configure()` wires all 8 workflows into a `RedispatchRouter`.
Its `register_pids` puts all five EDIFACT PIDs — IFTSTA 21037/21038, MSCONS
13021/13022 and ORDERS 17209 — into the `PidRouter`, every one of them routed to
`redispatch-aktivierung` and correlated by conversation ID.

### AcknowledgementDocument routing

`AcknowledgementDocument` is **not** registered in the type-based router.
Inbound ACKs carry a `ReceivingDocumentIdentification` field identifying the
workflow instance they belong to. The `makod` dispatcher resolves that
correlation key against the `ProcessRegistry` and delivers the ACK directly to
the originating workflow.

### Deadline scheduler note

The 5-minute Activation (ACO) deadline requires the `DeadlineScheduler` to poll
at ≤ 30-second intervals. Use a dedicated scheduler instance for Redispatch
workflows — the standard Werktage-based GPKE/WiM scheduler is insufficient.

---

## Related crates

| Crate | Role |
|---|---|
| [`mako-redispatch`]https://docs.rs/mako-redispatch**this crate** | Event-sourced process engine — 8 workflows, `RedispatchRouter`, `RedispatchModule` |
| [`redispatch-xml`]https://docs.rs/redispatch-xml | The XML format layer — parse · serialize · validate (joined with this crate in `makod`) |
| [`edi-energy`]https://docs.rs/edi-energy | IFTSTA status messages (EDIFACT, PIDs 21037/21038) |
| [`mako-engine`]https://docs.rs/mako-engine | Event-sourced workflow runtime — `Workflow`, `Process`, `EventStore`, deadlines |
| [`mako-fristen`]https://docs.rs/mako-fristen | *When* an answer is due — Werktage, the MaKo holiday calendar, the per-PID Antwortfristen |
| [`makod`]https://hupe1980.github.io/mako/docs/services/makod/ | Production daemon — routes both the XML and the EDIFACT leg |

Part of **mako**, an open-source Rust platform for German energy market
communication (Marktkommunikation). Full documentation: <https://hupe1980.github.io/mako/>