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