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
//! Balance-group topology identifiers (MaBiS).
//!
//! `BilanzierungsgebietId` and `BilanzkreisId` model the balance-group topology
//! of BK6-24-174 Anlage 3 (MaBiS). MaBiS Summenzeitreihen key on them, which is
//! why they live in `mako-mabis`.
use ;
/// Balance-group topology identifiers, validated by `rubo4e`.
///
/// A Bilanzierungsgebiet and a Bilanzkreis are both 16-character EIC codes and
/// look alike, but they are different objects and ENTSO-E types them
/// differently: a **Bilanzkreis is a Party (`X`)** — it is held by a
/// Bilanzkreisverantwortlicher, a market participant — while a
/// **Bilanzierungsgebiet is an Area (`Y`)**, the grid region a Marktlokation
/// balances in. The German codes are issued on that basis by Energie Codes und
/// Services (EIC functions *Balance Group* and *Metering Grid Area*).
///
/// # Why these are validated rather than plain newtypes
///
/// They were `pub String` newtypes: the *type* separated them, the *content*
/// was unchecked, so a Bilanzkreis EIC in a Bilanzierungsgebiet field was
/// representable and — because MSCONS SG6 carries both as free text under
/// different `LOC` qualifiers — would have been accepted by the BIKO and filed
/// against the wrong object. That is the failure this module's
/// [`MabisZaehlpunktId`] documentation already calls out for `LOC+172`; it
/// applied equally to `LOC+107` and `LOC+237` and was simply not enforced.
///
/// Validation belongs here rather than nowhere, because this is a value mako
/// **produces**: it comes from mako's own `marktd` master data, not from a
/// counterparty. The rule stated on [`MabisZaehlpunktId`] — parse what the
/// system produces, keep what it receives representable — puts these on the
/// parsing side.
///
/// Failing here is also the *cheap* failure, and the argument for leaving it
/// unvalidated does not survive contact with the details:
///
/// * **It is not a choice between validating and filing.** The type decides
/// what the value *is*; the call site decides what happens when it does not
/// parse. `sync_engine` refuses that territory and names it — which is what
/// it already did when a MaBiS-Zählpunkt could not be resolved, three lines
/// away, for the same stated reason.
/// * **A malformed EIC is not quietly accepted downstream.** The BIKO validates
/// EICs too, so the realistic alternatives are a named refusal inside the
/// submission window, or a rejection discovered later — and the window is
/// still open in the first case.
/// * **The configured fallback is checked at start-up**, so a deployment error
/// surfaces at deploy rather than at 05:00 on the Erstaufschlag-Werktag.
pub use ;
// ── MabisZaehlpunktId ─────────────────────────────────────────────────────────
/// A MaBiS-Zählpunkt — the Meldepunkt a Summenzeitreihe is filed under.
///
/// # Why this is a type and not a `String`
///
/// MSCONS SG6 carries three `LOC` qualifiers whose values are all free text at
/// the MIG level: `172` the Meldepunkt, `107` the Bilanzierungsgebiet, `237` the
/// Bilanzkreis. A message that puts the territory EIC in `LOC+172` parses,
/// validates and is **accepted by the BIKO**, which then files the series
/// against the wrong Meldepunkt. Nothing downstream can tell that apart from a
/// correct submission.
///
/// [`BilanzierungsgebietId`] was already a newtype while this stayed a bare
/// `String`, so exactly one half of the dangerous pair was protected. With both
/// typed, passing one where the other belongs is a compile error rather than a
/// settlement filed against the wrong point.
///
/// # Format
///
/// A Zählpunktbezeichnung is **33 characters**. A Bilanzierungsgebiet EIC is 16,
/// so the length alone separates them — which is why the constructor enforces it
/// and why `Deserialize` goes through the same check rather than around it.
///
/// # Where this type is *not* used
///
/// Inbound commands keep a plain `String` — see
/// [`ZpLifecycleCommand::ReceiveAnfrage`](crate::zp_lifecycle::ZpLifecycleCommand).
/// Making a counterparty's malformed Meldepunkt unconstructible would leave the
/// workflow unable to record what actually arrived, and therefore unable to
/// reject it properly. Parsing into a type belongs on values this system
/// *produces*; a value it *receives* has to be representable before it can be
/// refused.
;
/// Deserialization goes through [`MabisZaehlpunktId::new`].
///
/// Deriving it would let a JSON payload construct a value the constructor
/// rejects, which is the whole hole this type closes — the identifier usually
/// *arrives* as JSON from marktd or a command API.
/// A value offered as a MaBiS-Zählpunkt that is not a Zählpunktbezeichnung.