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}