mako-markt
Market data library for German energy market communication (MaKo).
mako-markt is the domain library for Marktlokation (MaLo), Messlokation (MeLo),
VersorgungsStatus, NB network contracts, trading-partner, and process-correlation
management. It is the foundation for marktd, the production
Market Data Hub.
Key design choices:
- Typed
rubo4e::currentrecords —MaloRecord.datastores and returnsrubo4e::current::Marktlokation,MeloRecord.datastoresMesslokation, and so on. Schema is validated at every write boundary; invalid_typor enum values → 422. NbContractRecordcarries full BO4EVertragJSONB —data: serde_json::Valuestores the canonical BO4EVertragpayload alongside typed SQL columns (netzebene,bilanzierungsmethode,billing_schedule).vertragsartandvertragsstatusare extracted as indexed columns for fast SQL filtering.- 25 active
rubo4e::currenttypes —Marktlokation,Messlokation,Zaehler,Geraet,Vertrag,Energiemenge,Lastgang,Rechnung, and more.
Design principles
| Principle | Detail |
|---|---|
| Stateless library | No axum, no sqlx, no async runtime in this crate. All I/O lives in services/marktd. |
| Validated domain identifiers | [MaloId], [MeloId], and [MarktpartnerId] validate format and checksum at construction time — invalid IDs are rejected at the system boundary. |
| Temporal role assignments | lokationszuordnung entries carry valid_from/valid_to date ranges. Queries are always resolved against a reference date (German local time, CET/CEST). |
Generic AppState |
Seven generic type parameters — one per repository trait — enable fully static dispatch with no dyn Trait overhead. |
| AFIT | All repository traits use async fn in trait (stable since Rust 1.75, MSRV 1.89). |
Crate structure
mako_markt
├── domain Validated IDs (MaloId, MeloId, MarktpartnerId), Sparte, ProcessStatus
├── repository Repository traits + AppState + record types + PageResult
│ MaloRecord (data: Marktlokation JSONB, typed columns)
│ MeloRecord (data: Messlokation JSONB, standorteigenschaften JSONB)
│ NbContractRecord (data: Vertrag JSONB, vertragsart/vertragsstatus columns)
│ ZaehlerRecord (data: Zaehler JSONB), GeraetRecord (data: Geraet JSONB)
│ VersorgungsStatusRepository, LieferStatus, VersorgungsStatusRecord
│ PriCatRepository, PriCatVersion, PriCatDispatchState
├── error MdmError — RFC 7807-ready with status_u16, error_code, error_title
├── cloudevents InboundMakoEvent, MarktEvent, HMAC-SHA256 signing/verification
│ Emitted: de.markt.malo.updated, de.markt.nb-contract.updated,
│ de.markt.pricat.published, de.markt.versorgung.beliefert
├── makod_client HTTP client for the makod admin API
└── testing InMemory* test doubles (feature = "testing")
includes: InMemoryPriCatRepository
Domain identifiers
MaloId — 11-digit BDEW Marktlokations-ID
Validated with the BDEW alternating-weight check digit algorithm (BDEW Identifikatoren AWH V1.2 §2.1):
use MaloId;
let id = new?; // validates checksum
println!; // "51238696780"
MeloId — 33-character Messlokations-ID
use MeloId;
let id = new?;
MarktpartnerId — 13-digit BDEW/DVGW/GS1 Codenummer
Derives the NAD DE3055 agency code from the prefix (99… → 293, 98… → 332, other → 9):
use MarktpartnerId;
let mp_id = "9900357000004".?;
assert_eq!; // BDEW Strom
assert_eq!;
Note: Only GS1-issued 13-digit codes are true GLNs (NAD DE3055
9). BDEW-Codenummern (99…,293) and DVGW-Codenummern (98…,332) are not GLNs. UseMarktpartnerIdfor all market-participant identifiers — neverString.
Repository traits
All traits use AFIT and return Result<_, MdmError>. Every trait has two implementations:
| Implementation | Use |
|---|---|
Pg*Repository in services/marktd/src/pg/ |
Production — PostgreSQL via sqlx 0.8+ |
VersorgungsStatusRepository
Persists the current supply state for each MaLo. Records are automatically derived
from de.mako.process.completed events by marktd's EventBus pipeline.
LieferStatus values:
| Variant | Meaning |
|---|---|
Beliefert |
MaLo is actively supplied by a nominated LF |
Unbeliefert |
MaLo has no active supplier (Grundversorgungsfall) |
Grundversorgung |
§36 EnWG basic supply is active |
Ersatzversorgung |
§38 EnWG emergency supply is active (max 3 months) |
Ruhend |
Supply suspended, MaLo registered but dormant |
Stillgelegt |
MaLo decommissioned |
The at: Date parameter is always the current German local date (CET/CEST) so that
lokationszuordnung validity is evaluated against the correct calendar date, not UTC.
Temporal Lokationszuordnung
Each MaloRecord carries only the assignments valid at the requested reference date.
The storage layer uses a LEFT JOIN … AND valid_from <= $at AND (valid_to IS NULL OR valid_to >= $at).
CloudEvents
Outbound events emitted by marktd conform to CloudEvents 1.0 structured-mode JSON
(application/cloudevents+json). They carry markt* extension attributes and are
HMAC-SHA256 signed for delivery to ERP subscribers.
use ;
let event = new
.with_extensions;
let body = to_vec?;
let sig = compute_signature;
// send with `X-Markt-Signature: {sig}` header
Event source: urn:markt:tenant:{tenant_gln}
Error handling
MdmError is a thiserror-derived enum. Every variant maps to a stable HTTP status,
machine-readable error_code, and a human-readable error_title for RFC 7807 Problem
Details responses:
| Variant | Status | Code |
|---|---|---|
InvalidMaloId |
422 | invalid_malo_id |
InvalidMeloId |
422 | invalid_melo_id |
InvalidGln |
422 | invalid_gln |
NotFound |
404 | not_found |
VersionConflict |
412 | version_conflict |
Forbidden |
403 | forbidden |
Unprocessable |
422 | unprocessable |
Internal |
500 | internal_error |
Testing
Enable the testing feature to get InMemory* test doubles for every repository trait:
[]
= { = "../../crates/mako-markt", = ["testing"] }
use ;
use Arc;
let state = new;
Feature flags
| Flag | Enables |
|---|---|
| (default) | All domain types, traits, CloudEvents, makod client |
testing |
InMemory* test doubles — never enable in production builds |
PriCatRepository — PRICAT 27003 version history and dispatch
PriCatRepository stores versioned PRICAT snapshots and an audit log of every
outbound dispatch attempt.
Every PUT /api/v1/preisblaetter/{nb_mp_id} call in marktd:
- Writes to
preisblaetter(existing single-row store forinvoicd) - Inserts a versioned snapshot in
pricat_versions - Emits
de.markt.pricat.publishedvia the internal event channel
Dispatch states:
| State | Meaning |
|---|---|
Pending |
Stored; dispatch not yet started |
Queued |
Dispatch task picked this version up |
Done |
All active LF partners successfully reached |
Error |
Last attempt failed; retried on next background scan |
Auto-dispatch on LF partner registration: when a new LF partner is upserted
via PUT /api/v1/partners/{mp_id} in marktd, the latest PRICAT version for the
operator's NB GLN is automatically re-queued for dispatch to the new partner.
Relationship to makod and marktd
┌─────────────────────────────────────────────────┐
│ services/marktd (binary) │
│ axum 0.8+ · sqlx 0.8+ · utoipa 5 · jiff 0.2 │
│ Pg*Repository ←─── implements traits ─────┐ │
│ fanout worker ←─── mpsc events channel │ │
│ OIDC/JWT auth ←─── Cedar not used here │ │
└───────────────────────────┬─────────────────┘ │
│ uses │
┌───────────────────────────▼─────────────────┐ │
│ crates/mako-markt (library — this crate) │ │
│ domain · repository traits · AppState │ │
│ error · cloudevents · makod_client │ │
│ testing (feature = "testing") │ │
└─────────────────────────────────────────────┘
│ makes HTTP calls to
┌───────────▼─────────────────────────────────┐
│ services/makod (production daemon) │
│ EDIFACT · AS4 · event-sourced workflows │
└─────────────────────────────────────────────┘
mako-markt depends on neither axum nor sqlx. Both are confined to services/marktd.
This keeps the library independently testable with zero framework overhead.