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}