Skip to main content

redispatch_xml/documents/
stammdaten.rs

1//! `Stammdaten` -- technical master data exchange (ANB/DV to VNB, VNB to UNB) for registered Redispatch resources.
2use serde::{Deserialize, Serialize};
3
4use crate::types::{Decimal3, DocumentId, MarketParticipantId, Mrid, UtcDateTime};
5
6// ── German-localised coding scheme for Stammdaten ────────────────────────────
7
8/// `Codierung` coding scheme used in `Stammdaten` (German-localised variant).
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum Codierung {
11    /// GS1 (GLN/GSRN).
12    #[serde(rename = "A10")]
13    Gs1,
14    /// Germany National coding scheme (BDEW-Code).
15    #[serde(rename = "NDE")]
16    Nde,
17}
18
19/// Sender / receiver reference for `Stammdaten` documents.
20///
21/// The `Code` and `Codierung` attributes are German-language equivalents of
22/// `v` and `codingScheme` used in ENTSO-E documents.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct StammdatenParticipantRef {
25    /// 13-digit market participant identifier.
26    #[serde(rename = "@Code")]
27    pub code: MarketParticipantId,
28    /// Coding scheme for the identifier.
29    #[serde(rename = "@Codierung")]
30    pub codierung: Codierung,
31}
32
33// ── DocumentType ──────────────────────────────────────────────────────────────
34
35/// `DocumentType` codes for `Stammdaten`.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub enum StammdatenDocType {
38    /// Reduced master data (reduzierte Stammdaten).
39    #[serde(rename = "Z02")]
40    Reduced,
41    /// Enriched master data (angereicherte Stammdaten).
42    #[serde(rename = "Z03")]
43    Enriched,
44    /// Grid operator aggregate master data.
45    #[serde(rename = "Z04")]
46    NbAggregate,
47    /// Balance responsible party master data.
48    #[serde(rename = "Z14")]
49    Bilanzkreis,
50}
51
52/// Sender market role (`Senderrolle`) in `Stammdaten`.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
54pub enum StammdatenSenderRole {
55    /// Grid operator (Netzbetreiber, A18).
56    #[serde(rename = "A18")]
57    GridOperator,
58    /// Resource provider (Anlagenbetreiber / Direktvermarkter, A27).
59    #[serde(rename = "A27")]
60    ResourceProvider,
61    /// Data provider (Datenprovider, A39).
62    #[serde(rename = "A39")]
63    DataProvider,
64    /// Supplier (Lieferant, Z01).
65    #[serde(rename = "Z01")]
66    Supplier,
67}
68
69/// Receiver market role (`Empfaengerrolle`) in `Stammdaten`.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
71pub enum StammdatenReceiverRole {
72    /// Balance responsible party (Bilanzkreisverantwortlicher, A08).
73    #[serde(rename = "A08")]
74    BalanceResponsibleParty,
75    /// Grid operator (Netzbetreiber, A18).
76    #[serde(rename = "A18")]
77    GridOperator,
78    /// Data provider (Datenprovider, A39).
79    #[serde(rename = "A39")]
80    DataProvider,
81    /// Supplier (Lieferant, Z01).
82    #[serde(rename = "Z01")]
83    Supplier,
84}
85
86/// Message status: indicates whether this is a creation, update, or deactivation.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
88pub enum Meldungsstatus {
89    /// Initial creation of master data.
90    #[serde(rename = "A14")]
91    Creation,
92    /// Update to existing master data.
93    #[serde(rename = "A15")]
94    Update,
95    /// Deactivation of master data.
96    #[serde(rename = "A16")]
97    Deactivation,
98}
99
100/// German control zone (`Regelzone`) codes used in `Stammdaten`.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
102pub enum Regelzone {
103    /// TransnetBW control zone (EIC 10YDE-ENBW-----N).
104    #[serde(rename = "10YDE-ENBW-----N")]
105    TransnetBw,
106    /// TenneT DE control zone (EIC 10YDE-EON------1).
107    #[serde(rename = "10YDE-EON------1")]
108    TennetDe,
109    /// Amprion control zone (EIC 10YDE-RWENET---I).
110    #[serde(rename = "10YDE-RWENET---I")]
111    Amprion,
112    /// 50Hertz control zone (EIC 10YDE-VE-------2).
113    #[serde(rename = "10YDE-VE-------2")]
114    FiftyHertz,
115    /// Stadtwerke Flensburg control zone (EIC 10YFLENSBURG---3).
116    #[serde(rename = "10YFLENSBURG---3")]
117    Flensburg,
118    /// DB Energie (Bahnstrom) control zone (EIC 11YRBAHNSTROM--P).
119    #[serde(rename = "11YRBAHNSTROM--P")]
120    Bahnstrom,
121}
122
123/// Energy carrier (`Energietraeger`) codes used in `SR_Objekt`.
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
125#[non_exhaustive]
126pub enum Energietraeger {
127    /// Natural gas (B01).
128    #[serde(rename = "B01")]
129    NaturalGas,
130    /// Lignite (brown coal, B02).
131    #[serde(rename = "B02")]
132    LigniteCoal,
133    /// Hard coal (B03).
134    #[serde(rename = "B03")]
135    HardCoal,
136    /// Oil (B04).
137    #[serde(rename = "B04")]
138    Oil,
139    /// Uranium / nuclear (B05).
140    #[serde(rename = "B05")]
141    Uranium,
142    /// Biomass (B06).
143    #[serde(rename = "B06")]
144    Biomass,
145    /// Wind energy (B07).
146    #[serde(rename = "B07")]
147    Wind,
148    /// Solar / photovoltaic (B08).
149    #[serde(rename = "B08")]
150    Solar,
151    /// Run-of-river hydro (B09).
152    #[serde(rename = "B09")]
153    RunOfRiver,
154    /// Pumped-storage hydro (B10).
155    #[serde(rename = "B10")]
156    PumpedStorage,
157    /// Geothermal (B11).
158    #[serde(rename = "B11")]
159    Geothermal,
160    /// Waste-to-energy (B12).
161    #[serde(rename = "B12")]
162    WasteToEnergy,
163    /// Other renewable energy source (B13).
164    #[serde(rename = "B13")]
165    OtherRenewable,
166    /// Mixed energy carrier (B14).
167    #[serde(rename = "B14")]
168    Mixed,
169    /// Pumped-storage hydro with natural inflow (B15).
170    #[serde(rename = "B15")]
171    PumpedStorageWithNaturalInflow,
172    /// Other non-renewable energy source (B16).
173    #[serde(rename = "B16")]
174    OtherNonRenewable,
175    /// Other storage technology (B17).
176    #[serde(rename = "B17")]
177    OtherStorage,
178    /// Hydrogen (B18).
179    #[serde(rename = "B18")]
180    Hydrogen,
181    /// Offshore wind energy (B19).
182    #[serde(rename = "B19")]
183    Offshore,
184    /// Battery storage (B20).
185    #[serde(rename = "B20")]
186    Battery,
187    /// EEG-remunerated renewable energy (Z01).
188    #[serde(rename = "Z01")]
189    Eeg,
190    /// KWKG-remunerated combined heat and power (Z02).
191    #[serde(rename = "Z02")]
192    Kwkg,
193}
194
195/// Billing model (`Bilanzierungsmodell`) for a controllable resource.
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
197pub enum Bilanzierungsmodell {
198    /// Plan value.
199    #[serde(rename = "Z01")]
200    Planwert,
201    /// Forecast.
202    #[serde(rename = "Z02")]
203    Prognose,
204    /// Forecast with planning data delivery.
205    #[serde(rename = "Z03")]
206    PrognoseWithPlanningData,
207}
208
209/// Call type (`Abrufart_Aufforderungsfall`) for a controllable resource.
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
211pub enum AbrufartAufforderungsfall {
212    /// Delta instruction (Deltaanweisung).
213    #[serde(rename = "Z01")]
214    Delta,
215    /// Setpoint (Sollwert).
216    #[serde(rename = "Z02")]
217    Sollwert,
218}
219
220/// Tolerance case (`Status_Duldungsfall`) for a controllable resource.
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
222pub enum StatusDuldungsfall {
223    /// Tolerance case applies (A01).
224    #[serde(rename = "A01")]
225    Yes,
226    /// Tolerance case does not apply (A02).
227    #[serde(rename = "A02")]
228    No,
229}
230
231/// Compensation type (`Verguetungsart`).
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
233pub enum Verguetungsart {
234    /// EEG (Renewable Energy Act).
235    #[serde(rename = "Z01")]
236    Eeg,
237    /// KWKG (CHP Act).
238    #[serde(rename = "Z02")]
239    Kwkg,
240    /// Other.
241    #[serde(rename = "Z03")]
242    Other,
243}
244
245// ── Grid operator reference ───────────────────────────────────────────────────
246
247/// Network operator reference used in `SR_Objekt`.
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249pub struct NbRef {
250    /// 13-digit market participant identifier.
251    #[serde(rename = "@Code")]
252    pub code: MarketParticipantId,
253    /// Coding scheme.
254    #[serde(rename = "@Codierung")]
255    pub codierung: Codierung,
256}
257
258/// Affected grid operator reference (includes cascade position 1–6).
259#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
260pub struct BetroffenerNb {
261    /// 13-digit market participant identifier.
262    #[serde(rename = "@Code")]
263    pub code: MarketParticipantId,
264    /// Coding scheme.
265    #[serde(rename = "@Codierung")]
266    pub codierung: Codierung,
267    /// Position in the cascade (1–6).
268    #[serde(rename = "@Pos")]
269    pub pos: u8,
270}
271
272// ── Steuerbarkeit ─────────────────────────────────────────────────────────────
273
274/// Measure unit for `Steuerbarkeit` steps / increments.
275#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
276pub enum SteuerbarkeitEinheit {
277    /// Megawatt.
278    #[serde(rename = "MAW")]
279    Megawatt,
280    /// Percent.
281    #[serde(rename = "P1")]
282    Percent,
283}
284
285/// Step-based controllability definition (`Stufen`).
286#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
287pub struct Stufen {
288    /// Individual step values (percentage of installed capacity).
289    #[serde(rename = "Einzelstufe", default)]
290    pub einzelstufen: Vec<Decimal3>,
291    /// Unit (always `P1` — percent).
292    #[serde(rename = "@Einheit", default, skip_serializing_if = "Option::is_none")]
293    pub einheit: Option<String>,
294}
295
296/// Increment-based controllability definition (`Schritte`).
297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
298pub struct Schritte {
299    /// Unit of the increment (`MAW` or `P1`).
300    #[serde(rename = "Einheit")]
301    pub einheit: SteuerbarkeitEinheit,
302    /// Step size.
303    #[serde(rename = "Schrittweite")]
304    pub schrittweite: Decimal3,
305    /// Minimum value.
306    #[serde(rename = "Min")]
307    pub min: Decimal3,
308    /// Maximum value.
309    #[serde(rename = "Max")]
310    pub max: Decimal3,
311}
312
313/// Controllability definition of a steuerbare Ressource.
314/// Either step-based (`Stufen`) or increment-based (`Schritte`).
315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
316pub struct Steuerbarkeit {
317    /// Step-based controllability (optional — exclusive with `Schritte`).
318    #[serde(rename = "Stufen", default, skip_serializing_if = "Option::is_none")]
319    pub stufen: Option<Stufen>,
320    /// Increment-based controllability (optional — exclusive with `Stufen`).
321    #[serde(rename = "Schritte", default, skip_serializing_if = "Option::is_none")]
322    pub schritte: Option<Schritte>,
323    /// Whether the controllability values are fixed (optional attribute).
324    #[serde(
325        rename = "@Fixierung",
326        default,
327        skip_serializing_if = "Option::is_none"
328    )]
329    pub fixierung: Option<String>,
330}
331
332// ── Technische_Parameter ──────────────────────────────────────────────────────
333
334/// Technical parameters of a **Steuerbare Ressource**.
335///
336/// The XSD declares `Technische_Parameter` three times with **three different
337/// anonymous complexTypes** — once under `SR_Objekt` (this one, the dispatch
338/// timings), once under `Enthaltene_TR` ([`TrTechnischeParameter`], the plant
339/// nameplate) and once under `CR_Objekt` (the load gradients alone). Sharing
340/// one Rust type across them silently drops whichever fields the other shapes
341/// carry, which is what used to happen to the entire TR nameplate.
342///
343/// All fields are optional; only those relevant to the resource type are
344/// populated.
345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346pub struct TechnischeParameter {
347    /// Minimum dispatchable generation (MW).
348    #[serde(
349        rename = "Fahrbare_Mindesterzeugungsleistung",
350        default,
351        skip_serializing_if = "Option::is_none"
352    )]
353    pub fahrbare_mindesterzeugungsleistung: Option<Decimal3>,
354    /// Minimum run time (minutes).
355    #[serde(
356        rename = "Mindestbetriebszeit",
357        default,
358        skip_serializing_if = "Option::is_none"
359    )]
360    pub mindestbetriebszeit: Option<u32>,
361    /// Minimum downtime (minutes).
362    #[serde(
363        rename = "Mindeststillstandszeit",
364        default,
365        skip_serializing_if = "Option::is_none"
366    )]
367    pub mindeststillstandszeit: Option<u32>,
368    /// Cold start time, i.e. after > 48h downtime (minutes).
369    #[serde(
370        rename = "Anfahrzeit_kalt",
371        default,
372        skip_serializing_if = "Option::is_none"
373    )]
374    pub anfahrzeit_kalt: Option<u32>,
375    /// Warm start time, i.e. after ≤ 48h downtime (minutes).
376    #[serde(
377        rename = "Anfahrzeit_warm",
378        default,
379        skip_serializing_if = "Option::is_none"
380    )]
381    pub anfahrzeit_warm: Option<u32>,
382    /// Ramp-up time from cold start to synchronisation (minutes).
383    #[serde(
384        rename = "Hochfahrzeit_kalt",
385        default,
386        skip_serializing_if = "Option::is_none"
387    )]
388    pub hochfahrzeit_kalt: Option<u32>,
389    /// Ramp-up time from warm start to synchronisation (minutes).
390    #[serde(
391        rename = "Hochfahrzeit_warm",
392        default,
393        skip_serializing_if = "Option::is_none"
394    )]
395    pub hochfahrzeit_warm: Option<u32>,
396    /// Ramp-down time to grid disconnection (minutes).
397    #[serde(
398        rename = "Abfahrzeit",
399        default,
400        skip_serializing_if = "Option::is_none"
401    )]
402    pub abfahrzeit: Option<u32>,
403    /// Load gradient — upward ramp rate.
404    #[serde(
405        rename = "Lastgradient_Erhoehung",
406        default,
407        skip_serializing_if = "Option::is_none"
408    )]
409    pub lastgradient_erhoehung: Option<Lastgradient>,
410    /// Load gradient — downward ramp rate.
411    #[serde(
412        rename = "Lastgradient_Reduzierung",
413        default,
414        skip_serializing_if = "Option::is_none"
415    )]
416    pub lastgradient_reduzierung: Option<Lastgradient>,
417}
418
419/// A load gradient and the capacity it is expressed relative to.
420///
421/// `Basisgroesse` is what makes `@Gradient` interpretable: a gradient given in
422/// `%/min` is a percentage **of that base**, so a reader that drops it either
423/// has to guess the reference or cannot use the figure at all.
424#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
425pub struct Lastgradient {
426    /// The ramp rate.
427    #[serde(rename = "@Gradient")]
428    pub gradient: Decimal3,
429    /// Unit of the ramp rate (`Z01` = %/min, or `MAW`).
430    #[serde(rename = "@Einheit")]
431    pub einheit: String,
432    /// Capacity the gradient is relative to (MW).
433    #[serde(
434        rename = "Basisgroesse",
435        default,
436        skip_serializing_if = "Option::is_none"
437    )]
438    pub basisgroesse: Option<Decimal3>,
439}
440
441// ── TR-level Technische_Parameter ────────────────────────────────────────────
442
443/// Whether a 70 % Wirkleistungsbegrenzung applies (`Absenkung_70`).
444#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
445pub enum JaNein {
446    /// `A01` — yes.
447    #[serde(rename = "A01")]
448    Ja,
449    /// `A02` — no.
450    #[serde(rename = "A02")]
451    Nein,
452}
453
454/// Site coordinates of a Technische Ressource.
455#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
456pub struct Geokoordinaten {
457    /// Longitude, degrees east.
458    #[serde(rename = "@LaengeOst")]
459    pub laenge_ost: f64,
460    /// Latitude, degrees north.
461    #[serde(rename = "@BreiteNord")]
462    pub breite_nord: f64,
463}
464
465/// Technical parameters of a **Technische Ressource** — the plant nameplate.
466///
467/// A different complexType from [`TechnischeParameter`] despite sharing the
468/// element name; see that type's docs. These are the figures the Ausfallarbeit
469/// calculation reads: `BilAReM` Kap. 3.2.2.1 bounds `W_A` by `P_bean`, which is
470/// „die installierte Leistung der TR" less any Nichtbeanspruchbarkeit, and the
471/// Pauschal-Abrechnung of Kap. 3.2.2.3 multiplies an Anlagenfaktor by exactly
472/// that installed capacity.
473#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
474pub struct TrTechnischeParameter {
475    /// Nettonennleistung, production direction (MW).
476    #[serde(
477        rename = "Nettonennleistung_Prod",
478        default,
479        skip_serializing_if = "Option::is_none"
480    )]
481    pub nettonennleistung_prod: Option<Decimal3>,
482    /// Nettonennleistung, consumption direction (MW).
483    #[serde(
484        rename = "Nettonennleistung_Verb",
485        default,
486        skip_serializing_if = "Option::is_none"
487    )]
488    pub nettonennleistung_verb: Option<Decimal3>,
489    /// Nettoengpassleistung, production direction (MW).
490    #[serde(
491        rename = "Nettoengpassleistung_Prod",
492        default,
493        skip_serializing_if = "Option::is_none"
494    )]
495    pub nettoengpassleistung_prod: Option<Decimal3>,
496    /// Nettoengpassleistung, consumption direction (MW).
497    #[serde(
498        rename = "Nettoengpassleistung_Verb",
499        default,
500        skip_serializing_if = "Option::is_none"
501    )]
502    pub nettoengpassleistung_verb: Option<Decimal3>,
503    /// Bruttonennleistung (MW).
504    #[serde(
505        rename = "Bruttonennleistung",
506        default,
507        skip_serializing_if = "Option::is_none"
508    )]
509    pub bruttonennleistung: Option<Decimal3>,
510    /// Cumulated inverter capacity (MW).
511    #[serde(
512        rename = "Wechselrichterleistung_kumuliert",
513        default,
514        skip_serializing_if = "Option::is_none"
515    )]
516    pub wechselrichterleistung_kumuliert: Option<Decimal3>,
517    /// Whether the 70 % Wirkleistungsbegrenzung applies.
518    #[serde(
519        rename = "Absenkung_70",
520        default,
521        skip_serializing_if = "Option::is_none"
522    )]
523    pub absenkung_70: Option<JaNein>,
524    /// Plant type.
525    #[serde(
526        rename = "Anlagentyp",
527        default,
528        skip_serializing_if = "Option::is_none"
529    )]
530    pub anlagentyp: Option<String>,
531    /// Hub height of a wind turbine (m).
532    #[serde(
533        rename = "Nabenhoehe",
534        default,
535        skip_serializing_if = "Option::is_none"
536    )]
537    pub nabenhoehe: Option<Decimal3>,
538    /// Site coordinates.
539    #[serde(
540        rename = "Geokoordinaten",
541        default,
542        skip_serializing_if = "Option::is_none"
543    )]
544    pub geokoordinaten: Option<Geokoordinaten>,
545    /// Round-trip efficiency of a storage unit.
546    #[serde(
547        rename = "Wirkungsgrad_Speicher",
548        default,
549        skip_serializing_if = "Option::is_none"
550    )]
551    pub wirkungsgrad_speicher: Option<Decimal3>,
552    /// Usable energy content of a storage unit (MWh).
553    #[serde(
554        rename = "Nutzbarer_Energieinhalt_Speichers",
555        default,
556        skip_serializing_if = "Option::is_none"
557    )]
558    pub nutzbarer_energieinhalt_speichers: Option<Decimal3>,
559    /// Maximum charging power (MW).
560    #[serde(
561        rename = "Wirkleistung_Einspeichern_max",
562        default,
563        skip_serializing_if = "Option::is_none"
564    )]
565    pub wirkleistung_einspeichern_max: Option<Decimal3>,
566    /// Maximum discharging power (MW).
567    #[serde(
568        rename = "Wirkleistung_Ausspeichern_max",
569        default,
570        skip_serializing_if = "Option::is_none"
571    )]
572    pub wirkleistung_ausspeichern_max: Option<Decimal3>,
573}
574
575// ── Abrechnungsmodell ────────────────────────────────────────────────────────
576
577/// `Abrechnungsmodell` — the Ausfallarbeit settlement method elected for a
578/// Technische Ressource.
579///
580/// The election is the Anlagenbetreiber's, made by 30 November for the
581/// following calendar year (`BilAReM` Kap. 3.2.1); the ANB carries it in the
582/// Stammdaten. It is **mandatory** on every TR, because without it the
583/// Ausfallarbeit of a Redispatch-Maßnahme cannot be computed at all.
584///
585/// | Code | XSD label | `BilAReM` Kap. 3.2.1 |
586/// |------|-----------|----------------------|
587/// | `Z01` | `PAUSCHAL` | Pauschal-Abrechnung — grandfathered TR only, until 31.12.2028 |
588/// | `Z02` | `SPITZ` | Spitzabrechnung — measured Wetterdaten at the TR |
589/// | `Z03` | `SPITZLIGHT` | vereinfachte Spitzabrechnung — Referenzmesswerte or site weather data |
590///
591/// The XSD label `SPITZLIGHT` is the *vereinfachte* Spitzabrechnung, not a
592/// lighter variant of the Spitzabrechnung: it is the one that applies when no
593/// suitable weather data is measured **at** the TR, and it is the default when
594/// the Anlagenbetreiber elects nothing.
595#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
596pub enum Abrechnungsmodell {
597    /// `Z01` — Pauschal-Abrechnung (`BilAReM` Kap. 3.2.2.3 / 3.2.4.3).
598    #[serde(rename = "Z01")]
599    Pauschal,
600    /// `Z02` — Spitzabrechnung (`BilAReM` Kap. 3.2.2.1 / 3.2.4.1).
601    #[serde(rename = "Z02")]
602    Spitz,
603    /// `Z03` — vereinfachte Spitzabrechnung (`BilAReM` Kap. 3.2.2.2 / 3.2.4.2).
604    #[serde(rename = "Z03")]
605    SpitzLight,
606}
607
608/// `Betrieb` — the Stilllegungs-Status of a Technische Ressource on
609/// `Gueltig_ab`.
610///
611/// A container, not a flag: the XSD distinguishes the **vorläufige** from the
612/// **endgültige** Stilllegung, and the two have different consequences. A
613/// vorläufig stillgelegte Anlage still exists for Redispatch purposes; an
614/// endgültig stillgelegte one does not.
615#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
616pub struct Betrieb {
617    /// Whether the vorläufige Stilllegung is reached on `Gueltig_ab`.
618    #[serde(
619        rename = "Stilllegung_vorlaeufig_erreicht",
620        default,
621        skip_serializing_if = "Option::is_none"
622    )]
623    pub stilllegung_vorlaeufig_erreicht: Option<JaNein>,
624    /// Whether the endgültige Stilllegung is reached on `Gueltig_ab`.
625    #[serde(
626        rename = "Stilllegung_endgueltig_erreicht",
627        default,
628        skip_serializing_if = "Option::is_none"
629    )]
630    pub stilllegung_endgueltig_erreicht: Option<JaNein>,
631}
632
633// ── Marktlokation and Tranche ────────────────────────────────────────────────
634
635/// A Tranche of a Marktlokation, with the Bilanzkreis and Lieferant it belongs
636/// to.
637///
638/// `BilAReM` Kap. 2.1.2: „Ist die Einspeisung mehreren Tranchen … zugeordnet,
639/// wird der bilanzielle Ausgleich nach den für die Aufteilung der Einspeisung
640/// in Tranchen jeweils geltenden Regeln aufgeteilt." The split therefore needs
641/// both values, per Tranche.
642#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
643pub struct Tranche {
644    /// Tranche identifier.
645    #[serde(rename = "@Code")]
646    pub code: String,
647    /// Bilanzkreis of this Tranche (16-character EIC).
648    #[serde(rename = "Bilanzkreis_Tranche")]
649    pub bilanzkreis: String,
650    /// Lieferant of this Tranche.
651    #[serde(rename = "Lieferant_Tranche")]
652    pub lieferant: StammdatenParticipantRef,
653    /// Share of the Marktlokation this Tranche represents.
654    ///
655    /// `BilAReM` Kap. 2.1.2: the bilanzielle Ausgleich is split across Tranchen
656    /// „nach den für die Aufteilung der Einspeisung in Tranchen jeweils
657    /// geltenden Regeln", so the share is what the split is computed from.
658    #[serde(rename = "Tranchengroesse")]
659    pub tranchengroesse: Tranchengroesse,
660}
661
662/// The share of a Marktlokation one Tranche represents.
663#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
664pub struct Tranchengroesse {
665    /// Unit — always `P1` (percent).
666    #[serde(rename = "@Einheit")]
667    pub einheit: String,
668    /// The share, in percent.
669    #[serde(rename = "@Groesse")]
670    pub groesse: Decimal3,
671}
672
673/// A coded reference carried entirely in a `@Code` attribute.
674#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
675pub struct CodeRef {
676    /// The code.
677    #[serde(rename = "@Code")]
678    pub code: String,
679}
680
681/// The Marktlokation a Technische Ressource is billed through, and the
682/// **betroffener Bilanzkreis** the bilanzieller Ausgleich lands in.
683///
684/// A TR may name up to two (`maxOccurs="2"`) — a Stromspeichereinheit that both
685/// charges and discharges is billed through one MaLo per direction.
686#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
687pub struct Marktlokation {
688    /// The MaLo-ID.
689    #[serde(rename = "@Code")]
690    pub code: String,
691    /// Energy flow direction of this Marktlokation.
692    #[serde(rename = "@Lieferrichtung")]
693    pub lieferrichtung: String,
694    /// Bilanzkreis of the Marktlokation (16-character EIC), when the MaLo is
695    /// not split into Tranchen.
696    #[serde(
697        rename = "Bilanzkreis_Marktlokation",
698        default,
699        skip_serializing_if = "Option::is_none"
700    )]
701    pub bilanzkreis: Option<String>,
702    /// Tranchen of the Marktlokation, when the Einspeisung is split.
703    #[serde(rename = "Tranche", default, skip_serializing_if = "Vec::is_empty")]
704    pub tranchen: Vec<Tranche>,
705    /// Voltage level of the Marktlokation.
706    #[serde(rename = "Spannungsebene_Marktlokation")]
707    pub spannungsebene: CodeRef,
708    /// Transformation level, when the MaLo sits at one.
709    #[serde(
710        rename = "Umspannung_Marktlokation",
711        default,
712        skip_serializing_if = "Option::is_none"
713    )]
714    pub umspannung: Option<CodeRef>,
715    /// Messlokationen behind this Marktlokation (at least one).
716    #[serde(rename = "Messlokation")]
717    pub messlokationen: Vec<CodeRef>,
718    /// Lieferant of the Marktlokation, when it is not split into Tranchen.
719    #[serde(
720        rename = "Lieferant_Marktlokation",
721        default,
722        skip_serializing_if = "Option::is_none"
723    )]
724    pub lieferant: Option<StammdatenParticipantRef>,
725}
726
727// ── Enthaltene_TR (contained technical resources) ────────────────────────────
728
729/// A technical resource (Technische Ressource) contained within an
730/// `SR_Objekt`.
731///
732/// `BilAReM` Kap. 6.1.5: „Eine SR setzt sich aus **mindestens einer** TR
733/// zusammen" and „Jede TR ist genau einer SR zugeordnet."
734#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
735pub struct EnthaltenesTr {
736    /// MaStR number (Marktstammdatenregister).
737    ///
738    /// The XSD element is `MaStR-Nr`; the hyphen is preserved on the wire.
739    #[serde(rename = "MaStR-Nr", default, skip_serializing_if = "Option::is_none")]
740    pub ma_str_nr: Option<String>,
741    /// Human-readable name.
742    #[serde(rename = "Klarname", default, skip_serializing_if = "Option::is_none")]
743    pub klarname: Option<String>,
744    /// Resource type code.
745    #[serde(rename = "Typ")]
746    pub typ: String,
747    /// Plant code (`Code_Kraftwerk`).
748    #[serde(
749        rename = "Code_Kraftwerk",
750        default,
751        skip_serializing_if = "Option::is_none"
752    )]
753    pub code_kraftwerk: Option<String>,
754    /// Storage units this TR is assigned to.
755    #[serde(
756        rename = "Zuordnung_Speicher",
757        default,
758        skip_serializing_if = "Vec::is_empty"
759    )]
760    pub zuordnung_speicher: Vec<StammdatenParticipantRef>,
761    /// The Marktlokationen this TR is billed through (at most two — one per
762    /// direction for a Stromspeichereinheit).
763    #[serde(
764        rename = "Marktlokation",
765        default,
766        skip_serializing_if = "Vec::is_empty"
767    )]
768    pub marktlokationen: Vec<Marktlokation>,
769    /// EEG-Anlagenschlüssel of the plants behind this TR.
770    #[serde(
771        rename = "EEG_Anlagenschluessel",
772        default,
773        skip_serializing_if = "Vec::is_empty"
774    )]
775    pub eeg_anlagenschluessel: Vec<String>,
776    /// Ausfallarbeit settlement method — **mandatory**, because without it the
777    /// Ausfallarbeit of a Redispatch-Maßnahme cannot be computed.
778    #[serde(rename = "Abrechnungsmodell")]
779    pub abrechnungsmodell: Abrechnungsmodell,
780    /// Betreiber der technischen Ressource (BTR).
781    #[serde(
782        rename = "Betreiber_TR",
783        default,
784        skip_serializing_if = "Option::is_none"
785    )]
786    pub betreiber_tr: Option<StammdatenParticipantRef>,
787    /// Whether the Stilllegungszeitpunkt is reached on `Gueltig_ab`.
788    #[serde(rename = "Betrieb", default, skip_serializing_if = "Option::is_none")]
789    pub betrieb: Option<Betrieb>,
790    /// Technical parameters of this TR — the plant nameplate, a **different**
791    /// complexType from the SR-level one despite the shared element name.
792    #[serde(
793        rename = "Technische_Parameter",
794        default,
795        skip_serializing_if = "Option::is_none"
796    )]
797    pub technische_parameter: Option<TrTechnischeParameter>,
798}
799
800// ── SR_Objekt ─────────────────────────────────────────────────────────────────
801
802/// A steuerbare Ressource (controllable resource) object.
803///
804/// Each `SR_Objekt` describes one resource or cluster that participates in
805/// the Redispatch 2.0 process.
806#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
807pub struct SrObjekt {
808    /// Human-readable name (optional, max 35 chars, `[A-Z0-9\-+_]*`).
809    #[serde(rename = "Klarname", default, skip_serializing_if = "Option::is_none")]
810    pub klarname: Option<String>,
811    /// Network connection point operator.
812    #[serde(rename = "Anschluss_Netzbetreiber")]
813    pub anschluss_netzbetreiber: NbRef,
814    /// Ordering grid operator (optional — absent when same as connection NB).
815    #[serde(
816        rename = "Anweisender_Netzbetreiber",
817        default,
818        skip_serializing_if = "Option::is_none"
819    )]
820    pub anweisender_netzbetreiber: Option<NbRef>,
821    /// Affected grid operators in cascade order (up to 6).
822    #[serde(
823        rename = "Betroffene_Netzbetreiber",
824        default,
825        skip_serializing_if = "Vec::is_empty"
826    )]
827    pub betroffene_netzbetreiber: Vec<BetroffenerNb>,
828    /// Additional affected grid operators beyond the cascade of 6.
829    #[serde(
830        rename = "Weitere_betroffene_Netzbetreiber",
831        default,
832        skip_serializing_if = "Vec::is_empty"
833    )]
834    pub weitere_betroffene_netzbetreiber: Vec<NbRef>,
835    /// Einsatzverantwortlicher (EIV) — the party responsible for deploying the
836    /// SR.
837    ///
838    /// `BilAReM` Kap. 6.1.5: „Jede SR ist genau einem EIV zugeordnet", and
839    /// Kap. 6.1.6 names the default: the LF of the betroffene Marktlokation,
840    /// unless another company was designated. The element is optional in the
841    /// XSD because a `Z02` reduzierte Stammdaten message omits it, not because
842    /// an SR may lack one.
843    #[serde(
844        rename = "Einsatzverantwortlicher",
845        default,
846        skip_serializing_if = "Option::is_none"
847    )]
848    pub einsatzverantwortlicher: Option<StammdatenParticipantRef>,
849    /// Energy carrier.
850    #[serde(
851        rename = "Energietraeger",
852        default,
853        skip_serializing_if = "Option::is_none"
854    )]
855    pub energietraeger: Option<Energietraeger>,
856    /// Compensation type.
857    #[serde(
858        rename = "Verguetungsart",
859        default,
860        skip_serializing_if = "Option::is_none"
861    )]
862    pub verguetungsart: Option<Verguetungsart>,
863    /// Tolerance case status.
864    #[serde(
865        rename = "Status_Duldungsfall",
866        default,
867        skip_serializing_if = "Option::is_none"
868    )]
869    pub status_duldungsfall: Option<StatusDuldungsfall>,
870    /// Controllability definition.
871    #[serde(
872        rename = "Steuerbarkeit",
873        default,
874        skip_serializing_if = "Option::is_none"
875    )]
876    pub steuerbarkeit: Option<Steuerbarkeit>,
877    /// Call type for demand requests.
878    #[serde(
879        rename = "Abrufart_Aufforderungsfall",
880        default,
881        skip_serializing_if = "Option::is_none"
882    )]
883    pub abrufart_aufforderungsfall: Option<AbrufartAufforderungsfall>,
884    /// Billing model.
885    #[serde(rename = "Bilanzierungsmodell")]
886    pub bilanzierungsmodell: Bilanzierungsmodell,
887    /// Individual allocation quota percentages.
888    #[serde(
889        rename = "Individuelle_Quote",
890        default,
891        skip_serializing_if = "Option::is_none"
892    )]
893    pub individuelle_quote: Option<IndividuelleQuote>,
894    /// Bearbeitungszeit beim EIV in minutes — from an Aufforderung reaching the
895    /// EIV to its implementation in the plant.
896    #[serde(
897        rename = "Bearbeitungszeit_EIV",
898        default,
899        skip_serializing_if = "Option::is_none"
900    )]
901    pub bearbeitungszeit_eiv: Option<Decimal3>,
902    /// Control zone (Regelzone / TSO EIC code).
903    #[serde(rename = "Regelzone")]
904    pub regelzone: Regelzone,
905    /// Technical parameters of the resource (optional).
906    #[serde(
907        rename = "Technische_Parameter",
908        default,
909        skip_serializing_if = "Option::is_none"
910    )]
911    pub technische_parameter: Option<TechnischeParameter>,
912    /// Contained technical resources.
913    ///
914    /// `minOccurs="1"` in the XSD, and `BilAReM` Kap. 6.1.5 says why: „Eine SR
915    /// setzt sich aus mindestens einer TR zusammen." An SR with none is a
916    /// resource nothing can be dispatched against.
917    #[serde(rename = "Enthaltene_TR")]
918    pub enthaltene_tr: Vec<EnthaltenesTr>,
919}
920
921/// One share of the bilanzieller Ausgleich, and where it is booked.
922///
923/// Each Quote names the **Redispatch-Bilanzkreis** the corresponding share of
924/// the Ausgleichsfahrplan is scheduled against, and the Lieferant it belongs
925/// to. `BilAReM` Kap. 2.1.2 makes both load-bearing: the Ausgleich runs „durch
926/// die Anmeldung korrespondierender Fahrpläne", and „jeder Netzbetreiber
927/// verwendet genau einen Bilanzkreis als Redispatch-Bilanzkreis".
928#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
929pub struct Quote {
930    /// Unit of `wert` — always `P1` (percent) in the current XSD.
931    #[serde(rename = "@Einheit")]
932    pub einheit: String,
933    /// The share, in percent.
934    #[serde(rename = "@Wert")]
935    pub wert: Decimal3,
936    /// Bilanzkreis the Ausgleichsfahrplan for this share is scheduled against
937    /// (16-character EIC).
938    #[serde(rename = "Bilanzkreis_Ausgleichsfahrplan")]
939    pub bilanzkreis_ausgleichsfahrplan: String,
940    /// Lieferant this share belongs to.
941    #[serde(rename = "Lieferant")]
942    pub lieferant: StammdatenParticipantRef,
943}
944
945/// Individual allocation quota definition — up to twenty shares.
946#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
947pub struct IndividuelleQuote {
948    /// The shares. `maxOccurs="20"` in the XSD.
949    #[serde(rename = "Quote")]
950    pub quoten: Vec<Quote>,
951}
952
953// ── Existenzende and the anfNB Redispatch-Bilanzkreis ────────────────────────
954
955/// End of existence of one or more resources.
956#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
957pub struct Existenzende {
958    /// References of the resources whose existence is ending.
959    #[serde(rename = "Objektreferenz")]
960    pub objektreferenzen: Vec<StammdatenParticipantRef>,
961}
962
963/// The Redispatch-Bilanzkreis of one anfordernder Netzbetreiber.
964#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
965pub struct AnfordernderNetzbetreiber {
966    /// The anfNB's Redispatch-Bilanzkreis (16-character EIC).
967    #[serde(rename = "Bilanzkreis_anfNB")]
968    pub bilanzkreis: String,
969    /// The anfNB's Marktpartner-ID.
970    #[serde(rename = "Marktpartner_ID")]
971    pub marktpartner_id: StammdatenParticipantRef,
972}
973
974/// Which Redispatch-Bilanzkreis each anfordernder Netzbetreiber uses for one SR.
975///
976/// `BilAReM` Kap. 2.3.2 lists this among the three things a Planwertmodell
977/// Zuordnungsmitteilung must contain: „die Bezeichnung der SR mit ihrer SR-ID,
978/// das Datum der Wirksamkeit der Zuordnung und die **Nennung des
979/// Redispatch-Bilanzkreises** des ANB." Without it the LF and the EIV know an
980/// SR moved into the Planwertmodell but not where the Ausgleich will be booked.
981#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
982pub struct BilanzkreisAusgleichsfahrplanAnfNb {
983    /// The SR this applies to.
984    #[serde(rename = "SR_Objekt_Referenz")]
985    pub sr_objekt_referenz: StammdatenParticipantRef,
986    /// One entry per anfordernder Netzbetreiber (up to twenty).
987    #[serde(rename = "anfordernder_Netzbetreiber")]
988    pub anfordernde_netzbetreiber: Vec<AnfordernderNetzbetreiber>,
989}
990
991// ── Stammdaten ────────────────────────────────────────────────────────────────
992
993/// `Stammdaten` — master data for steuerbare Ressourcen in Redispatch 2.0.
994///
995/// XSD version: 1.4b (Fehlerkorrektur 2026-02-19)  
996/// Namespace: `urn:kwep_stammdaten:1:0`
997///
998/// Contains the static attributes of controllable resources (generation plants,
999/// storage, flexible loads) that participate in the Redispatch 2.0 process.
1000/// Submitted by resource providers (EIV) and DSOs (VNB) to TSOs (ÜNB).
1001#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1002#[serde(rename = "Stammdaten")]
1003pub struct Stammdaten {
1004    /// Unique document identifier (max 35 chars).
1005    #[serde(rename = "DocumentIdentification")]
1006    pub document_identification: DocumentId,
1007    /// Document type.
1008    #[serde(rename = "DocumentType")]
1009    pub document_type: StammdatenDocType,
1010    /// Document creation timestamp (UTC, second precision).
1011    #[serde(rename = "Erstellungszeitpunkt")]
1012    pub erstellungszeitpunkt: UtcDateTime,
1013    /// Sender identification.
1014    #[serde(rename = "Sender")]
1015    pub sender: StammdatenParticipantRef,
1016    /// Sender's market role.
1017    #[serde(rename = "Senderrolle")]
1018    pub senderrolle: StammdatenSenderRole,
1019    /// Receiver identification.
1020    #[serde(rename = "Empfaenger")]
1021    pub empfaenger: StammdatenParticipantRef,
1022    /// Receiver's market role.
1023    #[serde(rename = "Empfaengerrolle")]
1024    pub empfaengerrolle: StammdatenReceiverRole,
1025    /// Reference document identification (optional; used for updates).
1026    #[serde(
1027        rename = "RefDokumentID",
1028        default,
1029        skip_serializing_if = "Option::is_none"
1030    )]
1031    pub ref_dokument_id: Option<Mrid>,
1032    /// Original sender when forwarded via data provider (optional).
1033    #[serde(
1034        rename = "OriginalSender",
1035        default,
1036        skip_serializing_if = "Option::is_none"
1037    )]
1038    pub original_sender: Option<StammdatenParticipantRef>,
1039    /// Original document identifier when forwarded (optional).
1040    #[serde(
1041        rename = "OriginalDokumentID",
1042        default,
1043        skip_serializing_if = "Option::is_none"
1044    )]
1045    pub original_dokument_id: Option<Mrid>,
1046    /// Original creation timestamp when forwarded (optional).
1047    #[serde(
1048        rename = "OriginalErstellungszeitpunkt",
1049        default,
1050        skip_serializing_if = "Option::is_none"
1051    )]
1052    pub original_erstellungszeitpunkt: Option<UtcDateTime>,
1053    /// Validity start timestamp (UTC; represents German local time midnight).
1054    #[serde(rename = "Gueltig_ab")]
1055    pub gueltig_ab: UtcDateTime,
1056    /// Message status: creation, update, or deactivation.
1057    #[serde(rename = "Meldungsstatus")]
1058    pub meldungsstatus: Meldungsstatus,
1059    /// Controllable resource objects described in this document.
1060    #[serde(rename = "SR_Objekt", default)]
1061    pub sr_objekte: Vec<SrObjekt>,
1062    /// End of existence of one or more resources.
1063    #[serde(
1064        rename = "Existenzende",
1065        default,
1066        skip_serializing_if = "Option::is_none"
1067    )]
1068    pub existenzende: Option<Existenzende>,
1069    /// The Redispatch-Bilanzkreis each anfordernder Netzbetreiber uses for one
1070    /// SR — one of the three things a Planwertmodell Zuordnungsmitteilung must
1071    /// carry (`BilAReM` Kap. 2.3.2).
1072    #[serde(
1073        rename = "Bilanzkreis_Ausgleichsfahrplan_anfNB",
1074        default,
1075        skip_serializing_if = "Option::is_none"
1076    )]
1077    pub bilanzkreis_ausgleichsfahrplan_anf_nb: Option<BilanzkreisAusgleichsfahrplanAnfNb>,
1078}