Skip to main content

automapper_validation/generated/fv2504/
utilts_conditions_fv2504.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2504/UTILTS_AHB_1_0_Fehlerkorrektur_20250218.xml
4// Generated: 2026-03-12T10:04:46Z
5// </auto-generated>
6
7// LLM-produced bodies aren't idiomatic Rust — silence stylistic lints at
8// file scope so `cargo clippy -D warnings` stays green. Correctness,
9// suspicious, and perf categories stay on so real bugs still surface.
10#![allow(clippy::style, clippy::complexity)]
11
12#[allow(unused_imports)]
13use crate::eval::format_validators::*;
14use crate::eval::{Cluster, ConditionEvaluator, ConditionResult, EvaluationContext};
15// HAND-EDITED: shared cluster helper for condition [61].
16// Preserve across regeneration — see CLAUDE.md "Codelist Providers".
17use crate::eval::ebd_cluster::any_sts_e01_in_cluster;
18
19/// Generated condition evaluator for UTILTS FV2504.
20pub struct UtiltsConditionEvaluatorFV2504 {
21    // External condition IDs that require runtime context.
22    external_conditions: std::collections::HashSet<u32>,
23}
24
25impl Default for UtiltsConditionEvaluatorFV2504 {
26    fn default() -> Self {
27        let mut external_conditions = std::collections::HashSet::new();
28        external_conditions.insert(1);
29        external_conditions.insert(22);
30        external_conditions.insert(25);
31        external_conditions.insert(26);
32        external_conditions.insert(37);
33        // HAND-EDITED: [61] is no longer external — EBD cluster lookup handles it.
34        // external_conditions.insert(61);
35        external_conditions.insert(62);
36        external_conditions.insert(494);
37        Self {
38            external_conditions,
39        }
40    }
41}
42
43impl ConditionEvaluator for UtiltsConditionEvaluatorFV2504 {
44    fn message_type(&self) -> &str {
45        "UTILTS"
46    }
47
48    fn format_version(&self) -> &str {
49        "FV2504"
50    }
51
52    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
53        match condition {
54            1 => self.evaluate_1(ctx),
55            2 => self.evaluate_2(ctx),
56            5 => self.evaluate_5(ctx),
57            6 => self.evaluate_6(ctx),
58            7 => self.evaluate_7(ctx),
59            8 => self.evaluate_8(ctx),
60            9 => self.evaluate_9(ctx),
61            10 => self.evaluate_10(ctx),
62            11 => self.evaluate_11(ctx),
63            12 => self.evaluate_12(ctx),
64            13 => self.evaluate_13(ctx),
65            14 => self.evaluate_14(ctx),
66            15 => self.evaluate_15(ctx),
67            21 => self.evaluate_21(ctx),
68            22 => self.evaluate_22(ctx),
69            24 => self.evaluate_24(ctx),
70            25 => self.evaluate_25(ctx),
71            26 => self.evaluate_26(ctx),
72            27 => self.evaluate_27(ctx),
73            29 => self.evaluate_29(ctx),
74            30 => self.evaluate_30(ctx),
75            31 => self.evaluate_31(ctx),
76            32 => self.evaluate_32(ctx),
77            33 => self.evaluate_33(ctx),
78            34 => self.evaluate_34(ctx),
79            36 => self.evaluate_36(ctx),
80            37 => self.evaluate_37(ctx),
81            41 => self.evaluate_41(ctx),
82            42 => self.evaluate_42(ctx),
83            43 => self.evaluate_43(ctx),
84            44 => self.evaluate_44(ctx),
85            46 => self.evaluate_46(ctx),
86            47 => self.evaluate_47(ctx),
87            48 => self.evaluate_48(ctx),
88            49 => self.evaluate_49(ctx),
89            50 => self.evaluate_50(ctx),
90            53 => self.evaluate_53(ctx),
91            54 => self.evaluate_54(ctx),
92            55 => self.evaluate_55(ctx),
93            56 => self.evaluate_56(ctx),
94            57 => self.evaluate_57(ctx),
95            58 => self.evaluate_58(ctx),
96            59 => self.evaluate_59(ctx),
97            61 => self.evaluate_61(ctx),
98            62 => self.evaluate_62(ctx),
99            490 => self.evaluate_490(ctx),
100            491 => self.evaluate_491(ctx),
101            494 => self.evaluate_494(ctx),
102            501 => self.evaluate_501(ctx),
103            502 => self.evaluate_502(ctx),
104            504 => self.evaluate_504(ctx),
105            505 => self.evaluate_505(ctx),
106            506 => self.evaluate_506(ctx),
107            507 => self.evaluate_507(ctx),
108            508 => self.evaluate_508(ctx),
109            509 => self.evaluate_509(ctx),
110            510 => self.evaluate_510(ctx),
111            511 => self.evaluate_511(ctx),
112            512 => self.evaluate_512(ctx),
113            513 => self.evaluate_513(ctx),
114            514 => self.evaluate_514(ctx),
115            515 => self.evaluate_515(ctx),
116            516 => self.evaluate_516(ctx),
117            517 => self.evaluate_517(ctx),
118            518 => self.evaluate_518(ctx),
119            519 => self.evaluate_519(ctx),
120            520 => self.evaluate_520(ctx),
121            521 => self.evaluate_521(ctx),
122            522 => self.evaluate_522(ctx),
123            523 => self.evaluate_523(ctx),
124            524 => self.evaluate_524(ctx),
125            525 => self.evaluate_525(ctx),
126            526 => self.evaluate_526(ctx),
127            527 => self.evaluate_527(ctx),
128            528 => self.evaluate_528(ctx),
129            529 => self.evaluate_529(ctx),
130            530 => self.evaluate_530(ctx),
131            531 => self.evaluate_531(ctx),
132            532 => self.evaluate_532(ctx),
133            533 => self.evaluate_533(ctx),
134            534 => self.evaluate_534(ctx),
135            912 => self.evaluate_912(ctx),
136            913 => self.evaluate_913(ctx),
137            914 => self.evaluate_914(ctx),
138            915 => self.evaluate_915(ctx),
139            930 => self.evaluate_930(ctx),
140            931 => self.evaluate_931(ctx),
141            932 => self.evaluate_932(ctx),
142            933 => self.evaluate_933(ctx),
143            937 => self.evaluate_937(ctx),
144            939 => self.evaluate_939(ctx),
145            940 => self.evaluate_940(ctx),
146            947 => self.evaluate_947(ctx),
147            950 => self.evaluate_950(ctx),
148            951 => self.evaluate_951(ctx),
149            960 => self.evaluate_960(ctx),
150            963 => self.evaluate_963(ctx),
151            964 => self.evaluate_964(ctx),
152            965 => self.evaluate_965(ctx),
153            969 => self.evaluate_969(ctx),
154            2001 => self.evaluate_2001(ctx),
155            2002 => self.evaluate_2002(ctx),
156            2004 => self.evaluate_2004(ctx),
157            2005 => self.evaluate_2005(ctx),
158            2006 => self.evaluate_2006(ctx),
159            2007 => self.evaluate_2007(ctx),
160            _ => ConditionResult::Unknown,
161        }
162    }
163
164    fn is_external(&self, condition: u32) -> bool {
165        self.external_conditions.contains(&condition)
166    }
167    fn is_known(&self, condition: u32) -> bool {
168        matches!(
169            condition,
170            1 | 2
171                | 5
172                | 6
173                | 7
174                | 8
175                | 9
176                | 10
177                | 11
178                | 12
179                | 13
180                | 14
181                | 15
182                | 21
183                | 22
184                | 24
185                | 25
186                | 26
187                | 27
188                | 29
189                | 30
190                | 31
191                | 32
192                | 33
193                | 34
194                | 36
195                | 37
196                | 41
197                | 42
198                | 43
199                | 44
200                | 46
201                | 47
202                | 48
203                | 49
204                | 50
205                | 53
206                | 54
207                | 55
208                | 56
209                | 57
210                | 58
211                | 59
212                | 61
213                | 62
214                | 490
215                | 491
216                | 494
217                | 501
218                | 502
219                | 504
220                | 505
221                | 506
222                | 507
223                | 508
224                | 509
225                | 510
226                | 511
227                | 512
228                | 513
229                | 514
230                | 515
231                | 516
232                | 517
233                | 518
234                | 519
235                | 520
236                | 521
237                | 522
238                | 523
239                | 524
240                | 525
241                | 526
242                | 527
243                | 528
244                | 529
245                | 530
246                | 531
247                | 532
248                | 533
249                | 534
250                | 912
251                | 913
252                | 914
253                | 915
254                | 930
255                | 931
256                | 932
257                | 933
258                | 937
259                | 939
260                | 940
261                | 947
262                | 950
263                | 951
264                | 960
265                | 963
266                | 964
267                | 965
268                | 969
269                | 2001
270                | 2002
271                | 2004
272                | 2005
273                | 2006
274                | 2007
275        )
276    }
277}
278
279impl UtiltsConditionEvaluatorFV2504 {
280    /// [8] Rechenschrittidentifikator aus einem SG8 SEQ+Z37 (Bestandteil des Rechenschritts) DE1050 desselben SG5 IDE+24 und derselben Zeitraum-ID wie bei diesem SG8
281    // REVIEW: Validates that RFF+Z23 references in SG8 SEQ+Z37 instances point to existing SEQ+Z37 DE1050 values. Follows the Example 27 pattern with SG5/SG8 group path. The Zeitraum-ID same-scope constraint and same-SG5 IDE+24 scoping are not fully enforced — cross-SG5 step IDs could be accepted — but within a single-transaction UTILTS message this is typically equivalent. RFF+Z23 is the standard Rechenschrittidentifikator reference qualifier based on Example 27. (medium confidence)
282    fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
283        let nav = match ctx.navigator() {
284            Some(n) => n,
285            None => return ConditionResult::Unknown,
286        };
287        let sg8_count = nav.group_instance_count(&["SG5", "SG8"]);
288        let mut valid_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
289        for i in 0..sg8_count {
290            let seq_segs = nav.find_segments_in_group("SEQ", &["SG5", "SG8"], i);
291            for seq in &seq_segs {
292                if seq
293                    .elements
294                    .first()
295                    .and_then(|e| e.first())
296                    .is_some_and(|v| v == "Z37")
297                {
298                    if let Some(val) = seq.elements.get(1).and_then(|e| e.first()) {
299                        if !val.is_empty() {
300                            valid_ids.insert(val.clone());
301                        }
302                    }
303                }
304            }
305        }
306        if valid_ids.is_empty() {
307            return ConditionResult::Unknown;
308        }
309        for i in 0..sg8_count {
310            let rff_segs = nav.find_segments_in_group("RFF", &["SG5", "SG8"], i);
311            for rff in &rff_segs {
312                if rff
313                    .elements
314                    .first()
315                    .and_then(|e| e.first())
316                    .is_some_and(|v| v == "Z23")
317                {
318                    if let Some(ref_val) = rff.elements.first().and_then(|e| e.get(1)) {
319                        if !ref_val.is_empty() && !valid_ids.contains(ref_val) {
320                            return ConditionResult::False;
321                        }
322                    }
323                }
324            }
325        }
326        ConditionResult::True
327    }
328
329    /// [9] Der hier angegebene Rechenschrittidentifikator darf nicht identisch mit dem Rechenschrittidentifikator aus diesem SG8 SEQ+Z37 DE1050 sein
330    // REVIEW: Checks that within each SG8 SEQ+Z37 instance, the RFF+Z23 reference value does not equal the current SG8's own SEQ DE1050 Rechenschrittidentifikator (no self-reference). Iterates all SG8 instances globally and returns False on first self-referential cycle detected. (medium confidence)
331    fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
332        let nav = match ctx.navigator() {
333            Some(n) => n,
334            None => return ConditionResult::Unknown,
335        };
336        let sg8_count = nav.group_instance_count(&["SG5", "SG8"]);
337        for i in 0..sg8_count {
338            let seq_segs = nav.find_segments_in_group("SEQ", &["SG5", "SG8"], i);
339            let own_id = seq_segs
340                .iter()
341                .find(|s| {
342                    s.elements
343                        .first()
344                        .and_then(|e| e.first())
345                        .is_some_and(|v| v == "Z37")
346                })
347                .and_then(|s| s.elements.get(1))
348                .and_then(|e| e.first())
349                .filter(|v| !v.is_empty())
350                .cloned();
351            let Some(own_id) = own_id else {
352                continue;
353            };
354            let rff_segs = nav.find_segments_in_group("RFF", &["SG5", "SG8"], i);
355            for rff in &rff_segs {
356                if rff
357                    .elements
358                    .first()
359                    .and_then(|e| e.first())
360                    .is_some_and(|v| v == "Z23")
361                {
362                    if let Some(ref_val) = rff.elements.first().and_then(|e| e.get(1)) {
363                        if ref_val == &own_id {
364                            return ConditionResult::False;
365                        }
366                    }
367                }
368            }
369        }
370        ConditionResult::True
371    }
372
373    /// [11] Wenn in SG8 SEQ+Z37 SG9 CCI+++Z86 CAV+Z69/Z70 (Addition / Subtraktion) vorhanden, darf es in dem Vorgang beliebig viele weitere SG8 SEQ+Z37 mit identischem Rechenschrittidentifikator mit derselben ...
374    // REVIEW: When multiple SG8 SEQ+Z37 share the same Rechenschrittidentifikator (duplicates), all operator CAV codes across those instances must be exclusively Z69 (Addition) or Z70 (Subtraktion). Navigates SG9 children of each SG8 to collect operator codes, groups by step ID, and checks the constraint for duplicates. Zeitraum-ID same-scope matching is not enforced due to API limitations — treats global step_id grouping as approximation. (medium confidence)
375    fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
376        let nav = match ctx.navigator() {
377            Some(n) => n,
378            None => return ConditionResult::Unknown,
379        };
380        let sg8_count = nav.group_instance_count(&["SG5", "SG8"]);
381        if sg8_count == 0 {
382            return ConditionResult::Unknown;
383        }
384        // Map step_id -> (instance_count, all_operator_codes_across_all_instances)
385        let mut step_data: std::collections::HashMap<String, (usize, Vec<String>)> =
386            std::collections::HashMap::new();
387        for i in 0..sg8_count {
388            let seq_segs = nav.find_segments_in_group("SEQ", &["SG5", "SG8"], i);
389            let step_id = seq_segs
390                .iter()
391                .find(|s| {
392                    s.elements
393                        .first()
394                        .and_then(|e| e.first())
395                        .is_some_and(|v| v == "Z37")
396                })
397                .and_then(|s| s.elements.get(1))
398                .and_then(|e| e.first())
399                .filter(|v| !v.is_empty())
400                .cloned();
401            let Some(step_id) = step_id else {
402                continue;
403            };
404            let sg9_count = nav.child_group_instance_count(&["SG5", "SG8"], i, "SG9");
405            let mut ops: Vec<String> = Vec::new();
406            for j in 0..sg9_count {
407                let cavs = nav.find_segments_in_child_group("CAV", &["SG5", "SG8"], i, "SG9", j);
408                for cav in &cavs {
409                    if let Some(code) = cav.elements.first().and_then(|e| e.first()) {
410                        if matches!(code.as_str(), "Z69" | "Z70" | "Z80" | "Z81" | "Z82" | "Z83") {
411                            ops.push(code.clone());
412                        }
413                    }
414                }
415            }
416            let entry = step_data.entry(step_id).or_insert((0, Vec::new()));
417            entry.0 += 1;
418            entry.1.extend(ops);
419        }
420        // Rule: for any step_id with duplicate SG8 instances, ALL operators must be Z69 or Z70
421        for (_step_id, (count, operators)) in &step_data {
422            if *count > 1 && operators.iter().any(|op| op != "Z69" && op != "Z70") {
423                return ConditionResult::False;
424            }
425        }
426        ConditionResult::True
427    }
428
429    /// [12] Wenn in SG8 SEQ+Z37 SG9 CCI+++Z86 CAV+Z83 (Positivwert) vorhanden, darf es in dem Vorgang keine weitere SG8 SEQ+Z37 mit identischem Rechenschrittidentifikator und derselben Zeitraum-ID geben
430    // REVIEW: When a calculation step SG8 SEQ+Z37 contains the Z83 (Positivwert) operator in its SG9 CAV, no other SG8 SEQ+Z37 may share the same Rechenschrittidentifikator within the Vorgang. Groups step IDs by count and checks that any step with Z83 appears exactly once. Zeitraum-ID scoping approximated by global step_id grouping. (medium confidence)
431    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
432        let nav = match ctx.navigator() {
433            Some(n) => n,
434            None => return ConditionResult::Unknown,
435        };
436        let sg8_count = nav.group_instance_count(&["SG5", "SG8"]);
437        if sg8_count == 0 {
438            return ConditionResult::Unknown;
439        }
440        // Map step_id -> (instance_count, has_z83_operator)
441        let mut step_data: std::collections::HashMap<String, (usize, bool)> =
442            std::collections::HashMap::new();
443        for i in 0..sg8_count {
444            let seq_segs = nav.find_segments_in_group("SEQ", &["SG5", "SG8"], i);
445            let step_id = seq_segs
446                .iter()
447                .find(|s| {
448                    s.elements
449                        .first()
450                        .and_then(|e| e.first())
451                        .is_some_and(|v| v == "Z37")
452                })
453                .and_then(|s| s.elements.get(1))
454                .and_then(|e| e.first())
455                .filter(|v| !v.is_empty())
456                .cloned();
457            let Some(step_id) = step_id else {
458                continue;
459            };
460            let sg9_count = nav.child_group_instance_count(&["SG5", "SG8"], i, "SG9");
461            let mut has_z83 = false;
462            for j in 0..sg9_count {
463                let cavs = nav.find_segments_in_child_group("CAV", &["SG5", "SG8"], i, "SG9", j);
464                if cavs.iter().any(|c| {
465                    c.elements
466                        .first()
467                        .and_then(|e| e.first())
468                        .is_some_and(|v| v == "Z83")
469                }) {
470                    has_z83 = true;
471                }
472            }
473            let entry = step_data.entry(step_id).or_insert((0, false));
474            entry.0 += 1;
475            entry.1 |= has_z83;
476        }
477        // Rule: if Z83 (Positivwert) operator is present for a step_id, that step_id must be unique
478        for (_step_id, (count, has_z83)) in &step_data {
479            if *has_z83 && *count > 1 {
480                return ConditionResult::False;
481            }
482        }
483        ConditionResult::True
484    }
485
486    /// [25] Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle LF
487    /// EXTERNAL: Requires context from outside the message.
488    fn evaluate_25(&self, ctx: &EvaluationContext) -> ConditionResult {
489        ctx.external.evaluate("recipient_is_lf")
490    }
491
492    /// [26] sofern per ORDERS reklamiert
493    /// EXTERNAL: Requires context from outside the message.
494    // REVIEW: Cannot be determined from EDIFACT message content alone — requires external knowledge of whether a prior ORDERS message was used to make a claim. Depends on business process context outside the current message. (medium confidence)
495    fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
496        ctx.external.evaluate("claimed_via_orders")
497    }
498
499    /// [37] Wenn ein Gültigkeitsende bereits angegeben werden kann.
500    /// EXTERNAL: Requires context from outside the message.
501    // REVIEW: Whether a validity end date can already be specified is an organizational business decision at message creation time. Checking DTM+Z35 presence would be circular (condition guards whether to include that very field). Must be resolved from business context external to the message. (medium confidence)
502    fn evaluate_37(&self, ctx: &EvaluationContext) -> ConditionResult {
503        ctx.external.evaluate("validity_end_known")
504    }
505
506    /// [42] Der in diesem Datenlement angegebene Code der Schaltzeitdefinition muss innerhalb eines Vorgangs (IDE) eindeutig sein.
507    // REVIEW: Uniqueness of Schaltzeitdefinition codes within an IDE (SG5 group). The only Z44-keyed element in the provided SG8 schema is DTM+Z44 (Schaltzeitänderungszeitpunkt), so uniqueness is enforced on the DTM value within each SG5 instance. Medium confidence because the schema reference only shows DTM segments for SG8 — the actual 'Code' data element could reside in a CCI/SEQ segment not shown here. (medium confidence)
508    fn evaluate_42(&self, ctx: &EvaluationContext) -> ConditionResult {
509        let nav = match ctx.navigator() {
510            Some(n) => n,
511            None => return ConditionResult::Unknown,
512        };
513        let sg5_count = nav.group_instance_count(&["SG5"]);
514        for i in 0..sg5_count {
515            let sg8_count = nav.child_group_instance_count(&["SG5"], i, "SG8");
516            let mut seen = std::collections::HashSet::new();
517            for j in 0..sg8_count {
518                let dtms = nav.find_segments_in_child_group("DTM", &["SG5"], i, "SG8", j);
519                for dtm in &dtms {
520                    if dtm
521                        .elements
522                        .first()
523                        .and_then(|e| e.first())
524                        .is_some_and(|v| v == "Z44")
525                    {
526                        if let Some(val) = dtm.elements.first().and_then(|e| e.get(1)) {
527                            if !val.is_empty() && !seen.insert(val.clone()) {
528                                return ConditionResult::False;
529                            }
530                        }
531                    }
532                }
533            }
534        }
535        ConditionResult::True
536    }
537
538    /// [43] Der in diesem Datenlement angegebene Code der Leistungskurvendefinition muss innerhalb eines Vorgangs (IDE) eindeutig sein.
539    // REVIEW: Uniqueness of Leistungskurvendefinition codes within an IDE (SG5 group). Mirrors condition 42 but targets DTM+Z45 (Leistungskurvenänderungszeitpunkt). Same caveat: the actual 'Code' element might live in a CCI/SEQ segment absent from the provided schema reference — medium confidence. (medium confidence)
540    fn evaluate_43(&self, ctx: &EvaluationContext) -> ConditionResult {
541        let nav = match ctx.navigator() {
542            Some(n) => n,
543            None => return ConditionResult::Unknown,
544        };
545        let sg5_count = nav.group_instance_count(&["SG5"]);
546        for i in 0..sg5_count {
547            let sg8_count = nav.child_group_instance_count(&["SG5"], i, "SG8");
548            let mut seen = std::collections::HashSet::new();
549            for j in 0..sg8_count {
550                let dtms = nav.find_segments_in_child_group("DTM", &["SG5"], i, "SG8", j);
551                for dtm in &dtms {
552                    if dtm
553                        .elements
554                        .first()
555                        .and_then(|e| e.first())
556                        .is_some_and(|v| v == "Z45")
557                    {
558                        if let Some(val) = dtm.elements.first().and_then(|e| e.get(1)) {
559                            if !val.is_empty() && !seen.insert(val.clone()) {
560                                return ConditionResult::False;
561                            }
562                        }
563                    }
564                }
565            }
566        }
567        ConditionResult::True
568    }
569
570    /// [56] Wenn dieses DTM+Z25 (Verwendung der Daten ab) im SG6 RFF (Verwendungszeitraum der Daten) mit der Zeitraum ID "1" im DE1156 ist, muss das Datum der darauffolgende oder ein älterer Tag 0:00 Uhr deut...
571    // REVIEW: Checks that DTM+Z25 (Verwendung der Daten ab) in any SG6 whose associated RFF has Zeitraum-ID '1' in DE1156 (C506 component [0][2]) is at most the day following DTM+137 (message date) at 0:00. Date arithmetic is implemented inline without external crates. Timezone nuance ('0:00 Uhr deutscher Zeit' = CET/CEST midnight, not UTC midnight) is approximated — threshold uses CCYYMMDD+10000 of the next calendar day in UTC, which introduces up to 2-hour margin of error around DST transitions. The Zeitraum-ID '1' is checked at elements[0][2] per the explicit DE1156 reference in the condition text. (medium confidence)
572    fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
573        let msg_dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
574        let msg_date_val = match msg_dtm_segs
575            .first()
576            .and_then(|s| s.elements.first())
577            .and_then(|e| e.get(1))
578        {
579            Some(v) => v.clone(),
580            None => return ConditionResult::Unknown,
581        };
582        if msg_date_val.len() < 8 {
583            return ConditionResult::Unknown;
584        }
585        let year: u32 = match msg_date_val[..4].parse::<u32>() {
586            Ok(v) => v,
587            Err(_) => return ConditionResult::Unknown,
588        };
589        let month: u32 = match msg_date_val[4..6].parse::<u32>() {
590            Ok(v) => v,
591            Err(_) => return ConditionResult::Unknown,
592        };
593        let day: u32 = match msg_date_val[6..8].parse::<u32>() {
594            Ok(v) => v,
595            Err(_) => return ConditionResult::Unknown,
596        };
597        let days_in_month: u32 = match month {
598            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
599            4 | 6 | 9 | 11 => 30,
600            2 => {
601                if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) {
602                    29
603                } else {
604                    28
605                }
606            }
607            _ => return ConditionResult::Unknown,
608        };
609        let (ny, nm, nd): (u32, u32, u32) = if day >= days_in_month {
610            if month == 12 {
611                (year + 1, 1, 1)
612            } else {
613                (year, month + 1, 1)
614            }
615        } else {
616            (year, month, day + 1)
617        };
618        let threshold = format!("{:04}{:02}{:02}0000", ny, nm, nd);
619        let nav = match ctx.navigator() {
620            Some(n) => n,
621            None => return ConditionResult::Unknown,
622        };
623        let sg5_count = nav.group_instance_count(&["SG5"]);
624        for i in 0..sg5_count {
625            let sg6_count = nav.child_group_instance_count(&["SG5"], i, "SG6");
626            for j in 0..sg6_count {
627                let rffs = nav.find_segments_in_child_group("RFF", &["SG5"], i, "SG6", j);
628                let has_zeitraum_1 = rffs.iter().any(|rff| {
629                    rff.elements
630                        .first()
631                        .and_then(|e| e.get(2))
632                        .is_some_and(|v| v == "1")
633                });
634                if has_zeitraum_1 {
635                    let dtms = nav.find_segments_in_child_group("DTM", &["SG5"], i, "SG6", j);
636                    for dtm in &dtms {
637                        if dtm
638                            .elements
639                            .first()
640                            .and_then(|e| e.first())
641                            .is_some_and(|v| v == "Z25")
642                        {
643                            if let Some(dtm_val) = dtm.elements.first().and_then(|e| e.get(1)) {
644                                if dtm_val.as_str() > threshold.as_str() {
645                                    return ConditionResult::False;
646                                }
647                            }
648                        }
649                    }
650                }
651            }
652        }
653        ConditionResult::True
654    }
655
656    /// [57] Wenn dieses DTM+Z25 (Verwendung der Daten ab) nicht im SG6 RFF+Z49/ Z53 (Verwendungszeitraum der Daten: Gültige Daten/ Keine Daten) mit der Zeitraum ID "1" im DE1156 ist, muss das Datum dem DTM+Z2...
657    // REVIEW: Complex cross-SG6 ordering condition: for each SG6 with Zeitraum-ID != 1, its DTM+Z25 must equal the DTM+Z26 of the SG6 with the next lower Zeitraum-ID. Implemented via navigator collecting (id, z25, z26) tuples per SG6 instance, then verifying the sequential boundary constraint. (medium confidence)
658    fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
659        let nav = match ctx.navigator() {
660            Some(n) => n,
661            None => return ConditionResult::Unknown,
662        };
663        let sg6_path: &[&str] = &["SG5", "SG6"];
664        let sg6_count = nav.group_instance_count(sg6_path);
665        if sg6_count == 0 {
666            return ConditionResult::Unknown;
667        }
668        // Build (zeitraum_id, dtm_z25, dtm_z26) per SG6
669        let mut entries: Vec<(u32, Option<String>, Option<String>)> = Vec::new();
670        for i in 0..sg6_count {
671            let rff_segs = nav.find_segments_in_group("RFF", sg6_path, i);
672            let mut zeitraum_id: Option<u32> = None;
673            for rff in &rff_segs {
674                let qual = rff
675                    .elements
676                    .first()
677                    .and_then(|e| e.first())
678                    .map(|s| s.as_str());
679                if matches!(qual, Some("Z49") | Some("Z53")) {
680                    if let Some(id_str) = rff.elements.first().and_then(|e| e.get(2)) {
681                        if let Ok(id) = id_str.parse::<u32>() {
682                            zeitraum_id = Some(id);
683                        }
684                    }
685                }
686            }
687            let id = match zeitraum_id {
688                Some(id) => id,
689                None => continue,
690            };
691            let dtm_segs = nav.find_segments_in_group("DTM", sg6_path, i);
692            let mut dtm_z25: Option<String> = None;
693            let mut dtm_z26: Option<String> = None;
694            for dtm in &dtm_segs {
695                let qual = dtm
696                    .elements
697                    .first()
698                    .and_then(|e| e.first())
699                    .map(|s| s.as_str());
700                let val = dtm
701                    .elements
702                    .first()
703                    .and_then(|e| e.get(1))
704                    .filter(|v| !v.is_empty())
705                    .cloned();
706                match qual {
707                    Some("Z25") => dtm_z25 = val,
708                    Some("Z26") => dtm_z26 = val,
709                    _ => {}
710                }
711            }
712            entries.push((id, dtm_z25, dtm_z26));
713        }
714        // For non-ID-1 entries, check DTM+Z25 == DTM+Z26 of next-lower Zeitraum-ID
715        for (id, dtm_z25, _) in &entries {
716            if *id == 1 {
717                continue;
718            }
719            let current_z25 = match dtm_z25 {
720                Some(v) => v,
721                None => continue,
722            };
723            let next_lower = entries
724                .iter()
725                .filter(|(other_id, _, _)| *other_id < *id)
726                .map(|(other_id, _, _)| *other_id)
727                .max();
728            let lower_id = match next_lower {
729                Some(lid) => lid,
730                None => return ConditionResult::Unknown,
731            };
732            match entries.iter().find(|(eid, _, _)| *eid == lower_id) {
733                Some((_, _, Some(lower_z26))) => {
734                    if current_z25 != lower_z26 {
735                        return ConditionResult::False;
736                    }
737                }
738                Some((_, _, None)) => return ConditionResult::Unknown,
739                None => return ConditionResult::Unknown,
740            }
741        }
742        ConditionResult::True
743    }
744
745    /// [490] wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Prozesszeitpunkt bei MESZ mit UTC“ ist
746    fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
747        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
748        match dtm_segs
749            .first()
750            .and_then(|s| s.elements.first())
751            .and_then(|e| e.get(1))
752        {
753            Some(val) => is_mesz_utc(val),
754            None => ConditionResult::False, // segment absent → condition not applicable
755        }
756    }
757
758    /// [491] wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Prozesszeitpunkt bei MEZ mit UTC“ ist
759    fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
760        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
761        match dtm_segs
762            .first()
763            .and_then(|s| s.elements.first())
764            .and_then(|e| e.get(1))
765        {
766            Some(val) => is_mez_utc(val),
767            None => ConditionResult::False, // segment absent → condition not applicable
768        }
769    }
770
771    /// [1] Nur MP-ID aus Sparte Strom
772    /// EXTERNAL: Requires context from outside the message.
773    // REVIEW: Whether the MP-ID belongs to the electricity sector (Sparte Strom) cannot be determined from the EDIFACT message alone — requires external market participant registry lookup. (medium confidence)
774    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
775        ctx.external.evaluate("mp_id_is_strom_sector")
776    }
777
778    /// [2] Wenn SG5 STS+Z23+Z34 (Berechnungsformel muss beim Absender angefragt werden) in einem SG5 IDE vorhanden
779    fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
780        ctx.any_group_has_qualified_value("STS", 0, "Z23", 1, 0, &["Z34"], &["SG5"])
781    }
782
783    /// [5] Wenn das SG8 RFF+Z19 (Referenz auf eine Messlokation) in derselben SG8 SEQ+Z37 nicht vorhanden
784    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
785        ctx.any_group_has_qualifier_without("RFF", 0, "Z19", "SEQ", 0, "Z37", &["SG4", "SG8"])
786    }
787
788    /// [6] Wenn das SG8 RFF+Z23 (Referenz auf Rechenschritt) in derselben SG8 SEQ+Z37 nicht vorhanden
789    fn evaluate_6(&self, ctx: &EvaluationContext) -> ConditionResult {
790        ctx.any_group_has_qualifier_without("RFF", 0, "Z23", "SEQ", 0, "Z37", &["SG4", "SG8"])
791    }
792
793    /// [7] Wenn in derselben SG8 SEQ+Z37 das SG8 RFF+Z19 (Referenz auf eine Messlokation) vorhanden
794    fn evaluate_7(&self, ctx: &EvaluationContext) -> ConditionResult {
795        ctx.any_group_has_co_occurrence("SEQ", 0, &["Z37"], "RFF", 0, 0, &["Z19"], &["SG4", "SG8"])
796    }
797
798    /// [10] wenn vorhanden
799    fn evaluate_10(&self, _ctx: &EvaluationContext) -> ConditionResult {
800        // "wenn vorhanden" — conditional modifier meaning the associated rule applies
801        // only when the element is present. As a standalone boolean predicate this
802        // always evaluates to True: the condition itself imposes no additional
803        // constraint beyond the field's own optionality.
804        ConditionResult::True
805    }
806
807    /// [13] Wenn in SG8 SEQ+Z37 SG9 CCI+++Z86 CAV+Z80/Z81 (Divisor / Dividend) vorhanden, muss in diesem Vorgang genau eine zweite SG8 SEQ+Z37 mit identischen Rechenschrittidentifikator und derselben Zeitraum-...
808    // REVIEW: Complex cross-SG8 pairing invariant: for each Z80 (Divisor) or Z81 (Dividend) operator in SG9 CCI+Z86, there must be exactly one matching SG8 with the same Rechenschrittidentifikator (SEQ.elements[1][0]) and Zeitraum-ID (RFF+Z46) that carries the complementary operator. Requires full navigator traversal. (medium confidence)
809    fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
810        {
811            let nav = match ctx.navigator() {
812                Some(n) => n,
813                None => return ConditionResult::Unknown,
814            };
815
816            let sg8_count = nav.group_instance_count(&["SG5", "SG8"]);
817            // Collect (rs_id, zt_id, operator) for all Z37 SG8s that have CCI+++Z86 and CAV+Z80/Z81
818            let mut div_instances: Vec<(String, String, String)> = Vec::new();
819
820            for i in 0..sg8_count {
821                let seqs = nav.find_segments_in_group("SEQ", &["SG5", "SG8"], i);
822                let seq_z37 = seqs.iter().find(|s| {
823                    s.elements
824                        .first()
825                        .and_then(|e| e.first())
826                        .is_some_and(|v| v == "Z37")
827                });
828                let seq_z37 = match seq_z37 {
829                    Some(s) => s,
830                    None => continue,
831                };
832
833                let rs_id = seq_z37
834                    .elements
835                    .get(1)
836                    .and_then(|e| e.first())
837                    .cloned()
838                    .unwrap_or_default();
839
840                let rffs = nav.find_segments_in_group("RFF", &["SG5", "SG8"], i);
841                let zt_id = rffs
842                    .iter()
843                    .find(|s| {
844                        s.elements
845                            .first()
846                            .and_then(|e| e.first())
847                            .is_some_and(|v| v == "Z46")
848                    })
849                    .and_then(|s| s.elements.first())
850                    .and_then(|e| e.get(1))
851                    .cloned()
852                    .unwrap_or_default();
853
854                let sg9_count = nav.child_group_instance_count(&["SG5", "SG8"], i, "SG9");
855                for j in 0..sg9_count {
856                    let ccis =
857                        nav.find_segments_in_child_group("CCI", &["SG5", "SG8"], i, "SG9", j);
858                    let has_z86 = ccis.iter().any(|s| {
859                        s.elements
860                            .get(2)
861                            .and_then(|e| e.first())
862                            .is_some_and(|v| v == "Z86")
863                    });
864                    if !has_z86 {
865                        continue;
866                    }
867                    let cavs =
868                        nav.find_segments_in_child_group("CAV", &["SG5", "SG8"], i, "SG9", j);
869                    for cav in &cavs {
870                        let op = cav
871                            .elements
872                            .first()
873                            .and_then(|e| e.first())
874                            .map(|s| s.as_str());
875                        if matches!(op, Some("Z80") | Some("Z81")) {
876                            div_instances.push((
877                                rs_id.clone(),
878                                zt_id.clone(),
879                                op.unwrap().to_string(),
880                            ));
881                        }
882                    }
883                }
884            }
885
886            if div_instances.is_empty() {
887                return ConditionResult::Unknown;
888            }
889
890            // Each Z80 must have exactly one Z81 counterpart with same IDs, and vice versa
891            for (rs_id, zt_id, op) in &div_instances {
892                let counterpart = if op == "Z80" { "Z81" } else { "Z80" };
893                let count = div_instances
894                    .iter()
895                    .filter(|(rs, zt, o)| rs == rs_id && zt == zt_id && o == counterpart)
896                    .count();
897                if count != 1 {
898                    return ConditionResult::False;
899                }
900            }
901
902            ConditionResult::True
903        }
904    }
905
906    /// [14] Wenn in SG8 SEQ+Z37 SG9 CCI+++Z86 CAV+Z82 (Faktor) vorhanden, darf es in dem Vorgang beliebig viele weitere SG8 SEQ+Z37 mit identischem Rechenschrittidentifikator und derselben Zeitraum-ID geben, d...
907    // REVIEW: When a Z82 (Faktor/Multiplikation) operator is present in a group of SG8s sharing the same Rechenschrittidentifikator and Zeitraum-ID, ALL members of that group must exclusively carry Z82 — no mixing with Z80/Z81. Returns True when this homogeneity constraint holds. (medium confidence)
908    fn evaluate_14(&self, ctx: &EvaluationContext) -> ConditionResult {
909        {
910            let nav = match ctx.navigator() {
911                Some(n) => n,
912                None => return ConditionResult::Unknown,
913            };
914
915            let sg8_count = nav.group_instance_count(&["SG5", "SG8"]);
916            // Collect (rs_id, zt_id, operators) for all Z37 SG8s with CCI+++Z86
917            let mut instances: Vec<(String, String, Vec<String>)> = Vec::new();
918
919            for i in 0..sg8_count {
920                let seqs = nav.find_segments_in_group("SEQ", &["SG5", "SG8"], i);
921                let seq_z37 = seqs.iter().find(|s| {
922                    s.elements
923                        .first()
924                        .and_then(|e| e.first())
925                        .is_some_and(|v| v == "Z37")
926                });
927                let seq_z37 = match seq_z37 {
928                    Some(s) => s,
929                    None => continue,
930                };
931
932                let rs_id = seq_z37
933                    .elements
934                    .get(1)
935                    .and_then(|e| e.first())
936                    .cloned()
937                    .unwrap_or_default();
938
939                let rffs = nav.find_segments_in_group("RFF", &["SG5", "SG8"], i);
940                let zt_id = rffs
941                    .iter()
942                    .find(|s| {
943                        s.elements
944                            .first()
945                            .and_then(|e| e.first())
946                            .is_some_and(|v| v == "Z46")
947                    })
948                    .and_then(|s| s.elements.first())
949                    .and_then(|e| e.get(1))
950                    .cloned()
951                    .unwrap_or_default();
952
953                let sg9_count = nav.child_group_instance_count(&["SG5", "SG8"], i, "SG9");
954                let mut ops: Vec<String> = Vec::new();
955                for j in 0..sg9_count {
956                    let ccis =
957                        nav.find_segments_in_child_group("CCI", &["SG5", "SG8"], i, "SG9", j);
958                    if !ccis.iter().any(|s| {
959                        s.elements
960                            .get(2)
961                            .and_then(|e| e.first())
962                            .is_some_and(|v| v == "Z86")
963                    }) {
964                        continue;
965                    }
966                    let cavs =
967                        nav.find_segments_in_child_group("CAV", &["SG5", "SG8"], i, "SG9", j);
968                    for cav in &cavs {
969                        if let Some(op) = cav.elements.first().and_then(|e| e.first()) {
970                            ops.push(op.clone());
971                        }
972                    }
973                }
974                if !ops.is_empty() {
975                    instances.push((rs_id, zt_id, ops));
976                }
977            }
978
979            // Find any (rs_id, zt_id) key that has a Z82 operator
980            let z82_keys: Vec<(&str, &str)> = instances
981                .iter()
982                .filter(|(_, _, ops)| ops.iter().any(|op| op == "Z82"))
983                .map(|(rs, zt, _)| (rs.as_str(), zt.as_str()))
984                .collect();
985
986            if z82_keys.is_empty() {
987                return ConditionResult::Unknown;
988            }
989
990            // For each group keyed by (rs_id, zt_id) that has Z82, all operators must be exclusively Z82
991            for (rs_id, zt_id) in &z82_keys {
992                for (rs, zt, ops) in &instances {
993                    if rs.as_str() == *rs_id && zt.as_str() == *zt_id {
994                        if ops.iter().any(|op| op != "Z82") {
995                            return ConditionResult::False;
996                        }
997                    }
998                }
999            }
1000
1001            ConditionResult::True
1002        }
1003    }
1004
1005    /// [15] Wenn in einem SG5 IDE+24 nur eine SEQ+Z37 mit einer SG8 RFF+Z19 (Messlokation) und der selben Zeitraum-ID vorhanden ist
1006    // REVIEW: Checks that within the current SG5 (IDE+24) transaction group there is exactly one SG8 that contains both SEQ+Z37 (Bestandteil des Rechenschritts) and RFF+Z19 (Messlokation reference), and that all such matching SG8s reference the same Zeitraum-ID via RFF+Z46. (medium confidence)
1007    fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
1008        {
1009            let nav = match ctx.navigator() {
1010                Some(n) => n,
1011                None => return ConditionResult::Unknown,
1012            };
1013
1014            let sg8_count = nav.group_instance_count(&["SG5", "SG8"]);
1015            let mut matching_count = 0usize;
1016            let mut first_zt_id: Option<String> = None;
1017
1018            for i in 0..sg8_count {
1019                let seqs = nav.find_segments_in_group("SEQ", &["SG5", "SG8"], i);
1020                let has_z37 = seqs.iter().any(|s| {
1021                    s.elements
1022                        .first()
1023                        .and_then(|e| e.first())
1024                        .is_some_and(|v| v == "Z37")
1025                });
1026                if !has_z37 {
1027                    continue;
1028                }
1029
1030                let rffs = nav.find_segments_in_group("RFF", &["SG5", "SG8"], i);
1031                let has_z19 = rffs.iter().any(|s| {
1032                    s.elements
1033                        .first()
1034                        .and_then(|e| e.first())
1035                        .is_some_and(|v| v == "Z19")
1036                });
1037                if !has_z19 {
1038                    continue;
1039                }
1040
1041                // Verify Zeitraum-ID consistency across all matching instances
1042                let zt_id = rffs
1043                    .iter()
1044                    .find(|s| {
1045                        s.elements
1046                            .first()
1047                            .and_then(|e| e.first())
1048                            .is_some_and(|v| v == "Z46")
1049                    })
1050                    .and_then(|s| s.elements.first())
1051                    .and_then(|e| e.get(1))
1052                    .cloned();
1053
1054                if let Some(ref zt) = zt_id {
1055                    match &first_zt_id {
1056                        None => first_zt_id = Some(zt.clone()),
1057                        Some(first) => {
1058                            if first != zt {
1059                                return ConditionResult::False;
1060                            }
1061                        }
1062                    }
1063                }
1064
1065                matching_count += 1;
1066            }
1067
1068            ConditionResult::from(matching_count == 1)
1069        }
1070    }
1071
1072    /// [21] Wenn in dieser CAV+ZD3 der Wert im DE7110 mit Z32 (sonstiger Zählzeitdefinitionstyp) vorhanden ist
1073    fn evaluate_21(&self, ctx: &EvaluationContext) -> ConditionResult {
1074        ctx.has_qualified_value("CAV", 0, "ZD3", 0, 3, &["Z32"])
1075    }
1076
1077    /// [22] Wenn MP-ID in SG2 NAD+MS (Nachrichtenabsender) in der Rolle NB
1078    /// EXTERNAL: Requires context from outside the message.
1079    // REVIEW: Whether the MP-ID in NAD+MS (Nachrichtenabsender) holds the role of NB (Netzbetreiber) cannot be determined from the EDIFACT message alone — requires external market participant role registry. (medium confidence)
1080    fn evaluate_22(&self, ctx: &EvaluationContext) -> ConditionResult {
1081        ctx.external.evaluate("sender_is_nb")
1082    }
1083
1084    /// [24] Wenn SG5 STS+Z36+Z45 (Definitionen werden verwendet) vorhanden
1085    fn evaluate_24(&self, ctx: &EvaluationContext) -> ConditionResult {
1086        ctx.has_qualified_value("STS", 0, "Z36", 1, 0, &["Z45"])
1087    }
1088
1089    /// [27] Wenn in SG9 CAV+ZD4+Z26 (keine Verwendung des Hochlastzeitfensters) vorhanden
1090    fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
1091        ctx.has_qualified_value("CAV", 0, "ZD4", 0, 3, &["Z26"])
1092    }
1093
1094    /// [29] Wenn in SG8 SEQ+Z43 DTM+Z33 (Zählzeitänderungszeitpunkt) im DE2379 der Code 303 vorhanden
1095    // REVIEW: In the same SG8, SEQ+Z43 (Zählzeitdefinition) must be present and DTM must have format code 303 at elements[0][2]. In a SEQ+Z43 SG8, the only DTM present is DTM+Z33, so checking format code 303 without qualifying by Z33 is safe. (medium confidence)
1096    fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
1097        ctx.any_group_has_co_occurrence("SEQ", 0, &["Z43"], "DTM", 0, 2, &["303"], &["SG5", "SG8"])
1098    }
1099
1100    /// [30] Der Wert von CCYY in diesem DE muss genau um eins höher sein, als der Wert CCYY des SG5 DTM+Z34 (Gültigkeitsbeginn) DE2380
1101    // REVIEW: Compares CCYY of DTM+Z35 (Gültigkeitsende) to CCYY of DTM+Z34 (Gültigkeitsbeginn), checking that the end year is exactly start year + 1. 'Diesem DE' most plausibly refers to DTM+Z35 since it is the natural counterpart to DTM+Z34 and its year is constrained relative to Z34. (medium confidence)
1102    fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
1103        // CCYY in this DE must be exactly one higher than CCYY of SG5 DTM+Z34 DE2380
1104        // This applies to DTM+Z35 (Gültigkeitsende) whose year must be Z34 year + 1
1105        let z34_segs = ctx.find_segments_with_qualifier("DTM", 0, "Z34");
1106        let z35_segs = ctx.find_segments_with_qualifier("DTM", 0, "Z35");
1107        let z34_year = z34_segs
1108            .first()
1109            .and_then(|s| s.elements.first())
1110            .and_then(|e| e.get(1))
1111            .and_then(|v| v.get(..4))
1112            .and_then(|y| y.parse::<u32>().ok());
1113        let this_year = z35_segs
1114            .first()
1115            .and_then(|s| s.elements.first())
1116            .and_then(|e| e.get(1))
1117            .and_then(|v| v.get(..4))
1118            .and_then(|y| y.parse::<u32>().ok());
1119        match (z34_year, this_year) {
1120            (Some(start), Some(end)) => ConditionResult::from(end == start + 1),
1121            _ => ConditionResult::Unknown,
1122        }
1123    }
1124
1125    /// [31] Wenn im DE2379 dieses Segments der Code 303 vorhanden
1126    // REVIEW: Checks if any DTM segment has format code 303 at elements[0][2] (DE2379). Applies to DTM+Z33/Z44/Z45 which can carry format codes 303 or 401. Message-wide check is sufficient since these are the only DTMs with variable format codes. (medium confidence)
1127    fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
1128        ConditionResult::from(ctx.find_segments("DTM").iter().any(|s| {
1129            s.elements
1130                .first()
1131                .and_then(|e| e.get(2))
1132                .map(|v| v == "303")
1133                .unwrap_or(false)
1134        }))
1135    }
1136
1137    /// [32] Der Zeitpunkt in diesem DE muss ≥ dem Zeitpunkt aus dem DE2380 des Gültigkeitsbeginn der ausgerollten Definition (SG5 DTM+Z34) sein
1138    // REVIEW: Validates that SG8 change timestamps (Z33/Z44/Z45) are >= the SG5 DTM+Z34 validity start. 'Diesem DE' refers to the change-point timestamp being validated in SG8. First 12 characters (YYYYMMDDHHmm) are used for consistent lexicographic date comparison in format 303. (medium confidence)
1139    fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
1140        // Timestamp in this DE >= DTM+Z34 (Gültigkeitsbeginn der ausgerollten Definition)
1141        // Applies to SG8 change-point timestamps (Z33 Zählzeit, Z44 Schaltzeit, Z45 Leistungskurve)
1142        let z34_segs = ctx.find_segments_with_qualifier("DTM", 0, "Z34");
1143        let z34_value = match z34_segs
1144            .first()
1145            .and_then(|s| s.elements.first())
1146            .and_then(|e| e.get(1))
1147        {
1148            Some(v) => v.clone(),
1149            None => return ConditionResult::Unknown,
1150        };
1151        let threshold = z34_value.get(..12).unwrap_or(z34_value.as_str());
1152        for qual in &["Z33", "Z44", "Z45"] {
1153            let segs = ctx.find_segments_with_qualifier("DTM", 0, qual);
1154            for seg in &segs {
1155                if let Some(val) = seg.elements.first().and_then(|e| e.get(1)) {
1156                    let v = val.get(..12).unwrap_or(val);
1157                    if v < threshold {
1158                        return ConditionResult::False;
1159                    }
1160                }
1161            }
1162        }
1163        ConditionResult::True
1164    }
1165
1166    /// [33] Der Zeitpunkt in diesem DE muss ≤ dem Zeitpunkt aus dem DE2380 des Gültigkeitsende der ausgerollten Definition (SG5 DTM+Z35) sein
1167    // REVIEW: Validates that SG8 change timestamps (Z33/Z44/Z45) are <= the SG5 DTM+Z35 validity end. Symmetric counterpart to condition 32. First 12 characters used for format-303 string comparison. (medium confidence)
1168    fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
1169        // Timestamp in this DE <= DTM+Z35 (Gültigkeitsende der ausgerollten Definition)
1170        // Applies to SG8 change-point timestamps (Z33 Zählzeit, Z44 Schaltzeit, Z45 Leistungskurve)
1171        let z35_segs = ctx.find_segments_with_qualifier("DTM", 0, "Z35");
1172        let z35_value = match z35_segs
1173            .first()
1174            .and_then(|s| s.elements.first())
1175            .and_then(|e| e.get(1))
1176        {
1177            Some(v) => v.clone(),
1178            None => return ConditionResult::Unknown,
1179        };
1180        let threshold = z35_value.get(..12).unwrap_or(z35_value.as_str());
1181        for qual in &["Z33", "Z44", "Z45"] {
1182            let segs = ctx.find_segments_with_qualifier("DTM", 0, qual);
1183            for seg in &segs {
1184                if let Some(val) = seg.elements.first().and_then(|e| e.get(1)) {
1185                    let v = val.get(..12).unwrap_or(val);
1186                    if v > threshold {
1187                        return ConditionResult::False;
1188                    }
1189                }
1190            }
1191        }
1192        ConditionResult::True
1193    }
1194
1195    /// [34] Wenn im DE2379 dieses Segments der Code 401 vorhanden
1196    // REVIEW: Checks if any DTM segment has format code 401 at elements[0][2] (DE2379). Counterpart to condition [31] for the 401 format code (point-in-time without timezone vs. UTC offset). (medium confidence)
1197    fn evaluate_34(&self, ctx: &EvaluationContext) -> ConditionResult {
1198        ConditionResult::from(ctx.find_segments("DTM").iter().any(|s| {
1199            s.elements
1200                .first()
1201                .and_then(|e| e.get(2))
1202                .map(|v| v == "401")
1203                .unwrap_or(false)
1204        }))
1205    }
1206
1207    /// [36] Wenn in SG8 SEQ+Z43 DTM+Z33 (Zählzeitänderungszeitpunkt) im DE2379 der Code 401 vorhanden
1208    // REVIEW: In the same SG8, SEQ+Z43 (Zählzeitdefinition) must be present and DTM+Z33 must have format code 401 at elements[0][2]. Counterpart to condition [29] for the 401 format code. (medium confidence)
1209    fn evaluate_36(&self, ctx: &EvaluationContext) -> ConditionResult {
1210        ctx.any_group_has_co_occurrence("SEQ", 0, &["Z43"], "DTM", 0, 2, &["401"], &["SG5", "SG8"])
1211    }
1212
1213    /// [41] Wenn SG8 SEQ+Z42 (Zählzeitdefinition) vorhanden
1214    fn evaluate_41(&self, ctx: &EvaluationContext) -> ConditionResult {
1215        ctx.any_group_has_qualifier("SEQ", 0, "Z42", &["SG5", "SG8"])
1216    }
1217
1218    /// [44] Der in diesem Datenlement angegebene Code der Zählzeitdefinition muss innerhalb eines Vorgangs (IDE) eindeutig sein.
1219    // REVIEW: RFF+Z27 (elements[0][1]) in SG8 holds the Zählzeitdefinition code per the segment reference. Collects all Z27 reference values message-wide and checks for duplicates. Returns True when all codes are distinct (unique), False when a duplicate exists, Unknown when no Z27 segments found. Group navigator would be needed to properly scope to SG5 boundaries — approximated as message-wide. (medium confidence)
1220    fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
1221        // RFF+Z27 in SG8 holds the Zählzeitdefinition code — must be unique within the IDE (SG5) scope.
1222        // Approximate with message-wide check: all Z27 values must be distinct.
1223        let rff_segs = ctx.find_segments("RFF");
1224        let z27_values: Vec<&str> = rff_segs
1225            .iter()
1226            .filter(|s| {
1227                s.elements
1228                    .first()
1229                    .and_then(|e| e.first())
1230                    .is_some_and(|q| q == "Z27")
1231            })
1232            .filter_map(|s| {
1233                s.elements
1234                    .first()
1235                    .and_then(|e| e.get(1))
1236                    .map(|s| s.as_str())
1237            })
1238            .collect();
1239        if z27_values.is_empty() {
1240            return ConditionResult::Unknown;
1241        }
1242        let unique_count: std::collections::HashSet<&str> = z27_values.iter().copied().collect();
1243        ConditionResult::from(unique_count.len() == z27_values.len())
1244    }
1245
1246    /// [46] Wenn in SG8 SEQ+Z73 DTM+Z44 (Schaltzeitänderungszeitpunkt) im DE2379 der Code 303 vorhanden
1247    fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
1248        ctx.any_group_has_co_occurrence("SEQ", 0, &["Z73"], "DTM", 0, 2, &["303"], &["SG5", "SG8"])
1249    }
1250
1251    /// [47] Wenn in SG8 SEQ+Z73 DTM+Z44 (Schaltzeitänderungszeitpunkt) im DE2379 der Code 401 vorhanden
1252    fn evaluate_47(&self, ctx: &EvaluationContext) -> ConditionResult {
1253        ctx.any_group_has_co_occurrence("SEQ", 0, &["Z73"], "DTM", 0, 2, &["401"], &["SG5", "SG8"])
1254    }
1255
1256    /// [48] Wenn in SG8 SEQ+Z74 DTM+Z45 (Leistungskurvenänderungszeitpunkt) im DE2379 der Code 303 vorhanden
1257    fn evaluate_48(&self, ctx: &EvaluationContext) -> ConditionResult {
1258        ctx.any_group_has_co_occurrence("SEQ", 0, &["Z74"], "DTM", 0, 2, &["303"], &["SG5", "SG8"])
1259    }
1260
1261    /// [49] Wenn in SG8 SEQ+Z74 DTM+Z45 (Leistungskurvenänderungszeitpunkt) im DE2379 der Code 401 vorhanden
1262    fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
1263        ctx.any_group_has_co_occurrence("SEQ", 0, &["Z74"], "DTM", 0, 2, &["401"], &["SG5", "SG8"])
1264    }
1265
1266    /// [50] In jedem DE2379 dieses DTM-Segments innerhalb eines IDE+24 (Vorgangs) muss der gleiche Code angegeben werden
1267    // REVIEW: Checks that all DTM segments in the message use the same DE2379 format code. Full per-IDE+24 scoping would require a navigator, but message-wide consistency is a practical approximation — in well-formed UTILTS messages a single IDE is common. Returns Unknown when no DTMs are found. (medium confidence)
1268    fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
1269        {
1270            let dtms = ctx.find_segments("DTM");
1271            let format_codes: Vec<&str> = dtms
1272                .iter()
1273                .filter_map(|s| s.elements.first()?.get(2).map(|s| s.as_str()))
1274                .filter(|s| !s.is_empty())
1275                .collect();
1276            if format_codes.is_empty() {
1277                return ConditionResult::Unknown;
1278            }
1279            let first = format_codes[0];
1280            ConditionResult::from(format_codes.iter().all(|&c| c == first))
1281        }
1282    }
1283
1284    /// [53] Wenn im DE3155 in demselben COM der Code EM vorhanden ist
1285    fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
1286        {
1287            let coms = ctx.find_segments("COM");
1288            ConditionResult::from(coms.iter().any(|s| {
1289                s.elements
1290                    .first()
1291                    .and_then(|e| e.get(1))
1292                    .is_some_and(|v| v == "EM")
1293            }))
1294        }
1295    }
1296
1297    /// [54] Wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
1298    fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
1299        {
1300            let coms = ctx.find_segments("COM");
1301            ConditionResult::from(coms.iter().any(|s| {
1302                s.elements
1303                    .first()
1304                    .and_then(|e| e.get(1))
1305                    .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
1306            }))
1307        }
1308    }
1309
1310    /// [55] Es ist der Wert einzutragen, der sich aus der Wiederholungshäufigkeit des SG6 RFF+Z49/ Z53 (Verwendungszeitraum der Daten: Gültige Daten/ Keine Daten) ergibt. Bedeutet: Das erste SG6 RFF+Z49/ Z53...
1311    fn evaluate_55(&self, _ctx: &EvaluationContext) -> ConditionResult {
1312        // Hinweis: The value to enter is derived from the repetition index of SG6 RFF+Z49/Z53:
1313        // first occurrence = "1", second = "2", third = "3", etc.
1314        // This is an informational annotation describing how to populate the data element.
1315        ConditionResult::True
1316    }
1317
1318    /// [58] Wenn im selben SG6 RFF+Z49/ Z53 (Verwendungszeitraum der Daten: Gültige Daten/ Keine Daten) im DE1156 (Zeitraum-ID) eine Zeitraum ID genannt ist, die kleiner ist als in einem anderen SG6 RFF+Z49/ ...
1319    // REVIEW: True when at least two SG6 RFF+Z49/Z53 instances exist with different DE1156 Zeitraum-IDs (i.e. min < max across all parsed integer IDs). This satisfies the 'smaller than another' ordering condition. Returns False for single-instance or equal-ID cases. (medium confidence)
1320    fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
1321        {
1322            let rffs = ctx.find_segments("RFF");
1323            let ids: Vec<i64> = rffs
1324                .iter()
1325                .filter(|s| {
1326                    s.elements
1327                        .first()
1328                        .and_then(|e| e.first())
1329                        .is_some_and(|q| q == "Z49" || q == "Z53")
1330                })
1331                .filter_map(|s| s.elements.first()?.get(2)?.parse::<i64>().ok())
1332                .collect();
1333            if ids.len() < 2 {
1334                return ConditionResult::False;
1335            }
1336            let min = *ids.iter().min().unwrap();
1337            let max = *ids.iter().max().unwrap();
1338            ConditionResult::from(min < max)
1339        }
1340    }
1341
1342    /// [59] Es ist die Zeitraum-ID vom DE1156 aus einem passenden SG6 RFF+Z49 (Verwendungszeitraum der Daten) einzutragen
1343    // REVIEW: Cross-group Zeitraum-ID correlation: SG8 RFF+Z46 DE1154 (reference to Zeitraum-ID) must match a DE1156 value from SG6 RFF+Z49 (Verwendungszeitraum der Daten). Collects DE1156 values from all RFF+Z49 instances and checks whether any RFF+Z46 reference value matches. Returns Unknown when either set is empty. (medium confidence)
1344    fn evaluate_59(&self, ctx: &EvaluationContext) -> ConditionResult {
1345        {
1346            let rffs = ctx.find_segments("RFF");
1347            let sg6_ids: Vec<String> = rffs
1348                .iter()
1349                .filter(|s| {
1350                    s.elements
1351                        .first()
1352                        .and_then(|e| e.first())
1353                        .is_some_and(|q| q == "Z49")
1354                })
1355                .filter_map(|s| s.elements.first()?.get(2).cloned())
1356                .filter(|v| !v.is_empty())
1357                .collect();
1358            if sg6_ids.is_empty() {
1359                return ConditionResult::Unknown;
1360            }
1361            let sg8_refs: Vec<String> = rffs
1362                .iter()
1363                .filter(|s| {
1364                    s.elements
1365                        .first()
1366                        .and_then(|e| e.first())
1367                        .is_some_and(|q| q == "Z46")
1368                })
1369                .filter_map(|s| s.elements.first()?.get(1).cloned())
1370                .filter(|v| !v.is_empty())
1371                .collect();
1372            if sg8_refs.is_empty() {
1373                return ConditionResult::Unknown;
1374            }
1375            ConditionResult::from(sg8_refs.iter().any(|r| sg6_ids.contains(r)))
1376        }
1377    }
1378
1379    /// [61] Wenn in einem STS+E01 im DE9013 (Status der Antwort) ein Antwortcode aus dem Cluster Ablehnung vorhanden ist
1380    // HAND-EDITED: driven by per-EBD cluster data (mako_prozesse).
1381    // The condition is existential — any Ablehnung STS+E01 → True.
1382    fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
1383        any_sts_e01_in_cluster(ctx, Cluster::is_ablehnung)
1384    }
1385
1386    /// [62] Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle MSB
1387    /// EXTERNAL: Requires context from outside the message.
1388    fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
1389        ctx.external.evaluate("recipient_is_msb")
1390    }
1391
1392    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt.
1393    /// EXTERNAL: Requires context from outside the message.
1394    // REVIEW: The date must be the document creation time or earlier (i.e. not in the future relative to when the document was created). This requires comparing the EDIFACT field value against an external reference timestamp (the actual document creation time or processing time), which is not available within the message segments themselves. (medium confidence)
1395    fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
1396        ctx.external.evaluate("document_date_not_in_future")
1397    }
1398
1399    /// [501] Hinweis: Verwendung der ID der Marktlokation
1400    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1401        ConditionResult::True
1402    }
1403
1404    /// [502] Hinweis: Verwendung der ID der Messlokation
1405    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1406        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1407        ConditionResult::True
1408    }
1409
1410    /// [504] Hinweis: Wert aus BGM+Z55 DE1004 der ORDERS mit der die Reklamation einer Definition erfolgt ist
1411    fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1412        // Hinweis: Wert aus BGM+Z55 DE1004 der ORDERS mit der die Reklamation einer Definition erfolgt ist — informational note, always applies
1413        ConditionResult::True
1414    }
1415
1416    /// [505] Hinweis: Jede ausgerollte Zählzeitdefinition ist in einem eigenen IDE anzugeben
1417    fn evaluate_505(&self, _ctx: &EvaluationContext) -> ConditionResult {
1418        // Hinweis: Jede ausgerollte Zählzeitdefinition ist in einem eigenen IDE anzugeben — informational note, always applies
1419        ConditionResult::True
1420    }
1421
1422    /// [506] Hinweis: Zeitpunkt, ab dem die Übersicht der Zählzeitdefinitionen gültig ist
1423    fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
1424        // Hinweis: Zeitpunkt, ab dem die Übersicht der Zählzeitdefinitionen gültig ist — informational note, always applies
1425        ConditionResult::True
1426    }
1427
1428    /// [507] Hinweis: Es ist die Zeit nach der deutschen gesetzlichen Zeit anzugeben
1429    fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
1430        // Hinweis: Es ist die Zeit nach der deutschen gesetzlichen Zeit anzugeben — informational note, always applies
1431        ConditionResult::True
1432    }
1433
1434    /// [508] Hinweis: Zeitpunkt, ab dem die Übersicht der Schaltzeitdefinitionen gültig ist
1435    fn evaluate_508(&self, _ctx: &EvaluationContext) -> ConditionResult {
1436        // Hinweis: Zeitpunkt, ab dem die Übersicht der Schaltzeitdefinitionen gültig ist — informational note, always applies
1437        ConditionResult::True
1438    }
1439
1440    /// [509] Hinweis: Zeitpunkt, ab dem die Übersicht der Leistungskurvendefinition gültig ist
1441    fn evaluate_509(&self, _ctx: &EvaluationContext) -> ConditionResult {
1442        // Hinweis: Zeitpunkt, ab dem die Übersicht der Leistungskurvendefinition gültig ist — informational note, always applies
1443        ConditionResult::True
1444    }
1445
1446    /// [510] Hinweis: Für jeden Zählzeitänderungszeitpunkt (SG8 DTM+Z33) ist diese Sementgruppe einmal anzugeben
1447    fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
1448        // Hinweis: Für jeden Zählzeitänderungszeitpunkt (SG8 DTM+Z33) ist diese Segmentgruppe einmal anzugeben — informational note, always applies
1449        ConditionResult::True
1450    }
1451
1452    /// [511] Hinweis: Der Zählzeitänderungszeitpunkt (SG8DTM+Z33) dieser SG8 darf in keiner anderen SG8 „Zählzeitdefinition“ wiederholt werden
1453    // REVIEW: Checks that no DTM+Z33 DE2380 value is repeated across SG8 instances. Collects all Z33 qualifier DTM segments and verifies uniqueness. Medium confidence because SG8-scoped iteration falls back to message-wide, which is correct here since each DTM+Z33 belongs to a distinct SG8. (medium confidence)
1454    fn evaluate_511(&self, ctx: &EvaluationContext) -> ConditionResult {
1455        // Uniqueness check: no DTM+Z33 value in DE2380 may appear in more than one SG8
1456        let dtm_z33_segments = ctx.find_segments_with_qualifier("DTM", 0, "Z33");
1457        let values: Vec<&str> = dtm_z33_segments
1458            .iter()
1459            .filter_map(|s| s.elements.first()?.get(1).map(|v| v.as_str()))
1460            .filter(|v| !v.is_empty())
1461            .collect();
1462        if values.is_empty() {
1463            return ConditionResult::Unknown;
1464        }
1465        // Check for duplicates: if all values are unique, condition is satisfied (True)
1466        let mut seen = std::collections::HashSet::new();
1467        let all_unique = values.iter().all(|v| seen.insert(*v));
1468        ConditionResult::from(all_unique)
1469    }
1470
1471    /// [512] Hinweis: Wenn der Code 303 im DE2379 des Zählzeitänderungszeitpunkt (SG8 DTM+Z33) genutzt wird, muss genau ein Wert im DE2380 des Zählzeitänderungszeitpunkt (SG8 DTM+Z33) identisch mit dem Wert...
1472    // REVIEW: When DTM+Z33 uses format code 303 in elements[0][2], exactly one DE2380 value (elements[0][1]) across all Z33 instances must equal the DE2380 value of DTM+Z34 from SG5. Medium confidence due to cross-SG group scoping — the message-wide fallback gives correct semantics here since we're correlating across SG5 and SG8 boundaries. (medium confidence)
1473    fn evaluate_512(&self, ctx: &EvaluationContext) -> ConditionResult {
1474        // When format code 303 is used in DTM+Z33 DE2379,
1475        // exactly one DE2380 value of DTM+Z33 must match the DE2380 of SG5 DTM+Z34
1476        let dtm_z33_segments = ctx.find_segments_with_qualifier("DTM", 0, "Z33");
1477        // Only consider Z33 segments that use format code 303
1478        let z33_303_values: Vec<&str> = dtm_z33_segments
1479            .iter()
1480            .filter(|s| {
1481                s.elements
1482                    .first()
1483                    .and_then(|e| e.get(2))
1484                    .is_some_and(|v| v == "303")
1485            })
1486            .filter_map(|s| s.elements.first()?.get(1).map(|v| v.as_str()))
1487            .filter(|v| !v.is_empty())
1488            .collect();
1489        if z33_303_values.is_empty() {
1490            // No Z33 with format 303 present — condition not applicable
1491            return ConditionResult::Unknown;
1492        }
1493        // Collect DE2380 values from SG5 DTM+Z34
1494        let dtm_z34_segments = ctx.find_segments_with_qualifier("DTM", 0, "Z34");
1495        let z34_values: Vec<&str> = dtm_z34_segments
1496            .iter()
1497            .filter_map(|s| s.elements.first()?.get(1).map(|v| v.as_str()))
1498            .filter(|v| !v.is_empty())
1499            .collect();
1500        if z34_values.is_empty() {
1501            return ConditionResult::Unknown;
1502        }
1503        // Exactly one Z33 DE2380 value must match a Z34 DE2380 value
1504        let match_count = z33_303_values
1505            .iter()
1506            .filter(|v| z34_values.contains(v))
1507            .count();
1508        ConditionResult::from(match_count == 1)
1509    }
1510
1511    /// [513] Hinweis: Wenn der Code 401 im DE2379 des Zählzeitänderungszeitpunkt (SG8 DTM+Z33) genutzt wird, muss genau ein Wert = 0000 im DE2380 des Zählzeitänderungszeitpunkt (SG8 DTM+Z33) sein
1512    fn evaluate_513(&self, ctx: &EvaluationContext) -> ConditionResult {
1513        // When format code 401 is used in DTM+Z33 DE2379,
1514        // exactly one DE2380 value must equal "0000"
1515        let dtm_z33_segments = ctx.find_segments_with_qualifier("DTM", 0, "Z33");
1516        // Only consider Z33 segments that use format code 401
1517        let z33_401_values: Vec<&str> = dtm_z33_segments
1518            .iter()
1519            .filter(|s| {
1520                s.elements
1521                    .first()
1522                    .and_then(|e| e.get(2))
1523                    .is_some_and(|v| v == "401")
1524            })
1525            .filter_map(|s| s.elements.first()?.get(1).map(|v| v.as_str()))
1526            .filter(|v| !v.is_empty())
1527            .collect();
1528        if z33_401_values.is_empty() {
1529            // No Z33 with format 401 present — condition not applicable
1530            return ConditionResult::Unknown;
1531        }
1532        // Exactly one value must equal "0000"
1533        let zero_count = z33_401_values.iter().filter(|v| **v == "0000").count();
1534        ConditionResult::from(zero_count == 1)
1535    }
1536
1537    /// [514] Hinweis: Für jeden Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) ist diese Sementgruppe einmal anzugeben
1538    fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
1539        // Hinweis: Für jeden Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) ist diese Segmentgruppe einmal anzugeben — informational note, always applies
1540        ConditionResult::True
1541    }
1542
1543    /// [515] Hinweis: Kein Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) darf mehrfach vorkommen
1544    fn evaluate_515(&self, ctx: &EvaluationContext) -> ConditionResult {
1545        let dtm_z44 = ctx.find_segments_with_qualifier("DTM", 0, "Z44");
1546        let mut values = std::collections::HashSet::new();
1547        for seg in &dtm_z44 {
1548            let c507 = match seg.elements.first() {
1549                Some(e) => e,
1550                None => continue,
1551            };
1552            let value = c507.get(1).map(|s| s.as_str()).unwrap_or("");
1553            if !value.is_empty() {
1554                if !values.insert(value.to_string()) {
1555                    return ConditionResult::False;
1556                }
1557            }
1558        }
1559        if dtm_z44.is_empty() {
1560            ConditionResult::Unknown
1561        } else {
1562            ConditionResult::True
1563        }
1564    }
1565
1566    /// [516] Hinweis: Wenn der Code 303 im DE2379 des Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) genutzt wird, muss genau ein Wert im DE2380 des Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) identisch mit dem We...
1567    // REVIEW: When format code 303 is used in DTM+Z44 DE2379, at least one DTM+Z44 DE2380 value must match a SG5 DTM+Z34 DE2380 value. Cross-group check; medium confidence due to SG5 context dependency. (medium confidence)
1568    fn evaluate_516(&self, ctx: &EvaluationContext) -> ConditionResult {
1569        let dtm_z44 = ctx.find_segments_with_qualifier("DTM", 0, "Z44");
1570        let dtm_z34 = ctx.find_segments_with_qualifier("DTM", 0, "Z34");
1571        let mut has_303 = false;
1572        let mut z34_values: std::collections::HashSet<String> = std::collections::HashSet::new();
1573        for seg in &dtm_z34 {
1574            let c507 = match seg.elements.first() {
1575                Some(e) => e,
1576                None => continue,
1577            };
1578            let value = c507.get(1).map(|s| s.clone()).unwrap_or_default();
1579            if !value.is_empty() {
1580                z34_values.insert(value);
1581            }
1582        }
1583        for seg in &dtm_z44 {
1584            let c507 = match seg.elements.first() {
1585                Some(e) => e,
1586                None => continue,
1587            };
1588            let format_code = c507.get(2).map(|s| s.as_str()).unwrap_or("");
1589            if format_code == "303" {
1590                has_303 = true;
1591                let value = c507.get(1).map(|s| s.as_str()).unwrap_or("");
1592                if z34_values.contains(value) {
1593                    return ConditionResult::True;
1594                }
1595            }
1596        }
1597        if has_303 {
1598            ConditionResult::False
1599        } else {
1600            ConditionResult::Unknown
1601        }
1602    }
1603
1604    /// [517] Hinweis: Wenn der Code 401 im DE2379 des Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) genutzt wird, muss genau ein Wert = 0000 im DE2380 des Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) sein
1605    fn evaluate_517(&self, ctx: &EvaluationContext) -> ConditionResult {
1606        let dtm_z44 = ctx.find_segments_with_qualifier("DTM", 0, "Z44");
1607        let mut has_401 = false;
1608        for seg in &dtm_z44 {
1609            let c507 = match seg.elements.first() {
1610                Some(e) => e,
1611                None => continue,
1612            };
1613            let format_code = c507.get(2).map(|s| s.as_str()).unwrap_or("");
1614            if format_code == "401" {
1615                has_401 = true;
1616                let value = c507.get(1).map(|s| s.as_str()).unwrap_or("");
1617                if value == "0000" {
1618                    return ConditionResult::True;
1619                }
1620            }
1621        }
1622        if has_401 {
1623            ConditionResult::False
1624        } else {
1625            ConditionResult::Unknown
1626        }
1627    }
1628
1629    /// [518] Hinweis: Für jeden Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) ist diese Sementgruppe einmal anzugeben
1630    fn evaluate_518(&self, _ctx: &EvaluationContext) -> ConditionResult {
1631        // Hinweis: Für jeden Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) ist diese Segmentgruppe einmal anzugeben — informational note, always applies
1632        ConditionResult::True
1633    }
1634
1635    /// [519] Hinweis: Kein Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) darf mehrfach vorkommen
1636    fn evaluate_519(&self, ctx: &EvaluationContext) -> ConditionResult {
1637        let dtm_z45 = ctx.find_segments_with_qualifier("DTM", 0, "Z45");
1638        let mut values = std::collections::HashSet::new();
1639        for seg in &dtm_z45 {
1640            let c507 = match seg.elements.first() {
1641                Some(e) => e,
1642                None => continue,
1643            };
1644            let value = c507.get(1).map(|s| s.as_str()).unwrap_or("");
1645            if !value.is_empty() {
1646                if !values.insert(value.to_string()) {
1647                    return ConditionResult::False;
1648                }
1649            }
1650        }
1651        if dtm_z45.is_empty() {
1652            ConditionResult::Unknown
1653        } else {
1654            ConditionResult::True
1655        }
1656    }
1657
1658    /// [520] Hinweis: Wenn der Code 303 im DE2379 des Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) genutzt wird, muss genau ein Wert im DE2380 des Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) identisch ...
1659    // REVIEW: When format code 303 is used in DTM+Z45 DE2379, at least one DTM+Z45 DE2380 value must match a SG5 DTM+Z34 DE2380 value. Same pattern as 516 but for Z45. (medium confidence)
1660    fn evaluate_520(&self, ctx: &EvaluationContext) -> ConditionResult {
1661        let dtm_z45 = ctx.find_segments_with_qualifier("DTM", 0, "Z45");
1662        let dtm_z34 = ctx.find_segments_with_qualifier("DTM", 0, "Z34");
1663        let mut has_303 = false;
1664        let mut z34_values: std::collections::HashSet<String> = std::collections::HashSet::new();
1665        for seg in &dtm_z34 {
1666            let c507 = match seg.elements.first() {
1667                Some(e) => e,
1668                None => continue,
1669            };
1670            let value = c507.get(1).map(|s| s.clone()).unwrap_or_default();
1671            if !value.is_empty() {
1672                z34_values.insert(value);
1673            }
1674        }
1675        for seg in &dtm_z45 {
1676            let c507 = match seg.elements.first() {
1677                Some(e) => e,
1678                None => continue,
1679            };
1680            let format_code = c507.get(2).map(|s| s.as_str()).unwrap_or("");
1681            if format_code == "303" {
1682                has_303 = true;
1683                let value = c507.get(1).map(|s| s.as_str()).unwrap_or("");
1684                if z34_values.contains(value) {
1685                    return ConditionResult::True;
1686                }
1687            }
1688        }
1689        if has_303 {
1690            ConditionResult::False
1691        } else {
1692            ConditionResult::Unknown
1693        }
1694    }
1695
1696    /// [521] Hinweis: Wenn der Code 401 im DE2379 des Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45)
1697    // REVIEW: Incomplete condition text but follows the same pattern as 517 (DTM+Z44/401/'0000') but for DTM+Z45. Medium confidence due to incomplete specification. (medium confidence)
1698    fn evaluate_521(&self, ctx: &EvaluationContext) -> ConditionResult {
1699        let dtm_z45 = ctx.find_segments_with_qualifier("DTM", 0, "Z45");
1700        let mut has_401 = false;
1701        for seg in &dtm_z45 {
1702            let c507 = match seg.elements.first() {
1703                Some(e) => e,
1704                None => continue,
1705            };
1706            let format_code = c507.get(2).map(|s| s.as_str()).unwrap_or("");
1707            if format_code == "401" {
1708                has_401 = true;
1709                let value = c507.get(1).map(|s| s.as_str()).unwrap_or("");
1710                if value == "0000" {
1711                    return ConditionResult::True;
1712                }
1713            }
1714        }
1715        if has_401 {
1716            ConditionResult::False
1717        } else {
1718            ConditionResult::Unknown
1719        }
1720    }
1721
1722    /// [522] Hinweis: Jede ausgerollte Schaltzeitdefinition ist in einem eigenen IDE anzugeben
1723    fn evaluate_522(&self, _ctx: &EvaluationContext) -> ConditionResult {
1724        // Hinweis: Jede ausgerollte Schaltzeitdefinition ist in einem eigenen IDE anzugeben — informational note, always applies
1725        ConditionResult::True
1726    }
1727
1728    /// [523] Hinweis: Jede ausgerollte Leistungskurvendefinition ist in einem eigenen IDE anzugeben
1729    fn evaluate_523(&self, _ctx: &EvaluationContext) -> ConditionResult {
1730        // Hinweis: Jede ausgerollte Leistungskurvendefinition ist in einem eigenen IDE anzugeben — informational note, always applies
1731        ConditionResult::True
1732    }
1733
1734    /// [524] Hinweis: Es ist der Code einer Zählzeitdefinition anzugeben
1735    fn evaluate_524(&self, _ctx: &EvaluationContext) -> ConditionResult {
1736        // Hinweis: Es ist der Code einer Zählzeitdefinition anzugeben — informational note, always applies
1737        ConditionResult::True
1738    }
1739
1740    /// [525] Hinweis: Es ist der Code einer Schaltzeitdefinition anzugeben
1741    fn evaluate_525(&self, _ctx: &EvaluationContext) -> ConditionResult {
1742        // Hinweis: Es ist der Code einer Schaltzeitdefinition anzugeben — informational note, always applies
1743        ConditionResult::True
1744    }
1745
1746    /// [526] Hinweis: Es ist der Code einer Leistungskurvendefinition anzugeben
1747    fn evaluate_526(&self, _ctx: &EvaluationContext) -> ConditionResult {
1748        // Hinweis: Es ist der Code einer Leistungskurvendefinition anzugeben — informational note, always applies
1749        ConditionResult::True
1750    }
1751
1752    /// [527] Hinweis: Dieser Code ist anzugeben, wenn es sich um eine einmalig zu übermittelnde Definition handelt
1753    fn evaluate_527(&self, _ctx: &EvaluationContext) -> ConditionResult {
1754        // Hinweis: Dieser Code ist anzugeben, wenn es sich um eine einmalig zu übermittelnde Definition handelt — informational note, always applies
1755        ConditionResult::True
1756    }
1757
1758    /// [528] Hinweis: Dieser Code ist anzugeben, wenn es sich um eine jährlich zu übermittelnde Definition handelt
1759    fn evaluate_528(&self, _ctx: &EvaluationContext) -> ConditionResult {
1760        // Hinweis: Dieser Code ist anzugeben, wenn es sich um eine jährlich zu übermittelnde Definition handelt — informational note, always applies
1761        ConditionResult::True
1762    }
1763
1764    /// [529] Hinweis: Verwendung der ID der Netzlokation
1765    fn evaluate_529(&self, _ctx: &EvaluationContext) -> ConditionResult {
1766        // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
1767        ConditionResult::True
1768    }
1769
1770    /// [530] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
1771    fn evaluate_530(&self, _ctx: &EvaluationContext) -> ConditionResult {
1772        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
1773        ConditionResult::True
1774    }
1775
1776    /// [531] Hinweis: Für weitere Details siehe Kapitel 4.1 "Übermittlung einer Vielzahl von Berechnungsformeln in einem Vorgang"
1777    fn evaluate_531(&self, _ctx: &EvaluationContext) -> ConditionResult {
1778        // Hinweis: Für weitere Details siehe Kapitel 4.1 "Übermittlung einer Vielzahl von Berechnungsformeln in einem Vorgang" — informational note, always applies
1779        ConditionResult::True
1780    }
1781
1782    /// [532] Hinweis: Es ist die Zeitraum-ID vom DE1156 aus einem passenden SG6 RFF+Z49/Z53 (Verwendungszeitraum der Daten: "Gültige Daten", "Keine Daten") aus der Übermittlung der Berechnungsformel aus SG6 R...
1783    // REVIEW: Cross-group Zeitraum-ID matching: collect DE1156 values from SG6 RFF+Z49/Z53 segments, then verify that SG8 RFF+Z46 references (DE1154) point to one of those IDs. Medium confidence because the condition is a hint about correct data linkage across groups, and the exact scoping (per-SG5/SG6 instance vs message-wide) may require navigator-based logic for full correctness. (medium confidence)
1784    fn evaluate_532(&self, ctx: &EvaluationContext) -> ConditionResult {
1785        {
1786            // Collect Zeitraum-IDs (DE1156 = elements[0][2]) from SG6 RFF+Z49 and RFF+Z53
1787            let rff_segments = ctx.find_segments("RFF");
1788            let zeitraum_ids: Vec<String> = rff_segments
1789                .iter()
1790                .filter(|s| {
1791                    s.elements
1792                        .first()
1793                        .and_then(|e| e.first())
1794                        .map(|q| q == "Z49" || q == "Z53")
1795                        .unwrap_or(false)
1796                })
1797                .filter_map(|s| {
1798                    s.elements
1799                        .first()
1800                        .and_then(|e| e.get(2))
1801                        .filter(|v| !v.is_empty())
1802                        .cloned()
1803                })
1804                .collect();
1805
1806            if zeitraum_ids.is_empty() {
1807                return ConditionResult::Unknown;
1808            }
1809
1810            // Check SG8 RFF+Z46 references (DE1154 = elements[0][1]) against collected Zeitraum-IDs
1811            let rff_z46_segments = ctx.find_segments_with_qualifier("RFF", 0, "Z46");
1812            if rff_z46_segments.is_empty() {
1813                return ConditionResult::Unknown;
1814            }
1815
1816            let any_match = rff_z46_segments.iter().any(|s| {
1817                s.elements
1818                    .first()
1819                    .and_then(|e| e.get(1))
1820                    .map(|ref_id| zeitraum_ids.iter().any(|zid| zid == ref_id))
1821                    .unwrap_or(false)
1822            });
1823
1824            ConditionResult::from(any_match)
1825        }
1826    }
1827
1828    /// [533] Hinweis: Für jeden übermittelten Zeitraum aus der Übermittlung der Berechnungsformel ist genau einmal das Segement anzugeben
1829    fn evaluate_533(&self, _ctx: &EvaluationContext) -> ConditionResult {
1830        // Hinweis: Für jeden übermittelten Zeitraum aus der Übermittlung der Berechnungsformel ist genau einmal das Segment anzugeben — informational note, always applies
1831        ConditionResult::True
1832    }
1833
1834    /// [534] Hinweis: Wert aus SG5 IDE+24 DE7402 mit der die Übermitt-lung der Berechnungsformel erfolgt ist.
1835    fn evaluate_534(&self, _ctx: &EvaluationContext) -> ConditionResult {
1836        // Hinweis: Wert aus SG5 IDE+24 DE7402 — value comes from the Vorgangsnummer of the Berechnungsformel transmission; informational note, always applies
1837        ConditionResult::True
1838    }
1839
1840    /// [912] Format: Wert kann mit maximal 6 Nachkommastellen angegeben werden
1841    // REVIEW: Format condition: max 6 decimal places. Applies to a numeric quantity value; QTY is the standard numeric segment in UTILTS. The AHB row context is not in the structure reference but QTY.elements[0][1] is the standard value position. (medium confidence)
1842    fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
1843        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 6))
1844    }
1845
1846    /// [913] Format: Mögliche Werte: 1 bis 99999
1847    // REVIEW: Format condition: value must be in range [1, 99999]. Both bounds checked with validate_numeric and combined with AND logic. Applied to QTY value element as the most common numeric data element in UTILTS. (medium confidence)
1848    fn evaluate_913(&self, ctx: &EvaluationContext) -> ConditionResult {
1849        // Format: Mögliche Werte: 1 bis 99999
1850        let segs = ctx.find_segments("QTY");
1851        match segs
1852            .first()
1853            .and_then(|s| s.elements.first())
1854            .and_then(|e| e.get(1))
1855        {
1856            Some(val) => {
1857                let ge1 = validate_numeric(val, ">=", 1.0);
1858                let le99999 = validate_numeric(val, "<=", 99999.0);
1859                match (ge1, le99999) {
1860                    (ConditionResult::True, ConditionResult::True) => ConditionResult::True,
1861                    (ConditionResult::False, _) | (_, ConditionResult::False) => {
1862                        ConditionResult::False
1863                    }
1864                    _ => ConditionResult::Unknown,
1865                }
1866            }
1867            None => ConditionResult::False, // segment absent → condition not applicable
1868        }
1869    }
1870
1871    /// [914] Format: Möglicher Wert: &gt; 0
1872    // REVIEW: Format condition: value must be strictly greater than 0. Applied to QTY value element; validate_numeric with '>' operator handles this directly. (medium confidence)
1873    fn evaluate_914(&self, ctx: &EvaluationContext) -> ConditionResult {
1874        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">", 0.0))
1875    }
1876
1877    /// [915] Format: Möglicher Wert: ≠ 1
1878    // REVIEW: Format condition: value must not equal 1. Applied to the QTY quantity value (element[0][1]) as this is the standard numeric data element in UTILTS. Medium confidence because the exact segment/element this applies to is inferred from context rather than stated explicitly. (medium confidence)
1879    fn evaluate_915(&self, ctx: &EvaluationContext) -> ConditionResult {
1880        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "!=", 1.0))
1881    }
1882
1883    /// [930] Format: max. 2 Nachkommastellen
1884    fn evaluate_930(&self, ctx: &EvaluationContext) -> ConditionResult {
1885        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 2))
1886    }
1887
1888    /// [931] Format: ZZZ = +00
1889    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1890        ctx.format_check("DTM", 0, 1, validate_timezone_utc)
1891    }
1892
1893    /// [932] Format: HHMM = 2200
1894    fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
1895        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2200"))
1896    }
1897
1898    /// [933] Format: HHMM = 2300
1899    fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
1900        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2300"))
1901    }
1902
1903    /// [937] Format: keine Nachkommastelle
1904    fn evaluate_937(&self, ctx: &EvaluationContext) -> ConditionResult {
1905        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 0))
1906    }
1907
1908    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
1909    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
1910        ctx.format_check("COM", 0, 0, validate_email)
1911    }
1912
1913    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
1914    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
1915        ctx.format_check("COM", 0, 0, validate_phone)
1916    }
1917
1918    /// [947] Format: MMDDHHMM = 12312300
1919    fn evaluate_947(&self, ctx: &EvaluationContext) -> ConditionResult {
1920        ctx.format_check("DTM", 0, 1, |val| validate_mmddhhmm_equals(val, "12312300"))
1921    }
1922
1923    /// [950] Format: Marktlokations-ID
1924    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
1925        ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
1926    }
1927
1928    /// [951] Format: Zählpunktbezeichnung
1929    // REVIEW: Zählpunktbezeichnung (metering point designation) is a 33-character alphanumeric ID. The validate_zahlpunkt helper checks this format. The segment is typically LOC with a metering point qualifier (Z17 for Messlokation or Z19 for SteuerbareRessource in UTILTS). Using both as fallback. (medium confidence)
1930    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
1931        ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_zahlpunkt)
1932    }
1933
1934    /// [960] Format: Netzlokations-ID
1935    // HAND-EDITED: a Netzlokations-ID is "E" + ten alphanumerics (`E1688117482`),
1936    // not MaLo-shaped; `validate_malo_id` rejected every real one (#173).
1937    fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
1938        ctx.format_check_qualified("LOC", 0, "Z18", 1, 0, validate_nelo_id)
1939    }
1940
1941    /// [963] Format: Möglicher Wert: ≤ 100
1942    // REVIEW: Format condition: value must be <= 100. Applied to QTY segment value (element 0, component 1). Uses validate_numeric helper. (medium confidence)
1943    fn evaluate_963(&self, ctx: &EvaluationContext) -> ConditionResult {
1944        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "<=", 100.0))
1945    }
1946
1947    /// [964] Format: HHMM ≥ 0000
1948    // REVIEW: Format condition: HHMM >= 0000. Combined with condition 965 (HHMM <= 2359), together they validate a valid time range. Using validate_hhmm_range covering both bounds. Applied to DTM segment time value. (medium confidence)
1949    fn evaluate_964(&self, ctx: &EvaluationContext) -> ConditionResult {
1950        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_range(val, "0000", "2359"))
1951    }
1952
1953    /// [965] Format: HHMM ≤ 2359
1954    // REVIEW: Format condition: HHMM <= 2359. Paired with condition 964 (HHMM >= 0000). Together they validate a valid HHMM time. Using validate_hhmm_range for both bounds on the DTM segment. (medium confidence)
1955    fn evaluate_965(&self, ctx: &EvaluationContext) -> ConditionResult {
1956        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_range(val, "0000", "2359"))
1957    }
1958
1959    /// [969] Format: Möglicher Wer: ≤ 1
1960    // REVIEW: 900-series format condition: 'Möglicher Wert: ≤ 1' means the value must be <= 1. Applied to QTY quantity value (most common numeric value in UTILTS). validate_numeric with "<=" operator handles this. Medium confidence because the exact target segment is inferred from context. (medium confidence)
1961    fn evaluate_969(&self, ctx: &EvaluationContext) -> ConditionResult {
1962        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "<=", 1.0))
1963    }
1964
1965    /// [2001] Segment bzw. Segmentgruppe ist genau einmal anzugeben
1966    fn evaluate_2001(&self, _ctx: &EvaluationContext) -> ConditionResult {
1967        // Hinweis: Segment bzw. Segmentgruppe ist genau einmal anzugeben — informational cardinality note, always applies
1968        ConditionResult::True
1969    }
1970
1971    /// [2002] Für jeden Code der Zählzeit aus SG8 SEQ+Z42 (Zählzeitdefinition) SG9 CCI+Z39 (Code der Zählzeitdefinition) sind mindestens zwei Register anzugeben, bei denen in dieser SG8 das SG8 RFF+Z27 mit d...
1972    // REVIEW: For each CCI+Z39 code (Zählzeitdefinition code, elements[2][0]) found in SG9 groups, at least 2 SG8 instances must have RFF+Z27 (elements[0][0]=Z27, elements[0][1]=code value) matching that code. Uses message-wide scan since navigator API does not expose a simple find-in-group method for non-child groups. (medium confidence)
1973    fn evaluate_2002(&self, ctx: &EvaluationContext) -> ConditionResult {
1974        // Collect CCI+Z39 codes from SG9 (Code der Zählzeitdefinition) message-wide
1975        let cci_segments = ctx.find_segments("CCI");
1976        let codes: Vec<String> = cci_segments
1977            .iter()
1978            .filter(|s| {
1979                s.elements
1980                    .first()
1981                    .and_then(|e| e.first())
1982                    .is_some_and(|v| v == "Z39")
1983            })
1984            .filter_map(|s| s.elements.get(2).and_then(|e| e.first()).cloned())
1985            .filter(|c| !c.is_empty())
1986            .collect();
1987        if codes.is_empty() {
1988            return ConditionResult::Unknown;
1989        }
1990        // For each code, at least 2 SG8 instances must have RFF+Z27 referencing that code
1991        let rff_z27 = ctx.find_segments_with_qualifier("RFF", 0, "Z27");
1992        for code in &codes {
1993            let count = rff_z27
1994                .iter()
1995                .filter(|s| {
1996                    s.elements
1997                        .first()
1998                        .and_then(|e| e.get(1))
1999                        .is_some_and(|v| v == code)
2000                })
2001                .count();
2002            if count < 2 {
2003                return ConditionResult::False;
2004            }
2005        }
2006        ConditionResult::True
2007    }
2008
2009    /// [2004] Segment ist genau einmal für jede Zeitraum-ID aus dem DE1156 der SG6 RFF+Z49 (Verwendungszeitraum der Daten: "Gültige Daten") anzugeben
2010    // REVIEW: For each Zeitraum-ID from SG6 RFF+Z49 DE1156 (elements[0][2]), exactly one SG5 STS must reference it. STS+E01 (Status der Antwort) stores Zeitraum-ID at elements[2][3] (DE9012). STS+Z23 (Status der Berechnungsformel) stores it at elements[2][0] (DE9013). Checks count == 1 for each Zeitraum-ID. (medium confidence)
2011    fn evaluate_2004(&self, ctx: &EvaluationContext) -> ConditionResult {
2012        // Collect Zeitraum-IDs from SG6 RFF+Z49 DE1156 (elements[0][2])
2013        let rff_z49 = ctx.find_segments_with_qualifier("RFF", 0, "Z49");
2014        if rff_z49.is_empty() {
2015            return ConditionResult::Unknown;
2016        }
2017        let zeitraum_ids: Vec<String> = rff_z49
2018            .iter()
2019            .filter_map(|s| s.elements.first().and_then(|e| e.get(2)).cloned())
2020            .filter(|id| !id.is_empty())
2021            .collect();
2022        if zeitraum_ids.is_empty() {
2023            return ConditionResult::Unknown;
2024        }
2025        let sts_segments = ctx.find_segments("STS");
2026        for zid in &zeitraum_ids {
2027            // STS+E01: Zeitraum-ID at elements[2][3] (DE9012)
2028            // STS+Z23: Zeitraum-ID at elements[2][0] (DE9013)
2029            let count = sts_segments
2030                .iter()
2031                .filter(|s| {
2032                    let qual = s
2033                        .elements
2034                        .first()
2035                        .and_then(|e| e.first())
2036                        .map(|v| v.as_str())
2037                        .unwrap_or("");
2038                    match qual {
2039                        "E01" => s
2040                            .elements
2041                            .get(2)
2042                            .and_then(|e| e.get(3))
2043                            .is_some_and(|v| v == zid),
2044                        "Z23" => s
2045                            .elements
2046                            .get(2)
2047                            .and_then(|e| e.first())
2048                            .is_some_and(|v| v == zid),
2049                        _ => false,
2050                    }
2051                })
2052                .count();
2053            if count != 1 {
2054                return ConditionResult::False;
2055            }
2056        }
2057        ConditionResult::True
2058    }
2059
2060    /// [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 ("Sontiges") enthalten ist
2061    // REVIEW: Finds STS+E01 segments where DE9013 (Code des Prüfschritts, elements[2][0]) = 'A99' (Sonstiges), then collects Zeitraum-IDs from DE9012 (elements[2][3]). For each such ID, checks that exactly one SG8 RFF+Z46 (elements[0][1]) references it, confirming the annotated segment appears exactly once per qualifying Zeitraum-ID. (medium confidence)
2062    fn evaluate_2005(&self, ctx: &EvaluationContext) -> ConditionResult {
2063        // Collect Zeitraum-IDs from STS+E01 where DE9013 (elements[2][0]) = A99 (Sonstiges)
2064        let sts_e01 = ctx.find_segments_with_qualifier("STS", 0, "E01");
2065        let zeitraum_ids: Vec<String> = sts_e01
2066            .iter()
2067            .filter(|s| {
2068                s.elements
2069                    .get(2)
2070                    .and_then(|e| e.first())
2071                    .is_some_and(|v| v == "A99")
2072            })
2073            .filter_map(|s| s.elements.get(2).and_then(|e| e.get(3)).cloned())
2074            .filter(|id| !id.is_empty())
2075            .collect();
2076        if zeitraum_ids.is_empty() {
2077            return ConditionResult::Unknown;
2078        }
2079        // For each Zeitraum-ID, exactly one SG8 RFF+Z46 (Referenz auf Zeitraum-ID) must reference it
2080        let rff_z46 = ctx.find_segments_with_qualifier("RFF", 0, "Z46");
2081        for zid in &zeitraum_ids {
2082            let count = rff_z46
2083                .iter()
2084                .filter(|s| {
2085                    s.elements
2086                        .first()
2087                        .and_then(|e| e.get(1))
2088                        .is_some_and(|v| v == zid)
2089                })
2090                .count();
2091            if count != 1 {
2092                return ConditionResult::False;
2093            }
2094        }
2095        ConditionResult::True
2096    }
2097
2098    /// [2006] Segmentgruppe ist mindestens einmal für jede Zeitraum-ID aus dem DE9013 der SG5 STS+Z23+Z33 (Berechnungsformel angefügt) anzugeben
2099    // REVIEW: Finds STS+Z23 (Status der Berechnungsformel) where status code (elements[1][0]) = 'Z33' (formula attached), collects Zeitraum-IDs from DE9013 (elements[2][0]). For each such ID, checks that at least one SG8 RFF+Z46 (elements[0][1]) references it, confirming the calculation formula group appears at minimum once per qualifying Zeitraum-ID. (medium confidence)
2100    fn evaluate_2006(&self, ctx: &EvaluationContext) -> ConditionResult {
2101        // Collect Zeitraum-IDs from STS+Z23 (Berechnungsformel) where status code = Z33 (angefügt)
2102        let sts_z23 = ctx.find_segments_with_qualifier("STS", 0, "Z23");
2103        let zeitraum_ids: Vec<String> = sts_z23
2104            .iter()
2105            .filter(|s| {
2106                s.elements
2107                    .get(1)
2108                    .and_then(|e| e.first())
2109                    .is_some_and(|v| v == "Z33")
2110            })
2111            .filter_map(|s| s.elements.get(2).and_then(|e| e.first()).cloned())
2112            .filter(|id| !id.is_empty())
2113            .collect();
2114        if zeitraum_ids.is_empty() {
2115            return ConditionResult::Unknown;
2116        }
2117        // For each Zeitraum-ID, at least one SG8 RFF+Z46 must reference it
2118        let rff_z46 = ctx.find_segments_with_qualifier("RFF", 0, "Z46");
2119        for zid in &zeitraum_ids {
2120            let count = rff_z46
2121                .iter()
2122                .filter(|s| {
2123                    s.elements
2124                        .first()
2125                        .and_then(|e| e.get(1))
2126                        .is_some_and(|v| v == zid)
2127                })
2128                .count();
2129            if count < 1 {
2130                return ConditionResult::False;
2131            }
2132        }
2133        ConditionResult::True
2134    }
2135
2136    /// [2007] Segmentgruppe ist genau einmal für jede Zeitraum-ID aus dem DE9013 der SG5 STS+Z23+Z33 (Berechnungsformel angefügt) anzugeben
2137    // REVIEW: The condition says the segment group must appear exactly once for each Zeitraum-ID from SG5 STS+Z23+Z33 (Berechnungsformel angefügt). From the MIG reference, STS with Statuskategorie Z23 has elements[0][0]=Z23, elements[1][0]=status code (Z33=angefügt), and elements[2][0]=Zeitraum-ID. The evaluator returns True when at least one such STS+Z23+Z33 is present in the message, which is when the cardinality rule triggers. Full cardinality counting (exactly one group per Zeitraum-ID) would require navigator-level group instance counting and cross-referencing, which goes beyond what the boolean ConditionResult can express — the condition is really a presence trigger for the group requirement. (medium confidence)
2138    fn evaluate_2007(&self, ctx: &EvaluationContext) -> ConditionResult {
2139        // Condition 2007: Segment group required exactly once per Zeitraum-ID from SG5 STS+Z23+Z33
2140        // Evaluates to True when at least one STS in SG5 has Statuskategorie=Z23 and Status=Z33
2141        // (Berechnungsformel angefügt), indicating the group must be present for that Zeitraum-ID.
2142        // STS Z23 structure: elements[0][0]=Z23 (Statuskategorie), elements[1][0]=Z33 (Status), elements[2][0]=Zeitraum-ID
2143        let sts_segments = ctx.find_segments("STS");
2144        let has_z23_z33 = sts_segments.iter().any(|s| {
2145            s.elements
2146                .first()
2147                .and_then(|e| e.first())
2148                .is_some_and(|v| v == "Z23")
2149                && s.elements
2150                    .get(1)
2151                    .and_then(|e| e.first())
2152                    .is_some_and(|v| v == "Z33")
2153        });
2154        ConditionResult::from(has_z23_z33)
2155    }
2156}