Experimental · pre-1.0 Rust · MIT or Apache-2.0 · BDEW EDI@Energy FV2025-04-01 → FV2026-10-01

German energy-market operations,
from raw EDIFACT to production — in Rust.

Every German electricity and gas market participant is required to exchange regulated messages with its counterparties — Marktkommunikation, MaKo. That is a few hundred prescribed processes carried over EDIFACT and AS4, each with a published decision tree and a deadline. mako implements them: the BDEW formats are read as data, every process runs as an event-sourced workflow, and settlement, billing and the ledger sit on the same events.

cargo add edi-energy for the format layer, or just dev makod for the daemon.

482 / 482published Prüfidentifikatoren imported
469Prüfidentifikatoren routed
71MaKo workflows
17EDI@Energy message types
17services
4role-scoped deployments

Three views of one catalogue. Every one of the 482 Prüfidentifikatoren the BDEW Anwendungsübersicht publishes is imported; 469 are routed to a workflow — including the DVGW gas codes BDEW does not publish — and 434 additionally carry validated AHB segment rules. The 35 that do not are exactly that DVGW block: ALOCAT, NOMINT/NOMRES and SSQNOT ride dvgw-edi, for which no BDEW AHB exists to import rules from. A release that would shrink any of the three fails the build.

Who runs this

Every MaKo message names a Marktrolle on each end. The BDEW Rollenmodell V2.2 defines twelve of them; these four carry the mass-market processes, and mako builds each as its own binary — a role-scoped deployment contains no other role's code, least privilege in support of § 6a EnWG informational unbundling.

NB Netzbetreiber

Distribution grid operator

Answers supplier registrations and terminations from the published Entscheidungsbäume, bills network use (NNE), settles Mehr-/Mindermengen, reports MaBiS Summenzeitreihen and executes Sperrung orders.

processd · netzbilanzd · mabis-syncd →

LF Lieferant

Energy supplier

Wins and switches customers, prices contracts against the tariff catalogue, bills retail consumption, issues EN 16931 e-invoices (XRechnung, ZUGFeRD) and collects by SEPA.

billingd · vertragd · invoicd →

MSB Messstellenbetreiber

Metering point operator

Runs the WiM device processes — installation, change of operator, fault handling — manages the smart-meter gateway lifecycle, and delivers metered values to everyone entitled to them.

edmd · makod →

ESA Energieserviceanbieter

Energy service provider

Orders measured values under the § 34 MsbG Wertebestellung, quotes its own measurement products, and invoices the service against the quotation the counterparty accepted.

makod — ESA messages →

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 reads the BDEW profiles as data, so an annual release is an import run rather than a rewrite. The domain layer never sees EDIFACT: it receives typed commands and emits typed events.

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.2"
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.

Parsing reference →  Validation reference →

The shape of the platform

Seventeen services along one path: transport and protocol at the edge, master data in the middle, settlement, billing and the customer's documents 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"] SPERRD["sperrd
Sperr-/Entsperraufträge"] 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"] MABIS["mabis-syncd
MaBiS Summenzeitreihen"] BILLINGD["billingd · invoicd
retail · EN 16931"] ACCT["accountingd
FI-CA ledger"] OUTPUTD["outputd
documents · delivery"] end subgraph ops["Operations"] OBSD["obsd
BNetzA KPIs · Fristen"] AGENTD["agentd
AI specialists"] PORTALD["portald
customer portal"] end PROCESSD --> MAKOD MAKOD --> SPERRD MAKOD --> MARKTD MARKTD --> VERTRAGD MARKTD --> EDMD EDMD --> NETZ EDMD --> EINSD EDMD --> BILLINGD EDMD --> MABIS VERTRAGD --> BILLINGD NETZ --> ACCT BILLINGD --> ACCT BILLINGD --> OUTPUTD ACCT --> OUTPUTD EINSD --> ACCT PORTALD --> BILLINGD OBSD -.->|"watches"| MAKOD AGENTD -.->|"MCP reads"| MARKTD AGENTD -.->|"MCP reads"| OBSD

One operator guide per service →

What you get

Protocol & process

Parse, validate and build all 17 EDI@Energy EDIFACT types plus the four DVGW gas formats, and 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, a hash-chained FI-CA ledger with § 147 AO period seals and Merkle inclusion proofs, and the documents themselves — rendered from operator templates and delivered with per-channel evidence.

billingd · invoicd · accountingd · outputd →

Operations & oversight

BNetzA KPI observability with Fristen tracking, a customer portal, and an agent plane over the platform's 163 MCP tools. Its 28 declarative specialists are advisory by construction: no active manifest grants a mutating tool, every model and tool call is a journaled effect, and a triage worklist carries what a person has to see.

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 API-Webdienste beside EDIFACT, the MaBiS-Hub. mako's domain layer is deliberately split from transport and format, so when the market moves, the platform moves with an import run — not a rewrite.

  • Auditable by construction — event-sourced processes, replayable state, hash-chained ledger with § 147 AO period seals.
  • Four market roles, one engine — NB, LF, MSB and ESA answer from the published Entscheidungsbäume, 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 NB demo: makod, marktd, processd
just build-demo
cd demos/nb-stp && docker compose up -d && bash smoke.sh

# Drive it from Python — generators, simulators, pytest plugin
pip install makotest

Full getting-started guide →

Experimental · pre-1.0: interfaces may change between releases, and a production deployment needs your own testing against your own counterparties. Dual-licensed MIT or Apache 2.0.