Skip to main content

redispatch_xml/documents/
kaskade.rs

1//! `Kaskade` -- emergency cascade measure document for congestion relief between TSO and DSO.
2use serde::{Deserialize, Serialize};
3
4use crate::documents::activation::EicCodingScheme;
5use crate::types::{Decimal3, Mrid, RevisionNumber, SimpleContent, UtcDateTime, UtcMinuteDateTime};
6
7// ── Namespace ─────────────────────────────────────────────────────────────────
8
9/// Expected XML namespace for `Kaskade`.
10pub const NAMESPACE: &str = "urn:iec62325.351:tc57wg16:451-6:outagedocument:3:0";
11
12// ── Enumerations ──────────────────────────────────────────────────────────────
13
14/// Status value for the `Kaskade` document.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub enum KaskadeStatus {
17    /// Activated (information).
18    #[serde(rename = "A07")]
19    Activated,
20    /// Ordered (Anweisung).
21    #[serde(rename = "A10")]
22    Ordered,
23    /// Deactivation.
24    #[serde(rename = "A16")]
25    Deactivation,
26    /// Preliminary.
27    #[serde(rename = "A35")]
28    Preliminary,
29}
30
31/// Document type for `Kaskade`.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum KaskadeType {
34    /// Emergency measures per § 13(2) EnWG.
35    #[serde(rename = "Z16")]
36    EmergencyMeasures,
37    /// Test message.
38    #[serde(rename = "Z17")]
39    TestMessage,
40}
41
42/// Market role type used in `Kaskade` sender/receiver.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
44pub enum KaskadeRoleType {
45    /// Grid operator (NB).
46    #[serde(rename = "A18")]
47    GridOperator,
48}
49
50/// Business type for `Kaskade` time series.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
52pub enum KaskadeBusinessType {
53    /// Production.
54    #[serde(rename = "A01")]
55    Production,
56    /// Consumption.
57    #[serde(rename = "A04")]
58    Consumption,
59}
60
61/// Curve type for `Kaskade` time series (always `A03`).
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
63pub enum CurveType {
64    /// Variable sized block.
65    #[serde(rename = "A03")]
66    VariableSizedBlock,
67}
68
69/// Measure unit for `Kaskade` quantity (always `MAW`).
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
71pub enum KaskadeMeasureUnit {
72    /// Megawatt.
73    #[serde(rename = "MAW")]
74    Megawatt,
75}
76
77/// Reason code for `Kaskade` time series.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub enum KaskadeReasonCode {
80    /// Local grid problem.
81    #[serde(rename = "Z19")]
82    LocalGridProblem,
83    /// System balance problem.
84    #[serde(rename = "Z20")]
85    SystemBalanceProblem,
86}
87
88// ── IEC 62325 Market Participant ──────────────────────────────────────────────
89
90/// `mRID` element with `codingScheme` attribute (IEC 62325 simpleContent).
91pub type ParticipantMrid = SimpleContent<String>;
92
93// The sender and receiver are **flat, dotted** elements on the wire —
94// `<sender_MarketParticipant.mRID>` and
95// `<sender_MarketParticipant.marketRole.type>` — not a nested
96// `<sender_MarketParticipant>` container. That is the ENTSO-E CIM convention
97// the BDEW XSD follows, and the difference is not cosmetic: a nested document
98// fails XSD validation at the counterparty, and an inbound flat one loses the
99// sender entirely, because `serde` skips elements the model does not declare.
100
101// ── Status ────────────────────────────────────────────────────────────────────
102
103/// Status sub-element: `<status><value>A07</value></status>`.
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct StatusElement {
106    /// Status code.
107    pub value: KaskadeStatus,
108}
109
110// ── Time period ───────────────────────────────────────────────────────────────
111
112/// Time interval (separate start/end elements, minute precision).
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114pub struct KaskadeTimeInterval {
115    /// Interval start timestamp (optional — absent for non-time-restricted measures).
116    #[serde(rename = "start", default, skip_serializing_if = "Option::is_none")]
117    pub start: Option<UtcMinuteDateTime>,
118    /// Interval end timestamp (required — marks when the emergency measure expires).
119    pub end: UtcMinuteDateTime,
120}
121
122/// The `Available_Period` element wrapping the time interval and optional
123/// point data.
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub struct AvailablePeriod {
126    /// Time interval for this period.
127    #[serde(rename = "timeInterval")]
128    pub time_interval: KaskadeTimeInterval,
129    /// Resolution (optional; `PT1M` when present).
130    #[serde(
131        rename = "resolution",
132        default,
133        skip_serializing_if = "Option::is_none"
134    )]
135    pub resolution: Option<String>,
136    /// Point values within this period.
137    #[serde(rename = "Point", default, skip_serializing_if = "Vec::is_empty")]
138    pub points: Vec<KaskadePoint>,
139}
140
141/// A single point within `Available_Period`.
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143pub struct KaskadePoint {
144    /// Position in the series (always 1 for emergency measures).
145    pub position: u32,
146    /// Quantity in MW (the curtailed / required power).
147    pub quantity: Decimal3,
148}
149
150// ── Reason ────────────────────────────────────────────────────────────────────
151
152/// Reason for the cascade outage.
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
154pub struct KaskadeReason {
155    /// Reason code: local grid problem (`Z19`) or system balance (`Z20`).
156    pub code: KaskadeReasonCode,
157    /// Optional free-text description (max 512 chars).
158    #[serde(
159        rename = "ReasonText",
160        default,
161        skip_serializing_if = "Option::is_none"
162    )]
163    pub reason_text: Option<String>,
164}
165
166// ── ResourceObject ────────────────────────────────────────────────────────────
167
168/// Coding scheme for resource object identifiers within `Kaskade`.
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
170pub enum ResourceObjScheme {
171    /// EIC (Energy Identification Code) coding scheme (A01).
172    #[serde(rename = "A01")]
173    Eic,
174    /// National resource coding scheme (A02).
175    #[serde(rename = "A02")]
176    NationalResource,
177    /// NDE (German national format) coding scheme.
178    #[serde(rename = "NDE")]
179    Nde,
180    /// Other or proprietary coding scheme (Z01).
181    #[serde(rename = "Z01")]
182    Other,
183}
184
185/// A network connection point or resource object reference (simpleContent).
186pub type ResourceObjectRef = SimpleContent<String, ResourceObjScheme>;
187
188// ── BiddingZoneDomain ─────────────────────────────────────────────────────────
189
190/// Bidding zone domain reference (control zone EIC, simpleContent + A01).
191pub type BiddingZoneMrid = SimpleContent<String, EicCodingScheme>;
192
193// `biddingZone_Domain.mRID` and `quantity_Measure_Unit.name` are likewise flat
194// dotted elements, not containers.
195
196// ── KaskadeTimeSeries ─────────────────────────────────────────────────────────
197
198/// The time series within a `Kaskade` document.
199#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
200pub struct KaskadeTimeSeries {
201    /// Time-series identifier.
202    #[serde(rename = "mRID")]
203    pub m_rid: Mrid,
204    /// `mRID` of the original document this message relates to (optional).
205    #[serde(
206        rename = "senders_document_mRID",
207        default,
208        skip_serializing_if = "Option::is_none"
209    )]
210    pub senders_document_m_rid: Option<Mrid>,
211    /// Revision number of the original document (optional).
212    #[serde(
213        rename = "senders_revisionNumber",
214        default,
215        skip_serializing_if = "Option::is_none"
216    )]
217    pub senders_revision_number: Option<RevisionNumber>,
218    /// Creation timestamp of the original document (optional).
219    #[serde(
220        rename = "senders_createdDateTime",
221        default,
222        skip_serializing_if = "Option::is_none"
223    )]
224    pub senders_created_date_time: Option<UtcDateTime>,
225    /// Business type: production (`A01`) or consumption (`A04`).
226    #[serde(rename = "businessType")]
227    pub business_type: KaskadeBusinessType,
228    /// Network connection points / resource objects (0+).
229    #[serde(
230        rename = "ResourceObject",
231        default,
232        skip_serializing_if = "Vec::is_empty"
233    )]
234    pub resource_objects: Vec<ResourceObjectRef>,
235    /// Bidding zone domain (control zone EIC).
236    #[serde(rename = "biddingZone_Domain.mRID")]
237    pub bidding_zone_domain_m_rid: BiddingZoneMrid,
238    /// Power unit (always `MAW`).
239    #[serde(rename = "quantity_Measure_Unit.name")]
240    pub quantity_measure_unit_name: KaskadeMeasureUnit,
241    /// Curve type (always `A03`).
242    #[serde(rename = "curveType")]
243    pub curve_type: CurveType,
244    /// Available period with time interval and optional point data.
245    #[serde(rename = "Available_Period")]
246    pub available_period: AvailablePeriod,
247    /// Reason for the cascade measure.
248    #[serde(rename = "Reason")]
249    pub reason: KaskadeReason,
250}
251
252// ── Kaskade ───────────────────────────────────────────────────────────────────
253
254/// `Kaskade` — cascade outage / emergency measure notification.
255///
256/// XSD version: 1.0 (Fehlerkorrektur 2026-02-19)  
257/// Namespace: `urn:iec62325.351:tc57wg16:451-6:outagedocument:3:0`
258///
259/// Sent by a grid operator to notify downstream operators of an emergency
260/// curtailment measure under § 13(2) EnWG. All fields are in IEC 62325 /
261/// ENTSO-E direct-text style (not attr-v).
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
263#[serde(rename = "Kaskade")]
264pub struct Kaskade {
265    /// Document creation timestamp (UTC, second precision).
266    #[serde(rename = "createdDateTime")]
267    pub created_date_time: UtcDateTime,
268    /// Unique message identifier.
269    #[serde(rename = "mRID")]
270    pub m_rid: Mrid,
271    /// Revision number (1–999).
272    #[serde(rename = "revisionNumber")]
273    pub revision_number: RevisionNumber,
274    /// Status of this document revision.
275    pub status: StatusElement,
276    /// Document type: emergency measure (`Z16`) or test (`Z17`).
277    #[serde(rename = "type")]
278    pub doc_type: KaskadeType,
279    /// Sender market participant identifier.
280    #[serde(rename = "sender_MarketParticipant.mRID")]
281    pub sender_m_rid: ParticipantMrid,
282    /// Sender market role.
283    #[serde(rename = "sender_MarketParticipant.marketRole.type")]
284    pub sender_market_role: KaskadeRoleType,
285    /// Receiver market participant identifier.
286    #[serde(rename = "receiver_MarketParticipant.mRID")]
287    pub receiver_m_rid: ParticipantMrid,
288    /// Receiver market role.
289    #[serde(rename = "receiver_MarketParticipant.marketRole.type")]
290    pub receiver_market_role: KaskadeRoleType,
291    /// The curtailment / emergency time series.
292    #[serde(rename = "TimeSeries")]
293    pub time_series: KaskadeTimeSeries,
294}