Skip to main content

automapper_validation/generated/fv2610/
utilts_conditions_fv2610.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2610/UTILTS_AHB_1_1_20260401.xml
4// Generated: 2026-04-22T18:06:18Z
5// Delegates unchanged conditions to UtiltsConditionEvaluatorFV2504 (FV2504).
6// </auto-generated>
7
8#![allow(clippy::style, clippy::complexity)]
9
10#[allow(unused_imports)]
11use crate::eval::format_validators::*;
12use crate::eval::{ConditionEvaluator, ConditionResult, EvaluationContext};
13use crate::generated::fv2504::UtiltsConditionEvaluatorFV2504;
14
15/// Condition evaluator for UTILTS FV2610.
16///
17/// Delegates to [`UtiltsConditionEvaluatorFV2504`] for conditions whose AHB description is
18/// unchanged from that version. Local overrides: [15, 63, 511, 2005].
19pub struct UtiltsConditionEvaluatorFV2610 {
20    fallback: UtiltsConditionEvaluatorFV2504,
21}
22
23impl Default for UtiltsConditionEvaluatorFV2610 {
24    fn default() -> Self {
25        Self {
26            fallback: UtiltsConditionEvaluatorFV2504::default(),
27        }
28    }
29}
30
31/// Condition IDs whose implementation differs from the fallback.
32const FV2610_OVERRIDES: &[u32] = &[15, 63, 511, 2005];
33
34impl ConditionEvaluator for UtiltsConditionEvaluatorFV2610 {
35    fn message_type(&self) -> &str {
36        "UTILTS"
37    }
38
39    fn format_version(&self) -> &str {
40        "FV2610"
41    }
42
43    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
44        match condition {
45            15 => self.evaluate_15(ctx),
46            63 => self.evaluate_63(ctx),
47            511 => self.evaluate_511(ctx),
48            2005 => self.evaluate_2005(ctx),
49            other => self.fallback.evaluate(other, ctx),
50        }
51    }
52
53    fn is_external(&self, condition: u32) -> bool {
54        matches!(condition, 63) || self.fallback.is_external(condition)
55    }
56
57    fn is_known(&self, condition: u32) -> bool {
58        FV2610_OVERRIDES.contains(&condition) || self.fallback.is_known(condition)
59    }
60}
61
62impl UtiltsConditionEvaluatorFV2610 {
63    /// [15] Wenn in einem SG5 IDE+24 nur eine SEQ+Z37 mit einer SG8 RFF+Z19 (Messlokation) und derselben Zeitraum-ID vorhanden ist
64    // REVIEW: Navigates SG5 groups to find IDE+24, then counts SG8 child instances where SEQ+Z37 and RFF+Z19 coexist with a matching Zeitraum-ID. 'Derselben Zeitraum-ID' is interpreted as: the identifier in SEQ elements[1][0] must equal the reference value in RFF C506 elements[0][1]. True if exactly one such paired SG8 is found. Element positions for the Zeitraum-ID in SEQ and RFF assume standard BDEW UTILTS structure (RFF+Z19:id → elements[0][1], SEQ+Z37+id → elements[1][0]) — no segment structure reference was provided to confirm. (medium confidence)
65    fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
66        let nav = match ctx.navigator() {
67            Some(n) => n,
68            None => return ConditionResult::Unknown,
69        };
70        let sg5_count = nav.group_instance_count(&["SG5"]);
71        for sg5_i in 0..sg5_count {
72            let ide_segs = nav.find_segments_in_group("IDE", &["SG5"], sg5_i);
73            let has_ide_24 = ide_segs.iter().any(|s| {
74                s.elements
75                    .first()
76                    .and_then(|e| e.first())
77                    .is_some_and(|v| v == "24")
78            });
79            if !has_ide_24 {
80                continue;
81            }
82            let sg8_count = nav.child_group_instance_count(&["SG5"], sg5_i, "SG8");
83            let mut matching_count = 0usize;
84            for sg8_j in 0..sg8_count {
85                let seq_segs =
86                    nav.find_segments_in_child_group("SEQ", &["SG5"], sg5_i, "SG8", sg8_j);
87                let seq_zeitraum_id = seq_segs
88                    .iter()
89                    .find(|s| {
90                        s.elements
91                            .first()
92                            .and_then(|e| e.first())
93                            .is_some_and(|v| v == "Z37")
94                    })
95                    .and_then(|s| s.elements.get(1).and_then(|e| e.first()).cloned());
96                let Some(seq_id) = seq_zeitraum_id else {
97                    continue;
98                };
99                let rff_segs =
100                    nav.find_segments_in_child_group("RFF", &["SG5"], sg5_i, "SG8", sg8_j);
101                let has_z19_with_id = rff_segs.iter().any(|s| {
102                    s.elements
103                        .first()
104                        .and_then(|e| e.first())
105                        .is_some_and(|v| v == "Z19")
106                        && s.elements
107                            .first()
108                            .and_then(|e| e.get(1))
109                            .is_some_and(|v| v == &seq_id)
110                });
111                if has_z19_with_id {
112                    matching_count += 1;
113                }
114            }
115            if matching_count > 0 {
116                return ConditionResult::from(matching_count == 1);
117            }
118        }
119        ConditionResult::Unknown
120    }
121
122    /// [63] Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle NB
123    /// EXTERNAL: Requires context from outside the message.
124    fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
125        ctx.external.evaluate("recipient_is_nb")
126    }
127
128    /// [511] Hinweis: Der Zählzeitänderungszeitpunkt (SG8 DTM+Z33) dieser SG8 darf in keiner anderen SG8 „Zählzeitdefinition“ wiederholt werden
129    fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
130        // Hinweis: DTM+Z33 Zählzeitänderungszeitpunkt must not repeat across SG8 Zählzeitdefinition groups — informational annotation
131        ConditionResult::True
132    }
133
134    /// [2005] Segment ist genau einmal für jede Zeitraum-ID aus dem DE9012 der SG5 STS+E01 ("Status der Antwort") anzugeben, wenn im selben SG5 STS+E01 im DE9013 der Code A99 ("Sonstiges") enthalten ist
135    // REVIEW: Finds SG5 STS+E01 segments where DE9013 (C556 comp 0, elements[2][0]) = 'A99' and collects their Zeitraum-IDs from DE9012 (C556 comp 3, elements[2][3]). The annotated segment is FTX+ACB (UTILTS 25010 FV2610 Number=00018), which carries the Zeitraum-ID at C107.DE4441 (elements[2][0]). For each qualifying ID, exactly one FTX+ACB must reference it.
136    fn evaluate_2005(&self, ctx: &EvaluationContext) -> ConditionResult {
137        // Collect Zeitraum-IDs from SG5 STS+E01 where DE9013 = A99.
138        let sts_e01 = ctx.find_segments_with_qualifier("STS", 0, "E01");
139        let zeitraum_ids: Vec<String> = sts_e01
140            .iter()
141            .filter(|s| {
142                s.elements
143                    .get(2)
144                    .and_then(|e| e.first())
145                    .is_some_and(|v| v == "A99")
146            })
147            .filter_map(|s| s.elements.get(2).and_then(|e| e.get(3)).cloned())
148            .filter(|id| !id.is_empty())
149            .collect();
150        if zeitraum_ids.is_empty() {
151            return ConditionResult::Unknown;
152        }
153        // For each Zeitraum-ID, exactly one FTX+ACB (C107.DE4441 at elements[2][0]) must reference it.
154        let ftx_acb = ctx.find_segments_with_qualifier("FTX", 0, "ACB");
155        for zid in &zeitraum_ids {
156            let count = ftx_acb
157                .iter()
158                .filter(|s| {
159                    s.elements
160                        .get(2)
161                        .and_then(|e| e.first())
162                        .is_some_and(|v| v == zid)
163                })
164                .count();
165            if count != 1 {
166                return ConditionResult::False;
167            }
168        }
169        ConditionResult::True
170    }
171}