Open source · Rust · BDEW EDI@Energy FV2026-04-01 · FV2026-10-01
German energy-market operations,
from raw EDIFACT to production — in Rust.
Open-source, full-stack German energy-market operations platform in Rust: BDEW MaKo / EDI@Energy market communication across every Prüfidentifikator in the catalogue, an event-sourced process runtime, NNE/EEG settlement, retail billing, AS4 transport and BO4E integration — every regulated process as a correct, auditable workflow.
What happens to a message
A market partner sends an interchange over AS4. Everything after that is the same path for every process — parse, validate against the AHB in force on that date, route to a workflow, and answer inside the Frist.
graph LR
MP["Marktpartner"]
AS4["AS4 / ebMS3
sign · encrypt · receipt"]
VAL["Parse + validate
MIG · AHB · semantic"]
WF["Workflow
event-sourced, replayable"]
FRIST["Frist
BDEW Werktag calendar"]
OUT["Outbox
persist before dispatch"]
ERP["ERP · CloudEvents"]
MP -->|"UTILMD · MSCONS · ORDERS · INVOIC"| AS4
AS4 --> VAL
VAL -->|"Prüfidentifikator"| WF
WF --> FRIST
WF --> OUT
OUT -->|"answer interchange"| AS4
OUT --> ERP
AS4 -->|"CONTRL / APERAK"| MP
The format layer is generated from the BDEW profiles, so an annual release is a codegen run rather than a rewrite. The domain layer never sees EDIFACT: it receives typed commands and emits typed events.
Two numbers describe the same catalogue from different sides: 458 Prüfidentifikatoren are routed to a workflow, and 349 additionally carry validated AHB segment rules. The gap is a declared set of routed-without-rules PIDs, not an accident — and a release that would shrink either number fails the build.
What it looks like
The format layer stands alone. Add one crate and an interchange becomes a typed message, validated against the AHB profile in force on its date.
use edi_energy::{EdiEnergyMessage, Platform};
let msg = Platform::with_all_profiles().parse(wire)?;
msg.try_message_type(); // Some(Utilmd)
msg.detect_release()?.as_str(); // "S2.1"
msg.detect_pruefidentifikator()?; // 55001 — Anmeldung Lieferbeginn
let report = msg.validate()?; // MIG + AHB + semantic
report.is_valid(); // true
An unregistered Prüfidentifikator is reported, never silently accepted — and the profile is chosen by the message's own date, because two Formatversionen can share one wire release code and only the date tells them apart.
The shape of the platform
Seventeen services along one path: transport and protocol at the edge, master data in the middle, settlement and billing at the end — each owning its own data, talking over typed REST and CloudEvents.
graph TB
subgraph edge["Edge — protocol"]
MAKOD["makod
AS4 · EDIFACT · workflows"]
PROCESSD["processd
STP decisions"]
end
subgraph core["Core — market data"]
MARKTD["marktd
BO4E hub · fan-out"]
VERTRAGD["vertragd · productd
contracts · products"]
EDMD["edmd
metering · SMGW"]
end
subgraph money["Settlement and billing"]
NETZ["netzbilanzd
NNE · MMM"]
EINSD["einsd
EEG · KWKG"]
BILLINGD["billingd · invoicd
retail · EN 16931"]
ACCT["accountingd
FI-CA ledger"]
end
subgraph ops["Operations"]
OBSD["obsd
BNetzA KPIs · Fristen"]
AGENTD["agentd
AI specialists"]
PORTALD["portald
customer portal"]
end
PROCESSD --> MAKOD
MAKOD --> MARKTD
MARKTD --> VERTRAGD
MARKTD --> EDMD
EDMD --> NETZ
EDMD --> EINSD
EDMD --> BILLINGD
VERTRAGD --> BILLINGD
NETZ --> ACCT
BILLINGD --> ACCT
EINSD --> ACCT
PORTALD --> BILLINGD
OBSD -.->|"watches"| MAKOD
AGENTD -.->|"MCP reads"| MARKTD
AGENTD -.->|"MCP reads"| OBSD
What you get
Protocol & process
Parse, validate and build all 17 EDI@Energy EDIFACT types; run every MaKo process as an event-sourced, replayable workflow over AS4/ebMS3 — BDEW AS4-Profil, sign and encrypt, signed receipts, retry with duplicate elimination.
makod · processd →Market data
A typed BO4E market-data hub — MaLo/MeLo, contracts, supply status, device configuration, grid-operator registry — with durable CloudEvents fan-out and OIDC/JWT on every route.
marktd · vertragd · productd →Metering & settlement
Quarter-hourly metering with §60 MsbG substitute values and SMGW lifecycle; role-neutral grid settlement (NNE/MMM/MSB), EEG/KWKG feed-in and MaBiS Summenzeitreihen — each position carrying a legal-reference calculation trace.
edmd · netzbilanzd · einsd →Billing & ledger
Retail billing with EN 16931 e-invoicing (XRechnung, ZUGFeRD), SEPA collection and returns, and a hash-chained FI-CA ledger with § 147 AO period seals and Merkle inclusion proofs.
billingd · invoicd · accountingd →Operations & oversight
BNetzA KPI observability with Fristen tracking, a customer portal, and an agent plane over the 163 MCP tools the platform exposes — 28 declarative specialists whose every model and tool call is a journaled effect, advisory by construction, with a four-eyes worklist in front of anything that acts.
obsd · portald · agentd →Testable from Python
makotest binds the same Rust core the platform runs — BDEW check digits, the Werktag calendar, the published answer Fristen, EDIFACT build and AHB validation — so a test and production can never disagree about what “valid” means, nor about when a Frist expires.
makotest →Built for the regulatory pace
German market communication moves fast — LFW24, §14a, §41a dynamic tariffs, §42b/c energy sharing, the EDIFACT→API transition, the MaBiS-Hub cutover. mako’s domain layer is deliberately split from transport and format, so when the market moves, the platform moves with a codegen run — not a rewrite.
- Auditable by construction — event-sourced processes, replayable state, hash-chained ledger with § 147 AO period seals.
- Every regulated process modeled, for every market role — and a role-scoped build contains only its own arm's code.
- Validation that fails loudly — an unregistered Prüfidentifikator is reported, never quietly accepted.
- Fristen that agree with each other — one BDEW Werktag calendar, from the APERAK clock to an agent's approval window.
- Readable, verifiable, extensible — source you can audit and run.
Start in minutes
# Parse & validate an EDIFACT interchange
cargo add edi-energy
# Run the protocol daemon
docker run --rm -p 8080:8080 \
ghcr.io/hupe1980/makod:latest
# Drive it from Python — generators, simulators, pytest plugin
pip install makotest