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 — the whole TR nameplate, in the case of `Enthaltene_TR` — so each
342/// shape gets its own type.
343///
344/// All fields are optional; only those relevant to the resource type are
345/// populated.
346#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
347pub struct TechnischeParameter {
348    /// Minimum dispatchable generation (MW).
349    #[serde(
350        rename = "Fahrbare_Mindesterzeugungsleistung",
351        default,
352        skip_serializing_if = "Option::is_none"
353    )]
354    pub fahrbare_mindesterzeugungsleistung: Option<Decimal3>,
355    /// Minimum run time (minutes).
356    #[serde(
357        rename = "Mindestbetriebszeit",
358        default,
359        skip_serializing_if = "Option::is_none"
360    )]
361    pub mindestbetriebszeit: Option<u32>,
362    /// Minimum downtime (minutes).
363    #[serde(
364        rename = "Mindeststillstandszeit",
365        default,
366        skip_serializing_if = "Option::is_none"
367    )]
368    pub mindeststillstandszeit: Option<u32>,
369    /// Cold start time, i.e. after > 48h downtime (minutes).
370    #[serde(
371        rename = "Anfahrzeit_kalt",
372        default,
373        skip_serializing_if = "Option::is_none"
374    )]
375    pub anfahrzeit_kalt: Option<u32>,
376    /// Warm start time, i.e. after ≤ 48h downtime (minutes).
377    #[serde(
378        rename = "Anfahrzeit_warm",
379        default,
380        skip_serializing_if = "Option::is_none"
381    )]
382    pub anfahrzeit_warm: Option<u32>,
383    /// Ramp-up time from cold start to synchronisation (minutes).
384    #[serde(
385        rename = "Hochfahrzeit_kalt",
386        default,
387        skip_serializing_if = "Option::is_none"
388    )]
389    pub hochfahrzeit_kalt: Option<u32>,
390    /// Ramp-up time from warm start to synchronisation (minutes).
391    #[serde(
392        rename = "Hochfahrzeit_warm",
393        default,
394        skip_serializing_if = "Option::is_none"
395    )]
396    pub hochfahrzeit_warm: Option<u32>,
397    /// Ramp-down time to grid disconnection (minutes).
398    #[serde(
399        rename = "Abfahrzeit",
400        default,
401        skip_serializing_if = "Option::is_none"
402    )]
403    pub abfahrzeit: Option<u32>,
404    /// Load gradient — upward ramp rate.
405    #[serde(
406        rename = "Lastgradient_Erhoehung",
407        default,
408        skip_serializing_if = "Option::is_none"
409    )]
410    pub lastgradient_erhoehung: Option<Lastgradient>,
411    /// Load gradient — downward ramp rate.
412    #[serde(
413        rename = "Lastgradient_Reduzierung",
414        default,
415        skip_serializing_if = "Option::is_none"
416    )]
417    pub lastgradient_reduzierung: Option<Lastgradient>,
418}
419
420/// A load gradient and the capacity it is expressed relative to.
421///
422/// `Basisgroesse` is what makes `@Gradient` interpretable: a gradient given in
423/// `%/min` is a percentage **of that base**, so a reader that drops it either
424/// has to guess the reference or cannot use the figure at all.
425#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
426pub struct Lastgradient {
427    /// The ramp rate.
428    #[serde(rename = "@Gradient")]
429    pub gradient: Decimal3,
430    /// Unit of the ramp rate (`Z01` = %/min, or `MAW`).
431    #[serde(rename = "@Einheit")]
432    pub einheit: String,
433    /// Capacity the gradient is relative to (MW).
434    #[serde(
435        rename = "Basisgroesse",
436        default,
437        skip_serializing_if = "Option::is_none"
438    )]
439    pub basisgroesse: Option<Decimal3>,
440}
441
442// ── TR-level Technische_Parameter ────────────────────────────────────────────
443
444/// Whether a 70 % Wirkleistungsbegrenzung applies (`Absenkung_70`).
445#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
446pub enum JaNein {
447    /// `A01` — yes.
448    #[serde(rename = "A01")]
449    Ja,
450    /// `A02` — no.
451    #[serde(rename = "A02")]
452    Nein,
453}
454
455/// Site coordinates of a Technische Ressource.
456#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
457pub struct Geokoordinaten {
458    /// Longitude, degrees east.
459    #[serde(rename = "@LaengeOst")]
460    pub laenge_ost: f64,
461    /// Latitude, degrees north.
462    #[serde(rename = "@BreiteNord")]
463    pub breite_nord: f64,
464}
465
466/// Technical parameters of a **Technische Ressource** — the plant nameplate.
467///
468/// A different complexType from [`TechnischeParameter`] despite sharing the
469/// element name; see that type's docs. These are the figures the Ausfallarbeit
470/// calculation reads: `BilAReM` Kap. 3.2.2.1 bounds `W_A` by `P_bean`, which is
471/// „die installierte Leistung der TR" less any Nichtbeanspruchbarkeit, and the
472/// Pauschal-Abrechnung of Kap. 3.2.2.3 multiplies an Anlagenfaktor by exactly
473/// that installed capacity.
474#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
475pub struct TrTechnischeParameter {
476    /// Nettonennleistung, production direction (MW).
477    #[serde(
478        rename = "Nettonennleistung_Prod",
479        default,
480        skip_serializing_if = "Option::is_none"
481    )]
482    pub nettonennleistung_prod: Option<Decimal3>,
483    /// Nettonennleistung, consumption direction (MW).
484    #[serde(
485        rename = "Nettonennleistung_Verb",
486        default,
487        skip_serializing_if = "Option::is_none"
488    )]
489    pub nettonennleistung_verb: Option<Decimal3>,
490    /// Nettoengpassleistung, production direction (MW).
491    #[serde(
492        rename = "Nettoengpassleistung_Prod",
493        default,
494        skip_serializing_if = "Option::is_none"
495    )]
496    pub nettoengpassleistung_prod: Option<Decimal3>,
497    /// Nettoengpassleistung, consumption direction (MW).
498    #[serde(
499        rename = "Nettoengpassleistung_Verb",
500        default,
501        skip_serializing_if = "Option::is_none"
502    )]
503    pub nettoengpassleistung_verb: Option<Decimal3>,
504    /// Bruttonennleistung (MW).
505    #[serde(
506        rename = "Bruttonennleistung",
507        default,
508        skip_serializing_if = "Option::is_none"
509    )]
510    pub bruttonennleistung: Option<Decimal3>,
511    /// Cumulated inverter capacity (MW).
512    #[serde(
513        rename = "Wechselrichterleistung_kumuliert",
514        default,
515        skip_serializing_if = "Option::is_none"
516    )]
517    pub wechselrichterleistung_kumuliert: Option<Decimal3>,
518    /// Whether the 70 % Wirkleistungsbegrenzung applies.
519    #[serde(
520        rename = "Absenkung_70",
521        default,
522        skip_serializing_if = "Option::is_none"
523    )]
524    pub absenkung_70: Option<JaNein>,
525    /// Plant type.
526    #[serde(
527        rename = "Anlagentyp",
528        default,
529        skip_serializing_if = "Option::is_none"
530    )]
531    pub anlagentyp: Option<String>,
532    /// Hub height of a wind turbine (m).
533    #[serde(
534        rename = "Nabenhoehe",
535        default,
536        skip_serializing_if = "Option::is_none"
537    )]
538    pub nabenhoehe: Option<Decimal3>,
539    /// Site coordinates.
540    #[serde(
541        rename = "Geokoordinaten",
542        default,
543        skip_serializing_if = "Option::is_none"
544    )]
545    pub geokoordinaten: Option<Geokoordinaten>,
546    /// Round-trip efficiency of a storage unit.
547    #[serde(
548        rename = "Wirkungsgrad_Speicher",
549        default,
550        skip_serializing_if = "Option::is_none"
551    )]
552    pub wirkungsgrad_speicher: Option<Decimal3>,
553    /// Usable energy content of a storage unit (MWh).
554    #[serde(
555        rename = "Nutzbarer_Energieinhalt_Speichers",
556        default,
557        skip_serializing_if = "Option::is_none"
558    )]
559    pub nutzbarer_energieinhalt_speichers: Option<Decimal3>,
560    /// Maximum charging power (MW).
561    #[serde(
562        rename = "Wirkleistung_Einspeichern_max",
563        default,
564        skip_serializing_if = "Option::is_none"
565    )]
566    pub wirkleistung_einspeichern_max: Option<Decimal3>,
567    /// Maximum discharging power (MW).
568    #[serde(
569        rename = "Wirkleistung_Ausspeichern_max",
570        default,
571        skip_serializing_if = "Option::is_none"
572    )]
573    pub wirkleistung_ausspeichern_max: Option<Decimal3>,
574}
575
576// ── Abrechnungsmodell ────────────────────────────────────────────────────────
577
578/// `Abrechnungsmodell` — the Ausfallarbeit settlement method elected for a
579/// Technische Ressource.
580///
581/// The election is the Anlagenbetreiber's, made by 30 November for the
582/// following calendar year (`BilAReM` Kap. 3.2.1); the ANB carries it in the
583/// Stammdaten. It is **mandatory** on every TR, because without it the
584/// Ausfallarbeit of a Redispatch-Maßnahme cannot be computed at all.
585///
586/// | Code | XSD label | `BilAReM` Kap. 3.2.1 |
587/// |------|-----------|----------------------|
588/// | `Z01` | `PAUSCHAL` | Pauschal-Abrechnung — grandfathered TR only, until 31.12.2028 |
589/// | `Z02` | `SPITZ` | Spitzabrechnung — measured Wetterdaten at the TR |
590/// | `Z03` | `SPITZLIGHT` | vereinfachte Spitzabrechnung — Referenzmesswerte or site weather data |
591///
592/// The XSD label `SPITZLIGHT` is the *vereinfachte* Spitzabrechnung, not a
593/// lighter variant of the Spitzabrechnung: it is the one that applies when no
594/// suitable weather data is measured **at** the TR, and it is the default when
595/// the Anlagenbetreiber elects nothing.
596#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
597pub enum Abrechnungsmodell {
598    /// `Z01` — Pauschal-Abrechnung (`BilAReM` Kap. 3.2.2.3 / 3.2.4.3).
599    #[serde(rename = "Z01")]
600    Pauschal,
601    /// `Z02` — Spitzabrechnung (`BilAReM` Kap. 3.2.2.1 / 3.2.4.1).
602    #[serde(rename = "Z02")]
603    Spitz,
604    /// `Z03` — vereinfachte Spitzabrechnung (`BilAReM` Kap. 3.2.2.2 / 3.2.4.2).
605    #[serde(rename = "Z03")]
606    SpitzLight,
607}
608
609/// `Betrieb` — the Stilllegungs-Status of a Technische Ressource on
610/// `Gueltig_ab`.
611///
612/// A container, not a flag: the XSD distinguishes the **vorläufige** from the
613/// **endgültige** Stilllegung, and the two have different consequences. A
614/// vorläufig stillgelegte Anlage still exists for Redispatch purposes; an
615/// endgültig stillgelegte one does not.
616#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
617pub struct Betrieb {
618    /// Whether the vorläufige Stilllegung is reached on `Gueltig_ab`.
619    #[serde(
620        rename = "Stilllegung_vorlaeufig_erreicht",
621        default,
622        skip_serializing_if = "Option::is_none"
623    )]
624    pub stilllegung_vorlaeufig_erreicht: Option<JaNein>,
625    /// Whether the endgültige Stilllegung is reached on `Gueltig_ab`.
626    #[serde(
627        rename = "Stilllegung_endgueltig_erreicht",
628        default,
629        skip_serializing_if = "Option::is_none"
630    )]
631    pub stilllegung_endgueltig_erreicht: Option<JaNein>,
632}
633
634// ── Marktlokation and Tranche ────────────────────────────────────────────────
635
636/// A Tranche of a Marktlokation, with the Bilanzkreis and Lieferant it belongs
637/// to.
638///
639/// `BilAReM` Kap. 2.1.2: „Ist die Einspeisung mehreren Tranchen … zugeordnet,
640/// wird der bilanzielle Ausgleich nach den für die Aufteilung der Einspeisung
641/// in Tranchen jeweils geltenden Regeln aufgeteilt." The split therefore needs
642/// both values, per Tranche.
643#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
644pub struct Tranche {
645    /// Tranche identifier.
646    #[serde(rename = "@Code")]
647    pub code: String,
648    /// Bilanzkreis of this Tranche (16-character EIC).
649    #[serde(rename = "Bilanzkreis_Tranche")]
650    pub bilanzkreis: String,
651    /// Lieferant of this Tranche.
652    #[serde(rename = "Lieferant_Tranche")]
653    pub lieferant: StammdatenParticipantRef,
654    /// Share of the Marktlokation this Tranche represents.
655    ///
656    /// `BilAReM` Kap. 2.1.2: the bilanzielle Ausgleich is split across Tranchen
657    /// „nach den für die Aufteilung der Einspeisung in Tranchen jeweils
658    /// geltenden Regeln", so the share is what the split is computed from.
659    #[serde(rename = "Tranchengroesse")]
660    pub tranchengroesse: Tranchengroesse,
661}
662
663/// The share of a Marktlokation one Tranche represents.
664#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
665pub struct Tranchengroesse {
666    /// Unit — always `P1` (percent).
667    #[serde(rename = "@Einheit")]
668    pub einheit: String,
669    /// The share, in percent.
670    #[serde(rename = "@Groesse")]
671    pub groesse: Decimal3,
672}
673
674/// A coded reference carried entirely in a `@Code` attribute.
675#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
676pub struct CodeRef {
677    /// The code.
678    #[serde(rename = "@Code")]
679    pub code: String,
680}
681
682/// The Marktlokation a Technische Ressource is billed through, and the
683/// **betroffener Bilanzkreis** the bilanzieller Ausgleich lands in.
684///
685/// A TR may name up to two (`maxOccurs="2"`) — a Stromspeichereinheit that both
686/// charges and discharges is billed through one MaLo per direction.
687#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
688pub struct Marktlokation {
689    /// The MaLo-ID.
690    #[serde(rename = "@Code")]
691    pub code: String,
692    /// Energy flow direction of this Marktlokation.
693    #[serde(rename = "@Lieferrichtung")]
694    pub lieferrichtung: String,
695    /// Bilanzkreis of the Marktlokation (16-character EIC), when the MaLo is
696    /// not split into Tranchen.
697    #[serde(
698        rename = "Bilanzkreis_Marktlokation",
699        default,
700        skip_serializing_if = "Option::is_none"
701    )]
702    pub bilanzkreis: Option<String>,
703    /// Tranchen of the Marktlokation, when the Einspeisung is split.
704    #[serde(rename = "Tranche", default, skip_serializing_if = "Vec::is_empty")]
705    pub tranchen: Vec<Tranche>,
706    /// Voltage level of the Marktlokation.
707    #[serde(rename = "Spannungsebene_Marktlokation")]
708    pub spannungsebene: CodeRef,
709    /// Transformation level, when the MaLo sits at one.
710    #[serde(
711        rename = "Umspannung_Marktlokation",
712        default,
713        skip_serializing_if = "Option::is_none"
714    )]
715    pub umspannung: Option<CodeRef>,
716    /// Messlokationen behind this Marktlokation (at least one).
717    #[serde(rename = "Messlokation")]
718    pub messlokationen: Vec<CodeRef>,
719    /// Lieferant of the Marktlokation, when it is not split into Tranchen.
720    #[serde(
721        rename = "Lieferant_Marktlokation",
722        default,
723        skip_serializing_if = "Option::is_none"
724    )]
725    pub lieferant: Option<StammdatenParticipantRef>,
726}
727
728// ── Enthaltene_TR (contained technical resources) ────────────────────────────
729
730/// A technical resource (Technische Ressource) contained within an
731/// `SR_Objekt`.
732///
733/// `BilAReM` Kap. 6.1.5: „Eine SR setzt sich aus **mindestens einer** TR
734/// zusammen" and „Jede TR ist genau einer SR zugeordnet."
735#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
736pub struct EnthaltenesTr {
737    /// MaStR number (Marktstammdatenregister).
738    ///
739    /// The XSD element is `MaStR-Nr`; the hyphen is preserved on the wire.
740    #[serde(rename = "MaStR-Nr", default, skip_serializing_if = "Option::is_none")]
741    pub ma_str_nr: Option<String>,
742    /// Human-readable name.
743    #[serde(rename = "Klarname", default, skip_serializing_if = "Option::is_none")]
744    pub klarname: Option<String>,
745    /// Resource type code.
746    #[serde(rename = "Typ")]
747    pub typ: String,
748    /// Plant code (`Code_Kraftwerk`).
749    #[serde(
750        rename = "Code_Kraftwerk",
751        default,
752        skip_serializing_if = "Option::is_none"
753    )]
754    pub code_kraftwerk: Option<String>,
755    /// Storage units this TR is assigned to.
756    #[serde(
757        rename = "Zuordnung_Speicher",
758        default,
759        skip_serializing_if = "Vec::is_empty"
760    )]
761    pub zuordnung_speicher: Vec<StammdatenParticipantRef>,
762    /// The Marktlokationen this TR is billed through (at most two — one per
763    /// direction for a Stromspeichereinheit).
764    #[serde(
765        rename = "Marktlokation",
766        default,
767        skip_serializing_if = "Vec::is_empty"
768    )]
769    pub marktlokationen: Vec<Marktlokation>,
770    /// EEG-Anlagenschlüssel of the plants behind this TR.
771    #[serde(
772        rename = "EEG_Anlagenschluessel",
773        default,
774        skip_serializing_if = "Vec::is_empty"
775    )]
776    pub eeg_anlagenschluessel: Vec<String>,
777    /// Ausfallarbeit settlement method — **mandatory**, because without it the
778    /// Ausfallarbeit of a Redispatch-Maßnahme cannot be computed.
779    #[serde(rename = "Abrechnungsmodell")]
780    pub abrechnungsmodell: Abrechnungsmodell,
781    /// Betreiber der technischen Ressource (BTR).
782    #[serde(
783        rename = "Betreiber_TR",
784        default,
785        skip_serializing_if = "Option::is_none"
786    )]
787    pub betreiber_tr: Option<StammdatenParticipantRef>,
788    /// Whether the Stilllegungszeitpunkt is reached on `Gueltig_ab`.
789    #[serde(rename = "Betrieb", default, skip_serializing_if = "Option::is_none")]
790    pub betrieb: Option<Betrieb>,
791    /// Technical parameters of this TR — the plant nameplate, a **different**
792    /// complexType from the SR-level one despite the shared element name.
793    #[serde(
794        rename = "Technische_Parameter",
795        default,
796        skip_serializing_if = "Option::is_none"
797    )]
798    pub technische_parameter: Option<TrTechnischeParameter>,
799}
800
801// ── SR_Objekt ─────────────────────────────────────────────────────────────────
802
803/// A steuerbare Ressource (controllable resource) object.
804///
805/// Each `SR_Objekt` describes one resource or cluster that participates in
806/// the Redispatch 2.0 process.
807#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
808pub struct SrObjekt {
809    /// Human-readable name (optional, max 35 chars, `[A-Z0-9\-+_]*`).
810    #[serde(rename = "Klarname", default, skip_serializing_if = "Option::is_none")]
811    pub klarname: Option<String>,
812    /// Network connection point operator.
813    #[serde(rename = "Anschluss_Netzbetreiber")]
814    pub anschluss_netzbetreiber: NbRef,
815    /// Ordering grid operator (optional — absent when same as connection NB).
816    #[serde(
817        rename = "Anweisender_Netzbetreiber",
818        default,
819        skip_serializing_if = "Option::is_none"
820    )]
821    pub anweisender_netzbetreiber: Option<NbRef>,
822    /// Affected grid operators in cascade order (up to 6).
823    #[serde(
824        rename = "Betroffene_Netzbetreiber",
825        default,
826        skip_serializing_if = "Vec::is_empty"
827    )]
828    pub betroffene_netzbetreiber: Vec<BetroffenerNb>,
829    /// Additional affected grid operators beyond the cascade of 6.
830    #[serde(
831        rename = "Weitere_betroffene_Netzbetreiber",
832        default,
833        skip_serializing_if = "Vec::is_empty"
834    )]
835    pub weitere_betroffene_netzbetreiber: Vec<NbRef>,
836    /// Einsatzverantwortlicher (EIV) — the party responsible for deploying the
837    /// SR.
838    ///
839    /// `BilAReM` Kap. 6.1.5: „Jede SR ist genau einem EIV zugeordnet", and
840    /// Kap. 6.1.6 names the default: the LF of the betroffene Marktlokation,
841    /// unless another company was designated. The element is optional in the
842    /// XSD because a `Z02` reduzierte Stammdaten message omits it, not because
843    /// an SR may lack one.
844    #[serde(
845        rename = "Einsatzverantwortlicher",
846        default,
847        skip_serializing_if = "Option::is_none"
848    )]
849    pub einsatzverantwortlicher: Option<StammdatenParticipantRef>,
850    /// Energy carrier.
851    #[serde(
852        rename = "Energietraeger",
853        default,
854        skip_serializing_if = "Option::is_none"
855    )]
856    pub energietraeger: Option<Energietraeger>,
857    /// Compensation type.
858    #[serde(
859        rename = "Verguetungsart",
860        default,
861        skip_serializing_if = "Option::is_none"
862    )]
863    pub verguetungsart: Option<Verguetungsart>,
864    /// Tolerance case status.
865    #[serde(
866        rename = "Status_Duldungsfall",
867        default,
868        skip_serializing_if = "Option::is_none"
869    )]
870    pub status_duldungsfall: Option<StatusDuldungsfall>,
871    /// Controllability definition.
872    #[serde(
873        rename = "Steuerbarkeit",
874        default,
875        skip_serializing_if = "Option::is_none"
876    )]
877    pub steuerbarkeit: Option<Steuerbarkeit>,
878    /// Call type for demand requests.
879    #[serde(
880        rename = "Abrufart_Aufforderungsfall",
881        default,
882        skip_serializing_if = "Option::is_none"
883    )]
884    pub abrufart_aufforderungsfall: Option<AbrufartAufforderungsfall>,
885    /// Billing model.
886    #[serde(rename = "Bilanzierungsmodell")]
887    pub bilanzierungsmodell: Bilanzierungsmodell,
888    /// Individual allocation quota percentages.
889    #[serde(
890        rename = "Individuelle_Quote",
891        default,
892        skip_serializing_if = "Option::is_none"
893    )]
894    pub individuelle_quote: Option<IndividuelleQuote>,
895    /// Bearbeitungszeit beim EIV in minutes — from an Aufforderung reaching the
896    /// EIV to its implementation in the plant.
897    #[serde(
898        rename = "Bearbeitungszeit_EIV",
899        default,
900        skip_serializing_if = "Option::is_none"
901    )]
902    pub bearbeitungszeit_eiv: Option<Decimal3>,
903    /// Control zone (Regelzone / TSO EIC code).
904    #[serde(rename = "Regelzone")]
905    pub regelzone: Regelzone,
906    /// Technical parameters of the resource (optional).
907    #[serde(
908        rename = "Technische_Parameter",
909        default,
910        skip_serializing_if = "Option::is_none"
911    )]
912    pub technische_parameter: Option<TechnischeParameter>,
913    /// Contained technical resources.
914    ///
915    /// `minOccurs="1"` in the XSD, and `BilAReM` Kap. 6.1.5 says why: „Eine SR
916    /// setzt sich aus mindestens einer TR zusammen." An SR with none is a
917    /// resource nothing can be dispatched against.
918    #[serde(rename = "Enthaltene_TR")]
919    pub enthaltene_tr: Vec<EnthaltenesTr>,
920}
921
922/// One share of the bilanzieller Ausgleich, and where it is booked.
923///
924/// Each Quote names the **Redispatch-Bilanzkreis** the corresponding share of
925/// the Ausgleichsfahrplan is scheduled against, and the Lieferant it belongs
926/// to. `BilAReM` Kap. 2.1.2 makes both load-bearing: the Ausgleich runs „durch
927/// die Anmeldung korrespondierender Fahrpläne", and „jeder Netzbetreiber
928/// verwendet genau einen Bilanzkreis als Redispatch-Bilanzkreis".
929#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
930pub struct Quote {
931    /// Unit of `wert` — always `P1` (percent) in the current XSD.
932    #[serde(rename = "@Einheit")]
933    pub einheit: String,
934    /// The share, in percent.
935    #[serde(rename = "@Wert")]
936    pub wert: Decimal3,
937    /// Bilanzkreis the Ausgleichsfahrplan for this share is scheduled against
938    /// (16-character EIC).
939    #[serde(rename = "Bilanzkreis_Ausgleichsfahrplan")]
940    pub bilanzkreis_ausgleichsfahrplan: String,
941    /// Lieferant this share belongs to.
942    #[serde(rename = "Lieferant")]
943    pub lieferant: StammdatenParticipantRef,
944}
945
946/// Individual allocation quota definition — up to twenty shares.
947#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
948pub struct IndividuelleQuote {
949    /// The shares. `maxOccurs="20"` in the XSD.
950    #[serde(rename = "Quote")]
951    pub quoten: Vec<Quote>,
952}
953
954// ── Existenzende and the anfNB Redispatch-Bilanzkreis ────────────────────────
955
956/// End of existence of one or more resources.
957#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
958pub struct Existenzende {
959    /// References of the resources whose existence is ending.
960    #[serde(rename = "Objektreferenz")]
961    pub objektreferenzen: Vec<StammdatenParticipantRef>,
962}
963
964/// The Redispatch-Bilanzkreis of one anfordernder Netzbetreiber.
965#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
966pub struct AnfordernderNetzbetreiber {
967    /// The anfNB's Redispatch-Bilanzkreis (16-character EIC).
968    #[serde(rename = "Bilanzkreis_anfNB")]
969    pub bilanzkreis: String,
970    /// The anfNB's Marktpartner-ID.
971    #[serde(rename = "Marktpartner_ID")]
972    pub marktpartner_id: StammdatenParticipantRef,
973}
974
975/// Which Redispatch-Bilanzkreis each anfordernder Netzbetreiber uses for one SR.
976///
977/// `BilAReM` Kap. 2.3.2 lists this among the three things a Planwertmodell
978/// Zuordnungsmitteilung must contain: „die Bezeichnung der SR mit ihrer SR-ID,
979/// das Datum der Wirksamkeit der Zuordnung und die **Nennung des
980/// Redispatch-Bilanzkreises** des ANB." Without it the LF and the EIV know an
981/// SR moved into the Planwertmodell but not where the Ausgleich will be booked.
982#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
983pub struct BilanzkreisAusgleichsfahrplanAnfNb {
984    /// The SR this applies to.
985    #[serde(rename = "SR_Objekt_Referenz")]
986    pub sr_objekt_referenz: StammdatenParticipantRef,
987    /// One entry per anfordernder Netzbetreiber (up to twenty).
988    #[serde(rename = "anfordernder_Netzbetreiber")]
989    pub anfordernde_netzbetreiber: Vec<AnfordernderNetzbetreiber>,
990}
991
992// ── Stammdaten ────────────────────────────────────────────────────────────────
993
994/// `Stammdaten` — master data for steuerbare Ressourcen in Redispatch 2.0.
995///
996/// XSD version: 1.4b (Fehlerkorrektur 2026-02-19)  
997/// Namespace: `urn:kwep_stammdaten:1:0`
998///
999/// Contains the static attributes of controllable resources (generation plants,
1000/// storage, flexible loads) that participate in the Redispatch 2.0 process.
1001/// Submitted by resource providers (EIV) and DSOs (VNB) to TSOs (ÜNB).
1002#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1003#[serde(rename = "Stammdaten")]
1004pub struct Stammdaten {
1005    /// Unique document identifier (max 35 chars).
1006    #[serde(rename = "DocumentIdentification")]
1007    pub document_identification: DocumentId,
1008    /// Document type.
1009    #[serde(rename = "DocumentType")]
1010    pub document_type: StammdatenDocType,
1011    /// Document creation timestamp (UTC, second precision).
1012    #[serde(rename = "Erstellungszeitpunkt")]
1013    pub erstellungszeitpunkt: UtcDateTime,
1014    /// Sender identification.
1015    #[serde(rename = "Sender")]
1016    pub sender: StammdatenParticipantRef,
1017    /// Sender's market role.
1018    #[serde(rename = "Senderrolle")]
1019    pub senderrolle: StammdatenSenderRole,
1020    /// Receiver identification.
1021    #[serde(rename = "Empfaenger")]
1022    pub empfaenger: StammdatenParticipantRef,
1023    /// Receiver's market role.
1024    #[serde(rename = "Empfaengerrolle")]
1025    pub empfaengerrolle: StammdatenReceiverRole,
1026    /// Reference document identification (optional; used for updates).
1027    #[serde(
1028        rename = "RefDokumentID",
1029        default,
1030        skip_serializing_if = "Option::is_none"
1031    )]
1032    pub ref_dokument_id: Option<Mrid>,
1033    /// Original sender when forwarded via data provider (optional).
1034    #[serde(
1035        rename = "OriginalSender",
1036        default,
1037        skip_serializing_if = "Option::is_none"
1038    )]
1039    pub original_sender: Option<StammdatenParticipantRef>,
1040    /// Original document identifier when forwarded (optional).
1041    #[serde(
1042        rename = "OriginalDokumentID",
1043        default,
1044        skip_serializing_if = "Option::is_none"
1045    )]
1046    pub original_dokument_id: Option<Mrid>,
1047    /// Original creation timestamp when forwarded (optional).
1048    #[serde(
1049        rename = "OriginalErstellungszeitpunkt",
1050        default,
1051        skip_serializing_if = "Option::is_none"
1052    )]
1053    pub original_erstellungszeitpunkt: Option<UtcDateTime>,
1054    /// Validity start timestamp (UTC; represents German local time midnight).
1055    #[serde(rename = "Gueltig_ab")]
1056    pub gueltig_ab: UtcDateTime,
1057    /// Message status: creation, update, or deactivation.
1058    #[serde(rename = "Meldungsstatus")]
1059    pub meldungsstatus: Meldungsstatus,
1060    /// Controllable resource objects described in this document.
1061    #[serde(rename = "SR_Objekt", default)]
1062    pub sr_objekte: Vec<SrObjekt>,
1063    /// End of existence of one or more resources.
1064    #[serde(
1065        rename = "Existenzende",
1066        default,
1067        skip_serializing_if = "Option::is_none"
1068    )]
1069    pub existenzende: Option<Existenzende>,
1070    /// The Redispatch-Bilanzkreis each anfordernder Netzbetreiber uses for one
1071    /// SR — one of the three things a Planwertmodell Zuordnungsmitteilung must
1072    /// carry (`BilAReM` Kap. 2.3.2).
1073    #[serde(
1074        rename = "Bilanzkreis_Ausgleichsfahrplan_anfNB",
1075        default,
1076        skip_serializing_if = "Option::is_none"
1077    )]
1078    pub bilanzkreis_ausgleichsfahrplan_anf_nb: Option<BilanzkreisAusgleichsfahrplanAnfNb>,
1079}