Skip to main content

automapper_validation/generated/fv2610/
utilmd_strom_conditions_fv2610.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2610/UTILMD_AHB_Strom_2_2_Fehlerkorrektur_20260629_.xml
4// Generated: 2026-04-22T18:25:39Z
5// Delegates unchanged conditions to UtilmdStromConditionEvaluatorFV2604 (FV2604).
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::fv2604::UtilmdStromConditionEvaluatorFV2604;
14
15/// Condition evaluator for UTILMD_Strom FV2610.
16///
17/// Delegates to [`UtilmdStromConditionEvaluatorFV2604`] for conditions whose AHB description is
18/// unchanged from that version. Local overrides: [23, 24, 41, 63, 67, 79, 80, 81, 82, 158, 159, 181, 185, 186, 187, 203, 263, 272, 274, 275, 276, 353, 354, 381, 382, 383, 422, 423, 424, 531, 555, 571, 701, 2002].
19pub struct UtilmdStromConditionEvaluatorFV2610 {
20    fallback: UtilmdStromConditionEvaluatorFV2604,
21}
22
23impl Default for UtilmdStromConditionEvaluatorFV2610 {
24    fn default() -> Self {
25        Self {
26            fallback: UtilmdStromConditionEvaluatorFV2604::default(),
27        }
28    }
29}
30
31/// Condition IDs whose implementation differs from the fallback.
32const FV2610_OVERRIDES: &[u32] = &[
33    23, 24, 41, 63, 67, 79, 80, 81, 82, 158, 159, 181, 185, 186, 187, 203, 263, 272, 274, 275, 276,
34    353, 354, 381, 382, 383, 422, 423, 424, 531, 555, 571, 701, 2002,
35];
36
37impl ConditionEvaluator for UtilmdStromConditionEvaluatorFV2610 {
38    fn message_type(&self) -> &str {
39        "UTILMD_Strom"
40    }
41
42    fn format_version(&self) -> &str {
43        "FV2610"
44    }
45
46    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
47        match condition {
48            23 => self.evaluate_23(ctx),
49            24 => self.evaluate_24(ctx),
50            41 => self.evaluate_41(ctx),
51            63 => self.evaluate_63(ctx),
52            67 => self.evaluate_67(ctx),
53            79 => self.evaluate_79(ctx),
54            80 => self.evaluate_80(ctx),
55            81 => self.evaluate_81(ctx),
56            82 => self.evaluate_82(ctx),
57            158 => self.evaluate_158(ctx),
58            159 => self.evaluate_159(ctx),
59            181 => self.evaluate_181(ctx),
60            203 => self.evaluate_203(ctx),
61            263 => self.evaluate_263(ctx),
62            353 => self.evaluate_353(ctx),
63            354 => self.evaluate_354(ctx),
64            381 => self.evaluate_381(ctx),
65            382 => self.evaluate_382(ctx),
66            383 => self.evaluate_383(ctx),
67            422 => self.evaluate_422(ctx),
68            423 => self.evaluate_423(ctx),
69            424 => self.evaluate_424(ctx),
70            531 => self.evaluate_531(ctx),
71            555 => self.evaluate_555(ctx),
72            571 => self.evaluate_571(ctx),
73            701 => self.evaluate_701(ctx),
74            2002 => self.evaluate_2002(ctx),
75            185 => self.evaluate_185(ctx),
76            186 => self.evaluate_186(ctx),
77            187 => self.evaluate_187(ctx),
78            272 => self.evaluate_272(ctx),
79            274 => self.evaluate_274(ctx),
80            275 => self.evaluate_275(ctx),
81            276 => self.evaluate_276(ctx),
82            other => self.fallback.evaluate(other, ctx),
83        }
84    }
85
86    fn is_external(&self, condition: u32) -> bool {
87        matches!(
88            condition,
89            181 | 185 | 186 | 187 | 274 | 275 | 276 | 353 | 354 | 381 | 382 | 383 | 422 | 423 | 424
90        ) || (!FV2610_OVERRIDES.contains(&condition) && self.fallback.is_external(condition))
91    }
92
93    fn is_known(&self, condition: u32) -> bool {
94        FV2610_OVERRIDES.contains(&condition) || self.fallback.is_known(condition)
95    }
96}
97
98impl UtilmdStromConditionEvaluatorFV2610 {
99    /// [23] Wenn in dieser SG4 das STS+E01++A05 (Status der Antwort) vorhanden
100    fn evaluate_23(&self, ctx: &EvaluationContext) -> ConditionResult {
101        // HAND-EDITED: "in dieser/derselben SG4" — the enclosing SG4 only, when the
102        // scope reaches one; the message as before otherwise.
103        if let Some(scope) = ctx.enclosing_scope("SG4") {
104            return ConditionResult::from(
105                ctx.find_segments_at(scope, "STS")
106                    .iter()
107                    .any(|s| s.get_element(0) == "E01" && s.get_component(2, 0) == "A05"),
108            );
109        }
110        ctx.has_segment_matching_in_group("STS", &[(0, 0, "E01"), (2, 0, "A05")], &["SG4"])
111    }
112
113    /// [24] Wenn in dieser SG4 das STS+E01++A25 (Status der Antwort) vorhanden
114    fn evaluate_24(&self, ctx: &EvaluationContext) -> ConditionResult {
115        // HAND-EDITED: "in dieser/derselben SG4" — the enclosing SG4 only, when the
116        // scope reaches one; the message as before otherwise.
117        if let Some(scope) = ctx.enclosing_scope("SG4") {
118            return ConditionResult::from(
119                ctx.find_segments_at(scope, "STS")
120                    .iter()
121                    .any(|s| s.get_element(0) == "E01" && s.get_component(2, 0) == "A25"),
122            );
123        }
124        ctx.has_segment_matching_in_group("STS", &[(0, 0, "E01"), (2, 0, "A25")], &["SG4"])
125    }
126
127    /// [41] Es ist eine Produktpaket-ID aus dem DE1050 von einem SG8 SEQ+Z79 (Bestandteil eines Produktpakets) zu nennen
128    // REVIEW: Cross-reference validation: the current field must contain a Produktpaket-ID from DE1050 (elements[1][0]) of some SG8 SEQ+Z79. Collects all valid Produktpaket-IDs across SG8 instances and checks the resolved field value is among them. Medium confidence: 'zu nennen' interpreted as a cross-reference constraint; if it is purely informational the body should return True unconditionally. (medium confidence)
129    fn evaluate_41(&self, ctx: &EvaluationContext) -> ConditionResult {
130        let nav = match ctx.navigator() {
131            Some(n) => n,
132            None => return ConditionResult::Unknown,
133        };
134        let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
135        let mut valid_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
136        for i in 0..sg8_count {
137            let seq_segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], i);
138            for seq in &seq_segs {
139                if seq
140                    .elements
141                    .first()
142                    .and_then(|e| e.first())
143                    .is_some_and(|v| v == "Z79")
144                {
145                    if let Some(val) = seq.elements.get(1).and_then(|e| e.first()) {
146                        if !val.is_empty() {
147                            valid_ids.insert(val.clone());
148                        }
149                    }
150                }
151            }
152        }
153        if valid_ids.is_empty() {
154            return ConditionResult::Unknown;
155        }
156        if let Some(current_val) = ctx.resolved_value {
157            return ConditionResult::from(valid_ids.contains(current_val));
158        }
159        ConditionResult::Unknown
160    }
161
162    /// [63] Wenn in dieser SG4 das STS+E01++A15 (Status der Antwort) vorhanden
163    fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
164        // HAND-EDITED: "in dieser/derselben SG4" — the enclosing SG4 only, when the
165        // scope reaches one; the message as before otherwise.
166        if let Some(scope) = ctx.enclosing_scope("SG4") {
167            return ConditionResult::from(
168                ctx.find_segments_at(scope, "STS")
169                    .iter()
170                    .any(|s| s.get_element(0) == "E01" && s.get_component(2, 0) == "A15"),
171            );
172        }
173        ctx.has_segment_matching_in_group("STS", &[(0, 0, "E01"), (2, 0, "A15")], &["SG4"])
174    }
175
176    /// [67] Wenn in keinem SG8 SEQ+Z79 (Bestandteil eine Produktpakets) CCI+Z66/CAV+ZH9 (Produkteigenschaft/ Code der Produkteigenschaft) der Code 9991000002933 (Ruhende Marktlokation ausprägen) vorhanden ist.
177    // REVIEW: Absence check: returns True when NO SG8 with SEQ+Z79 has an SG10 child containing CCI+Z66 (Produkteigenschaft) co-occurring with CAV bearing value 9991000002933 (Ruhende Marktlokation ausprägen). Returns False as soon as the disqualifying combination is found. Medium confidence: CAV element layout for ZH9/9991000002933 is ambiguous without the MIG XML — implementation checks both ZH9+9991000002933 as a composite and 9991000002933 as a bare first component. (medium confidence)
178    fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
179        let nav = match ctx.navigator() {
180            Some(n) => n,
181            None => return ConditionResult::Unknown,
182        };
183        let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
184        for i in 0..sg8_count {
185            let seq_segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], i);
186            let has_z79 = seq_segs.iter().any(|s| {
187                s.elements
188                    .first()
189                    .and_then(|e| e.first())
190                    .is_some_and(|v| v == "Z79")
191            });
192            if !has_z79 {
193                continue;
194            }
195            let sg10_count = nav.child_group_instance_count(&["SG4", "SG8"], i, "SG10");
196            for j in 0..sg10_count {
197                let ccis = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], i, "SG10", j);
198                let has_cci_z66 = ccis.iter().any(|s| {
199                    s.elements
200                        .first()
201                        .and_then(|e| e.first())
202                        .is_some_and(|v| v == "Z66")
203                });
204                if has_cci_z66 {
205                    let cavs =
206                        nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], i, "SG10", j);
207                    let has_target = cavs.iter().any(|s| {
208                        let first_elem = s.elements.first();
209                        let qualifier = first_elem.and_then(|e| e.first()).map(|v| v.as_str());
210                        let value = first_elem.and_then(|e| e.get(1)).map(|v| v.as_str());
211                        (qualifier == Some("ZH9") && value == Some("9991000002933"))
212                            || qualifier == Some("9991000002933")
213                    });
214                    if has_target {
215                        return ConditionResult::False;
216                    }
217                }
218            }
219        }
220        ConditionResult::True
221    }
222
223    /// [79] Wenn SG4 STS+7++Z33 (Auszug wegen Stilllegung) vorhanden
224    fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
225        ctx.has_segment_matching_in_group("STS", &[(0, 0, "7"), (2, 0, "Z33")], &["SG4"])
226    }
227
228    /// [80] Wenn in derselben SG8 SEQ+Z61 (Produkt-Daten der Steuerbaren Ressource) das PIA+5 (Produkt-Daten der Steuerbaren Ressource) nicht vorhanden
229    fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
230        // HAND-EDITED: "in dieser/derselben SG8" — the enclosing SG8 only, when the
231        // scope reaches one; the message as before otherwise.
232        if let Some(scope) = ctx.enclosing_scope("SG8") {
233            return ConditionResult::from(
234                ctx.find_segments_at(scope, "SEQ")
235                    .iter()
236                    .any(|s| matches!(s.get_element(0), "Z61"))
237                    && !ctx
238                        .find_segments_at(scope, "PIA")
239                        .iter()
240                        .any(|s| s.get_element(0) == "5"),
241            );
242        }
243        ctx.any_group_has_qualifier_without("SEQ", 0, "Z61", "PIA", 0, "5", &["SG4", "SG8"])
244    }
245
246    /// [81] Wenn in derselben SG8 SEQ+Z61/ ZB3/ ZB4 (Produkt-Daten der Steuerbaren Ressource) das SG10 CCI+11 (Details zum Produkt der Steuerbaren Ressource) nicht vorhanden
247    // REVIEW: Multi-qualifier SEQ check (Z61/ZB3/ZB4) combined with SG10 child absence requires the navigator. Returns True when any SG8 with a qualifying SEQ has no SG10 child with CCI+11. Medium confidence: three-way qualifier match and SG10 absence via child group iteration. (medium confidence)
248    fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
249        // HAND-EDITED: "in dieser/derselben SG8" — the enclosing SG8 only, when the
250        // scope reaches one; the message as before otherwise.
251        if let Some(scope) = ctx.enclosing_scope("SG8") {
252            return ConditionResult::from(
253                ctx.find_segments_at(scope, "SEQ")
254                    .iter()
255                    .any(|s| matches!(s.get_element(0), "Z61" | "ZB3" | "ZB4"))
256                    && !ctx
257                        .find_segments_at(scope, "CCI")
258                        .iter()
259                        .any(|s| s.get_element(0) == "11"),
260            );
261        }
262        let nav = match ctx.navigator() {
263            Some(n) => n,
264            None => return ConditionResult::Unknown,
265        };
266        let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
267        for i in 0..sg8_count {
268            let seq_segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], i);
269            let has_qualifying_seq = seq_segs.iter().any(|s| {
270                s.elements
271                    .first()
272                    .and_then(|e| e.first())
273                    .is_some_and(|v| matches!(v.as_str(), "Z61" | "ZB3" | "ZB4"))
274            });
275            if !has_qualifying_seq {
276                continue;
277            }
278            let sg10_count = nav.child_group_instance_count(&["SG4", "SG8"], i, "SG10");
279            let has_cci_11 = (0..sg10_count).any(|j| {
280                let ccis = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], i, "SG10", j);
281                ccis.iter().any(|s| {
282                    s.elements
283                        .first()
284                        .and_then(|e| e.first())
285                        .is_some_and(|v| v == "11")
286                })
287            });
288            if !has_cci_11 {
289                return ConditionResult::True;
290            }
291        }
292        ConditionResult::False
293    }
294
295    /// [82] Wenn in der selben SG8 (Daten der technischen Einrichtungen) das SG10 CCI+Z63 (Information zu technischen Einrichtungen) CAV+ZH7 (Technische Einrichtungen vorhanden) vorhanden
296    // REVIEW: Checks if any SG10 child of any SG8 contains both CCI+Z63 (Information zu technischen Einrichtungen) and CAV+ZH7 (Technische Einrichtungen vorhanden) in the same SG10 instance. Both qualifiers checked at elements[0][0] following the CAV+qualifier pattern from Example 10. Medium confidence: no parent SG8 qualifier filter specified; CCI element position assumed from established pattern. (medium confidence)
297    fn evaluate_82(&self, ctx: &EvaluationContext) -> ConditionResult {
298        let nav = match ctx.navigator() {
299            Some(n) => n,
300            None => return ctx.has_qualifier("CAV", 0, "ZH7"),
301        };
302        let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
303        for i in 0..sg8_count {
304            let sg10_count = nav.child_group_instance_count(&["SG4", "SG8"], i, "SG10");
305            for j in 0..sg10_count {
306                let ccis = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], i, "SG10", j);
307                let has_cci_z63 = ccis.iter().any(|s| {
308                    s.elements
309                        .first()
310                        .and_then(|e| e.first())
311                        .is_some_and(|v| v == "Z63")
312                });
313                if has_cci_z63 {
314                    let cavs =
315                        nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], i, "SG10", j);
316                    if cavs.iter().any(|s| {
317                        s.elements
318                            .first()
319                            .and_then(|e| e.first())
320                            .is_some_and(|v| v == "ZH7")
321                    }) {
322                        return ConditionResult::True;
323                    }
324                }
325            }
326        }
327        ConditionResult::False
328    }
329
330    /// [158] Wenn in derselben SG8 SEQ+Z57/ ZD9 (OBIS-Daten der Netzlokation) das PIA+5 (OBIS-Daten der Netzlokation) nicht vorhanden
331    fn evaluate_158(&self, ctx: &EvaluationContext) -> ConditionResult {
332        // HAND-EDITED: "in dieser/derselben SG8" — the enclosing SG8 only, when the
333        // scope reaches one; the message as before otherwise.
334        if let Some(scope) = ctx.enclosing_scope("SG8") {
335            return ConditionResult::from(
336                ctx.find_segments_at(scope, "SEQ")
337                    .iter()
338                    .any(|s| matches!(s.get_element(0), "Z57" | "ZD9"))
339                    && !ctx
340                        .find_segments_at(scope, "PIA")
341                        .iter()
342                        .any(|s| s.get_element(0) == "5"),
343            );
344        }
345        ctx.any_group_has_qualifier_without("SEQ", 0, "Z57", "PIA", 0, "5", &["SG4", "SG8"])
346            .or(ctx.any_group_has_qualifier_without(
347                "SEQ",
348                0,
349                "ZD9",
350                "PIA",
351                0,
352                "5",
353                &["SG4", "SG8"],
354            ))
355    }
356
357    /// [159] Wenn in derselben SG8 SEQ+Z57/ ZD9 (OBIS-Daten der Netzlokation) das SG10 CCI+11 (OBIS-Daten der Netzlokation) nicht vor...
358    fn evaluate_159(&self, ctx: &EvaluationContext) -> ConditionResult {
359        // HAND-EDITED: "in dieser/derselben SG8" — the enclosing SG8 only, when the
360        // scope reaches one; the message as before otherwise.
361        if let Some(scope) = ctx.enclosing_scope("SG8") {
362            return ConditionResult::from(
363                ctx.find_segments_at(scope, "SEQ")
364                    .iter()
365                    .any(|s| matches!(s.get_element(0), "Z57" | "ZD9"))
366                    && !ctx
367                        .find_segments_at(scope, "CCI")
368                        .iter()
369                        .any(|s| s.get_element(0) == "11"),
370            );
371        }
372        let with_cci = |seq: &str| {
373            ctx.filtered_parent_child_has_qualifier(
374                &["SG4", "SG8"],
375                "SEQ",
376                0,
377                seq,
378                "SG10",
379                "CCI",
380                0,
381                "11",
382            )
383        };
384        with_cci("Z57").or(with_cci("ZD9")).negate()
385    }
386
387    /// [181] Wenn der Versender datenschutzrechtliche Voraussetzungen geschaffen hat, um die Daten bereitstellen zu dürfen
388    /// EXTERNAL: Requires context from outside the message.
389    fn evaluate_181(&self, ctx: &EvaluationContext) -> ConditionResult {
390        ctx.external
391            .evaluate("sender_has_data_protection_prerequisites")
392    }
393
394    /// [203] Wenn STS+7++E06 / Z39 / ZC6 / ZC7/ ZT6/ ZT7 / Z02 / ZZD vorhanden
395    fn evaluate_203(&self, ctx: &EvaluationContext) -> ConditionResult {
396        ctx.has_qualified_value(
397            "STS",
398            0,
399            "7",
400            2,
401            0,
402            &["E06", "Z39", "ZC6", "ZC7", "ZT6", "ZT7", "Z02", "ZZD"],
403        )
404    }
405
406    /// [263] Wenn SG8 SEQ+Z01 (Daten der Marktlokation) SG10 CCI+Z30++Z07 (Verbrauch) vorhanden
407    fn evaluate_263(&self, ctx: &EvaluationContext) -> ConditionResult {
408        let nav = match ctx.navigator() {
409            Some(n) => n,
410            None => return ctx.has_qualified_value("CCI", 0, "Z30", 2, 0, &["Z07"]),
411        };
412        let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
413        for i in 0..sg8_count {
414            let seq_segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], i);
415            let has_z01 = seq_segs.iter().any(|s| {
416                s.elements
417                    .first()
418                    .and_then(|e| e.first())
419                    .is_some_and(|v| v == "Z01")
420            });
421            if !has_z01 {
422                continue;
423            }
424            let sg10_count = nav.child_group_instance_count(&["SG4", "SG8"], i, "SG10");
425            for j in 0..sg10_count {
426                let ccis = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], i, "SG10", j);
427                for cci in &ccis {
428                    let elem0 = cci
429                        .elements
430                        .first()
431                        .and_then(|e| e.first())
432                        .map(|s| s.as_str());
433                    let elem2 = cci
434                        .elements
435                        .get(2)
436                        .and_then(|e| e.first())
437                        .map(|s| s.as_str());
438                    if elem0 == Some("Z30") && elem2 == Some("Z07") {
439                        return ConditionResult::True;
440                    }
441                }
442            }
443        }
444        ConditionResult::False
445    }
446
447    /// [353] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in derselben Zeile sowohl in der Spalte „Lokation/NeLo“ als auch in der ...
448    /// EXTERNAL: Requires context from outside the message.
449    fn evaluate_353(&self, ctx: &EvaluationContext) -> ConditionResult {
450        ctx.external
451            .evaluate("verwendungszweck_code_valid_nelocation_nb")
452    }
453
454    /// [354] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in derselben Zeile sowohl in der Spalte „Lokation/NeLo“ als auch in der ...
455    /// EXTERNAL: Requires context from outside the message.
456    fn evaluate_354(&self, ctx: &EvaluationContext) -> ConditionResult {
457        ctx.external
458            .evaluate("verwendungszweck_code_valid_nelocation_lf")
459    }
460
461    /// [381] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in derselben Zeile sowohl in der Spalte „Lokation/MaLo“ als auch in der ...
462    /// EXTERNAL: Requires context from outside the message.
463    fn evaluate_381(&self, ctx: &EvaluationContext) -> ConditionResult {
464        ctx.external
465            .evaluate("verwendungszweck_code_valid_malocation_nb")
466    }
467
468    /// [382] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in derselben Zeile sowohl in der Spalte „Lokation/MaLo“ als auch in der ...
469    /// EXTERNAL: Requires context from outside the message.
470    fn evaluate_382(&self, ctx: &EvaluationContext) -> ConditionResult {
471        ctx.external
472            .evaluate("verwendungszweck_code_valid_malocation_lf")
473    }
474
475    /// [383] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in derselben Zeile sowohl in der Spalte „Lokation/MaLo“ als auch in der ...
476    /// EXTERNAL: Requires context from outside the message.
477    fn evaluate_383(&self, ctx: &EvaluationContext) -> ConditionResult {
478        ctx.external
479            .evaluate("verwendungszweck_code_valid_malocation_uenb")
480    }
481
482    /// [422] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in derselben Zeile sowohl in der Spalte „Lokation/Tranche“ als auch in d...
483    /// EXTERNAL: Requires context from outside the message.
484    fn evaluate_422(&self, ctx: &EvaluationContext) -> ConditionResult {
485        ctx.external
486            .evaluate("verwendungszweck_code_valid_tranche_nb")
487    }
488
489    /// [423] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in derselben Zeile sowohl in der Spalte „Lokation/Tranche“ als auch in d...
490    /// EXTERNAL: Requires context from outside the message.
491    // REVIEW: References external code list from Kapitel 2 (Codeliste der Verwendungszwecke) filtered by 'Lokation/Tranche' and 'Verwendbar für Marktrolle/LF' columns, plus OBIS-Kennzahl matching — all three criteria require external business context unavailable in the EDIFACT message alone (medium confidence)
492    fn evaluate_423(&self, ctx: &EvaluationContext) -> ConditionResult {
493        ctx.external.evaluate("verwendungszweck_valid_for_lf")
494    }
495
496    /// [424] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in derselben Zeile sowohl in der Spalte „Lokation/Tranche“ als auch in d...
497    /// EXTERNAL: Requires context from outside the message.
498    // REVIEW: References external code list from Kapitel 2 (Codeliste der Verwendungszwecke) filtered by 'Lokation/Tranche' and 'Verwendbar für Marktrolle/ÜNB' columns, plus OBIS-Kennzahl matching — all three criteria require external business context unavailable in the EDIFACT message alone (medium confidence)
499    fn evaluate_424(&self, ctx: &EvaluationContext) -> ConditionResult {
500        ctx.external.evaluate("verwendungszweck_valid_for_uenb")
501    }
502
503    /// [531] Hinweis: Es ist das Jahr anzugeben in dem die nächste Netznutzungsabrechnung erfolgt. Diese Information ist zu der Zeitraum-ID anzugeben, welche SG6 RFF+Z49 (Verwendungszeitraum der Daten: Gültig...
504    fn evaluate_531(&self, _ctx: &EvaluationContext) -> ConditionResult {
505        // Hinweis: year of next Netznutzungsabrechnung to be stated for the Zeitraum-ID in SG6 RFF+Z49 — informational note, always applies
506        ConditionResult::True
507    }
508
509    /// [555] Hinweis: Es sind die Produktpaket-ID aus der Anmeldung zu nennen, welche nicht umgesetzt werden konnten
510    fn evaluate_555(&self, _ctx: &EvaluationContext) -> ConditionResult {
511        // Hinweis: Produktpaket-IDs from the Anmeldung that could not be implemented — informational note, always applies
512        ConditionResult::True
513    }
514
515    /// [571] Hinweis: Es sind alle erforderlichen Produkte aus der Anmeldung des LF des im selben SG8 genannten Produktpaket-ID zu nennen welche nicht umgesetzt werden konnten
516    fn evaluate_571(&self, _ctx: &EvaluationContext) -> ConditionResult {
517        // Hinweis: all required products from LF Anmeldung for the Produktpaket-ID in the same SG8 that could not be implemented — informational note, always applies
518        ConditionResult::True
519    }
520
521    /// [701] Hinweis: Es ist die Malo-ID der Tranche zu nennen, der die Technische Ressource zugeordnet ist
522    fn evaluate_701(&self, _ctx: &EvaluationContext) -> ConditionResult {
523        // Hinweis: MaLo-ID of the Tranche to which the Technische Ressource is assigned — informational note, always applies
524        ConditionResult::True
525    }
526
527    /// [2002] Für jede Produktpaket-ID im SG8 SEQ+Z79 (Bestandteil eines Produktpakets) DE1050 genau einmal anzugeben
528    fn evaluate_2002(&self, ctx: &EvaluationContext) -> ConditionResult {
529        let nav = match ctx.navigator() {
530            Some(n) => n,
531            None => return ConditionResult::Unknown,
532        };
533        let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
534        let mut seen = std::collections::HashSet::new();
535        for i in 0..sg8_count {
536            let seq_segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], i);
537            for seq in &seq_segs {
538                if seq
539                    .elements
540                    .first()
541                    .and_then(|e| e.first())
542                    .is_some_and(|v| v == "Z79")
543                {
544                    if let Some(val) = seq.elements.get(1).and_then(|e| e.first()) {
545                        if !val.is_empty() && !seen.insert(val.clone()) {
546                            return ConditionResult::False;
547                        }
548                    }
549                }
550            }
551        }
552        if seen.is_empty() {
553            ConditionResult::Unknown
554        } else {
555            ConditionResult::True
556        }
557    }
558
559    /// [185] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in ders...
560    /// EXTERNAL: Requires context from outside the message.
561    fn evaluate_185(&self, ctx: &EvaluationContext) -> ConditionResult {
562        ctx.external
563            .evaluate("verwendungszweck_code_valid_malocation_nb_produkt")
564    }
565
566    /// [186] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in ders...
567    /// EXTERNAL: Requires context from outside the message.
568    fn evaluate_186(&self, ctx: &EvaluationContext) -> ConditionResult {
569        ctx.external
570            .evaluate("verwendungszweck_code_valid_malocation_lf_produkt")
571    }
572
573    /// [187] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in ders...
574    /// EXTERNAL: Requires context from outside the message.
575    fn evaluate_187(&self, ctx: &EvaluationContext) -> ConditionResult {
576        ctx.external
577            .evaluate("verwendungszweck_code_valid_malocation_uenb_produkt")
578    }
579
580    /// [274] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in ders...
581    /// EXTERNAL: Requires context from outside the message.
582    fn evaluate_274(&self, ctx: &EvaluationContext) -> ConditionResult {
583        ctx.external
584            .evaluate("verwendungszweck_code_valid_tranche_nb_produkt")
585    }
586
587    /// [275] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in ders...
588    /// EXTERNAL: Requires context from outside the message.
589    fn evaluate_275(&self, ctx: &EvaluationContext) -> ConditionResult {
590        ctx.external
591            .evaluate("verwendungszweck_code_valid_tranche_lf_produkt")
592    }
593
594    /// [276] Zulässig sind ausschließlich Codes aus der Spalte „Code“ der Codeliste der Verwendungszwecke (Kapitel 2), sofern in ders...
595    /// EXTERNAL: Requires context from outside the message.
596    fn evaluate_276(&self, ctx: &EvaluationContext) -> ConditionResult {
597        ctx.external
598            .evaluate("verwendungszweck_code_valid_tranche_uenb_produkt")
599    }
600
601    /// [272] Wenn im selben Segment im DE2379 der Code 106 vorhanden ist
602    fn evaluate_272(&self, ctx: &EvaluationContext) -> ConditionResult {
603        ConditionResult::from(ctx.self_segment_value_equals("DTM", 0, 2, "106"))
604    }
605}