# redispatch-xml
XML/XSD format parsing, serialization, and validation for **Redispatch 2.0**,
the German electricity-grid congestion-management protocol (§§ 13, 13a, 14 EnWG,
mandatory since 1 October 2021).
All nine BDEW Redispatch 2.0 document types are fully implemented: parse, serialize,
structural validation, and semantic validation. The crate targets MSRV 1.94 and is
`#![deny(unsafe_code)]`.
---
## Regulatory basis
| NABEG 2019, § 13 ff. EnWG | Bundestag | in force since 2021-10-01 |
| **BNetzA BK6-23-241** — Anlage „BilAReM" | BNetzA | Beschluss 07.05.2026; ÜNB settle under it since 01.07.2026 |
| BNetzA BK6-20-059 | BNetzA | TZ 1 repealed with the end of 30.06.2026; TZ 2 survives until the new EDI@Energy documents apply |
| BNetzA BK6-20-060 (Netzbetreiber-Koordination) | BNetzA | **repealed** by BK6-23-241 TZ 4 |
| BNetzA BK6-20-061 (Informationsbereitstellung) | BNetzA | **repealed** by BK6-23-241 TZ 3 |
| BDEW XML-Datenformate Redispatch 2.0 | BDEW | the XSDs this crate models |
All German grid operators (TSO/DSO) must implement Redispatch 2.0. Absence of a
conformant implementation is a regulatory violation under § 14 EnWG.
---
## Document types
All documents are CIM/IEC 62325-based XML, **not** EDIFACT. IFTSTA status
messages (EDIFACT) are handled by the `edi-energy` crate.
| `ActivationDocument` | 1.1f | 2025-10-01 |
| `PlannedResourceScheduleDocument` | 1.0f | 2025-10-01 |
| `AcknowledgementDocument` | 1.0f | 2025-10-01 |
| `Stammdaten` (master data) | 1.4b | 2025-10-01 |
| `StatusRequest_MarketDocument` | 1.1 | 2025-10-01 |
| `Unavailability_MarketDocument` | 1.1b | 2025-10-01 |
| `Kaskade` | 1.0 | 2025-10-01 |
| `NetworkConstraintDocument` | 1.1b | 2025-10-01 |
| `Kostenblatt` | 1.0d | 2025-10-01 |
XSD schemas and application guidelines are published by BDEW at
[bdew-mako.de](https://www.bdew-mako.de/market_communication/documents)
(topicGroupId 25 — XML-Datenformate Redispatch 2.0).
---
## XSD conformance is checked, not assumed
`tests/xsd_coverage.rs` reads the published XSDs from `regulatories/bdew-mako/`
and asserts that **every element BDEW declares appears in the model**, scoped per
document. Anything deliberately left out is listed in `NOT_MODELLED` with a
reason; an unexplained entry fails the test.
The check exists because the failure mode is invisible. `serde` ignores unknown
XML elements, so a field simply absent from a struct means an inbound document
carrying it is accepted and the value silently dropped — indistinguishable from
a document that genuinely omitted it. It caught, among others:
| `sender_MarketParticipant` modelled as a **nested container** in `Kaskade`, `StatusRequest` and `Unavailability` | The XSD declares the flat dotted `sender_MarketParticipant.mRID`. Every document mako emitted failed XSD validation at the counterparty; every inbound one lost its sender. Same for `receiver_…`, `biddingZone_Domain.mRID`, `quantity_Measure_Unit.name`, `unavailability_Time_Period.timeInterval` |
| `MeasureUnit` where the XSD says `MeasurementUnit` | Kostenblatt, PlannedResourceScheduleDocument. Only `ActivationDocument` uses the shorter spelling, and only in its `ActivationTimeSeries` |
| `Bilanzkreis_Ausgleichsfahrplan_anfNB` and the per-Quote `Bilanzkreis_Ausgleichsfahrplan` absent | The **Redispatch-Bilanzkreis**, which `BilAReM` Kap. 2.3.2 names as one of the three things a Planwertmodell-Zuordnung must carry — where the bilanzielle Ausgleich is booked |
| `ScheduleTimeSeries` absent from `ActivationDocument` | The korrespondierende Fahrpläne. `BilAReM` Kap. 2.1.2: „Der bilanzielle Ausgleich erfolgt durch die Anmeldung korrespondierender Fahrpläne" — the half of the process that moves the energy on paper |
| `Abrechnungsmodell` absent from `Enthaltene_TR` | The Spitz / vereinfachte Spitz / Pauschal election. Without it the Ausfallarbeit of a Maßnahme cannot be computed at all |
| `Available_Period` / `Point` absent from `Unavailability` `TimeSeries` | The availability curve itself. The model reduced an unavailability to a date range, dropping the per-interval capacity that bounds `P_bean` in `BilAReM` Kap. 3.2.2.1 |
| One `TechnischeParameter` type shared across three different XSD complexTypes | The whole TR nameplate (Nettonennleistung, Nabenhöhe, storage capacities …) was dropped |
The XSDs are third-party publications and are **not tracked in git**, so the test
skips — visibly — when `regulatories/bdew-mako/` is absent.
`regulatories/README.md` carries the download URL for each file.
---
## Quick start
```rust
use redispatch_xml::{parse, parse_and_validate, serialize, Document};
// Auto-detect document type and parse
let doc = parse(xml_bytes)?;
// Parse + structural/semantic validation in one step
let doc = parse_and_validate(xml_bytes)?;
// Serialize a Document back to XML bytes
let bytes = serialize(&doc)?;
// Serialize a specific type (when document type is known at compile time)
use redispatch_xml::{serialize_as, documents::activation::ActivationDocument};
let bytes = serialize_as(&activation_doc, /* add_xml_decl: */ true)?;
// Parse a specific type directly
use redispatch_xml::parse_as;
let doc: ActivationDocument = parse_as(xml_bytes)?;
```
---
## API overview
| `parse(xml)` | Detect type, deserialize, validate namespace |
| `parse_as::<T>(xml)` | Deserialize into a known type `T` |
| `parse_and_validate(xml)` | Parse + structural + semantic validation |
| `detect(xml)` | Return `DocumentType` without deserializing |
| `serialize(doc)` | Serialize `Document` enum to XML bytes |
| `serialize_as(doc, decl)` | Serialize any `Serialize` type to XML bytes |
| `validate(doc)` | Run structural + semantic validation, return `ValidationResult` |
| `Document::mrid(&self)` | Primary document identifier — correlation key for process routing |
| `Document::sender_id(&self)` | Sender GLN / EIC (13 digits) |
| `Document::receiver_id(&self)` | Receiver GLN / EIC (13 digits) |
| `ValidationResult::into_errors()` | Consume result — `Ok(warnings)` or `Err(errors)` with the full list |
---
## Type system highlights
- **`DocumentId`** / **`MarketParticipantId`** — validated newtypes with `Display`,
`AsRef<str>`, `TryFrom<&str>`, `TryFrom<String>`, custom serde.
- **`TimeInterval`** — parses/serializes `"yyyy-mm-ddThh:mmZ/yyyy-mm-ddThh:mmZ"`,
validates UTC and start-before-end. Implements `Display`.
- **`Decimal3`** — non-negative `f64` serialized as `"NNN.NNN"` (3 dp). Implements `Display`.
- **`AttrV<T>`** — ENTSO-E attr-v pattern wrapper with `From<T>`, `Display`, `Deref`.
- All public fallible constructors are annotated `#[must_use]`.
- Enums open for extension: `Direction`, `MeasureUnit`, `CodingScheme`, `ControlZone`
are all `#[non_exhaustive]`.
---
## Market roles
| ÜNB | Übertragungsnetzbetreiber (TSO) |
| VNB | Verteilnetzbetreiber (DSO) |
| ANB | Anlagenbetreiber (generation asset operator) |
| DV | Direktvermarkter |
| BKV | Bilanzkreisverantwortlicher |
---
## Related crates
| `redispatch-xml` ← **this crate** | XML format layer (parse / serialize / validate) |
| `mako-redispatch` | Event-sourced process engine — 8 workflows, `RedispatchRouter`, `RedispatchModule` |
| `edi-energy` | IFTSTA status messages (EDIFACT, PIDs 21037/21038) |
| `mako-engine` | Event-sourced workflow runtime (`Workflow`, `Process`, `EventStore`) |