1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//! `mako-emob` — **NZR-EMob / Modell 2**: the virtual Bilanzierungsgebiet that
//! lets a driver bring their own supplier to a charge point.
//!
//! A Ladepunktbetreiber (LPB/CPO) runs a regelzonenweites Bilanzierungsgebiet of
//! its own, and every registered Übergabestelle's flows are an exchange between
//! the VNB's BG and that one (Anlage 6 zum Beschluss **BK6-20-160**). The
//! Marktlokation is then balanced in the LPB's BG — „**Modell 2**" — and the LPB
//! assigns each charging session's energy to the Bilanzkreis of the supplier the
//! customer chose, quarter hour by quarter hour, with intraday supplier changes.
//! **BK6-24-267** extends the model to a private Kundenanlage under § 20 Abs. 1,
//! 1a EnWG.
//!
//! This crate is the **allocation engine, its invariants, and the three UTILMD
//! legs that move a Marktlokation between the models** — pure domain: no I/O,
//! no wire rendering, no persistence.
//!
//! | Module | Answers |
//! |---|---|
//! | [`bg`] | may this Bilanzierungsgebiet exist, here, now? |
//! | [`uebergabestelle`] | may this Marktlokation enter Modell 2? |
//! | [`session`] | what quarter-hour energies does this Ladevorgang produce? |
//! | [`allocation`] | who gets this quarter hour, and what is left over? |
//! | [`fristen`] | when is each step due? |
//! | [`modellwechsel`] | which UTILMD leg moves it, and what does the answer carry? |
//! | [`wire`] | which UTILMD qualifier carries it? |
//! | [`ids`] | which identifiers are ours to mint, and which are not? |
//!
//! The Entscheidungsbäume (`E_0510`–`E_0513`) live in `mako_pruefung::emob` and
//! the answer Fristen in `mako_fristen::antwort`, where every other market
//! process keeps them.
//!
//! # The one invariant everything else serves
//!
//! Anlage 6 §IV.1 obliges the LPB to assign **the whole Bilanzierungsgebiet**,
//! every quarter hour:
//!
//! ```text
//! NGZ(t, richtung) = Σ zugeordnete Marktlokationen + Deltamenge
//! ```
//!
//! [`allocation::QuarterHourAllocation`] holds that exactly and returns a
//! [`allocation::ConservationProof`] beside every row. The Deltamenge is a
//! quantity, not a rounding error: it settles in a Bilanzkreis the LPB names, at
//! the LPB's own cost (§IV.2).
//!
//! ```rust
//! use mako_emob::allocation::{Anspruch, MaloKind, QuarterHourAllocation, Richtung};
//! use mako_emob::ids::VirtualMaloId;
//! use mako_emob::session::Viertelstunde;
//! use rust_decimal::dec;
//! use time::macros::datetime;
//!
//! let slot = Viertelstunde::containing(datetime!(2026-11-03 08:07:00 UTC));
//!
//! // 12 kWh crossed the Übergabestelle; two vehicles claim 9 of them.
//! let row = QuarterHourAllocation::allocate(
//! slot,
//! Richtung::Bezug,
//! dec!(12),
//! &[
//! Anspruch { malo: VirtualMaloId::new("veh-1")?, kind: MaloKind::Vehicle, kwh: dec!(6) },
//! Anspruch { malo: VirtualMaloId::new("veh-2")?, kind: MaloKind::Vehicle, kwh: dec!(3) },
//! ],
//! )?;
//!
//! assert_eq!(row.delta_kwh, dec!(3)); // the LPB's own Bilanzkreis carries these
//! assert!(row.proof.haelt()); // Anlage 6 §IV.1, checked
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # The market role is NB
//!
//! The BDEW Rollenmodell defines no LPB: „der LPB kommuniziert aus prozessualer
//! Sicht wie die Rolle NB" (AWH Kap. 1.4). Every message leaves as **NB**;
//! `mako_engine::marktrolle::Marktrolle::Lpb` is a *deployment* identity, the
//! `Nmsb`/`Amsb` pattern, without which the shared Prüfidentifikatoren are
//! ambiguous in a deployment that is both VNB and LPB.
//!
//! # Prüfidentifikatoren
//!
//! | PID | Message | From → To | Answered by |
//! |---|---|---|---|
//! | 55238 | Anmeldung in Modell 2 | NB (LPB) → NB (VNB) | 55239, `E_0513`→`E_0510` |
//! | 55240 | Beendigung der Zuordnung zur MaLo | NB (VNB) → LF | 55241, `E_0511` |
//! | 55242 | Abmeldung aus dem Modell 2 | NB (LPB) → NB (VNB) | 55243, `E_0512` |
//! | 55235 / 55236 | Zuordnung / Beendigung ZP der NGZ zur NZR | verantw. NB → benachb. NB, ÜNB | 55237, `E_0102` / `E_0103` |
//! | 55062 / 55063 | MaBiS-ZP für die tägliche BK-SZR eMob | NB (LPB) ↔ ÜNB | 55064 |
//! | 13018 | MSCONS Netzgangzeitreihe | NB (VNB) → NB (LPB), ÜNB | — |
//! | 13003 | MSCONS NZR (eMob), BK-SZR (Kat. A) eMob, tägliche BK-SZR eMob | NB ↔ NB, NB → BKV/ÜNB | 21001 / 21002 / 21004 |
//!
//! # Sources
//!
//! - **BK6-20-160** (21.12.2020) Anlage 6 „NZR-EMob"; Mitteilung Nr. 4 (03.05.2022)
//! - **BDEW AWH „Zum Modell 2 zur ladevorgangscharfen bilanziellen
//! Energiemengenzuordnungsmöglichkeit" V1.3** (01.04.2025)
//! - **BDEW AWH Ergänzung der Marktregeln … Bilanzkreisabrechnung (MaBiS)**
//! V1.0 (27.04.2022) — the Netzgangzeitreihe
//! - **BK6-24-267** (15.05.2025), bestandskräftig
//! - **UTILMD AHB Strom 2.2** Kap. 11; **EBD 4.3** Kap. 17
//! - **MaBiS** BK6-24-174 Anlage 3, Kap. 3.8 / 3.10 / 5
// German regulatory terms (MaLo, MaBiS, NGZ, Bilanzierungsgebiet…) are not Rust items.
pub use ;
pub use ;
pub use EmobError;
pub use ;
pub use ;
pub use ;
pub use ;
/// The Prüfidentifikatoren of the Modellwechsel, in process order.
///
/// `makod` registers these; `mako_pruefung::emob` decides the answers to the
/// three that carry one.
pub const MODELLWECHSEL_PIDS: = ;
/// The Prüfidentifikatoren of the Zuordnung des ZP der NGZ zur NZR.
///
/// MaBiS rather than Modell 2 — they come from the AWH Ergänzung der Marktregeln
/// (27.04.2022) and are answered from `mako_pruefung::mabis` with `E_0102` and
/// `E_0103`.
pub const ZP_NGZ_PIDS: = ;
// ── EngineModule ──────────────────────────────────────────────────────────────
/// Engine module for **NZR-EMob / Modell 2**.
///
/// | Workflow | PIDs |
/// |---|---|
/// | `emob-anmeldung` | UTILMD 55238 / 55239 |
/// | `emob-zuordnungsende` | UTILMD 55240 / 55241 |
/// | `emob-abmeldung` | UTILMD 55242 / 55243 |
///
/// # What this module does not own
///
/// 55235–55237 (Zuordnung des ZP der NGZ zur NZR) are **MaBiS**, registered by
/// `mako_mabis::MabisModule` and answered from `mako_pruefung::mabis` with
/// `E_0102` / `E_0103`. MSCONS 13018 (Netzgangzeitreihe) and 13003 (NZR and
/// the BK-SZR eMob) are MaBiS Summenzeitreihen and belong to `mabis-billing`.
/// Registering any of them here would take them off a settlement stream that
/// already handles them.
;