Skip to main content

energy_api/models/
electricity.rs

1//! Wire-format types for the EDI-Energy electricity market APIs.
2//!
3//! Covers three API families:
4//! - **Control Measures** (`controlMeasuresV1.yaml`) — Steuerungshandlungen
5//!   between NB/LF and MSB.
6//! - **MaLo Identification** (`maloIdentV1.yaml`) — MaLo-ID retrieval for the
7//!   24 h supplier-switch process (GPKE part 2).
8//! - **WiM Order** (`wimOrderV1.yaml`) — iMS Universalbestellprozess for smart
9//!   meter commissioning (PIDs 11021–11023).
10
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13
14// ── Shared identifiers ────────────────────────────────────────────────────────
15
16/// External transaction ID (UUID RFC 4122), chosen by the sender.
17pub type TransactionId = Uuid;
18/// Idempotency key for retries (UUID RFC 4122).
19pub type InitialTransactionId = Uuid;
20/// External reference correlating a response to a prior request (UUID RFC 4122).
21pub type ReferenceId = Uuid;
22/// 13-digit market partner identifier.
23pub type MarketPartnerId = i64;
24
25/// Network location identifier — pattern `E[A-Z0-9]{9}[0-9]`.
26#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
27pub struct NeloId(pub String);
28
29/// Controllable resource identifier — pattern `C[A-Z0-9]{9}[0-9]`.
30#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
31pub struct SrId(pub String);
32
33/// Either a network location ID or a controllable resource ID.
34#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
35#[serde(untagged)]
36pub enum LocationId {
37    /// A network location (Netzlokation), identified by `E[A-Z0-9]{9}[0-9]`.
38    NetworkLocation(NeloId),
39    /// A controllable resource (Steuerbare Ressource), identified by `C[A-Z0-9]{9}[0-9]`.
40    ControllableResource(SrId),
41}
42
43impl std::fmt::Display for NeloId {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        f.write_str(&self.0)
46    }
47}
48impl std::fmt::Display for SrId {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        f.write_str(&self.0)
51    }
52}
53impl std::fmt::Display for LocationId {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            LocationId::NetworkLocation(id) => id.fmt(f),
57            LocationId::ControllableResource(id) => id.fmt(f),
58        }
59    }
60}
61
62// ── Control Measures ──────────────────────────────────────────────────────────
63
64/// Maximum power value in kW (`"\d{0,6}(\.\d{1,3})?"`).
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct MaximumPowerValue(pub String);
67
68/// Regulate a location to a specific maximum power value.
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct CommandControl {
72    /// Target maximum power value in kW.
73    pub maximum_power_value: MaximumPowerValue,
74    /// Start of effect period — ISO 8601 UTC, second precision (e.g. `"2023-08-01T12:30:00Z"`).
75    pub execution_time_from: String,
76    /// Optional end of effect period.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub execution_time_until: Option<String>,
79}
80
81/// Reset a location to its initial / uncontrolled state.
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct CommandRegular {
85    /// Start of effect period — ISO 8601 UTC, second precision.
86    pub execution_time_from: String,
87}
88
89/// Reason for a negative response from the MSB.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91pub enum ReasonNegative {
92    /// Communication to the control box was disrupted.
93    #[serde(rename = "communicationFailure")]
94    CommunicationFailure,
95    /// MSB back-end is overloaded.
96    #[serde(rename = "overload")]
97    Overload,
98    /// MSB is procedurally unable to fulfil the request.
99    #[serde(rename = "unable")]
100    Unable,
101}
102
103/// Terminal state for negative (failure) responses.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
105pub enum StateNegative {
106    /// The command failed.
107    #[serde(rename = "failed")]
108    Failed,
109}
110
111/// Terminal state for positive (success) responses.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
113pub enum StatePositive {
114    /// The command succeeded.
115    #[serde(rename = "succeeded")]
116    Succeeded,
117}
118
119/// Preliminary state — command is executable in principle.
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
121pub enum PreliminaryStatePositive {
122    /// The command is executable in principle.
123    #[serde(rename = "possible")]
124    Possible,
125}
126
127/// State indicating the final outcome is not yet known.
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
129pub enum StateUnknown {
130    /// The final outcome is not yet known.
131    #[serde(rename = "unknown")]
132    Unknown,
133}
134
135// ── MaLo Identification ───────────────────────────────────────────────────────
136
137/// Market location identifier — 11-digit string.
138#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
139pub struct MaloId(pub String);
140
141/// Metering location identifier — pattern `DE\d{11}[A-Z,\d]{20}`.
142#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
143pub struct MeloId(pub String);
144
145/// Technical resource identifier — pattern `D[A-Z0-9]{9}[0-9]`.
146#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
147pub struct TrId(pub String);
148
149/// Energy flow direction at a market location.
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
151pub enum EnergyDirection {
152    /// Energy consumed from the grid (Verbrauch).
153    #[serde(rename = "consumption")]
154    Consumption,
155    /// Energy fed into the grid (Einspeisung).
156    #[serde(rename = "production")]
157    Production,
158}
159
160/// Metering technology classification of a market location.
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162pub enum MeasurementTechnologyClassification {
163    /// Intelligentes Messsystem (iMSys) — full smart meter system.
164    #[serde(rename = "intelligentMeasuringSystem")]
165    IntelligentMeasuringSystem,
166    /// Konventionelles Messsystem — traditional metering without smart functions.
167    #[serde(rename = "conventionalMeasuringSystem")]
168    ConventionalMeasuringSystem,
169    /// No metering equipment installed (e.g. virtual market location).
170    #[serde(rename = "noMeasurement")]
171    NoMeasurement,
172}
173
174/// Whether the forecast basis may be changed.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
176pub enum OptionalChangeForecastBasis {
177    /// The forecast basis may be changed.
178    #[serde(rename = "possible")]
179    Possible,
180    /// The forecast basis may not be changed.
181    #[serde(rename = "notPossible")]
182    NotPossible,
183}
184
185/// Lifecycle property / category of a market location.
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
187pub enum MarketLocationProperty {
188    /// Customer facility market location (Kundenanlage).
189    #[serde(rename = "customerFacility")]
190    CustomerFacility,
191    /// Dormant market location (spec spelling: `"nonActice"`).
192    #[serde(rename = "nonActice")]
193    NonActive,
194    /// Standard market location.
195    #[serde(rename = "standard")]
196    Standard,
197}
198
199/// Tranche proportion type.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
201pub enum ProportionType {
202    /// Tranche size defined by bilateral agreement.
203    #[serde(rename = "bilateralAgreement")]
204    BilateralAgreement,
205    /// Tranche size expressed as a percentage.
206    #[serde(rename = "percent")]
207    Percent,
208}
209
210/// Input parameters for a MaLo identification request.
211#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct IdentificationParameter {
214    /// Effective date for identification — ISO 8601 UTC, day-boundary midnight.
215    pub identification_date_time: String,
216    /// Energy flow direction at the market location.
217    pub energy_direction: EnergyDirection,
218    /// Optional ID-based search criteria.
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub identification_parameter_id: Option<IdentificationParameterId>,
221    /// Address-based search criteria.
222    pub identification_parameter_address: IdentificationParameterAddress,
223}
224
225/// Optional ID-based identification parameters.
226#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct IdentificationParameterId {
229    /// Optional market location ID to match.
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub malo_id: Option<MaloId>,
232    /// Optional list of tranche IDs to match.
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub tranchen_ids: Option<Vec<String>>,
235    /// Optional list of metering location IDs (MeLo) to match.
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub melo_ids: Option<Vec<MeloId>>,
238    /// Optional list of meter serial numbers to match.
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub meter_numbers: Option<Vec<String>>,
241    /// Optional customer number to match.
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub customer_number: Option<String>,
244}
245
246/// Address-based identification parameters.
247#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
248#[serde(rename_all = "camelCase")]
249pub struct IdentificationParameterAddress {
250    /// Optional customer name to match.
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub name: Option<PersonName>,
253    /// Optional postal address to match.
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub address: Option<PostalAddress>,
256}
257
258/// Person or company name.
259#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase")]
261pub struct PersonName {
262    /// Family name(s).
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub surnames: Option<String>,
265    /// Given name(s).
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub firstnames: Option<String>,
268    /// Optional title (e.g. `"Dr."`).
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub title: Option<String>,
271    /// Company or organisation name.
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub company: Option<String>,
274}
275
276/// German postal address.
277#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
278#[serde(rename_all = "camelCase")]
279pub struct PostalAddress {
280    /// ISO 3166-1 alpha-2 country code (e.g. `"DE"`).
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub country_code: Option<String>,
283    /// German postal code (PLZ).
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub zip_code: Option<String>,
286    /// City name.
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub city: Option<String>,
289    /// Street name.
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub street: Option<String>,
292    /// House number.
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub house_number: Option<i32>,
295    /// House number suffix (Hausnummernzusatz, e.g. `"a"`).
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub house_number_addition: Option<String>,
298}
299
300// ── MaLo Identification response types ───────────────────────────────────────
301
302/// Positive identification result — all data the NB holds about the market
303/// location from `identificationDateTime` onwards.
304#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct MaloIdentResultPositive {
307    /// Market location master data.
308    pub data_market_location: DataMarketLocation,
309    /// Billing tranches at the market location.
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub data_tranches: Option<Vec<DataTranche>>,
312    /// Metering locations linked to this market location.
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub data_meter_locations: Option<Vec<DataMeterLocation>>,
315    /// Technical resources linked to this market location.
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub data_technical_resources: Option<Vec<DataTechnicalResource>>,
318    /// Controllable resources linked to this market location.
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub data_controllable_resources: Option<Vec<DataControllableResource>>,
321    /// Network locations linked to this market location.
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub data_network_locations: Option<Vec<DataNetworkLocation>>,
324}
325
326/// Negative identification result, referencing the applicable decision tree.
327#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
328#[serde(rename_all = "camelCase")]
329pub struct MaloIdentResultNegative {
330    /// Decision tree code from EDI@energy, e.g. `"E_0594"`.
331    pub decision_tree: String,
332    /// Response code from that tree, e.g. `"A10"`.
333    pub response_code: String,
334    /// Optional human-readable reason for the negative result.
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub reason: Option<String>,
337    /// NB that now holds the location (when it left this NB's grid area).
338    #[serde(skip_serializing_if = "Option::is_none")]
339    pub network_operator: Option<MarketPartnerId>,
340}
341
342/// Full data about the identified market location.
343#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct DataMarketLocation {
346    /// Market location identifier (11-digit MaLo-ID).
347    pub malo_id: MaloId,
348    /// Energy flow direction (consumption or production).
349    pub energy_direction: EnergyDirection,
350    /// Metering technology classification of this market location.
351    pub measurement_technology_classification: MeasurementTechnologyClassification,
352    /// Whether the forecast basis may be changed.
353    pub optional_change_forecast_basis: OptionalChangeForecastBasis,
354    /// Time-sliced lifecycle properties of this market location.
355    pub data_market_location_properties: Vec<MarketLocationProperties>,
356    /// Time-sliced network operator (Netzbetreiber) assignments.
357    pub data_market_location_network_operators: Vec<TimeSlicedMarketPartner>,
358    /// Time-sliced transmission system operator (ÜNB) assignments.
359    pub data_market_location_transmission_system_operators: Vec<TimeSlicedMarketPartner>,
360    /// Time-sliced measuring point operator (MSB) assignments.
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub data_market_location_measuring_point_operators: Option<Vec<TimeSlicedMarketPartner>>,
363    /// Time-sliced supplier (Lieferant) assignments.
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub data_market_location_suppliers: Option<Vec<TimeSlicedMarketPartner>>,
366    /// Customer name at this market location.
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub data_market_location_name: Option<PersonName>,
369    /// Customer address at this market location.
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub data_market_location_address: Option<PostalAddress>,
372}
373
374/// A market partner assignment valid for a specific time slice.
375#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct TimeSlicedMarketPartner {
378    /// 13-digit Marktpartner-ID.
379    pub market_partner_id: MarketPartnerId,
380    /// Start of the validity period (ISO 8601 UTC).
381    pub execution_time_from: String,
382    /// End of the validity period (ISO 8601 UTC); absent means open-ended.
383    #[serde(skip_serializing_if = "Option::is_none")]
384    pub execution_time_until: Option<String>,
385}
386
387/// Property of a market location valid for a specific time slice.
388#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
389#[serde(rename_all = "camelCase")]
390pub struct MarketLocationProperties {
391    /// The lifecycle property value.
392    pub market_location_property: MarketLocationProperty,
393    /// Start of the validity period (ISO 8601 UTC).
394    pub execution_time_from: String,
395    /// End of the validity period (ISO 8601 UTC); absent means open-ended.
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub execution_time_until: Option<String>,
398}
399
400/// Data about a metering location (Messlokation) at the market location.
401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
402#[serde(rename_all = "camelCase")]
403pub struct DataMeterLocation {
404    /// Metering location identifier (MeLo EIC).
405    pub melo_id: MeloId,
406    /// Physical meter serial number.
407    pub meter_number: String,
408    /// Time-sliced measuring point operator (MSB) assignments for this MeLo.
409    pub data_meter_location_measuring_point_operators: Vec<TimeSlicedMarketPartner>,
410}
411
412/// Data about a technical resource (Technische Ressource) at the market location.
413#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
414#[serde(rename_all = "camelCase")]
415pub struct DataTechnicalResource {
416    /// Technical resource identifier (TR-ID).
417    pub tr_id: TrId,
418}
419
420/// Data about a controllable resource (Steuerbare Ressource) at the market location.
421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
422#[serde(rename_all = "camelCase")]
423pub struct DataControllableResource {
424    /// Controllable resource identifier (SR-ID).
425    pub sr_id: SrId,
426    /// Time-sliced measuring point operator assignments for this controllable resource.
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub data_controllable_resource_measuring_point_operators: Option<Vec<SrMarketPartner>>,
429}
430
431/// Market partner assignment at a controllable resource.
432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
433#[serde(rename_all = "camelCase")]
434pub struct SrMarketPartner {
435    /// 13-digit Marktpartner-ID.
436    pub market_partner_id: MarketPartnerId,
437    /// Start of the validity period (ISO 8601 UTC).
438    pub execution_time_from: String,
439    /// End of the validity period (ISO 8601 UTC); absent means open-ended.
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub execution_time_until: Option<String>,
442    /// Market partner role type for this controllable resource.
443    pub market_partner_type_sr: String,
444}
445
446/// Data about a network location (Netzlokation) linked to the market location.
447#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
448#[serde(rename_all = "camelCase")]
449pub struct DataNetworkLocation {
450    /// Network location identifier (NeLo EIC).
451    pub nelo_id: NeloId,
452    /// Time-sliced measuring point operator (MSB) assignments for this NeLo.
453    pub data_network_location_measuring_point_operators: Vec<TimeSlicedMarketPartner>,
454}
455
456/// A billing tranche at a market location.
457#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
458#[serde(rename_all = "camelCase")]
459pub struct DataTranche {
460    /// Tranche identifier.
461    pub tranchen_id: String,
462    /// How the tranche proportion is expressed.
463    pub proportion: ProportionType,
464    /// Percentage value when `proportion` is [`ProportionType::Percent`].
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub percent: Option<f64>,
467    /// Time-sliced supplier assignments for this tranche.
468    pub data_tranche_suppliers: Vec<TimeSlicedMarketPartner>,
469}
470
471// ── WiM Order (iMS Universalbestellprozess) ───────────────────────────────────
472
473/// Device category for the iMS Universalbestellprozess.
474///
475/// Specifies which type of smart meter the Netzbetreiber is ordering from the MSB.
476#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
477#[serde(rename_all = "camelCase")]
478pub enum WimDeviceCategory {
479    /// Intelligentes Messsystem (iMSys) — full smart meter system.
480    #[serde(rename = "iMSys")]
481    IMSys,
482    /// Moderne Messeinrichtung (mME) — basic smart meter display.
483    #[serde(rename = "mME")]
484    Mme,
485    /// Moderne Messeinrichtung mit Kommunikationsadapter (mME+KME).
486    #[serde(rename = "mME+KME")]
487    MmeKme,
488}
489
490/// Rejection reason code for a WiM Ablehnung response.
491#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub enum WimRejectionReason {
494    /// MeLo does not exist in the MSB's service territory.
495    #[serde(rename = "meloUnknown")]
496    MeloUnknown,
497    /// MSB is not responsible for this MeLo.
498    #[serde(rename = "notResponsible")]
499    NotResponsible,
500    /// Requested device category is not installable at this MeLo.
501    #[serde(rename = "deviceCategoryNotSupported")]
502    DeviceCategoryNotSupported,
503    /// Regulatory prerequisites for iMSys rollout not yet met.
504    #[serde(rename = "rolloutPreconditionNotMet")]
505    RolloutPreconditionNotMet,
506    /// MSB technical capacity exhausted.
507    #[serde(rename = "capacityExhausted")]
508    CapacityExhausted,
509    /// Other / unspecified reason; see `reason_text` for details.
510    #[serde(rename = "other")]
511    Other,
512}
513
514/// Payload for a WiM Anmeldung (PID 11021) — NB orders iMS installation from MSB.
515///
516/// Sent by the Netzbetreiber to the Messstellenbetreiber over the REST channel.
517#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
518#[serde(rename_all = "camelCase")]
519pub struct WimAnmeldungRequest {
520    /// Messlokation EIC code at which the device should be installed.
521    pub melo_id: String,
522    /// 13-digit GLN of the Netzbetreiber (sender).
523    pub netzbetreiber_id: i64,
524    /// Requested process date (ISO 8601, date only, e.g. `"2026-06-01"`).
525    pub process_date: String,
526    /// Requested device category.
527    pub device_category: WimDeviceCategory,
528    /// Optional free-text notes (e.g. access instructions).
529    #[serde(skip_serializing_if = "Option::is_none")]
530    pub notes: Option<String>,
531}
532
533/// Payload for a WiM Bestätigung (PID 11022) — MSB confirms the order.
534///
535/// Sent by the MSB to the Netzbetreiber after accepting an Anmeldung.
536#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
537#[serde(rename_all = "camelCase")]
538pub struct WimBestaetigung {
539    /// UUID of the original Anmeldung transaction this response refers to.
540    pub reference_id: Uuid,
541    /// Confirmed installation date (ISO 8601, date only).
542    pub confirmed_process_date: String,
543    /// Assigned device identifier (EIC or MSB-internal reference).
544    #[serde(skip_serializing_if = "Option::is_none")]
545    pub device_id: Option<String>,
546}
547
548/// Payload for a WiM Ablehnung (PID 11023) — MSB rejects the order.
549///
550/// Sent by the MSB to the Netzbetreiber after refusing an Anmeldung.
551#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
552#[serde(rename_all = "camelCase")]
553pub struct WimAblehnung {
554    /// UUID of the original Anmeldung transaction this response refers to.
555    pub reference_id: Uuid,
556    /// Structured rejection reason code.
557    pub reason: WimRejectionReason,
558    /// Optional human-readable explanation (supplementary to `reason`).
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub reason_text: Option<String>,
561}