processd Operator Guide
Operator guide for processd, the process decision engine: NB Anmeldung straight-through processing, LF and MSB answers, role-gated binaries and Cedar ABAC.
On this page 14 sections
- Role isolation
- Routes and authorization
- NB module — Anmeldung STP
- NB module — EoG gap closure (§36/§38 EnWG)
- LF module — answering what the market asks a supplier
- LF module — LFN bootstrap
- Gleichbehandlung evidence
- Configuration reference
- MCP tools
- Monitoring
- MSB module — WiM MSB-Wechsel STP
- MSB module — REQOTE auto-response
- MSB module — ESA Wertebestellung (WiM Teil 2 Kap. 4)
- MSB module — §14a Steuerungsauftrag auto-ORDRSP
processd is the process decision engine. It receives the market messages
its operator must answer, walks the published
EBD
(Entscheidungsbaum) for the message's
PID, dispatches the
answer through makod before the
Frist, and queues
whatever the tree cannot decide for an operator. Terms are defined in the
glossary; which PID belongs to
which process is the process map.
graph TB
marktd["marktd :8180<br/>fan-out"]
processd["processd :8580<br/>(this service)"]
makod["makod :8080"]
pg["PostgreSQL<br/>anmeldung_decisions · abmeldeanfragen<br/>approval_queue · eog_activations<br/>neuanlage_faelle"]
marktd -->|"de.mako.process.initiated<br/>de.markt.versorgung.gap-detected<br/>HMAC POST /webhook"| processd
subgraph NB ["NB module (--features nb-only)"]
NC["mako-pruefung<br/>NB: E_0622 · E_0623 · E_0607 · E_0608 · G_0011 · G_0007<br/>LF: E_0609 · E_0624 · E_0614 · E_0615<br/>STP target ≥ 95%"]
EOG["EoG gap closure<br/>§36/§38 EnWG · §38 timer"]
NC --> pg
EOG --> pg
end
subgraph LF ["LF module (--features lf-only)"]
LFA["LF answers · Strom 55007 / 55010 / 55013 / 55016 / 55607<br/>Gas 44007 / 44010 / 44013 / 44016"]
LFA --> pg
end
processd --> NB
processd --> LF
NB -->|"gpke.lieferbeginn.*<br/>gpke.lieferende.*<br/>gpke.eog.anmelden<br/>POST /api/v1/commands"| makod
LF -->|"gpke.nb-lieferende.bestaetigen/ablehnen<br/>geli.lieferbeginn.anmelden<br/>POST /api/v1/commands"| makod
NB & LF -->|"GET /api/v1/versorgung<br/>GET /api/v1/malos/{id}/grid"| marktd
Role isolation
Feature flags select which decision paths compile in, so a deployment carries
only the answers its Marktrolle gives and an nb-only binary cannot answer as
an LF:
[features]
role-lf-strom = ["mako-pruefung/role-lf"] # LF answers 55007/55010/55013/55016/55607
role-lf-gas = ["mako-pruefung/role-lf"] # LF Gas 44007 / 44010 / 44013 / 44016
role-nb-strom = ["mako-pruefung/role-nb"] # GPKE STP (55001, 55077, 55004, 55600/55601), EoG closure
role-nb-gas = ["mako-pruefung/role-nb"] # GeLi Gas An-/Abmeldung STP (44001, 44004)
role-msb = ["mako-pruefung/role-msb"] # REQOTE→QUOTES, §14a ORDRSP, MSB-answered MSB-Wechsel,
# ESA Wertebestellung (35003/17007/17008/39002)
lf-only = ["role-lf-strom", "role-lf-gas"]
nb-only = ["role-nb-strom", "role-nb-gas"]
msb-only = ["role-msb"]
integrated = ["role-lf-strom", "role-lf-gas", "role-nb-strom", "role-nb-gas", "role-msb"]
The flags gate handlers, not linked code: mako-gpke, mako-geli-gas and
mako-wim are unconditional dependencies, so a role-gated build still links
their catalogues. The legal separation of §§ 6a/7/7a EnWG happens at the level
of the legal entity and its operations; the build only makes the operator's
obligations readable off the binary.
PID 55016 „Kündigung" is an LF process. The Anwendungsübersicht der
Prüfidentifikatoren 4.0 (lfd. Nr. 20030) has it going LFN → LFA, answered
55017/55018 by the Altlieferant under E_0614, so it belongs to
role-lf-strom.
Routes and authorization
Every REST route takes the Claims extractor and evaluates one
Cedar action against
services/processd/policies/processd.cedar. There is no global auth
middleware; tests/it/authorization_guard.rs checks every mounted route for a
Claims extractor and an authorization call, and holds the action sets of code
and policy to each other in both directions.
| Route | Cedar action | Granted to |
|---|---|---|
POST /webhook | — (marktd HMAC signature + timestamp) | marktd |
GET /api/v1/decisions | read-decisions | any caller of the tenant |
GET /api/v1/queue | read-queue | any caller of the tenant |
GET /api/v1/eog | read-eog | any caller of the tenant |
GET /api/v1/neuanlage | read-neuanlage | any caller of the tenant |
POST /api/v1/queue/{id}/approve · /reject | decide-queue | NB, LF or MSB |
PUT /api/v1/neuanlage/{id}/identifikation | identify-neuanlage | NB |
POST /api/v1/start-supply[-gas] · end-supply[-gas] | initiate-supply | LF |
POST/GET /mcp | use-mcp, then a per-tool action | any caller of the tenant |
Roles come from the mako_roles JWT claim, the tenant from mako_tenant, which
must equal the deployment's own. The runner adds /health/live,
/health/ready and /metrics.
processd refuses to start without [oidc] and without [webhook] inbound_secret; the refusal names each missing one. allow_insecure_no_auth = true accepts both, for local development (just dev processd reads
dev/config/processd.toml).
NB module — Anmeldung STP
Decision pipeline (nb_module.rs)
de.mako.process.initiated
├─ Anmeldung (55001 verb. MaLo / 55077 erz. MaLo / 44001 Gas)
│ → GET marktd /api/v1/versorgung/{malo_id} → VersorgungsStatus
│ → GET marktd /api/v1/malos/{malo_id}/grid → MaloGridRecord
│ → GET marktd /api/v1/partners/{lf_mp_id} → partner_known
│ → mako_pruefung::evaluate(…) ← E_0622 / E_3005, the Vorprüfung
│ → mako_pruefung::evaluate_lieferbeginn(…) ← E_0623 / E_3007, what is answered
└─ Abmeldung (55004 Strom / 44004 Gas)
→ GET marktd /api/v1/versorgung/{malo_id} → VersorgungsStatus
→ mako_pruefung::evaluate_abmeldung(…)
Accept → anmeldung_decisions(Accept, Zustimmungscode)
[if auto_accept] → makod …bestaetigen (antwort_code, antwort_ebd)
else → approval_queue
Reject → anmeldung_decisions(Reject, Antwortcode)
→ makod …ablehnen (antwort_code, antwort_ebd, bemerkung)
Escalate → anmeldung_decisions(Escalate) → approval_queue
AnfrageErforderlich
→ INSERT abmeldeanfragen (the waiting AnmeldungAnfrage)
→ makod gpke.beendigung-zuordnung.anfragen → UTILMD 55010 to the LFA
…09:00 Uhr des 1. WT, or the LFA answers 55011/55012…
de.mako.abmeldeanfrage.beantwortet
→ take abmeldeanfragen → mako_pruefung::evaluate_lieferbeginn(…)
→ the Accept / Reject / Escalate paths above
Two trees, and on an assigned Marktlokation two phases. E_0622 is a
Vorprüfung: surviving it means only that the Anmeldung is not directly
refusable. If the Marktlokation is assigned at the Zuordnungsbeginn, the NB owes
the incumbent LFA an Anfrage zur Beendigung der Zuordnung (55010) before
answering the LFN, because E_0623 Prüfschritte 20–50 read that answer (GPKE
Teil 2 § 2.1.2 SD Lieferbeginn Nr. 1 Prüfschritt 4, Nr. 3).
The waiting AnmeldungAnfrage is written to abmeldeanfragen before the
Anfrage goes out, so a fast loopback answer always finds it; phase two replays
the same pure evaluation with one more fact. Resolution is a single
UPDATE … WHERE resolved_at IS NULL, so of the LFA's answer and the 09:00 lapse
exactly one answers the LFN.
Silence releases the Marktlokation. „Verstreicht die Frist, ohne dass eine
Antwort beim NB eingeht, gilt dies als Bestätigung nach Fall a)." A Widerspruch
other than A30 / A41 refuses the Anmeldung with E_0623 A50 / A57
(Z35 in Gas). Those two codes need a second SG4 STS+Z35 restating the LFA's
E_0624 code (GPKE Teil 2 § 2.1.2 Nr. 6); processd puts it on the ablehnen
command and makod refuses to render the message without it.
Geschäftsvorfall. On 55077/55078 SG4 STS+7 DE 9013 element 3 carries
ZW0 (ganze Marktlokation), ZW1 (bestehende Tranche) or ZW2 (neu zu bildende
Tranche); E_0622 Prüfschritte 300/310 branch on it, and a message carrying
none escalates. Geschäftsvorfall 3 asks all Tranchen-LFA and decides on
arithmetic (Prüfschritte 500–540): at least one release (A53), enough
percentage (A54), and whether a residual share would stay in the NB's own
Bilanzkreis on a direktvermarktungspflichtige Marktlokation (A55 / A56).
| Geschäftsvorfall-3 detail | Behaviour |
|---|---|
| Direktvermarktungspflicht (Prüfschritt 540) | From einsd — installed capacity against the 100-kW ceiling of §21 Abs. 1 Satz 1 Nr. 1 EEG; §20 Satz 1 Nr. 3 EEG pays the Marktprämie only from a pure EE-Bilanzkreis. No register answer → escalate |
| The registered share | SG8 Produkt-Code 9991000002090, Produkteigenschaft „prozentuale Aufteilung" (Muss on GV 3). An Aufteilungsfaktor or Aufteilung auf Technische Ressourcen escalates |
| Answers | One per Tranchen-LFA before deciding; a lapsed 09:00 Frist resolves whatever is outstanding as Zustimmung; an LFA code E_0624 does not publish escalates |
Three trees, three alphabets. Strom splits into two branches that share no Antwortcode, and Gas answers from a different Codeliste:
| Anwendungsfall | Tree | „andere Anmeldung in Bearbeitung" | Fristüberschreitung | Zustimmung |
|---|---|---|---|---|
| Strom, verbrauchende / ruhende MaLo | E_0622 15–70 | A06 | A07 | A51 (E_0623) |
| Strom, erzeugende MaLo / Tranche | E_0622 220–830 | A45 | A34/A28/A29/A30/A32/A35/A44 | A58 (E_0623) |
| Gas | E_3005 / G_0011 | ZC5 | E17 | E15 (G_0012) |
| Abmeldung Strom, verbrauchende / ruhende MaLo | E_0607 10–140 | — | A02 | A11 |
| Abmeldung Strom, erzeugende MaLo / Tranche | E_0607 500–620 | — | A22 (A21 Datum) | A27 |
| Abmeldung Gas | E_3019 / G_0007 | — | E17 | E15 |
Every code is resolved through mako_pruefung::codes against the tree that
publishes it; RejectReason can only be built from a resolved entry, and
makod re-checks it at the command boundary, where the code's Cluster
decides the response PID. A Bestätigung carries a code too (SG4 STS+E01 is
Muss), so NbEntscheidung::Accept carries A51 / A58 / E15.
mako_pruefung::nb::evaluate — the Anmeldung
Strom, verbrauchende / ruhende Marktlokation (E_0622 Prüfschritte 15–70):
| Prüfschritt | Rule | On failure |
|---|---|---|
| — | MaloGridRecord exists for the MaLo | Escalate (a data gap, not a ground to refuse) |
| 15 | Vorlauffrist — one full Werktag between receipt and Zuordnungsbeginn (LFW24, all Transaktionsgründe) | Reject A07 |
| 30 | MaLo participates in MaKo | Reject A02 |
| 60 | Zuordnungsermächtigung: Bilanzierungsgebiet matches, LF in the partner directory | Reject A05 |
| 70 | No other Anmeldung in Bearbeitung | Reject A06 |
A ruhende Marktlokation is not refused: Prüfschritt 30's Hinweis names only stillgelegte Marktlokationen and the Modell-2-Zuordnung.
Strom, erzeugende Marktlokation (Prüfschritte 220–830) uses A45 / A25
for the same two questions, then picks one of the six Vorlauffristen of
GPKE Teil 2 § 2.1.1, keyed on (Geschäftsvorfall, bestehende, angemeldete Veräußerungsform). The angemeldete one is SG10 CCI+Z22; the bestehende one
and the Ausfallvergütung flag come from einsd
(GET /api/v1/anlagen/by-malo/{malo_id}/veraeusserungsform, [nb] einsd_url),
because wire code Z90 covers both Einspeisevergütung and Ausfallvergütung,
whose Fristen differ. A missing fact escalates; a CCI+Z22 code outside the
published four is dropped at ingest with a warning. The Monatserster rule rests
on § 21b Abs. 1 Satz 2 EEG 2023.
Gas (G_0011) runs the A03/A04/A16/A17 identification checks
first, then E17 Fristüberschreitung, E13 Bilanzierungsproblem and ZC5 /
Z08 for a conflicting or duplicate Anmeldung.
mako_pruefung::nb::evaluate_neuanlage — the Neuanlage, E_0608
Inbound 55600 / 55601 register a Marktlokation commissioned for the
first time. Prüfschritte 110 / 590 loop: a Marktlokation the NB cannot yet
identify is re-checked daily for 60 Werktage and only then refused A07 /
A16. processd keeps the case log neuanlage_faelle and a daily worker:
| Endpoint | Purpose |
|---|---|
GET /api/v1/neuanlage?status=offen | The Prüflauf view, with letzter_pruefungstag (a refusal is admissible only after it) and the pruefungen count |
PUT /api/v1/neuanlage/{id}/identifikation | The MaLo-ID the NB matched from its NIS/GIS; the next pass walks the tree with it |
The branches share no code (A01–A09 verbrauchend, A10–A19 erzeugend).
The Vorlauffrist is measured against the Übertragungstag, so a re-evaluation on
day 40 reaches the verdict of day one.
mako_pruefung::nb::evaluate_abmeldung — the Abmeldung, E_0607 / E_3019
Inbound 55004 / 44004, answered 55005/55006 (44005/44006). Strom splits into two branches that share no Antwortcode:
| Prüfschritt | Rule | verbrauchend / ruhend | erzeugend |
|---|---|---|---|
| — | MaLo known to this NB, and the LF holds an assignment or a Lieferende at this date is already settled | Escalate | Escalate |
| 50 / 500+520 | Vorlauffrist — verbrauchend: one full Werktag; erzeugend: Monatserster (A21) one month ahead (A22), § 21b Abs. 1 EEG 2023 | A02 | A21 / A22 |
| 90 / 570 | Aufhebung einer zukünftigen Zuordnung (ZH2) nennt den bestätigten Zeitpunkt | A06 | A23 |
| 80 | Z41 „Ende der ESV ohne Folgelieferung" setzt eine E/G voraus, die innerhalb von 3 Monaten vor dem Endezeitpunkt begann | A05 | — |
| 100–130 / 580–610 | Kein bereits bestätigtes Lieferende zum selben Datum | A09 | A25 |
| 140 / 620 | — | Zustimmung A11 | Zustimmung A27 |
Gas (G_0007) has one code space: E17 Vorlauffrist, Z08 bereits bestätigt,
E15 Zustimmung.
Not decided automatically: Prüfschritte 10–30 (Kundenanlagen-Herauslösung, § 20
Abs. 1d EnWG / § 10c EEG) need a fact the projection does not record, so A01
is unreachable; Prüfschritt 130 / 610 asks about an earlier message's
Transaktionsgrund and escalates. Escalation is the § 20 Abs. 1 Satz 1
EnWG-safe direction: an unfounded Ablehnung binds a customer to a supplier they
have left.
STP rate and [nb] auto_accept
The target is ≥ 95 % straight-through processing on a verbrauchende Anmeldung.
A missing malo_grid record escalates; provision grid records (the NB's own
topology, not MaStR) via marktd's NB-role PUT /api/v1/malos/{malo_id}/grid.
An erzeugende Marktlokation additionally needs [nb] einsd_url; without it
every 55077 escalates. Monitor via GET /api/v1/decisions or get_stp_rate.
Keep auto_accept = false (the default; PROCESSD_NB__AUTO_ACCEPT) until grid
records cover the portfolio, the partner directory holds every expected LF
MP-ID, and one manual review cycle confirmed the codes.
Affiliate guard — § 20 Abs. 1 Satz 1 EnWG
In a vertically integrated NB+LF utility, auto-acceptance is always blocked for An- and Abmeldungen from the operator's own LF:
initiating LF MP-ID == [identity] own_mp_id → initiator_is_affiliate = true
auto_accept overridden to false
→ approval_queue (operator review)
[identity] own_mp_id must match makod's primary party. Startup logs the coding
authority derived from it (293 = BDEW, 332 = DVGW) and warns on a GS1 GLN. The
flag lands on every anmeldung_decisions row and on obsd's ProcessProjection;
see the obsd Gleichbehandlung parity report.
Selbstzahler — the Lieferantenwechsel carve-out
GPKE Teil 1 (BK6-24-174 Anlage 1a), Vorbemerkung: „Ist der Letztverbraucher selbst Netznutzer, so tritt er in die Rolle des Lieferanten i.S. dieser Prozessbeschreibung […]. Eine Ausnahme bilden die Meldungen des Lieferanten im Rahmen des Lieferantenwechsels."
processd reads the Netznutzungsvertrag in force the day before the requested
Zuordnungsbeginn (GET /api/v1/nb-contracts/by-malo/{malo_id}?on=) and holds
the decision for the operator when it displaces a Selbstzahler:
transaktionsgrund == "E03" (Wechsel)
∧ incumbent netznutzer_typ == LETZTVERBRAUCHER
→ approval_queue (operator review), Antwortfrist attached
An Einzug (E01, E02) stays automated and skips the lookup.
NB module — EoG gap closure (§36/§38 EnWG)
Every consuming Marktlokation must be assigned to a Bilanzkreis at all times (GPKE Teil 2 Kap. 2.3). The EoG module closes supply gaps:
de.markt.versorgung.gap-detected (marktd: an interval no supplier
│ covers — a Lieferende the successor
│ does not follow on, or a Fall-b
│ Bestätigung 55002/55078/44002)
├─ record case in eog_activations (idempotent per MaLo)
├─ [eog.auto_activate] GET /api/v1/grundversorger/{nb_mp_id}?sparte=…
│ └─ found → Strom: gpke.eog.anmelden → UTILMD 55013;
│ Gas: geli.eog.anmelden → UTILMD G 44013 → makod → E/G
│ (Zuordnungsbeginn = day after Lieferende — retroactive allowed)
│ └─ missing → case stays `detected`; operator provisions the
│ §36 Abs. 2 Feststellung and re-triggers
└─ de.markt.versorgung.eog-begonnen → case `active`
(eog_art = Ersatz-/Grundversorgung as classified by the E/G in 55014;
eog_seit = Zuordnungsbeginn)stateDiagram-v2
[*] --> detected: gap-detected (uncovered interval)
detected --> detected: Grundversorger missing (operator provisions §36 Abs. 2)
detected --> angemeldet: eog.anmelden → UTILMD 55013 / G 44013
angemeldet --> active: E/G Bestätigung 55014 (eog-begonnen)
angemeldet --> detected: E/G Ablehnung 55015 (EBD E_0615)
active --> expiring: §38 timer, warn_days_before_expiry ahead
expiring --> expired: three-month maximum reached (§38 Abs. 4 S. 1)
active --> [*]: Grundversorgung (no statutory maximum)
expired --> [*]: operator follow-up (Grundversorgung regime / vertragliche Ersatzbelieferung / Anschlussunterbrechung)
A daily worker enforces the three-month maximum (§38 Abs. 4 S. 1 EnWG, calendar
months from eog_seit): warn_days_before_expiry ahead the case turns
expiring, at expiry expired; both emit
de.markt.versorgung.ersatz-auslaufend to [eog] notify_webhook_url. After
expiry, Haushaltskunden pass into Grundversorgung without a market message;
otherwise the NB secures the Bilanzkreis (vertragliche Ersatzbelieferung, STS
E06) or interrupts the Anschlussnutzung. Cases are listed by
GET /api/v1/eog?status=detected|angemeldet|active|expiring|expired.
LF module — answering what the market asks a supplier
| Sparte | Inbound | Process | EBD | Answers | Frist |
|---|---|---|---|---|---|
| Strom | 55007 | Lieferende von NB an LF | E_0609 | 55008 / 55009 | 05:00 Uhr des 1. WT nach dem ÜT |
| Strom | 55010 | Beendigung der Zuordnung | E_0624 | 55011 / 55012 | 09:00 Uhr des 1. WT nach dem ÜT |
| Strom | 55013 | Anmeldung E/G (§ 36 / § 38 EnWG) | E_0615 | 55014 / 55015 | 15:00 Uhr am ÜT, sonst 15:00 Uhr des 1. WT |
| Strom | 55016 | Kündigung (LFN → LFA) | E_0614 | 55017 / 55018 | Ablauf des 1. WT nach dem ÜT |
| Strom | 55607 | Ankündigung Zuordnung LF (erz. MaLo / Tranche) | E_0603–E_0606 | 55608 / 55609 | 15:00 Uhr am ÜT, sonst 15:00 Uhr des 1. WT |
| Gas | 44007 | Lieferende von NB an LF | E_3002 | 44008 / 44009 | Ablauf des 3. WT |
| Gas | 44010 | Beendigung der Zuordnung | E_3020 | 44011 / 44012 | Ablauf des 3. WT |
| Gas | 44013 | Anmeldung E/G | E_3008 | 44014 / 44015 | Ablauf des 2. WT |
| Gas | 44016 | Kündigung beim Altlieferanten | E_3001 | 44017 / 44018 | Ablauf des 3. WT |
For the two „am ÜT" rows GPKE Teil 2 (§ 2.3.2.2 resp. § 2.4.2.2) states two
windows depending on whether the Zuordnungsbeginn lies in the future.
mako-fristen publishes the same-day instant, rolling to 15:00 on the next
Werktag when that instant is already past, after the cut-off or on a
non-Werktag.
The same process carries the same command name in both Sparten —
{gpke,geli}.nb-lieferende.*, {gpke,geli}.beendigung-zuordnung.*,
{gpke,geli}.kuendigung.*.
How a decision is made
de.mako.process.initiated (an answerable PID)
→ LfAnfrage from the CloudEvent — Transaktionsgrund and its Ergänzung
(ZW3/ZW4/ZW5/ZAP, which selects the tree's branch), Termin, ÜT
→ LfVertragslage from marktd (supply state) + vertragd (contract state)
→ pruefe_*(…)
Antwort → makod command carrying the Antwortcode and its EBD
Eskalation → approval_queue, naming the Prüfschritt it stopped at
The Antwortcode's Cluster selects the answer PID: A36 „Vertragsverhältnis
wurde beendet" rides 55011, A35 „Es besteht eine Vertragsbindung" 55012.
Two facts come from the message itself:
- Object of the Vorgang —
SG4 STS+7DE 9013 element 3:ZW3erzeugende,ZW4verbrauchende,ZW5Tranche,ZAPruhende Marktlokation. The halves ofE_0609andE_0624answer from different code ranges, so a missing Ergänzung escalates. The same element carriesZW0–ZW2on a 55077 andZW8–ZX1on a 55607; the PID decides which code space applies. - Vorlauffrist (
E_0609Prüfschritt 40,E_3002E17) —mako_fristen::abmeldungper GPKE Teil 2 § 2.5.2 Nr. 1: a calendar month for EEG-Marktlokationen, the day before the last Werktag otherwise. An erzeugende Marktlokation escalates unless the deployment can say which applies.
55607: silence is not a lapse. GPKE Teil 2 § 2.4.2.2 Nr. 3 has the NB assign
the supplier „aufgrund fehlender Antwort" with the Bilanzkreis on file. The
substance of the answer is that Bilanzkreis (A01 / A99 are the only codes).
UTILMD AHB Strom 2.2 Bedingungen [161]–[164] map ZW8–ZX1 onto
E_0603–E_0606. The BK rides SG8 SEQ+Z79, Produkt-Code 9991000002082,
Merkmalswert in SG10 CAV+ZV4 (the AHB admits SG4 FTX+ACB on the 55609
only, Bedingung [48]). [[lf.bilanzkreise]] is keyed on the
Bilanzierungsgebiet because MaBiS § 10.2.1 grants the Zuordnungsermächtigung
„je ZRT, BG, BK und LF"; several BKs for one regime are an operator choice.
Contract facts
| Prüfschritt | Question | Source |
|---|---|---|
E_0624 5 | Ging die Anfrage bis 07:00 Uhr des nächsten WT ein? | SG4 DTM+154 on the message |
E_0624 20 | Besteht zum Folgetag noch eine Zuordnung? | marktd |
E_0624 50 | Ist der Kunde aus der Anfrage identisch mit dem Kunden beim LFA? | SG12 NAD+Z09, compared by vertragd |
E_0624 60 | Hat der LFA Informationen, dass sein Kunde nicht ausgezogen ist? | vertragd |
E_0624 90 / 220 | Bleibt das Vertragsverhältnis zum Folgetag bestehen? | vertragd — kuendigung_zum and vertragsende, whichever cuts first |
E_0614 40 / 50 / 80 | Wurde der Vertrag bereits gekündigt, und zu welchem Datum? | vertragd — kuendigung_zum only |
E_0614 70 / 580 | Ist der Vertrag zum Kündigungstermin kündbar? | vertragd — naechstmoeglicher_kuendigungstermin ≤ Termin |
E_0614 500 | Liegt zu dem genannten Objekt ein Vertrag vor? | vertragd — a contract row |
E_0609 40 | Wurde die Vorlauffrist eingehalten? | mako-fristen |
E_0615 20 | Liegt die MaLo im Grundversorgungsgebiet des Empfängers? | [lf] grundversorgungs_netzgebiete |
Prüfschritt 50 compares names, not strings: processd forwards NAD+Z09 as
?kunde= and vertragd matches token sets; a shared Nachname alone is
Unklar and escalates (see vertragd). Without
[vertragd] url every contract fact is Bekannt::Unbekannt and a decision
reaching one escalates. auto_respond = false means an operator decides: the
walk still runs and its outcome is queued with the Antwortfrist.
The E/G Anmeldung has its own switch
E_0615 / E_3008 is the only Anmeldung whose Zustimmung accepts a statutory
duty (§ 36 / § 38 EnWG), so dispatch needs [lf] eog_auto_respond = true on
top of auto_respond. An empty grundversorgungs_netzgebiete escalates every
55013 / 44013. A Zustimmung also needs the Versorgungsart (SG10 CCI+Z36:
ZC9, ZD0, ZE3) from [lf] eog_versorgungsart and the Bilanzkreis from
[[lf.bilanzkreise]]; missing either, the answer is queued with the field
named.
Approval queue
The queue is shared by every compiled role. Each entry stores the makod
command for approve and for reject, resolved at enqueue, and the bodies:
| Entry | Approve sends | Reject sends |
|---|---|---|
A held decision (auto_accept / auto_respond off, a § 20 EnWG affiliate, a Selbstzahler) | the answer the automatic path would have sent, frozen at enqueue (answer_payload) | the other command, with the operator's Antwortcode |
| An escalation | the operator's answer | the operator's answer |
What an answer is addressed by (the MeLo of a WiM order, the tx_id of a § 14a
Steuerungsauftrag, the location and Messprodukt of an ESA order) is frozen on
both (answer_payload, reject_payload). The operator states the rest:
{ "antwort_code": "A01", "antwort_tree": "E_0622", "antwort_codeliste": "E_0622", "bemerkung": "…" }
The body is optional and refuses unknown keys. A field the frozen answer
already holds cannot be replaced (422): an approval sends the answer that was
shown. Every queueable command has an answer contract (ANSWER_CONTRACTS in
src/pg/approval.rs) naming the fields makod needs; a decision missing one is
422 with the claim released, before anything is sent. cargo xtask check-approval-commands refuses an enqueue site naming a command without a
contract. The ESA Werteanfrage dispatches its refusal only; its Angebot needs
prices and OBIS-Kennzahlen (QUOTES AHB 1.1a §4.3) and goes through the makod
command API.
expires_at is the business Frist from mako_fristen::antwort less one hour:
| Trigger | Frist | Source |
|---|---|---|
| GPKE Anmeldung 55001 / 55077 | 11:00 Uhr des 1. WT nach dem ÜT | BK6-24-174 Teil 2, SD Lieferbeginn 5/6 |
| GPKE Abmeldung 55004 | 06:00 Uhr des 1. WT nach dem ÜT | BK6-24-174 Teil 2, SD Lieferende von LF an NB 2/3 |
| GPKE LF answer 55007 | 05:00 Uhr des 1. WT nach dem ÜT | BK6-24-174 Teil 2, SD Lieferende von NB an LF 2 |
| GPKE LFA answer 55010 | 09:00 Uhr des 1. WT nach dem ÜT | BK6-24-174 Teil 2, SD Lieferbeginn 4 |
| GPKE LFA answer 55016 | Ablauf des 1. WT nach dem ÜT | BK6-24-174 Teil 2, SD Kündigung 2 |
| GeLi Gas LF answer 44007 | Ablauf des 3. WT | AWH GeLi Gas 2.0 Kap. 2.3.2 Nr. 2 |
| GeLi Gas LFA answer 44010 | Ablauf des 3. WT | AWH GeLi Gas 2.0 Kap. 2.5.2 Nr. 4 |
| GeLi Gas LFA answer 44016 | Ablauf des 3. WT | GeLi Gas 3.0 Kap. 3.1 |
| GeLi Gas Anmeldung 44001 | Ablauf des 4. WT nach Eingang | BK7-24-01-009 Kap. 3.2.3 |
| GeLi Gas Abmeldung 44004 | Ablauf des 3. WT nach Eingang | BK7-24-01-009 Kap. 3.2.2 |
| WiM MSB-Wechsel 55039 / 55042 / 55051 / 55168 | 3 / 5 / 7 / 1 WT | WiM Strom Teil 1 |
| GPKE Neuanlage 55600 / 55601 | 00:00 Uhr des 61. WT nach dem ÜT | BK6-24-174 Teil 2 § 2.2.2 (60 WT täglicher Prüflauf, E_0608) |
| GPKE Sperr-/Entsperrauftrag 17115 / 17117 / 39000 | spätester ÜT ist der 1. WT nach dem ÜT | BK6-24-174 Teil 2 §§ 3.5.1.2 / 3.5.2.2 / 3.5.3.2 Nr. 2 |
| GPKE Anfrage Sperrung an den MSB 17116 | 3. WT nach dem ÜT — Fristverstreichen gilt als Zustimmung | BK6-24-174 Teil 2 § 3.5.1.2 Nr. 4 |
| GPKE Teil 4 Stammdaten-Rückmeldung 55109 / 55230 / 55557 / 55639–55643 / 55693 | 2. WT nach dem ÜT | BK6-22-024 Anlage 1d (GPKE Teil 4) §§ 1.4.3 / 1.4.4 Nr. 2 |
| GPKE Teil 2 Bearbeitungsstand Abrechnungsdaten 55156 / 55220 / 55673 | 2. WT nach dem ÜT | BK6-24-174 Teil 2 §§ 3.1.1.2 / 3.1.2.2 / 3.1.3.2 |
The 45-minute APERAK window is a separate clock, owned by makod.
GET /api/v1/queue → list Pending entries (review before expires_at)
POST /api/v1/queue/{id}/approve → dispatch the approve command via makod, mark Approved
POST /api/v1/queue/{id}/reject → dispatch the reject command via makod, mark Rejected
The handlers claim the entry (status = 'Pending' guard) before
dispatching and release the claim if the body is incomplete or the dispatch
fails, so a decided entry never re-sends and two operators cannot send both
answers. decided_by records the deciding principal's sub. A task running
every 60 s marks stale entries Expired; expired entries log a WARN and are
reconciled manually.
LF module — LFN bootstrap
Strom: POST /api/v1/start-supply
Initiates a GPKE Lieferbeginn (UTILMD 55001) with LFW24 Vorlauffrist validation.
curl -X POST http://processd:8580/api/v1/start-supply \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"malo_id": "10001234558", "lieferbeginn_datum": "2026-10-01",
"bilanzkreis": "11XBK-STD-----9"}'| Field | Required | Notes |
|---|---|---|
malo_id | ✓ | 11-digit Strom Marktlokations-ID |
lieferbeginn_datum | ✓ | ISO-8601 date (YYYY-MM-DD) |
bilanzkreis | ✓* | SG8 SEQ+Z79 Produktpaket, Produkt-Code 9991000002082 |
* Optional only where [[lf.bilanzkreise]] declares exactly one standard
entry. UTILMD AHB Strom 2.2 Kap. 5.3 makes the Produktpaket Muss on a 55001, so
a request resolving to none is 422 MISSING_BILANZKREIS.
Vorlauffrist (BK6-24-174 GPKE Teil 2, SD Lieferbeginn Prozessschritt 1):
„Spätester ÜT ist der Tag vor dem letzten WT vor dem Zuordnungsbeginn" —
day-granular, no time-of-day cutoff. The earliest Lieferbeginn is the calendar
day after the next Werktag after today (Berlin date); a date before today is
RETROACTIVE_DATE. The response carries earliest_lieferbeginn and
berlin_date_at_submission.
Gas: POST /api/v1/start-supply-gas
Initiates a GeLi Gas Lieferbeginn (UTILMD 44001).
curl -X POST http://processd:8580/api/v1/start-supply-gas \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"malo_id": "10001234558",
"zaehlpunkt": "DE00123456789012345678901234567890",
"process_date": "20261001",
"bilanzkreis": "9870000000006"
}'| Field | Required | Notes |
|---|---|---|
malo_id | ✓ | Gas-MaLo-ID — rendered into SG5 LOC+Z16; supplied by the ERP |
zaehlpunkt | ✓ | Zählpunktbezeichnung (RFF+Z13) |
process_date | ✓ | Lieferbeginn date (YYYYMMDD, CET/CEST) |
bilanzkreis | ✓* | SG10 CCI+Z19 DE 7037 (UTILMD AHB Gas 1.2, Muss on 44001) — Gas has no Produktpaket |
transaktionsgrund | — | E03 Wechsel (default), E01/E02 Einzug |
* Same fallback as Strom. A Lieferantenwechsel needs 10 WT Mindestvorlauf
(AWH GeLi Gas 2.0 Kap. 2.5.2 Nr. 1), enforced here; an Einzug passes with a
vorlauffrist_hinweis, because the NB corrects the date rather than rejecting
(Kap. 4). The GNB answers 44002 / 44003 by Ablauf des 4. WT (Kap. 3.2.3).
POST /api/v1/end-supply / end-supply-gas take malo_id and
lieferende_datum and forward gpke.lieferende.anmelden /
geli.lieferende.anmelden; the notice period is vertragd's to enforce.
Gas Datenabruf
Abrechnungsbrennwert and Zustandszahl are requested through makod's command
API (geli.datenabruf.anfragen, ORDERS 17103, payload malo_id and the
required zeitspanne_bis): the GNB answers within ten Werktage, but not before
the 10. Werktag of the month after the Zeitspanne ends (GeLi Gas 3.0 Kap. 4.2).
The answer is MSCONS 13007 or ORDRSP 19103; a delivery updates edmd
meter_billing_periods via record_gas_quality.
Gleichbehandlung evidence
Every anmeldung_decisions row carries initiator_is_affiliate (TRUE when
lf_mp_id == own_mp_id) — the evidence behind the § 7a Abs. 5 EnWG
Gleichbehandlungsbericht that obsd
assembles. The duty it evidences is § 20 Abs. 1 Satz 1 EnWG: a systematically
faster decision for affiliates is a violation.
Configuration reference
processd reads a TOML file (PROCESSD_CONFIG, default ./processd.toml);
"env:VAR_NAME" values defer secrets to the environment, and any key can be
overridden by PROCESSD_<SECTION>__<KEY> (e.g. PROCESSD_DATABASE__URL).
Unknown keys are refused, and a config error names every offending field.
cargo test -p processd --test it -- published_config parses the block below.
Full processd.toml reference
[http]
addr = "0.0.0.0:8580" # default
[database]
url = "env:DATABASE_URL" # required
pool_size = 10 # default
[identity]
own_mp_id = "9900357000004" # required — must match makod.toml [[party]] primary
tenant = "" # optional; defaults to own_mp_id
[makod]
url = "http://makod:8080" # required
api_key = "env:MAKOD_API_KEY" # required
[marktd]
url = "http://marktd:8180" # required
api_key = "env:MARKTD_API_KEY" # required
# Optional. The LF and MSB modules read contract facts from it; without it
# every contract fact is `Unbekannt` and the decisions asking one escalate.
[vertragd]
url = "http://vertragd:9780"
api_key = "env:VERTRAGD_API_KEY"
[webhook]
inbound_secret = "env:INBOUND_WEBHOOK_SECRET" # required unless allow_insecure_no_auth
[subscription]
# Self-registers with marktd on startup (PUT /api/v1/subscriptions/{subscriber_id},
# retried with backoff up to 30 s).
webhook_url = "http://processd:8580/webhook" # optional; omit to skip registration
subscriber_id = "processd" # default
# Comma-separated. The default is de.mako.process.initiated +
# de.markt.versorgung.{gap-detected,eog-begonnen,changed}; leaving one out
# silently disables the module that consumes it.
# event_types = "de.mako.process.initiated,de.markt.versorgung.gap-detected,de.markt.versorgung.eog-begonnen,de.markt.versorgung.changed"
[nb]
auto_accept = false # true → dispatch bestaetigen automatically on Accept
gas_bearbeitungsfrist_wt = 3 # AWH GeLi Gas 2.0 Kap. 2.2
einsd_url = "" # EEG-/KWKG-Register; without it every 55077 escalates
einsd_api_key = ""
[lf]
auto_respond = true # false → every inbound LF process routed to approval_queue
# NB MP-IDs whose area this supplier is Grund-/Ersatzversorger in (E_0615
# Prüfschritt 20). Empty → every 55013 / 44013 escalates.
grundversorgungs_netzgebiete = ["9900000000001"]
eog_auto_respond = false # E_0615 / E_3008 dispatch, on top of auto_respond
# SG10 CCI+Z36 on a 55014: ZC9 §38 Ersatzversorgung, ZD0 §36 Grundversorgung,
# ZE3 vertragliche Ersatzbelieferung. Unset → an automatic Zustimmung is queued.
eog_versorgungsart = "ZD0"
# The Bilanzkreise a 55607 Zustimmung (and a start-supply default) may name,
# by Bilanzierungsgebiet and regime. Omit `bilanzierungsgebiet` for the
# fallback row; no table → every 55607 escalates.
[[lf.bilanzkreise]]
bilanzierungsgebiet = "11YN-BG-EON---X"
eeg = ["11XBK-EEG-----1"]
kwkg = ["11XBK-KWKG----5"]
standard = ["11XBK-STD-----9"]
[msb]
auto_accept = false # true → dispatch the MSB-Wechsel Bestätigung
auto_preisanfrage = true # false → the REQOTE goes to the approval queue
[esa] # WiM Teil 2 Kap. 4 — the MSB's answers to an ESA
auto_accept = false # true → dispatch the E_0256/E_0257/E_0254 Zustimmungscode
auto_reject = false # true → dispatch a deterministic Ablehnungscode
accept_after_bindungsfrist = false # E_0256 Prüfschritt 2 — a commercial decision
[eog] # §36/§38 EnWG gap closure (NB role)
auto_activate = false # true → dispatch gpke.eog.anmelden on gap-detected
default_transaktionsgrund = "ZT6" # SG4 STS DE9013 for automatic Anmeldungen
warn_days_before_expiry = 14 # §38 Abs. 4 3-month warning lead
# notify_webhook_url = "https://erp.example/hooks/eog" # ersatz-auslaufend CloudEvents
# notify_webhook_secret = "env:EOG_WEBHOOK_SECRET"
# Required unless allow_insecure_no_auth = true.
[oidc]
issuer = "https://login.microsoftonline.com/{tenant-id}/v2.0"
audience = "api://mako-processd"
# jwks_refresh_secs = 300 # default
# [mcp] # optional /mcp bearer keys beside OIDC
# api_key = "env:PROCESSD_MCP_API_KEY"
# [[mcp.named_keys]] # one named key per agent, so audit logs say which
# name = "agentd"
# api_key = "env:AGENTD_MCP_KEY"| Flag / Env var | Default | Description |
|---|---|---|
--check | — | Probe the running instance's /health/ready on loopback and exit 0/non-zero (container HEALTHCHECK) |
PROCESSD_CONFIG | processd.toml | Path to the config file |
RUST_LOG / LOG_LEVEL | info | Log level |
OTEL_EXPORTER_OTLP_ENDPOINT | — | OTLP endpoint; unset disables tracing export |
MCP tools
/mcp (Streamable HTTP) takes an OIDC token or an [mcp] API key. The
transport checks use-mcp, then each tools/call against the tool's own Cedar
action; a tool with no mapped action is refused 403. approve_queue_entry /
reject_queue_entry stamp the verified caller as decided_by (an
unattributable decision is refused) and call processd's own
POST /api/v1/queue/{id}/approve / /reject over the loopback, so the REST
handler's decide-queue check runs as well.
| Tool | Cedar action | Description |
|---|---|---|
list_decisions | read-decisions | Last N Anmeldung decisions with ERC codes and affiliate flag |
get_decision | read-decisions | Single Anmeldung decision by process_id (UUID) |
get_stp_rate | read-decisions | STP rate over last N days vs. 95 % target |
get_stp_breakdown_by_erc | read-decisions | Rejection breakdown by ERC code |
list_affiliate_decisions | read-decisions | Decisions involving affiliated suppliers (Gleichbehandlung evidence) |
list_pending_approvals | read-queue | Pending approval queue entries (most urgent first) |
get_queue_entry | read-queue | Single queue entry by UUID |
approve_queue_entry | decide-queue | Dispatch the entry's answer; takes antwort_code / antwort_tree / antwort_codeliste / bemerkung for an escalation |
reject_queue_entry | decide-queue | Dispatch the refusal with the operator's antwort_code, or bemerkung as the Begründung |
Four guided prompts ship alongside them — triage-nb-rejection,
investigate-stp-drop, triage-msb-wechsel and trigger-lieferbeginn.
Monitoring
| Metric | Type | Description |
|---|---|---|
processd_decisions_total{decision,pid} | counter | NB STP decisions by outcome (Accept/Reject/Escalate) and PID (55001/55077/55004/44001/44004) |
processd_approval_queue_pending | gauge | Processes waiting for an operator decision |
processd_approval_queue_overdue | gauge | Pending entries past expires_at — the answer deadline has been missed |
processd_eog_open | gauge | Ersatz-/Grundversorgung cases not yet closed (§ 36/§ 38 EnWG) |
| Alert | Condition | Usual cause |
|---|---|---|
| STP rate | Accept / total < 90 % (target ≥ 95 %) | missing malo_grid records |
| Escalation share | Escalate / total > 5 % | grid or register coverage |
| Overdue | processd_approval_queue_overdue > 0 | a business answer Frist was missed |
| Decision latency | > 10 s | marktd connectivity |
MSB module — WiM MSB-Wechsel STP
An NB build (role-nb-strom / role-nb-gas) answers 55042/44042 (Anmeldung
MSB) and 55051/44051 (Ende MSB); role-msb answers 55039/44039 (Kündigung MSB)
and 55168/44168 (Verpflichtungsanfrage). One handler serves both Sparten
because AWH WiM Gas 2.0 restates WiM Strom Teil 1 use-case for use-case. STP
target: ≥ 80 %. The Prüfschritte are mako_pruefung::msb.
de.mako.process.initiated (PID 55039 / 55042 / 55051 / 55168)
→ GET marktd /api/v1/melos/{melo_id} ← MeLo known?
→ GET marktd /api/v1/partners/{msb_mp_id} ← Rahmenvertrag § 9 Abs. 1 Nr. 3 MsbG?
→ mako_pruefung::msb::pruefe_{anmeldung,abmeldung,kuendigung}
Accept → wim.geraetewechsel.bestaetigen (antwortcode) [if auto_accept]
else approval_queue with the WiM Frist
Reject → wim.geraetewechsel.ablehnen (antwortcode from the process's EBD)
Escalate → approval_queue with the WiM Frist
The Frist runs from the Übertragungstag in the CloudEvent's time, so a
redelivery does not restart it.
Anmeldung MSB (55042), E_0201, answered by the NB (WiM Strom Teil 1
Kap. 2.3.2 Nr. 2):
| Check | Outcome on failure |
|---|---|
| Messlokation known to this NB | ZC9 |
| Versicherung über die Beauftragung durch den AN vorhanden | ZB6 |
| Mindestvorlaufzeit eingehalten — 15 WT, 7 WT bei erstmaliger Einrichtung | E17, naming the earliest reachable Zuordnungsbeginn |
| Vertrag nach § 9 Abs. 1 Nr. 3 MsbG mit dem MSBN | Escalate — E_0201 publishes no code |
The metering technology is not a ground: §5 MsbG gives the Anschlussnutzer a free choice of MSB, §14 MsbG the right to switch.
Ende MSB (55051), E_0202, answered by the NB. A Zuordnungsende inside the
20-Werktage Mindestvorlauffrist is set to the nächstmögliche Zuordnungsende and
confirmed Z01 (Kap. 2.4.2 Nr. 2). An Außerbetriebnahme has no lead time.
Kündigung MSB (55039), E_0200, answered by the MSBA. Every Prüfschritt is
about the MSBA's Messstellenbetriebsvertrag in vertragd
(GET /api/v1/messstellenvertraege/{melo_id}/{msb_mp_id}):
| Contract state | Outcome |
|---|---|
vertragd unreachable or unconfigured | Escalate |
404 — this MSB holds no contract here | ZC9 |
beendet_am set | Z29 |
kuendigung_zum set | the Kap. 2.2.3 table — E15, Z01 to an earlier date, or Z34 |
Live, requested date on or after naechstmoeglich | E15 |
| Live, requested date inside the binding | Z12, naming naechstmoeglich |
Live, no naechstmoeglich recorded | Escalate |
„Nächstmöglicher Termin" requested (DTM+471) | Z01, naming the date |
vertragd derives naechstmoeglich from the notice period, capped by § 309
Nr. 9 lit. c BGB.
Verpflichtungsanfrage (55168), E_0240. Kap. 2.4.2 Nr. 4 leaves the answer
„nach eigenem Ermessen" to the gMSB, so it escalates with its 1-Werktag window.
Escalated decisions still write an anmeldung_decisions row.
MSB module — REQOTE auto-response
For PIDs 35001, 35002, 35004 and 35005 (REQOTE Preisanfrage) processd answers
from the active PreisblattMessung in marktd:
de.mako.process.initiated (PIDs 35001/35002/35004/35005, REQOTE)
→ GET marktd /api/v1/preisblaetter-messung/{own_mp_id} ← PreisblattMessung current?
Found → wim.preisanfrage.angebot-senden
(includes preisblatt_gueltigkeit in payload for makod QUOTES build)
Not found → approval_queue with the REQOTE Frist — an operator quotes
[msb] auto_preisanfrage = false (default true) routes every REQOTE to the
approval queue, e.g. while a Preisblatt is being updated.
MSB module — ESA Wertebestellung (WiM Teil 2 Kap. 4)
Serving an Energieserviceanbieter is a mandatory Zusatzleistung (§34 Abs. 2
S. 2 Nr. 10 MsbG), so role-msb always carries:
| Inbound PID | Process | Answered with | Frist | EBD |
|---|---|---|---|---|
| 35003 | Werteanfrage (REQOTE) | QUOTES 15003 | 5 WT | E_0252 |
| 17007 | Bestellung von Werten | ORDRSP 19011/19012 | 2 WT | E_0256 |
| 39002 | Stornierung der Bestellung | ORDRSP 19013/19014 | 2 WT | E_0257 |
| 17008 | Abbestellung von Werten | ORDRSP 19011/19012 | 2 WT | E_0254 |
de.mako.process.initiated (PID 35003 / 17007 / 17008 / 39002)
→ GET marktd /api/v1/esa/framework/{msb}/{esa} ← Rahmenvertrag established? (E_0256 Nr. 6)
→ GET marktd /api/v1/esa/consent-check ← Einwilligung still valid? (E_0256 Nr. 8)
→ GET marktd /api/v1/melos/{melo}/msb?at= ← MSB assigned for the period? (E_0256 Nr. 7)
→ GET marktd /api/v1/malos/{malo}/buendel?at= ← one MSB across the bundle? (E_0256 Nr. 11)
→ mako_pruefung::esa::wertebestellung::pruefe_{anfrage,bestellung,stornierung,beendigung}
Accept → wim.wertebestellung.*-beantworten (Zustimmungscode) [if auto_accept]
else approval_queue with the WiM Frist
Reject → the same command with the tree's Ablehnungscode [if auto_reject]
Escalate → approval_queue with the WiM Frist
The code's Cluster picks the answer PID, so Accept and Reject share one command.
[esa] auto_accept and auto_reject both default to false and are separate:
a wrong Bestätigung commits the MSB to a delivery, a wrong Ablehnung denies a
mandated Zusatzleistung.
The Werteanfrage is E_0252 („Anfrage prüfen", refusals A02–A07, EBD
4.3 Kap. 8.25.1), the MSB's tree. E_0253 and E_0258 are the ESA's checks of
the offer and the ORDRSP. A refusal goes out as
wim.wertebestellung.anfrage-ablehnen with the code's wording in FTX+ACB (the
15003 has no AJT). A surviving Anfrage queues with its 5-WT window: the
Angebot's Bindungsfrist, start, prices and OBIS registers are commercial terms.
A02–A07 mean different things in E_0252 and E_0256.
The Messprodukt-Katalog. Whether this MSB offers an optional Kapitel-4.6
product is stated in marktd's esa_messprodukt_katalog
(PUT /api/v1/esa/messprodukte/{msb_mp_id}, dated, with separate flags for the
two Abo modes: A04 declined Abo, A05 declined one-shot). An absent row
escalates; only a catalogue that omits the product yields A02/A04/A05.
The seven Pflichtprodukte (BNetzA Mitteilung Nr. 3) are served whatever the
catalogue holds, from their dated cut-over (9991 00000 077 1 and 078 9:
„Optional ab 01.10.2023, Pflicht ab 06.08.2024").
Gerätetechnik (E_0252 Nr. 6, E_0256 Nr. 9) is judged on the union of
the Messlokation's registers:
| Rule | Source |
|---|---|
| A Kapitel-4.6.2 product without an iMS cannot be served | UC 4.1.1 Vorbedingung: „Bei Übermittlung von Werten aus dem iMS: Alle … benötigten Messlokationen sind mit einem iMS ausgestattet." |
| A direction with no register | Zaehlwerk.richtung — AUSSP Verbrauch, EINSP Erzeugung |
| Blindarbeit with no Blindarbeit register | the OBIS C group: 1/2 Wirkarbeit, 3–8 Blindarbeit |
Still escalated: a ¼h Lastgang on a non-iMS meter (mako holds the
Zaehlertyp, not the RLM capability); no meter or no registers on record; an
unknown Abo start on a 17008 (E_0254 Prüfschritt 2 compares against the
Bestellung's DTM+203, carried as abo_beginn).
MSB module — §14a Steuerungsauftrag auto-ORDRSP
On a WiM Steuerungsauftrag (makoworkflow = wim-steuerungsauftrag) processd
confirms when the SteuerbareRessource has istFernschaltbar = true and the
produktcode is in its contracted konfigurationsprodukte (GPKE Teil 3
Kap. 1.3); an uncontracted produktcode is refused
(wim.steuerungsauftrag.ablehnen).
de.mako.process.initiated (wim-steuerungsauftrag)
→ [parallel]
GET marktd /api/v1/steuerbare-ressourcen/{sr_id} ← istFernschaltbar?
GET marktd /api/v1/steuerbare-ressourcen/{sr_id}/konfigurationsprodukte ← contracted?
→ istFernschaltbar=true + produktcode contracted → bestaetigen
→ istFernschaltbar=true + produktcode NOT contracted → ablehnen (GPKE Teil 3 Kap. 1.3)
→ istFernschaltbar=false → Escalate (manual ORDRSP required)
→ SR not found → Escalate
Escalations queue with the 5-Werktage Steuerungsauftrag window less an hour.
Register contracted products via marktd's
PUT /api/v1/steuerbare-ressourcen/{sr_id}/konfigurationsprodukte.