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 docker run ghcr.io/hupe1980/makod for the daemon.

482 / 482published Prüfidentifikatoren imported
469Prüfidentifikatoren routed
71MaKo workflows
17EDI@Energy message types
17services
4market roles (NB·LF·MSB·ESA)

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

German market law splits the industry into four roles, and every message has one on each end. mako implements all four from one engine — and a deployment scoped to a role contains no other role's code, which is what § 6a EnWG unbundling asks for.

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

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 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 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 protocol daemon
docker run --rm -p 8080:8080 \
  ghcr.io/hupe1980/makod:latest

# 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.