mako-wim 0.20.0

WiM process engine for German smart-meter market communication (Wechsel des Messstellenbetreibers)
Documentation

mako-wim

WiM — Wechselprozesse im Messwesen, Strom und Gas

Process engine workflows for the German metering-point-operator change processes in both Sparten: BK6-24-174 Anlagen 2a/2b (WiM Strom Teil 1 and Teil 2) and the AWH WiM Gas 2.0 (gültig ab 01.10.2026), plus the EDI@Energy AHBs that carry them.

A Prüfidentifikator (PID) is the five-digit BDEW code every message in these processes carries. It names the exact Anwendungsfall, and with it the rules, the Frist and the answer tree that apply — so the PID, not the EDIFACT message type, is what routes.

One engine, two Sparten

AWH WiM Gas 2.0 restates WiM Strom Teil 1 use-case for use-case, Frist for Frist. Only the UTILMD PID namespace splits (55xxx / 44xxx); ORDERS, ORDRSP, REQOTE, QUOTES, IFTSTA and INSRPT are Sparte-neutral AHBs carrying both. The Sparte comes from the recipient MP-ID (BDEW Allgemeine Festlegungen §2.13) and decides four things:

Strom Gas
Antwort-Codeliste S_00xx G_00xx
Zuordnungszeitpunkt 00:00 Uhr 06:00 Uhr (Gastag)
APERAK positiv und negativ, 45 min für UTILMD/ORDERS nur negativ; nächster WT 12:00 (Folgeprozess) / 3 WT (Initialprozess)
Störungs-Fristen nach Messtechnik flach

Fristen

Three clocks run on an inbound MSB-Wechsel order, and they are separate messages with separate commands:

Clock Window Message Source
APERAK — technical acknowledgement Strom 45 Minuten (UTILMD/ORDERS am Werktag); Gas nächster WT 12:00 bzw. 3 WT auf einem Initialprozess APERAK BGM+312/313 (Gas: nur 313) APERAK AHB 1.1 §2.3.1/§2.4.1
Antwort — business Bestätigung/Ablehnung per PID, in beiden Sparten gleich: 55039·44039 → 3 WT, 55042·44042 → 5 WT, 55051·44051 → 7 WT, 55168·44168 → 1 WT the Antwort-PID's UTILMD WiM Teil 1 Kap. 2.2.2 / 2.3.2 / 2.4.2 · AWH WiM Gas 2.0 Kap. 3.3.2 / 3.5.2 / 3.6.2
Vorlauffrist — was the requested date admissible? 15 / 7 WT (Anmeldung), 20 WT (Abmeldung), ±9 WT Realisierungskorridor the date inside the message WiM Teil 1 Kap. 2.3.2 Nr. 1 / 2.4.2 Nr. 1

Only the second discharges the Antwortfrist. The business window comes from antwort_frist_werktage(pid) and the Vorlauffristen from mako_fristen::vorlauf — never a flat value. Saturday is not a Werktag (GPKE Teil 1 Kap. 1.7, which WiM Teil 1 §1.7 defers to), and 24.12. and 31.12. count as holidays.

Antwortcodes

Every answer carries SG4 STS+E01 (UTILMD) or SG2 AJT (ORDRSP) with a code from the process's own Entscheidungsbaum. The catalogue and the executable Prüfschritte are mako-pruefung (role-msb); this crate resolves against it before anything reaches the outbox, so a code from the wrong tree is refused at the command rather than sent.

DE 1131 / DE 1082 names the Codeliste, not the Entscheidungsbaum. The AHB column reads „Codeliste Strom Nr. S_0090" for every WiM MSB-Wechsel answer, and the cluster picks which of the pair. AntwortCode::wire_codeliste() returns the wire value.

Process EBD Strom / Gas Bestätigung / Ablehnung Codeliste Strom Gas
Kündigung MSB E_0200 / E_2000 E15 Z01 Z44 / E11 Z12 Z29 Z34 (+ZC9 Strom) S_0090 / S_0054 G_0052 / G_0051
Anmeldung MSB E_0201 / E_2002 E15 Z01 Z44 / E11 E17 Z09 Z29 ZB6 (+ZC9 Strom) S_0055 / S_0056 G_0054 / G_0053
Ende MSB E_0202 / E_2005 E15 Z01 / E17 Z09 S_0059 / S_0060 G_0058 / G_0057
Verpflichtungsanfrage E_0240 / E_2006 E15 Z01 Z44 / E17 Z07 Z09 ZB6 S_0063 / S_0064 G_0070 / G_0071
Gesamtvorgang E_0232 / E_2003 — / Z66 S_0057 G_0055
Weiterverpflichtung E_0203 / E_2004 Z13 Z14 / Z22 S_0061 / S_0062 G_0072 / G_0073
Gerätewechselabsicht E_0204 / E_2007 ZB4 / ZB5; E17 Z07 in beiden S_0065 / S_0066 G_0059 / G_0060
Bestellung Geräteübernahme E_0247 / E_2011 Z13 / 5 Z32 S_0067 / S_0068 G_0061 / G_0074
Messlokationsänderung, direkt beauftragt E_0249 (NB) / E_0250 (LF) A02 / A01 (+ A03 A04) E_0249 / E_0250
Anfrage Änderung der Technik (35005) E_0278 (NB) / E_0281 (LF) — / A01A04 A99 resp. A01A06 A99 E_0278 / E_0281
Bestellung Änderung der Technik nach Angebot E_0279 (NB) / E_0283 (LF) A06 / A01A05 A99 E_0279 / E_0283
Messlokationsänderung durchführen E_0286 — / A01 A02 A03 A99 E_0286
Ersteinbau iMS (21029 → 21030/21031) E_0233 A03 / A01 A02 A04 E_0233

None of these alphabets is a GPKE one — A02 and A05 appear in no MSB-Wechsel tree — and the Gas lists are not the Strom lists.

PID Inventory

Legend: ✅ Implemented — full state machine + AHB rule enforcement, production-safe. ⚠️ Registered — PID routes to the workflow; handle() returns WorkflowError::NotImplemented for unhandled commands (no silent data loss). ✗ Not registered — PID is not in the router; inbound messages are dead-lettered.

MSB-Wechsel — UTILMD (WiM Strom Teil 1 Kap. 2)

PID Process name EDIFACT Module Status
55042 Anmeldung MSB (MSBN → NB) UTILMD S2.x geraetewechsel ✅ Implemented · Antwort 55043/55044, 5 WT (vorläufig)
55039 Kündigung MSB (MSBN → MSBA) UTILMD S2.x geraetewechsel ✅ Implemented · Antwort 55040/55041, 3 WT
55051 Ende MSB / Abmeldung (MSBA → NB) UTILMD S2.x geraetewechsel ✅ Implemented · Antwort 55052/55053, 7 WT
55168 Verpflichtungsanfrage / Aufforderung (NB → gMSB) UTILMD S2.x geraetewechsel ✅ Implemented · Antwort 55169/55170, 1 WT
44039 Kündigung MSB Gas (MSBN → MSBA) UTILMD G1.x geraetewechsel ✅ Implemented · Antwort 44040/44041, 3 WT
44042 Anmeldung MSB Gas (MSBN → NB) UTILMD G1.x geraetewechsel ✅ Implemented · Antwort 44043/44044, 5 WT (vorläufig)
44051 Ende MSB Gas (MSBA → NB) UTILMD G1.x geraetewechsel ✅ Implemented · Antwort 44052/44053, 7 WT
44168 Verpflichtungsanfrage Gas (NB → gMSB) UTILMD G1.x geraetewechsel ✅ Implemented · Antwort 44169 only — no Ablehnungs-PID exists, 1 WT
44183 Ende MSB von NB (Stilllegung, NB → MSB) UTILMD G1.x geraetewechsel ✅ Implemented · informational, no answer

Mitteilung über Gesamtvorgang — IFTSTA

The Anmeldebestätigung 55043 is vorläufig. WiM Teil 1 Kap. 2.1.1: the NB assigns the MSBN „zu dem Tag des vom MSBN mitgeteilten Termins des erfolgreichen Abschlusses des Gesamtvorgangs … mit dem Zeitpunkt 00:00 Uhr", and the MSBA's assignment ends at the same instant. AWH WiM Gas 2.0 Kap. 3.1.1 says the same with 06:00 Uhr — the Gastag boundary. This leg is what makes the Wechsel constitutive.

PID Process name Von → An Frist
21010 Statusmeldung (erfolgreich), DTM+2380 MSBN → NB 10 WT nach dem bestätigten Zuordnungsbeginn
21009 Statusmeldung (gescheitert) MSBN → NB
21012 Statusmeldung (erfolgreich) — die Zuordnung NB → MSBN 1 WT
21011 Statusmeldung (MSB-Scheitermeldung, Z66) NB → MSBN/MSBA/LF 1 WT
21013 Statusmeldung (gescheitert) — keine Meldung eingegangen NB → MSBN/MSBA/LF 11 WT

The numeric order is the reverse of the reading order: 21009 is the failure and 21010 the success (IFTSTA AHB 2.1 § 6.2).

Every failure path leaves the MSBA assigned. marktd derives the per-Messlokation MSB timeline from 21012 alone.

Geräteübernahme — ORDERS / ORDRSP

PID(s) Process name EDIFACT Module Status
17001 Bestellung Geräteübernahmeangebot (MSBN → MSBA) ORDERS 1.4b geraeteubernahme ✅ Implemented · Antwort 19001/19002, 2 WT
17009 Anzeige Gerätewechselabsicht (MSBN → MSBA) ORDERS 1.4b geraeteubernahme ✅ Implemented · Antwort 19015/19016, 2 WT vor dem Wechseltermin; ein Termin näher als 4 WT wird mit E17 abgelehnt
19001, 19002 ORDRSP Bestellbestätigung / Ablehnung (MSBA → MSBN) ORDRSP 1.4c geraeteubernahme ✅ Registered (nMSB role only)
19015, 19016 ORDRSP Eigenausbau ja/nein (MSBA → MSBN) ORDRSP 1.4c geraeteubernahme ✅ Registered (nMSB role only)
17002 → 19003/19004 Weiterverpflichtung des MSB (NB → MSBA) ORDERS 1.4b weiterverpflichtung ✅ Implemented · 1 WT, E_0203

The Anforderung eines Geräteübernahmeangebots is REQOTE 35001, answered by QUOTES 15001 within 4 WTpreisanfrage owns that leg.

19016 is named „Ablehnung Gerätewechselabsicht" but carries ZB5 „Kein Eigenausbau des MSBA": it settles who removes the old device, not whether the Gerätewechsel happens.

PIDs 19001/19002/19015/19016 are only registered when DeploymentRoles includes Marktrolle::Nmsb. On NB instances these PIDs belong to mako-gpke (GPKE Konfiguration). Never register both simultaneously.

All six ORDERS/ORDRSP PIDs carry the Strom and the Gas Use-Case; the recipient MP-ID's Sparte picks E_0247/E_0204 against E_2011/E_2007.

Stammdaten — ORDERS

PID(s) Process name EDIFACT Module Status
17132 Stammdaten Anforderung Strom (NB → MSB) ORDERS 1.4b stammdaten ✅ Implemented
17102–17133 Stammdatenübermittlung responses (MSB → NB) ORDERS 1.4b stammdaten ✅ Implemented

Weitere Prozesse

PID(s) Process name EDIFACT Module Status
39002 ESA Stornierung der Bestellung von Werten (ORDCHG) ORDCHG 1.1 wertebestellung ✅ Implemented
31009 MSB-Rechnung (MSB → NB/LF/ESA) INVOIC 2.8e invoic ✅ Implemented (send + receive) · Antwort zum Zahlungsziel, an den NB 4. WT davor
33001–33004 (REMADV) Zahlungsavis / itemized Abweisung REMADV 1.0a invoic ✅ Implemented (33003/34 = Strom Kopf+Summe / Position)
29001 (COMDIS) Ablehnung REMADV COMDIS 1.0 invoic ✅ Implemented · 2. WT vor dem Zahlungsziel
35001 → 15001 (REQOTE/QUOTES) Anforderung Geräteübernahmeangebot (MSBN → MSBA) REQOTE 1.3c preisanfrage ✅ Implemented · 4 WT
35002 → 15002 Anfrage Rechnungsabwicklung über den LF (LF → MSB) REQOTE 1.3c preisanfrage ✅ Implemented · 5 WT
35004 → 15004 Anfrage einer Konfiguration (GPKE Teil 3, NB/LF → MSB) REQOTE 1.3c preisanfrage ✅ Implemented · 2 WT
35005 → 15005 Anfrage Angebot Änderung Technik (NB/LF → MSB) REQOTE 1.3c preisanfrage ✅ Implemented · 10 WT
17005/17006 → 19009/19010 Rechnungsabwicklung MSB über LF ORDERS/ORDRSP rechnungsabwicklung ✅ Implemented · 8 WT
27001–27003 Preisliste (PRICAT) PRICAT 2.1 preisliste ✅ Implemented · Preisblatt an LF 3 Monate vor Wirksamwerden, an NB initial 3 WT / bei Änderung 20 WT vor Inkrafttreten
23001, 23003, 23004, 23008 Störungsmeldung / Antwort / Ergebnisbericht INSRPT 1.1a insrpt ✅ Implemented, beide Seiten · Antwort Strom 3 WT (kME ohne RLM, mME) / 1 WT (kME mit RLM, iMS), Gas flach 3 WT; Ergebnis Strom 7/4/2 WT nach Messtechnik und Spannungsebene, Gas flach 7 WT
23005, 23009 Informationsmeldungen an den NB (Gas) INSRPT 1.1a insrpt ✅ Implemented
23011, 23012 Weiterleitung an betroffene Marktlokationen (Strom) INSRPT 1.1a insrpt ✅ Implemented · 1 WT, und das Fenster überlebt den Ergebnisbericht — die 23012 ist danach fällig
31003 → 33001/33002 WiM-Rechnung (MSBA → NB / MSBN), beide Sparten INVOIC 2.8e invoic ✅ Implemented · Rechnung 20. WT nach Leistungsende, Antwort zum Zahlungsziel
31004 → 33001/33002 Stornorechnung, Sparte-neutral INVOIC 2.8e invoic ✅ Implemented
11021–11023 iMS Bestellung (Universalbestellprozess) REST/JSON steuerungsauftrag ✅ Implemented (API-Webdienste channel)

EDIFACT Format Versions

WiM rides eleven message types and they do not share a release cadence, so there is no single WiM format version. The profiles this crate validates against:

Message FV2025-10-01 FV2026-10-01
UTILMD Strom AHB 2.1 (MIG S2.1) AHB 2.2 (MIG S2.2)
REQOTE AHB 1.1 (MIG 1.3c) AHB 1.2 (MIG 1.3c)
QUOTES AHB 1.1 (MIG 1.3b) AHB 1.1a (MIG 1.3c)
PRICAT AHB 2.0f (MIG 2.0e) AHB 2.1 (MIG 2.1)
IFTSTA AHB 2.0h (MIG 2.0g) AHB 2.1 (MIG 2.1)
ORDERS / ORDRSP — (FV2026-04-01: AHB 1.1a, MIG 1.4b) AHB 1.1b (MIG 1.4c)

INSRPT ships only at FV2026-01-01 (AHB 1.1g, MIG 1.1a), INVOIC and REMADV from FV2026-04-01, ORDCHG at FV2025-04-01 and FV2026-10-01. The AHB and the MIG carry different version numbers for every message type except UTILMD — the tables above name the MIG release, which is what the „EDIFACT" column of the PID inventories cites.

Modules

Rust module Contents
geraetewechsel PIDs 55039/55042/55051/55168 and their Gas twins 44039/44042/44051/44168, plus 44183 and IFTSTA 21007/21009–21013/21015/21018/21036, of which 21009–21013 are the Gesamtvorgang leg (GESAMTVORGANG_PIDS) — MSB-Wechsel workflow + projection. Handles both directions: inbound UTILMD (ReceiveUtilmd → APERAK → DispatchAntwortReceiveGesamtvorgangDispatchZuordnung) and ERP-initiated outbound orders (InitiateDeviceChangeReceiveAntwortMeldeGesamtvorgangReceiveZuordnungsantwort). Antwortfrist per process via antwort_frist_werktage(); the Realisierungskorridor is enforced on the Gesamtvorgang date.
geraeteubernahme ORDERS 17001 → ORDRSP 19001/19002 (Bestellbestätigung/Ablehnung) and ORDERS 17009 → 19015/19016 (Eigenausbau ja/nein) — WiM Teil 1 Kap. 3.1/3.2. The 17009 Mindestvorlaufzeit is checked against mako_fristen::vorlauf before the answer window opens: a Gerätewechseltermin closer than the 4. Werktag is refused with E17 naming the earliest date still reachable, rather than accepted with a window that expired before the message arrived
weiterverpflichtung ORDERS 17002 → ORDRSP 19003/19004 — the NB keeping the abgebender MSB on the Messlokation while the gMSB prepares to take over (Kap. 2.4.2 Nr. 5/6, E_0203)
technik_aenderung REQOTE 35005 → QUOTES 15005 / IFTSTA 21033, ORDERS 17011/17118 → ORDRSP 19005/19006, IFTSTA 21025/21027 — Messlokationsänderung auf beiden Wegen; 10 WT Antwort, 20 WT Vorlauffrist nur auf der direkten Beauftragung (Kap. 3.3 / AWH Änd. Technik)
ersteinbau IFTSTA 21029 → 21030/21031 — Ersteinbau eines iMS in eine bestehende Messlokation, 3 WT Antwort aus E_0233 (Kap. 3.5, Strom only)
stammdaten PIDs 17102–17133, 17132 — Stammdaten Anforderung / Übermittlung
wertebestellung PIDs 35003/15003/17007/17008, ORDCHG 39002 (Stornierung, answered by ORDRSP 19013/19014), ORDRSP 19011/19012, IFTSTA 21042 — ESA Wertebestellung (WiM Teil 2 Kap. 4): Anfrage → Angebot → Bestellung → Stornierung/Abbestellung, plus MSB-initiated termination. Fristen keyed on the positive AS4-Zustellquittung (ÜT); answers carry an E_0254/E_0256/E_0257 Antwortcode.
invoic INVOIC 31009 (MSB-Rechnung, Strom) · 31003 (Dienstleistungen, beide Sparten) · 31004 (Stornorechnung, Sparte-neutral); inbound REMADV 33001–33004 and COMDIS 29001 — see below
preisanfrage PIDs 35001/35002/35004/35005 (REQOTE), 15001/15002/15004/15005 (QUOTES) — Preisanfrage
preisliste PIDs 27001–27003 — Preisliste PRICAT
esa_wertebestellung The ESA side of the same relationship (esa-wertebestellung): it sends 35003/17007/17008/39002 and ingests the MSB's 15003/19011–19014/21042/13027. Disjoint commands from wertebestellung, so one deployment may hold both roles
insrpt INSRPT 23001 → 23003/23004 (Entscheidungen), 23005/23009/23011/23012 (Informationsmeldungen), 23008 (Ergebnisbericht) — Störungsmeldung, both sides
rechnungsabwicklung ORDERS 17005/17006 → ORDRSP 19009/19010 — Rechnungsabwicklung des MSB über den LF, 8 WT (E_0205E_0209)
steuerungsauftrag PIDs 11021–11023 — iMS Steuerungsauftrag (API-Webdienste REST channel)

The invoic family

Both sides run here: the MSB sends with SendInvoic and awaits the REMADV; the NB/LF/ESA ingests with ReceiveInvoic, then settles or disputes.

Replies correlate by conversation ID (RFF+Z13 → the 31009 reference), so they resume this family even though the shared REMADV PID statically resolves to GPKE. The state machine itself is mako-invoic's, shared with the GPKE, GaBi Gas and GeLi Gas billing families; this module declares only the family and the Gas Ablehnungs-Entscheidungsbaum.

Usage

use mako_wim::{WimDeviceChangeWorkflow, DeviceChangeCommand};
use mako_engine::{builder::EngineBuilder, event_store::InMemoryEventStore};

// In tests (requires `testing` feature or `#[cfg(test)]`):
#[cfg(test)]
let ctx = EngineBuilder::new()
    .with_event_store(InMemoryEventStore::new())
    .build();

// In production, explicitly provide all stores:
// let ctx = EngineBuilder::with_stores(outbox, deadline, registry)
//     .with_event_store(my_slatedb_store)
//     .build();

let process = ctx.spawn::<WimDeviceChangeWorkflow>(tenant_id, workflow_id);
let events = process.execute(DeviceChangeCommand::ReceiveUtilmd {
    pid: 55042,  // Anmeldung MSB (nMSB → NB)
    //}).await?;

Geräteübernahme (WiM Teil 1, Kapitel 3.2)

Step Direction Message PID Frist
1 Anforderung Geräteübernahmeangebot MSBN → MSBA REQOTE
2 Geräteübernahmeangebot MSBA → MSBN QUOTES 15001 4 WT nach ÜT von Nr. 1
3 Bestellung MSBN → MSBA ORDERS 17001 3 WT nach ÜT von Nr. 2
4 Bestellbestätigung MSBA → MSBN ORDRSP 19001 / 19002 2 WT nach ÜT von Nr. 3
5 Zählerstand zur Geräteübernahme MSBA → MSBN MSCONS 3 WT vor Ablauf des 28. T

Adjacent processes sharing the workflow: ORDERS 17002 (Weiterverpflichtung MSBA) answered by ORDRSP 19003/19004, and ORDERS 17009 (Ankündigung Gerätewechselabsicht) answered by ORDRSP 19015/19016.

ESA Wertebestellung (WiM Teil 2, Kapitel 4)

§34 Abs. 2 S. 2 Nr. 10 MsbG makes serving an Energieserviceanbieter a mandatory, non-discriminatory Zusatzleistung, so an MSB must be able to process the order that authorises value delivery and the one that stops it. Both sides are modelled: wertebestellung (MSB) and esa_wertebestellung (ESA), over disjoint PID sets so one deployment may hold both roles.

UC step Direction Message PID Frist EBD
4.1 Nr. 1 Anfrage ESA → MSB REQOTE 35003 E_0252
4.1 Nr. 2 Angebot / Ablehnung MSB → ESA QUOTES 15003 5 WT nach ÜT der Anfrage
4.1 Nr. 3 Bestellung ESA → MSB ORDERS 17007 bis Ablauf der Bindungsfrist
4.1 Nr. 4 Antwort MSB → ESA ORDRSP 19011 / 19012 2 WT nach ÜT der Bestellung E_0256
4.1 Nr. 5 Stornierung ESA → MSB ORDCHG 39002 unverzüglich
4.1 Nr. 6 Antwort MSB → ESA ORDRSP 19013 / 19014 2 WT nach ÜT der Stornierung E_0257
4.2 Werteübermittlung MSB → ESA MSCONS 13027 per Messprodukt
4.3 Nr. 1 Abbestellung ESA → MSB ORDERS 17008 unverzüglich
4.3 Nr. 2 Antwort MSB → ESA ORDRSP 19011 / 19012 2 WT nach ÜT der Abbestellung E_0254
4.4 Nr. 1 Beendigung durch MSB MSB → ESA IFTSTA 21042 (STS+Z21 4405 = 105) unverzüglich

What is ordered

The esa module holds the Codeliste der Konfigurationen 1.4 Kapitel 4.6 catalogue — the only Messprodukte the role may order — as data: delivery path (4.6.1 EDIFACT back-end vs 4.6.2 SM-PKI from the iMS), Lokationsebene, Werteart, Energieflussrichtung, cadence, and whether BNetzA Mitteilung Nr. 3 makes the product mandatory. A Bestellgegenstand pairs a Messprodukt-Code with the DTM+76 Wunschtermin and the IMD+7081 Abonnement mode, and is carried through both aggregates: without it the process could not say what a confirmed delivery is supposed to contain.

A subscription is the (Meldepunkt, Messprodukt) pair (esa::business_key) — one Marktlokation can carry several products at once, so every follow-up message and command has to say which one it means.

An order is validated against the catalogue before it leaves the system — a product outside Kapitel 4.6, one defined for a different Lokationsebene than the request addresses, or a 4.6.2 product without its SM-PKI target is refused.

The product decides the Lokationsebene (esa::ebene_fuer_messprodukt). REQOTE AHB 1.2 §4.3 gives LOC+172 DE 3225 four permitted shapes and lets the Marktlokations-ID format ([950]) serve both the Marktlokation (hint [502]) and the Tranche (hint [504]), so an identifier cannot resolve the level — and the Tranche carries a Pflichtprodukt (9991 00000 306 4).

„Pflicht" and „nutzbar ab" are two different dates, and both are dates rather than flags: 9991 00000 077 1 and 078 9 read „Optional ab 01.10.2023, Pflicht ab 06.08.2024" while being usable from 01.10.2023. Verbindlichkeit::Pflicht { ab } carries its start and Messprodukt::ist_pflicht_am is what E_0252 Prüfschritt 1 asks — a Vergangenheitswerte-Anfrage may reach back before the cut-over.

The Angebot is a priced offer

UC 4.1.1 has the ESA asking for „die Übermittlung von Werten und die damit verbundenen Kosten". QUOTES AHB 1.1a §4.3 makes the substance Muss — SG4 CUX, one to three SG27 PIA+Z02 Artikel-IDs, one SG31 PRI+CAL each (Z01 Einrichtungs- / Z02 Transaktions- / Z03 Betriebspreis) and one to 23 PIA+5 … :SRW OBIS-Kennzahlen — so esa::Angebot carries all of it and the process keeps it past the Bestellung: it is what the INVOIC 31009 is checked against and what a delivery-surveillance sweep expects to arrive.

It is also the discriminator. DTM+273 is Muss on the only published 15003 use case, so a refusal carries a Bindungsfrist too; the priced position is what tells an Angebot from an Ablehnung.

The Prüfidentifikator is not in BGM

BGM DE 1004 is a Dokumentennummer throughout these handbooks; the PID travels in SG1 RFF+Z13. DE 1001 carries a BDEW document code: Z57 on the order handshake, Z83 on the MSCONS 13027 delivery, Z09 on the IFTSTA 21042.

Correlation

Only the opening REQOTE is keyed on a location. A conformant ORDERS, ORDCHG, ORDRSP or IFTSTA of Kapitel 4 carries no LOC at all and correlates by a Belegnummer, under the Zuordnungsschlüssel the BDEW Anwendungsübersicht der Prüfidentifikatoren 4.0 publishes per PID:

PID Schlüssel Segment Points at
35003 ZO-T17 SG11 LOC+172 the Meldepunkt
15003 ZG-T16 SG1 RFF+AAV the REQOTE
17007 ZG-T24 SG1 RFF+AAG the QUOTES Angebot
17008 ZG-T41 SG1 RFF+ACW the ORDERS Bestellung
39002 ZG-T51 SG1 RFF+ON the ORDERS Bestellung
19011 / 19012 ZG-T14 SG1 RFF+ON the ORDERS answered
19013 / 19014 ZG-T50 SG1 RFF+ACW the ORDCHG
21042 ZG-T47 SG15 RFF+AGI the ORDERS Bestellung
13027 ZG-T42 (of EZ-03) SG1 RFF+AGI the ORDERS Bestellung

esa::korrelation is that table; the renderer and the ingest dispatcher both read it, so the qualifier they emit and the one they look for cannot drift. Note that the same AGI qualifier sits in a different segment group per message — SG15 on the IFTSTA, SG1 on the MSCONS — which is why the lookup searches by qualifier rather than by position. EZ-03 continues past it to ZO-T20 (Gerätenummer) and ZO-T21 (OBIS-Kennzahl); those two assign the values to a Zählwerk and belong to edmd.

Answers are Antwortcodes, not booleans

SG2 AJT is Muss on all four answer PIDs (ORDRSP AHB 1.1b §4.15) and carries the Prüfschritt code in DE 4465 with its EBD in DE 1082. Conditions [17]/[18] require the code to sit in that tree's Zustimmungs- resp. Ablehnungs-Cluster, so the cluster selects the answer PID. The MSB commands therefore take an antwort_code resolved against mako_pruefung::esa::wertebestellung, never an accept flag alongside it.

And it is the whole content of a refusal on the receiving side too. Those four use cases publish no free-text segment at all: the only FTX a conformant 19011 may carry is SG27 FTX+Z27 — the MSB's IP address, Muss when the confirmed order named a Kapitel-4.6.2 SMGW product (FTX+Z28 for a range), and the source the ESA has to admit before the iMS can reach it.

The ESA side therefore records esa::Antwort, the typed (Antwortcode, EBD) pair, rather than prose — that is what tells A08 (Einwilligung abgelaufen: renew and re-order) from A10 (Lokationsbündel: split the request) from A09 (Gerätetechnik: nothing to retry). Antwort::widerspricht_pid flags a code whose Cluster contradicts the PID that carried it; such an answer is recorded as a conflict and resolved by the PID, since resolving it by the code would silently turn a confirmation into a refusal.

Every inbound step notifies

The MSB-side workflow emits a ProcessInitiated outbox entry on each of 35003/17007/39002/17008. That notification is the entire input to processd's ESA module — the four mako-pruefung walks, the operator queue and its Fristen — and its payload is a contract: every field is a Prüfschritt input, so an omitted one does not fail, it escalates a decision that could have been answered. In particular abo_beginn (the confirmed DTM+203 of the Bestellung) is what E_0254 Prüfschritt 2 compares a requested end against.

What a subscription remembers

Both aggregates keep lieferung_begonnen in their process data, not inside the state variant that happens to be current. It is a fact about the subscription: it has to survive a Storno or Abbestellung round trip, and a first delivery landing while a 39002 is in flight is exactly the case E_0257 A02 exists for. The MSB side additionally remembers its own Bindungsfrist (E_0256 Prüfschritt 1 asks about it one state later), the Abo start, the reach of its latest delivery and any date it already ended on — the three inputs E_0254 Prüfschritte 2–4 need.

An Angebot whose Bindungsfrist lapses is terminal on the ESA side: UC 4.1 Nr. 3 admits no Bestellung after it, so the process releases its (Meldepunkt, Messprodukt) business key rather than blocking every replacement order.

19011/19012 answer both the Bestellung and the Beendigung; the IMD+7081 on the answer is what says which tree its code came from.

Stornierung and Abbestellung are not interchangeable

UC 4.1 Nr. 5 admits a Stornierung only while the einmalige Übermittlung has not happened or the turnusmäßige has not begun; UC 4.3's Vorbedingung then states "Eine Stornierung der Bestellung ist nicht mehr möglich". MarkLieferungBegonnen flips the state that enforces this. On the MSB side the two trees make the boundary explicit: E_0254 A01 refuses a Beendigung of a one-shot order, and E_0257 refuses a Stornierung of a started delivery with different codes per Abo mode (A02 Abo, A03 einmalig).

Regulatory references

  • BDEW WiM Wechselprozesse im Messwesen
  • MsbG — Messstellenbetriebsgesetz
  • BNetzA BK6-24-174 Anlagen 2a/2b (WiM Strom Teil 1 und Teil 2)
  • BDEW/VKU/GEODE/FNBGas AWH WiM Gas 2.0 (gültig ab 01.10.2026)
  • EDI@Energy UTILMD Strom AHB 2.2 (MIG release S2.2, FV2026-10-01)
  • EDI@Energy APERAK AHB 1.1 (FV2026-10-01) — § 2.4.1 Strom, § 2.3.1 Gas. 2.2 is the APERAK MIG revision; AHB and MIG carry different version numbers for every message type except UTILMD

Related crates

The format layer and the domain packs meet in makod: a workflow crate knows the Pruefidentifikator and its own domain types, never an EDIFACT message type.

Crate Role
mako-wimthis crate WiM workflows for both Sparten, PID routing, WimModule
edi-energy EDI@Energy EDIFACT — parse · validate · build (UTILMD, MSCONS, ORDERS, INVOIC, APERAK, …); joined to these workflows in makod, not depended on
mako-engine Event-sourced workflow runtime — Workflow, Process, EventStore, deadlines
mako-fristen When an answer is due — Werktage, the MaKo holiday calendar, the per-PID Antwortfristen
mako-pruefung What the answer must be — the BDEW Entscheidungsbäume, executable
mako-invoic The INVOIC settle/dispute state machine every billing family shares
energy-api The parallel API-Webdienste channel for iMS values
makod Production daemon — routes, adapts and renders these workflows

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