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}