Skip to main content

redispatch_xml/validation/
semantic.rs

1//! Semantic validation — cross-field rules from the BDEW AWT.
2//!
3//! These rules require context from more than one field and cannot be derived
4//! from the XSD alone.
5
6use super::{ValidationError, ValidationResult};
7use crate::documents::activation::ActivationDocType;
8use crate::parse::Document;
9
10/// Run semantic checks on any [`Document`] variant.
11pub fn validate(doc: &Document, result: &mut ValidationResult) {
12    match doc {
13        Document::Activation(d) => {
14            // ACO (A96) and ACR (A41) documents must carry at least one time series.
15            match d.document_type.v {
16                ActivationDocType::RedispatchActivation | ActivationDocType::ActivationResponse => {
17                    if d.time_series.is_empty() {
18                        result.errors.push(ValidationError::Semantic(
19                            "ACO/ACR ActivationDocument must contain at least one ActivationTimeSeries"
20                                .to_string(),
21                        ));
22                    }
23                }
24                // AAR (A42) may have zero time series (tender reduction).
25                ActivationDocType::TenderReduction => {}
26            }
27        }
28        Document::Kostenblatt(d) => {
29            if d.time_series.is_empty() {
30                result.errors.push(ValidationError::Semantic(
31                    "Kostenblatt must contain at least one CostTimeSeries".to_string(),
32                ));
33            }
34        }
35        Document::PlannedResourceSchedule(d) => {
36            if d.time_series.is_empty() {
37                result.errors.push(ValidationError::Semantic(
38                    "PlannedResourceScheduleDocument must contain at least one PlannedResourceTimeSeries"
39                        .to_string(),
40                ));
41            }
42        }
43        Document::Stammdaten(d) => {
44            // A Stammdaten document must describe at least one SR_Objekt
45            // (controllable resource) unless it is a deactivation/withdrawal.
46            use crate::documents::stammdaten::Meldungsstatus;
47            if d.meldungsstatus != Meldungsstatus::Deactivation && d.sr_objekte.is_empty() {
48                result.errors.push(ValidationError::Semantic(
49                    "Stammdaten (creation/update) must contain at least one SR_Objekt".to_string(),
50                ));
51            }
52        }
53        Document::NetworkConstraint(d) => {
54            // A NetworkConstraintDocument without a withdrawal status must carry
55            // at least one time series.
56            if d.doc_status.is_none() && d.time_series.is_empty() {
57                result.errors.push(ValidationError::Semantic(
58                    "NetworkConstraintDocument must contain at least one NetworkConstraintTimeSeries \
59                     (or carry a DocStatus withdrawal)"
60                        .to_string(),
61                ));
62            }
63        }
64        Document::Unavailability(d) => {
65            // An unavailability document without a docStatus must carry at least
66            // one TimeSeries.
67            if d.doc_status.is_none() && d.time_series.is_empty() {
68                result.errors.push(ValidationError::Semantic(
69                    "Unavailability_MarketDocument must contain at least one TimeSeries \
70                     (or carry a docStatus withdrawal)"
71                        .to_string(),
72                ));
73            }
74        }
75        // Acknowledgement, StatusRequest, Kaskade: no additional semantic rules.
76        _ => {}
77    }
78}