Skip to main content

automapper_validation/generated/fv2604/
mscons_conditions_fv2604.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2604/MSCONS_AHB_3_1g_20251001.xml
4// Generated: 2026-03-12T11:36:06Z
5// </auto-generated>
6
7#[allow(unused_imports)]
8use crate::eval::format_validators::*;
9use crate::eval::{ConditionEvaluator, ConditionResult, EvaluationContext};
10
11/// Generated condition evaluator for MSCONS FV2604.
12pub struct MsconsConditionEvaluatorFV2604 {
13    // External condition IDs that require runtime context.
14    external_conditions: std::collections::HashSet<u32>,
15}
16
17impl Default for MsconsConditionEvaluatorFV2604 {
18    fn default() -> Self {
19        let mut external_conditions = std::collections::HashSet::new();
20        external_conditions.insert(1);
21        external_conditions.insert(22);
22        external_conditions.insert(32);
23        external_conditions.insert(33);
24        external_conditions.insert(35);
25        external_conditions.insert(36);
26        external_conditions.insert(42);
27        external_conditions.insert(67);
28        external_conditions.insert(77);
29        external_conditions.insert(80);
30        external_conditions.insert(81);
31        external_conditions.insert(97);
32        external_conditions.insert(117);
33        external_conditions.insert(118);
34        external_conditions.insert(126);
35        external_conditions.insert(127);
36        external_conditions.insert(138);
37        external_conditions.insert(141);
38        external_conditions.insert(492);
39        external_conditions.insert(493);
40        Self {
41            external_conditions,
42        }
43    }
44}
45
46impl ConditionEvaluator for MsconsConditionEvaluatorFV2604 {
47    fn message_type(&self) -> &str {
48        "MSCONS"
49    }
50
51    fn format_version(&self) -> &str {
52        "FV2604"
53    }
54
55    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
56        match condition {
57            1 => self.evaluate_1(ctx),
58            2 => self.evaluate_2(ctx),
59            11 => self.evaluate_11(ctx),
60            12 => self.evaluate_12(ctx),
61            17 => self.evaluate_17(ctx),
62            18 => self.evaluate_18(ctx),
63            22 => self.evaluate_22(ctx),
64            23 => self.evaluate_23(ctx),
65            24 => self.evaluate_24(ctx),
66            27 => self.evaluate_27(ctx),
67            28 => self.evaluate_28(ctx),
68            32 => self.evaluate_32(ctx),
69            33 => self.evaluate_33(ctx),
70            35 => self.evaluate_35(ctx),
71            36 => self.evaluate_36(ctx),
72            38 => self.evaluate_38(ctx),
73            42 => self.evaluate_42(ctx),
74            45 => self.evaluate_45(ctx),
75            46 => self.evaluate_46(ctx),
76            48 => self.evaluate_48(ctx),
77            49 => self.evaluate_49(ctx),
78            50 => self.evaluate_50(ctx),
79            51 => self.evaluate_51(ctx),
80            62 => self.evaluate_62(ctx),
81            67 => self.evaluate_67(ctx),
82            68 => self.evaluate_68(ctx),
83            69 => self.evaluate_69(ctx),
84            70 => self.evaluate_70(ctx),
85            71 => self.evaluate_71(ctx),
86            72 => self.evaluate_72(ctx),
87            73 => self.evaluate_73(ctx),
88            77 => self.evaluate_77(ctx),
89            78 => self.evaluate_78(ctx),
90            79 => self.evaluate_79(ctx),
91            80 => self.evaluate_80(ctx),
92            81 => self.evaluate_81(ctx),
93            83 => self.evaluate_83(ctx),
94            84 => self.evaluate_84(ctx),
95            85 => self.evaluate_85(ctx),
96            86 => self.evaluate_86(ctx),
97            87 => self.evaluate_87(ctx),
98            88 => self.evaluate_88(ctx),
99            90 => self.evaluate_90(ctx),
100            91 => self.evaluate_91(ctx),
101            92 => self.evaluate_92(ctx),
102            93 => self.evaluate_93(ctx),
103            94 => self.evaluate_94(ctx),
104            95 => self.evaluate_95(ctx),
105            96 => self.evaluate_96(ctx),
106            97 => self.evaluate_97(ctx),
107            98 => self.evaluate_98(ctx),
108            99 => self.evaluate_99(ctx),
109            100 => self.evaluate_100(ctx),
110            101 => self.evaluate_101(ctx),
111            108 => self.evaluate_108(ctx),
112            111 => self.evaluate_111(ctx),
113            113 => self.evaluate_113(ctx),
114            117 => self.evaluate_117(ctx),
115            118 => self.evaluate_118(ctx),
116            119 => self.evaluate_119(ctx),
117            121 => self.evaluate_121(ctx),
118            125 => self.evaluate_125(ctx),
119            126 => self.evaluate_126(ctx),
120            127 => self.evaluate_127(ctx),
121            128 => self.evaluate_128(ctx),
122            129 => self.evaluate_129(ctx),
123            130 => self.evaluate_130(ctx),
124            131 => self.evaluate_131(ctx),
125            132 => self.evaluate_132(ctx),
126            133 => self.evaluate_133(ctx),
127            134 => self.evaluate_134(ctx),
128            135 => self.evaluate_135(ctx),
129            138 => self.evaluate_138(ctx),
130            141 => self.evaluate_141(ctx),
131            142 => self.evaluate_142(ctx),
132            143 => self.evaluate_143(ctx),
133            144 => self.evaluate_144(ctx),
134            145 => self.evaluate_145(ctx),
135            146 => self.evaluate_146(ctx),
136            147 => self.evaluate_147(ctx),
137            148 => self.evaluate_148(ctx),
138            149 => self.evaluate_149(ctx),
139            150 => self.evaluate_150(ctx),
140            151 => self.evaluate_151(ctx),
141            152 => self.evaluate_152(ctx),
142            153 => self.evaluate_153(ctx),
143            154 => self.evaluate_154(ctx),
144            490 => self.evaluate_490(ctx),
145            491 => self.evaluate_491(ctx),
146            492 => self.evaluate_492(ctx),
147            493 => self.evaluate_493(ctx),
148            494 => self.evaluate_494(ctx),
149            495 => self.evaluate_495(ctx),
150            501 => self.evaluate_501(ctx),
151            502 => self.evaluate_502(ctx),
152            506 => self.evaluate_506(ctx),
153            509 => self.evaluate_509(ctx),
154            510 => self.evaluate_510(ctx),
155            511 => self.evaluate_511(ctx),
156            512 => self.evaluate_512(ctx),
157            513 => self.evaluate_513(ctx),
158            514 => self.evaluate_514(ctx),
159            515 => self.evaluate_515(ctx),
160            516 => self.evaluate_516(ctx),
161            518 => self.evaluate_518(ctx),
162            519 => self.evaluate_519(ctx),
163            520 => self.evaluate_520(ctx),
164            522 => self.evaluate_522(ctx),
165            523 => self.evaluate_523(ctx),
166            524 => self.evaluate_524(ctx),
167            525 => self.evaluate_525(ctx),
168            526 => self.evaluate_526(ctx),
169            528 => self.evaluate_528(ctx),
170            529 => self.evaluate_529(ctx),
171            530 => self.evaluate_530(ctx),
172            531 => self.evaluate_531(ctx),
173            532 => self.evaluate_532(ctx),
174            535 => self.evaluate_535(ctx),
175            538 => self.evaluate_538(ctx),
176            541 => self.evaluate_541(ctx),
177            543 => self.evaluate_543(ctx),
178            544 => self.evaluate_544(ctx),
179            545 => self.evaluate_545(ctx),
180            546 => self.evaluate_546(ctx),
181            547 => self.evaluate_547(ctx),
182            551 => self.evaluate_551(ctx),
183            553 => self.evaluate_553(ctx),
184            554 => self.evaluate_554(ctx),
185            556 => self.evaluate_556(ctx),
186            557 => self.evaluate_557(ctx),
187            558 => self.evaluate_558(ctx),
188            559 => self.evaluate_559(ctx),
189            560 => self.evaluate_560(ctx),
190            566 => self.evaluate_566(ctx),
191            567 => self.evaluate_567(ctx),
192            568 => self.evaluate_568(ctx),
193            569 => self.evaluate_569(ctx),
194            570 => self.evaluate_570(ctx),
195            571 => self.evaluate_571(ctx),
196            572 => self.evaluate_572(ctx),
197            573 => self.evaluate_573(ctx),
198            574 => self.evaluate_574(ctx),
199            575 => self.evaluate_575(ctx),
200            576 => self.evaluate_576(ctx),
201            577 => self.evaluate_577(ctx),
202            578 => self.evaluate_578(ctx),
203            579 => self.evaluate_579(ctx),
204            580 => self.evaluate_580(ctx),
205            581 => self.evaluate_581(ctx),
206            902 => self.evaluate_902(ctx),
207            904 => self.evaluate_904(ctx),
208            905 => self.evaluate_905(ctx),
209            906 => self.evaluate_906(ctx),
210            907 => self.evaluate_907(ctx),
211            908 => self.evaluate_908(ctx),
212            909 => self.evaluate_909(ctx),
213            910 => self.evaluate_910(ctx),
214            917 => self.evaluate_917(ctx),
215            918 => self.evaluate_918(ctx),
216            922 => self.evaluate_922(ctx),
217            925 => self.evaluate_925(ctx),
218            931 => self.evaluate_931(ctx),
219            932 => self.evaluate_932(ctx),
220            933 => self.evaluate_933(ctx),
221            934 => self.evaluate_934(ctx),
222            935 => self.evaluate_935(ctx),
223            937 => self.evaluate_937(ctx),
224            939 => self.evaluate_939(ctx),
225            940 => self.evaluate_940(ctx),
226            950 => self.evaluate_950(ctx),
227            951 => self.evaluate_951(ctx),
228            960 => self.evaluate_960(ctx),
229            2001 => self.evaluate_2001(ctx),
230            2002 => self.evaluate_2002(ctx),
231            2003 => self.evaluate_2003(ctx),
232            _ => ConditionResult::Unknown,
233        }
234    }
235
236    fn is_external(&self, condition: u32) -> bool {
237        self.external_conditions.contains(&condition)
238    }
239    fn is_known(&self, condition: u32) -> bool {
240        matches!(
241            condition,
242            1 | 2
243                | 11
244                | 12
245                | 17
246                | 18
247                | 22
248                | 23
249                | 24
250                | 27
251                | 28
252                | 32
253                | 33
254                | 35
255                | 36
256                | 38
257                | 42
258                | 45
259                | 46
260                | 48
261                | 49
262                | 50
263                | 51
264                | 62
265                | 67
266                | 68
267                | 69
268                | 70
269                | 71
270                | 72
271                | 73
272                | 77
273                | 78
274                | 79
275                | 80
276                | 81
277                | 83
278                | 84
279                | 85
280                | 86
281                | 87
282                | 88
283                | 90
284                | 91
285                | 92
286                | 93
287                | 94
288                | 95
289                | 96
290                | 97
291                | 98
292                | 99
293                | 100
294                | 101
295                | 108
296                | 111
297                | 113
298                | 117
299                | 118
300                | 119
301                | 121
302                | 125
303                | 126
304                | 127
305                | 128
306                | 129
307                | 130
308                | 131
309                | 132
310                | 133
311                | 134
312                | 135
313                | 138
314                | 141
315                | 142
316                | 143
317                | 144
318                | 145
319                | 146
320                | 147
321                | 148
322                | 149
323                | 150
324                | 151
325                | 152
326                | 153
327                | 154
328                | 490
329                | 491
330                | 492
331                | 493
332                | 494
333                | 495
334                | 501
335                | 502
336                | 506
337                | 509
338                | 510
339                | 511
340                | 512
341                | 513
342                | 514
343                | 515
344                | 516
345                | 518
346                | 519
347                | 520
348                | 522
349                | 523
350                | 524
351                | 525
352                | 526
353                | 528
354                | 529
355                | 530
356                | 531
357                | 532
358                | 535
359                | 538
360                | 541
361                | 543
362                | 544
363                | 545
364                | 546
365                | 547
366                | 551
367                | 553
368                | 554
369                | 556
370                | 557
371                | 558
372                | 559
373                | 560
374                | 566
375                | 567
376                | 568
377                | 569
378                | 570
379                | 571
380                | 572
381                | 573
382                | 574
383                | 575
384                | 576
385                | 577
386                | 578
387                | 579
388                | 580
389                | 581
390                | 902
391                | 904
392                | 905
393                | 906
394                | 907
395                | 908
396                | 909
397                | 910
398                | 917
399                | 918
400                | 922
401                | 925
402                | 931
403                | 932
404                | 933
405                | 934
406                | 935
407                | 937
408                | 939
409                | 940
410                | 950
411                | 951
412                | 960
413                | 2001
414                | 2002
415                | 2003
416        )
417    }
418}
419
420impl MsconsConditionEvaluatorFV2604 {
421    /// [23] Wenn UNH DE0070 mit 1 vorhanden
422    fn evaluate_23(&self, ctx: &EvaluationContext) -> ConditionResult {
423        let segs = ctx.find_segments("UNH");
424        match segs.first() {
425            Some(unh) => match unh.elements.get(3).and_then(|e| e.first()) {
426                Some(val) => ConditionResult::from(val == "1"),
427                None => ConditionResult::False,
428            },
429            None => ConditionResult::False, // segment absent → condition not applicable
430        }
431    }
432
433    /// [24] Bei Aufteilung, in der Nachricht mit der höchsten Übermittlungsnummer
434    // REVIEW: "Bei Aufteilung" = UNH S010 is present (splitting indicator exists). "höchste Übermittlungsnummer" = the last message in the sequence, identified by DE0073 (elements[3][1]) == "F" (final). For non-split messages S010 is absent → False. Confidence medium because EDIFACT S010 usage in MSCONS may vary. (medium confidence)
435    fn evaluate_24(&self, ctx: &EvaluationContext) -> ConditionResult {
436        let unh_segs = ctx.find_segments("UNH");
437        match unh_segs.first() {
438            Some(unh) => {
439                match unh.elements.get(3) {
440                    Some(s010) => {
441                        // S010 present = splitting in use.
442                        // DE0073 (components[1]) == "F" = final/last message in split sequence
443                        let is_final = s010.get(1).is_some_and(|v| v == "F");
444                        ConditionResult::from(is_final)
445                    }
446                    None => ConditionResult::False, // No S010 = no splitting
447                }
448            }
449            None => ConditionResult::False, // segment absent → condition not applicable
450        }
451    }
452
453    /// [130] Wenn innerhalb desselben LIN-Segments neben diesem Segment (SG10 DTM+7 Nutzungszeitpunkt) noch das SG10 DTM+60 (Ausführungs- / Änderungszeitpunkt) oder das SG10 DTM+9 (Ablesedatum) vorhanden, dar...
454    // REVIEW: Iterates each SG10 instance (child of SG6 in SG5). For each SG10 containing DTM+7, checks co-located DTM+60 or DTM+9. Parses CCYYMMDDHHMM via Julian Day Number to total minutes, computes absolute difference, then selects threshold based on DST transition: MEZ→MESZ (winter→summer) = 23h, MESZ→MEZ (summer→winter) = 25h, no transition = 24h. Uses is_mesz_utc on the earlier and later timestamps to detect the transition direction. Group path SG5→SG6→SG10 assumed from MSCONS structure; medium confidence because SG path and S010 semantics need verification. (medium confidence)
455    fn evaluate_130(&self, ctx: &EvaluationContext) -> ConditionResult {
456        fn parse_to_minutes(s: &str) -> Option<i64> {
457            let s = if s.len() >= 12 {
458                &s[..12]
459            } else {
460                return None;
461            };
462            if !s.bytes().all(|b| b.is_ascii_digit()) {
463                return None;
464            }
465            let year: i64 = s[0..4].parse().ok()?;
466            let month: i64 = s[4..6].parse().ok()?;
467            let day: i64 = s[6..8].parse().ok()?;
468            let hour: i64 = s[8..10].parse().ok()?;
469            let minute: i64 = s[10..12].parse().ok()?;
470            let a = (14 - month) / 12;
471            let y = year + 4800 - a;
472            let m_adj = month + 12 * a - 3;
473            let jdn = day + (153 * m_adj + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045;
474            Some(jdn * 1440 + hour * 60 + minute)
475        }
476        let nav = match ctx.navigator() {
477            Some(n) => n,
478            None => return ConditionResult::Unknown,
479        };
480        let sg6_count = nav.group_instance_count(&["SG5", "SG6"]);
481        for gi in 0..sg6_count {
482            let sg10_count = nav.child_group_instance_count(&["SG5", "SG6"], gi, "SG10");
483            for si in 0..sg10_count {
484                let dtms = nav.find_segments_in_child_group("DTM", &["SG5", "SG6"], gi, "SG10", si);
485                let t7 = dtms
486                    .iter()
487                    .find(|d| {
488                        d.elements
489                            .first()
490                            .and_then(|e| e.first())
491                            .is_some_and(|v| v == "7")
492                    })
493                    .and_then(|d| d.elements.first().and_then(|e| e.get(1)))
494                    .cloned();
495                if let Some(t7_val) = t7 {
496                    let t7_mins = match parse_to_minutes(&t7_val) {
497                        Some(v) => v,
498                        None => continue,
499                    };
500                    for &other_qual in &["60", "9"] {
501                        let t_other = dtms
502                            .iter()
503                            .find(|d| {
504                                d.elements
505                                    .first()
506                                    .and_then(|e| e.first())
507                                    .is_some_and(|v| v == other_qual)
508                            })
509                            .and_then(|d| d.elements.first().and_then(|e| e.get(1)))
510                            .cloned();
511                        if let Some(t_other_val) = t_other {
512                            let t_other_mins = match parse_to_minutes(&t_other_val) {
513                                Some(v) => v,
514                                None => continue,
515                            };
516                            let diff = (t7_mins - t_other_mins).abs();
517                            let (min_str, max_str) = if t7_mins <= t_other_mins {
518                                (t7_val.as_str(), t_other_val.as_str())
519                            } else {
520                                (t_other_val.as_str(), t7_val.as_str())
521                            };
522                            let min_in_mesz = matches!(is_mesz_utc(min_str), ConditionResult::True);
523                            let max_in_mesz = matches!(is_mesz_utc(max_str), ConditionResult::True);
524                            // MEZ→MESZ (winter→summer, March): threshold < 23h
525                            // MESZ→MEZ (summer→winter, October): threshold < 25h
526                            // Same timezone: threshold < 24h
527                            let threshold = if !min_in_mesz && max_in_mesz {
528                                23 * 60
529                            } else if min_in_mesz && !max_in_mesz {
530                                25 * 60
531                            } else {
532                                24 * 60
533                            };
534                            if diff >= threshold {
535                                return ConditionResult::False;
536                            }
537                        }
538                    }
539                }
540            }
541        }
542        ConditionResult::True
543    }
544
545    /// [151] Wenn BGM+Z27 (Bewegungsdaten im Kalenderjahr vor Lieferbeginn) vorhanden.
546    fn evaluate_151(&self, ctx: &EvaluationContext) -> ConditionResult {
547        ctx.has_qualifier("BGM", 0, "Z27")
548    }
549
550    /// [152] Wenn BGM+7 (Prozessdatenbericht) vorhanden.
551    fn evaluate_152(&self, ctx: &EvaluationContext) -> ConditionResult {
552        ctx.has_qualifier("BGM", 0, "7")
553    }
554
555    /// [153] Wenn SG9 PIA+5+7-0?:33.86.0 vorhanden
556    fn evaluate_153(&self, ctx: &EvaluationContext) -> ConditionResult {
557        // PIA+5+7-0?:33.86.0 — elements[0][0]="5", elements[1][0]="7-0:33.86.0" (EDIFACT ?-escape for colon)
558        ctx.has_segment_matching_in_group(
559            "PIA",
560            &[(0, 0, "5"), (1, 0, "7-0:33.86.0")],
561            &["SG5", "SG9"],
562        )
563    }
564
565    /// [154] Wenn SG9 PIA+5+7-0?:33.47.0 vorhanden
566    fn evaluate_154(&self, ctx: &EvaluationContext) -> ConditionResult {
567        // PIA+5+7-0?:33.47.0 — elements[0][0]="5", elements[1][0]="7-0:33.47.0" (EDIFACT ?-escape for colon)
568        ctx.has_segment_matching_in_group(
569            "PIA",
570            &[(0, 0, "5"), (1, 0, "7-0:33.47.0")],
571            &["SG5", "SG9"],
572        )
573    }
574
575    /// [578] Hinweis: Wenn es sich um die Übermittlung des Leistungswertes für die Netzentgelte mit Jahresleistungspreissystem handelt.
576    fn evaluate_578(&self, _ctx: &EvaluationContext) -> ConditionResult {
577        // Hinweis: Übermittlung des Leistungswertes für Netzentgelte mit Jahresleistungspreissystem — informational note, always applies
578        ConditionResult::True
579    }
580
581    /// [579] Hinweis: Wenn es sich um die Übermittlung des Leistungswertes für die Netzentgelte mit Monatsleistungspreissystem handelt.
582    fn evaluate_579(&self, _ctx: &EvaluationContext) -> ConditionResult {
583        // Hinweis: Übermittlung des Leistungswertes für Netzentgelte mit Monatsleistungspreissystem — informational note, always applies
584        ConditionResult::True
585    }
586
587    /// [580] Hinweis: Wenn es sich um die Übermittlung des Leistungswertes für die Netzentgelte mit Tagesleistungspreissystem handelt.
588    fn evaluate_580(&self, _ctx: &EvaluationContext) -> ConditionResult {
589        // Hinweis: Übermittlung des Leistungswertes für Netzentgelte mit Tagesleistungspreissystem — informational note, always applies
590        ConditionResult::True
591    }
592
593    /// [581] Hinweis: Wenn es sich um die Übermittlung des Monatsmaximum gemäß WiM handelt.
594    fn evaluate_581(&self, _ctx: &EvaluationContext) -> ConditionResult {
595        // Hinweis: Wenn es sich um die Übermittlung des Monatsmaximum gemäß WiM handelt.
596        // Informational note, always applies
597        ConditionResult::True
598    }
599
600    /// [1] Sofern per ORDERS angefordert
601    /// EXTERNAL: Requires context from outside the message.
602    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
603        ctx.external.evaluate("requested_via_orders")
604    }
605
606    /// [2] Wenn das Zeitintervall zwischen ersten SG10 DTM+163 und letzten SG10 DTM+164 mindestens einen Monat umfasst
607    // REVIEW: Parses year+month from DTM+163 (first) and DTM+164 (last) in format 303 (YYYYMMDDHHmm). Computes calendar-month difference. 'Mindestens einen Monat' interpreted as end month > start month (calendar boundary). Medium confidence due to ambiguity between calendar-month vs. 30-day interpretation. (medium confidence)
608    fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
609        let dtm_163 = ctx.find_segments_with_qualifier("DTM", 0, "163");
610        let dtm_164 = ctx.find_segments_with_qualifier("DTM", 0, "164");
611        let first_start = dtm_163
612            .first()
613            .and_then(|s| s.elements.first())
614            .and_then(|e| e.get(1))
615            .map(|s| s.as_str());
616        let last_end = dtm_164
617            .last()
618            .and_then(|s| s.elements.first())
619            .and_then(|e| e.get(1))
620            .map(|s| s.as_str());
621        match (first_start, last_end) {
622            (Some(start), Some(end)) if start.len() >= 6 && end.len() >= 6 => {
623                let start_year: u32 = start[0..4].parse().unwrap_or(0);
624                let start_month: u32 = start[4..6].parse().unwrap_or(0);
625                let end_year: u32 = end[0..4].parse().unwrap_or(0);
626                let end_month: u32 = end[4..6].parse().unwrap_or(0);
627                let start_total = start_year * 12 + start_month;
628                let end_total = end_year * 12 + end_month;
629                ConditionResult::from(end_total >= start_total + 1)
630            }
631            _ => ConditionResult::Unknown,
632        }
633    }
634
635    /// [11] Wenn SG9 PIA+5+7-0?:52.0.22/7-0?:54.0.16/7-0?:54.0.20/7-0?:54.0.22
636    fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
637        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
638        let obis_values = ["7-0:52.0.22", "7-0:54.0.16", "7-0:54.0.20", "7-0:54.0.22"];
639        ConditionResult::from(pia_segments.iter().any(|s| {
640            s.elements
641                .get(1)
642                .and_then(|e| e.first())
643                .is_some_and(|v| obis_values.contains(&v.as_str()))
644        }))
645    }
646
647    /// [12] Wenn nicht SG9 PIA+5+7-0?:52.0.22/7-0?:54.0.16/7-0?:54.0.20/7-0?:54.0.22
648    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
649        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
650        let obis_values = ["7-0:52.0.22", "7-0:54.0.16", "7-0:54.0.20", "7-0:54.0.22"];
651        ConditionResult::from(!pia_segments.iter().any(|s| {
652            s.elements
653                .get(1)
654                .and_then(|e| e.first())
655                .is_some_and(|v| obis_values.contains(&v.as_str()))
656        }))
657    }
658
659    /// [17] Wenn nicht SG9 PIA+5+1-b?:9.99.0 (b= Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien)
660    // REVIEW: Checks absence of PIA+5 with OBIS matching pattern '1-{channel}:9.99.0'. The 'b' is a variable channel, so pattern-matching on prefix '1-' and suffix ':9.99.0' is used. Negated because condition is 'Wenn NICHT'. Medium confidence due to pattern-based matching of OBIS code. (medium confidence)
661    fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
662        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
663        let has_match = pia_segments.iter().any(|s| {
664            s.elements
665                .get(1)
666                .and_then(|e| e.first())
667                .is_some_and(|v| v.starts_with("1-") && v.ends_with(":9.99.0"))
668        });
669        ConditionResult::from(!has_match)
670    }
671
672    /// [18] Wenn SG9 PIA+5+1-b?:9.99.0 (b= Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien)
673    // REVIEW: Affirmative version of condition 17. True when any PIA+5 has OBIS code matching the '1-{channel}:9.99.0' pattern (combined heat/power or special metering). Medium confidence due to pattern matching. (medium confidence)
674    fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
675        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
676        ConditionResult::from(pia_segments.iter().any(|s| {
677            s.elements
678                .get(1)
679                .and_then(|e| e.first())
680                .is_some_and(|v| v.starts_with("1-") && v.ends_with(":9.99.0"))
681        }))
682    }
683
684    /// [22] Wenn Aufteilung vorhanden
685    /// EXTERNAL: Requires context from outside the message.
686    fn evaluate_22(&self, ctx: &EvaluationContext) -> ConditionResult {
687        ctx.external.evaluate("message_splitting")
688    }
689
690    /// [27] Wenn SG9 PIA+5+1-1?:1.9.0 vorhanden
691    fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
692        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
693        ConditionResult::from(pia_segments.iter().any(|s| {
694            s.elements
695                .get(1)
696                .and_then(|e| e.first())
697                .is_some_and(|v| v == "1-1:1.9.0")
698        }))
699    }
700
701    /// [28] Wenn SG9 PIA+5+1-1?:1.9.0 nicht vorhanden
702    fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
703        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
704        ConditionResult::from(!pia_segments.iter().any(|s| {
705            s.elements
706                .get(1)
707                .and_then(|e| e.first())
708                .is_some_and(|v| v == "1-1:1.9.0")
709        }))
710    }
711
712    /// [32] wenn MP-ID in SG2 NAD+MS in der Rolle NB
713    /// EXTERNAL: Requires context from outside the message.
714    fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
715        ctx.external.evaluate("sender_is_nb")
716    }
717
718    /// [33] wenn MP-ID in SG2 NAD+MR in der Rolle LF
719    /// EXTERNAL: Requires context from outside the message.
720    fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
721        ctx.external.evaluate("recipient_is_lf")
722    }
723
724    /// [35] wenn MP-ID in SG2 NAD+MS in der Rolle MSB
725    /// EXTERNAL: Requires context from outside the message.
726    fn evaluate_35(&self, ctx: &EvaluationContext) -> ConditionResult {
727        ctx.external.evaluate("sender_is_msb")
728    }
729
730    /// [36] wenn MP-ID in SG2 NAD+MR in der Rolle NB
731    /// EXTERNAL: Requires context from outside the message.
732    fn evaluate_36(&self, ctx: &EvaluationContext) -> ConditionResult {
733        ctx.external.evaluate("recipient_is_nb")
734    }
735
736    /// [38] wenn in SG6 LOC+172 DE3225 die ID der Messlokation angegeben ist
737    fn evaluate_38(&self, ctx: &EvaluationContext) -> ConditionResult {
738        let loc_segments = ctx.find_segments_with_qualifier("LOC", 0, "172");
739        match loc_segments.first() {
740            Some(loc) => {
741                let has_id = loc
742                    .elements
743                    .get(1)
744                    .and_then(|e| e.first())
745                    .is_some_and(|v| !v.is_empty());
746                ConditionResult::from(has_id)
747            }
748            None => ConditionResult::False,
749        }
750    }
751
752    /// [42] Wenn MP-ID in SG2 NAD+MR in der Rolle MSB
753    /// EXTERNAL: Requires context from outside the message.
754    fn evaluate_42(&self, ctx: &EvaluationContext) -> ConditionResult {
755        ctx.external.evaluate("recipient_is_msb")
756    }
757
758    /// [45] Wenn SG9 PIA+5+7-b:99.41.16 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
759    fn evaluate_45(&self, ctx: &EvaluationContext) -> ConditionResult {
760        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
761        ConditionResult::from(pias.iter().any(|s| {
762            s.elements
763                .get(1)
764                .and_then(|e| e.first())
765                .is_some_and(|v| v.starts_with("7-") && v.ends_with(":99.41.16"))
766        }))
767    }
768
769    /// [46] Wenn Wert in SG6 LOC+172 DE3225 genau 11 Stellen
770    fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
771        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
772        if locs.is_empty() {
773            return ConditionResult::Unknown;
774        }
775        ConditionResult::from(locs.iter().any(|s| {
776            s.elements
777                .get(1)
778                .and_then(|e| e.first())
779                .is_some_and(|v| v.len() == 11)
780        }))
781    }
782
783    /// [48] Wenn SG9 PIA+5+7-0?:52.0.22
784    fn evaluate_48(&self, ctx: &EvaluationContext) -> ConditionResult {
785        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
786        ConditionResult::from(pias.iter().any(|s| {
787            s.elements
788                .get(1)
789                .and_then(|e| e.first())
790                .is_some_and(|v| v == "7-0:52.0.22")
791        }))
792    }
793
794    /// [49] Wenn SG9 PIA+5+7-b?:70.16.16/7-b?:70.16.20/7-b?:70.16.22 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
795    fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
796        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
797        ConditionResult::from(pias.iter().any(|s| {
798            s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
799                v.starts_with("7-")
800                    && (v.ends_with(":70.16.16")
801                        || v.ends_with(":70.16.20")
802                        || v.ends_with(":70.16.22"))
803            })
804        }))
805    }
806
807    /// [50] Wenn SG9 PIA+5+7-b?:70.18.16/7-b?:70.18.20/7-b?:70.18.22 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
808    fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
809        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
810        ConditionResult::from(pias.iter().any(|s| {
811            s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
812                v.starts_with("7-")
813                    && (v.ends_with(":70.18.16")
814                        || v.ends_with(":70.18.20")
815                        || v.ends_with(":70.18.22"))
816            })
817        }))
818    }
819
820    /// [51] Wenn SG9 PIA+5+7-0?:33.86.0 vorhanden ist, darf mittels Wiederholung SG9 LIN in derselben Nachricht das SG9 PIA+5+7-0?:52.0.22/7-0?:54.0.16/7-0?:54.0.20/7-0?:54.0.22 nicht mehr angegeben werden
821    // REVIEW: Mutual-exclusion constraint: if 7-0:33.86.0 is present, none of the listed 52/54.x codes may appear. Returns True when constraint is satisfied (i.e. 33.86.0 absent, or 33.86.0 present but excluded codes absent). Returns True when 33.86.0 is not present because the constraint simply does not apply. (medium confidence)
822    fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
823        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
824        let has_3386 = pias.iter().any(|s| {
825            s.elements
826                .get(1)
827                .and_then(|e| e.first())
828                .is_some_and(|v| v == "7-0:33.86.0")
829        });
830        if !has_3386 {
831            return ConditionResult::True;
832        }
833        let excluded = ["7-0:52.0.22", "7-0:54.0.16", "7-0:54.0.20", "7-0:54.0.22"];
834        ConditionResult::from(!pias.iter().any(|s| {
835            s.elements
836                .get(1)
837                .and_then(|e| e.first())
838                .is_some_and(|v| excluded.contains(&v.as_str()))
839        }))
840    }
841
842    /// [62] Wenn Wert in SG6 LOC+172 DE3225 genau 33 Stellen
843    fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
844        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
845        if locs.is_empty() {
846            return ConditionResult::Unknown;
847        }
848        ConditionResult::from(locs.iter().any(|s| {
849            s.elements
850                .get(1)
851                .and_then(|e| e.first())
852                .is_some_and(|v| v.len() == 33)
853        }))
854    }
855
856    /// [67] Wenn es sich um die Referenz auf eine ORDERS handelt
857    /// EXTERNAL: Requires context from outside the message.
858    // REVIEW: Checks whether the current MSCONS transaction references an ORDERS message. While RFF segments can carry cross-message references, the specific qualifier that marks an ORDERS reference is not provided in the segment structure reference, and determining whether a reference points to an ORDERS vs. another message type typically requires external business context about the originating transaction. (medium confidence)
859    fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
860        ctx.external.evaluate("is_orders_reference")
861    }
862
863    /// [68] Wenn BGM+7 vorhanden
864    fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
865        ctx.has_qualifier("BGM", 0, "7")
866    }
867
868    /// [69] Wenn BGM+Z28 vorhanden
869    fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
870        ctx.has_qualifier("BGM", 0, "Z28")
871    }
872
873    /// [70] Wenn BGM+BK vorhanden
874    fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
875        ctx.has_qualifier("BGM", 0, "BK")
876    }
877
878    /// [71] Wenn BGM+Z39 vorhanden
879    fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
880        ctx.has_qualifier("BGM", 0, "Z39")
881    }
882
883    /// [72] Wenn SG9 PIA+5+1-b?:1.6.0/1-b?:3.6.0/1-b?:4.6.0/1-66?:13.6.0/1-66?:14.6.0 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
884    fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
885        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
886        ConditionResult::from(pias.iter().any(|s| {
887            s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
888                (v.starts_with("1-")
889                    && (v.ends_with(":1.6.0") || v.ends_with(":3.6.0") || v.ends_with(":4.6.0")))
890                    || v == "1-66:13.6.0"
891                    || v == "1-66:14.6.0"
892            })
893        }))
894    }
895
896    /// [73] Wenn SG9 PIA+5+1-b?:1.9.e/1-b?:3.9.0/1-b?:4.9.0/1-66?:13.9.0/1-66?:14.9.0 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien, e=Tarif: Wert gemäß Codeliste der OBIS-Kennzahlen und Me...
897    fn evaluate_73(&self, ctx: &EvaluationContext) -> ConditionResult {
898        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
899        ConditionResult::from(pias.iter().any(|s| {
900            s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
901                (v.starts_with("1-") && v.contains(":1.9."))
902                    || (v.starts_with("1-") && v.ends_with(":3.9.0"))
903                    || (v.starts_with("1-") && v.ends_with(":4.9.0"))
904                    || v == "1-66:13.9.0"
905                    || v == "1-66:14.9.0"
906            })
907        }))
908    }
909
910    /// [77] Wenn MP-ID in SG2 NAD+MR der RB HKN-R
911    /// EXTERNAL: Requires context from outside the message.
912    fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
913        ctx.external
914            .evaluate("recipient_is_hkn_registration_authority")
915    }
916
917    /// [78] Wenn SG9 PIA+5+1-66?:13.6.0/1-66?:14.6.0/1-66?:13.9.0/1-66?:14.9.0 vorhanden
918    // REVIEW: PIA+5 means elements[0][0]='5'. DE7140 (OBIS code) is elements[1][0]. EDIFACT '?:' escape sequences are resolved by the parser to literal ':', so '1-66?:13.6.0' becomes '1-66:13.6.0'. Checks any SG9 group instance for PIA+5 with one of the four OBIS codes. Group path SG6->SG9 is approximate based on MSCONS structure. (medium confidence)
919    fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
920        ctx.any_group_has_qualified_value(
921            "PIA",
922            0,
923            "5",
924            1,
925            0,
926            &["1-66:13.6.0", "1-66:14.6.0", "1-66:13.9.0", "1-66:14.9.0"],
927            &["SG6", "SG9"],
928        )
929    }
930
931    /// [79] Wenn SG9 PIA+5+1-66?:13.6.0/1-66?:14.6.0/1-66?:13.9.0/1-66?:14.9.0 nicht vorhanden
932    // REVIEW: Logical negation of condition 78. True when none of the specified OBIS codes appears in any SG9 PIA+5 segment. (medium confidence)
933    fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
934        match ctx.any_group_has_qualified_value(
935            "PIA",
936            0,
937            "5",
938            1,
939            0,
940            &["1-66:13.6.0", "1-66:14.6.0", "1-66:13.9.0", "1-66:14.9.0"],
941            &["SG6", "SG9"],
942        ) {
943            ConditionResult::True => ConditionResult::False,
944            ConditionResult::False => ConditionResult::True,
945            ConditionResult::Unknown => ConditionResult::Unknown,
946        }
947    }
948
949    /// [80] Wenn MP-ID in SG2 NAD+MR in der Rolle ÜNB
950    /// EXTERNAL: Requires context from outside the message.
951    fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
952        ctx.external.evaluate("recipient_is_uenb")
953    }
954
955    /// [81] Wenn MP-ID in SG2 NAD+MS in der Rolle ÜNB
956    /// EXTERNAL: Requires context from outside the message.
957    fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
958        ctx.external.evaluate("sender_is_uenb")
959    }
960
961    /// [83] Wenn in derselben SG9 LIN die Angabe STS+10+Z38 nicht vorhanden
962    // REVIEW: STS 'Grundlage der Energiemenge' has elements[0][0]='10' (Statuskategorie) and elements[1][0]=status code. STS lives in SG10 (child of SG9). Condition is True when NO STS with category 10 and status Z38 exists within SG9/SG10 scope. Using any_group_has_qualified_value on SG9->SG10 path then negating. Group scope is approximate — a precise check would traverse SG9 instances and their SG10 children. (medium confidence)
963    fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
964        match ctx.any_group_has_qualified_value("STS", 0, "10", 1, 0, &["Z38"], &["SG9", "SG10"]) {
965            ConditionResult::True => ConditionResult::False,
966            ConditionResult::False => ConditionResult::True,
967            ConditionResult::Unknown => ConditionResult::Unknown,
968        }
969    }
970
971    /// [84] Wenn in derselben SG9 LIN die Angabe STS+10+Z39 nicht vorhanden
972    // REVIEW: Same pattern as condition 83 but for status code Z39. True when no STS+10+Z39 is present in any SG10 within the SG9 scope. (medium confidence)
973    fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
974        match ctx.any_group_has_qualified_value("STS", 0, "10", 1, 0, &["Z39"], &["SG9", "SG10"]) {
975            ConditionResult::True => ConditionResult::False,
976            ConditionResult::False => ConditionResult::True,
977            ConditionResult::Unknown => ConditionResult::Unknown,
978        }
979    }
980
981    /// [85] Wenn in derselben SG9 LIN die Angabe STS+10+Z36 nicht vorhanden
982    // REVIEW: Same pattern as condition 83 but for status code Z36. True when no STS+10+Z36 is present in any SG10 within the SG9 scope. (medium confidence)
983    fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
984        match ctx.any_group_has_qualified_value("STS", 0, "10", 1, 0, &["Z36"], &["SG9", "SG10"]) {
985            ConditionResult::True => ConditionResult::False,
986            ConditionResult::False => ConditionResult::True,
987            ConditionResult::Unknown => ConditionResult::Unknown,
988        }
989    }
990
991    /// [86] Wenn in derselben SG9 LIN die Angabe STS+10+Z37 nicht vorhanden
992    // REVIEW: Same pattern as condition 83 but for status code Z37. True when no STS+10+Z37 is present in any SG10 within the SG9 scope. (medium confidence)
993    fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
994        match ctx.any_group_has_qualified_value("STS", 0, "10", 1, 0, &["Z37"], &["SG9", "SG10"]) {
995            ConditionResult::True => ConditionResult::False,
996            ConditionResult::False => ConditionResult::True,
997            ConditionResult::Unknown => ConditionResult::Unknown,
998        }
999    }
1000
1001    /// [87] Wenn der Wert in DTM+163 DE2380 derselben SG6 LOC+172 mit demselben Wert in SG9 PIA+5 DE7140 der früheste angegebene Zeitpunkt ist
1002    // REVIEW: Checks that DTM+163 in SG6 (with LOC+172) equals the minimum DTM+163 across all SG10 instances beneath that SG6's SG9 children. Medium confidence because the SG9→SG10 child navigation uses a local child instance index (si) passed to the grandchild API, which may behave differently depending on navigator implementation details for two-level child traversal. (medium confidence)
1003    fn evaluate_87(&self, ctx: &EvaluationContext) -> ConditionResult {
1004        let nav = match ctx.navigator() {
1005            Some(n) => n,
1006            None => return ConditionResult::Unknown,
1007        };
1008        let sg6_count = nav.group_instance_count(&["SG5", "SG6"]);
1009        for gi in 0..sg6_count {
1010            let locs = nav.find_segments_in_group("LOC", &["SG5", "SG6"], gi);
1011            let has_loc_172 = locs.iter().any(|s| {
1012                s.elements
1013                    .first()
1014                    .and_then(|e| e.first())
1015                    .is_some_and(|v| v == "172")
1016            });
1017            if !has_loc_172 {
1018                continue;
1019            }
1020            let sg6_dtms = nav.find_segments_in_group("DTM", &["SG5", "SG6"], gi);
1021            let sg6_val = match sg6_dtms
1022                .iter()
1023                .find(|s| {
1024                    s.elements
1025                        .first()
1026                        .and_then(|e| e.first())
1027                        .is_some_and(|v| v == "163")
1028                })
1029                .and_then(|s| s.elements.first().and_then(|e| e.get(1)))
1030                .filter(|v| !v.is_empty())
1031                .cloned()
1032            {
1033                Some(v) => v,
1034                None => continue,
1035            };
1036            let sg9_count = nav.child_group_instance_count(&["SG5", "SG6"], gi, "SG9");
1037            let mut sg10_values: Vec<String> = Vec::new();
1038            for si in 0..sg9_count {
1039                let sg10_count = nav.child_group_instance_count(&["SG5", "SG6", "SG9"], si, "SG10");
1040                for ti in 0..sg10_count {
1041                    let dtms = nav.find_segments_in_child_group(
1042                        "DTM",
1043                        &["SG5", "SG6", "SG9"],
1044                        si,
1045                        "SG10",
1046                        ti,
1047                    );
1048                    for dtm in &dtms {
1049                        if dtm
1050                            .elements
1051                            .first()
1052                            .and_then(|e| e.first())
1053                            .is_some_and(|v| v == "163")
1054                        {
1055                            if let Some(val) = dtm.elements.first().and_then(|e| e.get(1)) {
1056                                if !val.is_empty() {
1057                                    sg10_values.push(val.clone());
1058                                }
1059                            }
1060                        }
1061                    }
1062                }
1063            }
1064            if let Some(min_val) = sg10_values.iter().min() {
1065                if &sg6_val != min_val {
1066                    return ConditionResult::False;
1067                }
1068            }
1069        }
1070        ConditionResult::True
1071    }
1072
1073    /// [88] Wenn der Wert in DTM+164 DE2380 derselben SG6 LOC+172 mit demselben Wert in SG9 PIA+5 DE7140 der späteste angegebene Zeitpunkt ist
1074    // REVIEW: Mirror of condition 87 but for DTM+164 (Ende Messperiode) — checks that the SG6 transmission period end equals the maximum DTM+164 across all SG10 measurement instances under that SG6. Same navigation caveat applies for two-level SG9→SG10 child traversal. (medium confidence)
1075    fn evaluate_88(&self, ctx: &EvaluationContext) -> ConditionResult {
1076        let nav = match ctx.navigator() {
1077            Some(n) => n,
1078            None => return ConditionResult::Unknown,
1079        };
1080        let sg6_count = nav.group_instance_count(&["SG5", "SG6"]);
1081        for gi in 0..sg6_count {
1082            let locs = nav.find_segments_in_group("LOC", &["SG5", "SG6"], gi);
1083            let has_loc_172 = locs.iter().any(|s| {
1084                s.elements
1085                    .first()
1086                    .and_then(|e| e.first())
1087                    .is_some_and(|v| v == "172")
1088            });
1089            if !has_loc_172 {
1090                continue;
1091            }
1092            let sg6_dtms = nav.find_segments_in_group("DTM", &["SG5", "SG6"], gi);
1093            let sg6_val = match sg6_dtms
1094                .iter()
1095                .find(|s| {
1096                    s.elements
1097                        .first()
1098                        .and_then(|e| e.first())
1099                        .is_some_and(|v| v == "164")
1100                })
1101                .and_then(|s| s.elements.first().and_then(|e| e.get(1)))
1102                .filter(|v| !v.is_empty())
1103                .cloned()
1104            {
1105                Some(v) => v,
1106                None => continue,
1107            };
1108            let sg9_count = nav.child_group_instance_count(&["SG5", "SG6"], gi, "SG9");
1109            let mut sg10_values: Vec<String> = Vec::new();
1110            for si in 0..sg9_count {
1111                let sg10_count = nav.child_group_instance_count(&["SG5", "SG6", "SG9"], si, "SG10");
1112                for ti in 0..sg10_count {
1113                    let dtms = nav.find_segments_in_child_group(
1114                        "DTM",
1115                        &["SG5", "SG6", "SG9"],
1116                        si,
1117                        "SG10",
1118                        ti,
1119                    );
1120                    for dtm in &dtms {
1121                        if dtm
1122                            .elements
1123                            .first()
1124                            .and_then(|e| e.first())
1125                            .is_some_and(|v| v == "164")
1126                        {
1127                            if let Some(val) = dtm.elements.first().and_then(|e| e.get(1)) {
1128                                if !val.is_empty() {
1129                                    sg10_values.push(val.clone());
1130                                }
1131                            }
1132                        }
1133                    }
1134                }
1135            }
1136            if let Some(max_val) = sg10_values.iter().max() {
1137                if &sg6_val != max_val {
1138                    return ConditionResult::False;
1139                }
1140            }
1141        }
1142        ConditionResult::True
1143    }
1144
1145    /// [90] Wenn BGM+Z41 vorhanden
1146    fn evaluate_90(&self, ctx: &EvaluationContext) -> ConditionResult {
1147        ctx.has_qualifier("BGM", 0, "Z41")
1148    }
1149
1150    /// [91] Wenn BGM+Z42 vorhanden
1151    fn evaluate_91(&self, ctx: &EvaluationContext) -> ConditionResult {
1152        ctx.has_qualifier("BGM", 0, "Z42")
1153    }
1154
1155    /// [92] Wenn SG10 QTY DE6063 mit Wert 67 vorhanden
1156    fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
1157        ctx.any_group_has_qualifier("QTY", 0, "67", &["SG9", "SG10"])
1158    }
1159
1160    /// [93] Wenn SG10 QTY DE6063 mit Wert 220 vorhanden
1161    fn evaluate_93(&self, ctx: &EvaluationContext) -> ConditionResult {
1162        ctx.any_group_has_qualifier("QTY", 0, "220", &["SG9", "SG10"])
1163    }
1164
1165    /// [94] Wenn SG10 QTY DE6063 mit Wert 201 vorhanden
1166    fn evaluate_94(&self, ctx: &EvaluationContext) -> ConditionResult {
1167        ctx.any_group_has_qualifier("QTY", 0, "201", &["SG9", "SG10"])
1168    }
1169
1170    /// [95] Wenn SG10 QTY DE6063 mit Wert 20 vorhanden
1171    fn evaluate_95(&self, ctx: &EvaluationContext) -> ConditionResult {
1172        ctx.any_group_has_qualifier("QTY", 0, "20", &["SG5", "SG10"])
1173    }
1174
1175    /// [96] Wenn SG10 QTY DE6063 mit Wert Z18 vorhanden
1176    fn evaluate_96(&self, ctx: &EvaluationContext) -> ConditionResult {
1177        ctx.any_group_has_qualifier("QTY", 0, "Z18", &["SG5", "SG10"])
1178    }
1179
1180    /// [97] Wenn es sich um die Übermittlung eines Wertes aufgrund der Umstellung der Gasqualität handelt
1181    /// EXTERNAL: Requires context from outside the message.
1182    // REVIEW: Whether this transmission is caused by a gas quality changeover (H-Gas/L-Gas conversion) cannot be determined from EDIFACT message content alone — it is a business context condition. (medium confidence)
1183    fn evaluate_97(&self, ctx: &EvaluationContext) -> ConditionResult {
1184        ctx.external.evaluate("gas_quality_change_transmission")
1185    }
1186
1187    /// [98] Wenn SG9 PIA+5+SOL:Z08 vorhanden
1188    fn evaluate_98(&self, ctx: &EvaluationContext) -> ConditionResult {
1189        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1190        ConditionResult::from(pias.iter().any(|s| {
1191            s.elements
1192                .get(1)
1193                .and_then(|e| e.first())
1194                .is_some_and(|v| v == "SOL")
1195                && s.elements
1196                    .get(1)
1197                    .and_then(|e| e.get(1))
1198                    .is_some_and(|v| v == "Z08")
1199        }))
1200    }
1201
1202    /// [99] Wenn SG9 PIA+5+WID:Z08 vorhanden
1203    fn evaluate_99(&self, ctx: &EvaluationContext) -> ConditionResult {
1204        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1205        ConditionResult::from(pias.iter().any(|s| {
1206            s.elements
1207                .get(1)
1208                .and_then(|e| e.first())
1209                .is_some_and(|v| v == "WID")
1210                && s.elements
1211                    .get(1)
1212                    .and_then(|e| e.get(1))
1213                    .is_some_and(|v| v == "Z08")
1214        }))
1215    }
1216
1217    /// [100] Wenn in derselben SG9 LIN das PIA+5+AUA:Z08 vorhanden
1218    fn evaluate_100(&self, ctx: &EvaluationContext) -> ConditionResult {
1219        ctx.any_group_has_qualified_value("PIA", 0, "5", 1, 0, &["AUA"], &["SG5", "SG9"])
1220    }
1221
1222    /// [101] Wenn in derselben SG9 LIN das PIA+5+FPA:Z08 vorhanden
1223    fn evaluate_101(&self, ctx: &EvaluationContext) -> ConditionResult {
1224        ctx.any_group_has_qualified_value("PIA", 0, "5", 1, 0, &["FPA"], &["SG5", "SG9"])
1225    }
1226
1227    /// [108] wenn SG9 PIA+5+7-b?:99.41.16/7-b?:99.42.16 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
1228    // REVIEW: Checks PIA+5 for OBIS codes matching pattern '7-{channel}:99.41.16' or '7-{channel}:99.42.16' (Wirkenergie-Kennzahlen). The '?' in AHB notation is the EDIFACT release character escaping the colon; parsed segments will contain literal colon. Channel variable 'b' matched via starts_with("7-"). (medium confidence)
1229    fn evaluate_108(&self, ctx: &EvaluationContext) -> ConditionResult {
1230        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1231        ConditionResult::from(pias.iter().any(|s| {
1232            let obis = match s.elements.get(1).and_then(|e| e.first()) {
1233                Some(v) => v.as_str(),
1234                None => return false,
1235            };
1236            let code_list = s
1237                .elements
1238                .get(1)
1239                .and_then(|e| e.get(1))
1240                .map(|v| v.as_str())
1241                .unwrap_or("");
1242            code_list == "Z08"
1243                && obis.starts_with("7-")
1244                && (obis.contains(":99.41.16") || obis.contains(":99.42.16"))
1245        }))
1246    }
1247
1248    /// [111] Wenn SG10 DTM+9 DE2379 in demselben Segment mit Wert 303 vorhanden
1249    fn evaluate_111(&self, ctx: &EvaluationContext) -> ConditionResult {
1250        let dtms = ctx.find_segments_with_qualifier("DTM", 0, "9");
1251        ConditionResult::from(dtms.iter().any(|s| {
1252            s.elements
1253                .first()
1254                .and_then(|e| e.get(2))
1255                .is_some_and(|v| v == "303")
1256        }))
1257    }
1258
1259    /// [113] wenn SG7 RFF+AGK (Konfigurations-ID) vorhanden
1260    fn evaluate_113(&self, ctx: &EvaluationContext) -> ConditionResult {
1261        ctx.has_qualifier("RFF", 0, "AGK")
1262    }
1263
1264    /// [117] Nur MP-ID aus Sparte Strom
1265    /// EXTERNAL: Requires context from outside the message.
1266    fn evaluate_117(&self, ctx: &EvaluationContext) -> ConditionResult {
1267        ctx.external.evaluate("marktpartner_is_strom")
1268    }
1269
1270    /// [118] Nur MP-ID aus Sparte Gas
1271    /// EXTERNAL: Requires context from outside the message.
1272    fn evaluate_118(&self, ctx: &EvaluationContext) -> ConditionResult {
1273        ctx.external.evaluate("marktpartner_is_gas")
1274    }
1275
1276    /// [119] wenn in SG6 LOC+172 DE3225 die ID der Marktlokation angegeben ist
1277    fn evaluate_119(&self, ctx: &EvaluationContext) -> ConditionResult {
1278        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
1279        match locs.first() {
1280            Some(loc) => ConditionResult::from(
1281                loc.elements
1282                    .get(1)
1283                    .and_then(|e| e.first())
1284                    .is_some_and(|v| !v.is_empty()),
1285            ),
1286            None => ConditionResult::False,
1287        }
1288    }
1289
1290    /// [121] wenn BGM+Z43 (Redispatch Ausfallarbeitüberführungszeitreihe) vorhanden
1291    fn evaluate_121(&self, ctx: &EvaluationContext) -> ConditionResult {
1292        ctx.has_qualifier("BGM", 0, "Z43")
1293    }
1294
1295    /// [125] wenn SG9 PIA+5+7-0?:52.0.22/7-b?:53.0.16/7-b?:55.0.16/7-b?:55.0.20/7-b?:55.0.22 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
1296    // REVIEW: Checks PIA+5 for specific OBIS code patterns: '7-0:52.0.22' (fixed channel 0), '7-{b}:53.0.16', '7-{b}:55.0.16', '7-{b}:55.0.20', '7-{b}:55.0.22' (variable channel). These are Blindenergie/Leistungsmessungs-Kennzahlen. Uses contains() to match the OBIS suffix after the channel variable. (medium confidence)
1297    fn evaluate_125(&self, ctx: &EvaluationContext) -> ConditionResult {
1298        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1299        ConditionResult::from(pias.iter().any(|s| {
1300            let obis = match s.elements.get(1).and_then(|e| e.first()) {
1301                Some(v) => v.as_str(),
1302                None => return false,
1303            };
1304            let code_list = s
1305                .elements
1306                .get(1)
1307                .and_then(|e| e.get(1))
1308                .map(|v| v.as_str())
1309                .unwrap_or("");
1310            code_list == "Z08"
1311                && obis.starts_with("7-")
1312                && (obis.contains(":52.0.22")
1313                    || obis.contains(":53.0.16")
1314                    || obis.contains(":55.0.16")
1315                    || obis.contains(":55.0.20")
1316                    || obis.contains(":55.0.22"))
1317        }))
1318    }
1319
1320    /// [126] wenn Plausibilisierungshinweise vorliegen
1321    /// EXTERNAL: Requires context from outside the message.
1322    fn evaluate_126(&self, ctx: &EvaluationContext) -> ConditionResult {
1323        ctx.external.evaluate("plausibility_notes_present")
1324    }
1325
1326    /// [127] wenn ein Korrekturgrund anzugeben ist
1327    /// EXTERNAL: Requires context from outside the message.
1328    fn evaluate_127(&self, ctx: &EvaluationContext) -> ConditionResult {
1329        ctx.external.evaluate("correction_reason_required")
1330    }
1331
1332    /// [128] Wenn es sich um eine Ablesung handelt, welche keine Ablesung aufgrund der Änderung an der Messtechnik oder deren Konfiguration ist (z.B. Kundenablesung).
1333    // REVIEW: A regular reading (e.g. Kundenablesung) is distinguished from a meter-change reading by the absence of DTM+60 (Ausführungs-/Änderungszeitpunkt) in the same SG10. DTM+9 presence confirms it is a reading at all; DTM+60 absence confirms it is not due to a meter/config change. Group path SG9→SG10 matches MSCONS LIN-group context; fallback to message-wide if no navigator. (medium confidence)
1334    fn evaluate_128(&self, ctx: &EvaluationContext) -> ConditionResult {
1335        // True when SG10 has a reading date (DTM+9, Ablesedatum) but no device change timestamp (DTM+60),
1336        // indicating a regular reading not caused by meter change or configuration change
1337        ctx.any_group_has_qualifier_without("DTM", 0, "9", "DTM", 0, "60", &["SG9", "SG10"])
1338    }
1339
1340    /// [129] Wenn es sich um eine Ablesung aufgrund der Änderung an der Messtechnik oder deren Konfiguration handelt (z.B. Gerätewechsel).
1341    // REVIEW: A meter-change reading is identified by the co-occurrence of DTM+9 (Ablesedatum) and DTM+60 (Ausführungs-/Änderungszeitpunkt) within the same SG10 instance. Uses direct navigator iteration to check co-occurrence within a single group instance (any_group_has_co_occurrence would have ambiguity for same-tag different qualifiers). Falls back to message-wide check without navigator. (medium confidence)
1342    fn evaluate_129(&self, ctx: &EvaluationContext) -> ConditionResult {
1343        // True when SG10 contains both DTM+9 (Ablesedatum) and DTM+60 (Ausführungs-/Änderungszeitpunkt),
1344        // indicating a reading caused by a meter change or configuration change (e.g. Gerätewechsel)
1345        let nav = match ctx.navigator() {
1346            Some(n) => n,
1347            None => {
1348                let has9 = ctx.has_qualifier("DTM", 0, "9");
1349                let has60 = ctx.has_qualifier("DTM", 0, "60");
1350                return match (has9, has60) {
1351                    (ConditionResult::True, ConditionResult::True) => ConditionResult::True,
1352                    (ConditionResult::Unknown, _) | (_, ConditionResult::Unknown) => {
1353                        ConditionResult::Unknown
1354                    }
1355                    _ => ConditionResult::False,
1356                };
1357            }
1358        };
1359        let sg10_count = nav.group_instance_count(&["SG9", "SG10"]);
1360        for i in 0..sg10_count {
1361            let dtms = nav.find_segments_in_group("DTM", &["SG9", "SG10"], i);
1362            let has9 = dtms.iter().any(|s| {
1363                s.elements
1364                    .first()
1365                    .and_then(|e| e.first())
1366                    .is_some_and(|v| v == "9")
1367            });
1368            let has60 = dtms.iter().any(|s| {
1369                s.elements
1370                    .first()
1371                    .and_then(|e| e.first())
1372                    .is_some_and(|v| v == "60")
1373            });
1374            if has9 && has60 {
1375                return ConditionResult::True;
1376            }
1377        }
1378        ConditionResult::False
1379    }
1380
1381    /// [131] wenn RFF+AGK (Konfigurations-ID) nicht vorhanden
1382    fn evaluate_131(&self, ctx: &EvaluationContext) -> ConditionResult {
1383        ctx.lacks_qualifier("RFF", 0, "AGK")
1384    }
1385
1386    /// [132] wenn LOC+172 (Identifikationsangabe) DE3225 nicht vorhanden
1387    // REVIEW: Checks if LOC+172 is absent entirely, or if DE3225 (elements[1][0], the Bezeichnung) is empty/missing within the LOC+172 segment. Returns True in either case as the condition is 'DE3225 nicht vorhanden'. (medium confidence)
1388    fn evaluate_132(&self, ctx: &EvaluationContext) -> ConditionResult {
1389        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
1390        match locs.first() {
1391            None => ConditionResult::True,
1392            Some(loc) => {
1393                let de3225_absent = loc
1394                    .elements
1395                    .get(1)
1396                    .and_then(|e| e.first())
1397                    .map(|v| v.is_empty())
1398                    .unwrap_or(true);
1399                ConditionResult::from(de3225_absent)
1400            }
1401        }
1402    }
1403
1404    /// [133] Wenn innerhalb desselben LIN-Segments neben diesem Segment (SG10 DTM+7 Nutzungszeitpunkt) noch das SG10 DTM+9 (Ablesedatum) mit dem Code 102 im DE2379 vorhanden ist, darf der Wert der Differenz zwi...
1405    // REVIEW: Checks that when DTM+7 (Nutzungszeitpunkt, format 303 CCYYMMDDHHMM) and DTM+9 (Ablesedatum, format 102 CCYYMMDD) co-exist in the same SG10 instance, the CCYYMMDD date part of DTM+7 must equal or be at most 1 calendar day different from DTM+9. Uses a Julian Day Number formula for correct cross-month/year date arithmetic. MSCONS SG10 path assumed as SG5.SG6.SG10 based on the tx_group=SG5 and SG6 being the measurement period container; medium confidence due to path uncertainty. (medium confidence)
1406    fn evaluate_133(&self, ctx: &EvaluationContext) -> ConditionResult {
1407        let nav = match ctx.navigator() {
1408            Some(n) => n,
1409            None => return ConditionResult::Unknown,
1410        };
1411        fn date_to_days(s: &str) -> Option<i64> {
1412            if s.len() < 8 {
1413                return None;
1414            }
1415            let y: i64 = s[..4].parse().ok()?;
1416            let m: i64 = s[4..6].parse().ok()?;
1417            let d: i64 = s[6..8].parse().ok()?;
1418            if m < 1 || m > 12 || d < 1 || d > 31 {
1419                return None;
1420            }
1421            let a = (14 - m) / 12;
1422            let yy = y + 4800 - a;
1423            let mm = m + 12 * a - 3;
1424            Some(d + (153 * mm + 2) / 5 + 365 * yy + yy / 4 - yy / 100 + yy / 400 - 32045)
1425        }
1426        let sg10_count = nav.group_instance_count(&["SG5", "SG6", "SG10"]);
1427        if sg10_count == 0 {
1428            return ConditionResult::Unknown;
1429        }
1430        for i in 0..sg10_count {
1431            let dtms = nav.find_segments_in_group("DTM", &["SG5", "SG6", "SG10"], i);
1432            let dtm7_val = dtms
1433                .iter()
1434                .find(|s| {
1435                    s.elements
1436                        .first()
1437                        .and_then(|e| e.first())
1438                        .is_some_and(|v| v == "7")
1439                })
1440                .and_then(|s| s.elements.first())
1441                .and_then(|e| e.get(1))
1442                .cloned();
1443            let dtm9_val = dtms
1444                .iter()
1445                .find(|s| {
1446                    s.elements
1447                        .first()
1448                        .and_then(|e| e.first())
1449                        .is_some_and(|v| v == "9")
1450                        && s.elements
1451                            .first()
1452                            .and_then(|e| e.get(2))
1453                            .is_some_and(|v| v == "102")
1454                })
1455                .and_then(|s| s.elements.first())
1456                .and_then(|e| e.get(1))
1457                .cloned();
1458            let (v7, v9) = match (dtm7_val, dtm9_val) {
1459                (Some(v7), Some(v9)) if !v7.is_empty() && !v9.is_empty() => (v7, v9),
1460                _ => continue,
1461            };
1462            let date7 = if v7.len() >= 8 { &v7[..8] } else { continue };
1463            let date9 = if v9.len() >= 8 { &v9[..8] } else { v9.as_str() };
1464            let days7 = match date_to_days(date7) {
1465                Some(d) => d,
1466                None => return ConditionResult::Unknown,
1467            };
1468            let days9 = match date_to_days(date9) {
1469                Some(d) => d,
1470                None => return ConditionResult::Unknown,
1471            };
1472            let diff = (days7 - days9).abs();
1473            if diff > 1 {
1474                return ConditionResult::False;
1475            }
1476        }
1477        ConditionResult::True
1478    }
1479
1480    /// [134] Wenn SG10 DTM+9 DE2379 in demselben Segment mit Wert 102 vorhanden
1481    fn evaluate_134(&self, ctx: &EvaluationContext) -> ConditionResult {
1482        ctx.has_qualified_value("DTM", 0, "9", 0, 2, &["102"])
1483    }
1484
1485    /// [135] Der Wert an der Stelle CCYYMMDD muss ≤ dem Wert an der Stelle CCYYMMDD im DE2380 des DTM+137 sein
1486    // REVIEW: Condition 135 compares the CCYYMMDD portion of a reading-date DTM against DTM+137 (Nachrichtendatum at message level). The most natural target is DTM+9 (Ablesedatum) in SG10. Both format 303 (CCYYMMDDhhmm+ZZ) and format 102 (CCYYMMDD) start with CCYYMMDD, so taking the first 8 characters works for both. DTM+137 uses format 303 so likewise the first 8 chars give the date. Lexicographic comparison of zero-padded CCYYMMDD strings is equivalent to numeric date comparison. Medium confidence because the condition description does not explicitly state which DTM qualifier it applies to; DTM+9 is the strongest candidate in the MSCONS SG10 context. (medium confidence)
1487    fn evaluate_135(&self, ctx: &EvaluationContext) -> ConditionResult {
1488        // The CCYYMMDD portion of the DTM value in the current context must be <= CCYYMMDD of DTM+137 (Nachrichtendatum).
1489        // Applied to DTM+9 (Ablesedatum) readings in SG10 — format 303 and 102 both start with CCYYMMDD.
1490        let dtm_137_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1491        let threshold: String = match dtm_137_segs
1492            .first()
1493            .and_then(|s| s.elements.first())
1494            .and_then(|e| e.get(1))
1495        {
1496            Some(v) => v.chars().take(8).collect(),
1497            None => return ConditionResult::Unknown,
1498        };
1499        let dtm9_segs = ctx.find_segments_with_qualifier("DTM", 0, "9");
1500        if dtm9_segs.is_empty() {
1501            return ConditionResult::Unknown;
1502        }
1503        for dtm in &dtm9_segs {
1504            let val = match dtm.elements.first().and_then(|e| e.get(1)) {
1505                Some(v) => v,
1506                None => continue,
1507            };
1508            let date_part: String = val.chars().take(8).collect();
1509            if date_part > threshold {
1510                return ConditionResult::False;
1511            }
1512        }
1513        ConditionResult::True
1514    }
1515
1516    /// [138] Wenn es sich um eine Korrekturenergiemenge auf einen Wert aus einem iMS handelt
1517    /// EXTERNAL: Requires context from outside the message.
1518    fn evaluate_138(&self, ctx: &EvaluationContext) -> ConditionResult {
1519        ctx.external.evaluate("is_correction_energy_from_ims")
1520    }
1521
1522    /// [141] Wenn MP-ID in SG2 NAD+MR in der Rolle MGV
1523    /// EXTERNAL: Requires context from outside the message.
1524    fn evaluate_141(&self, ctx: &EvaluationContext) -> ConditionResult {
1525        ctx.external.evaluate("recipient_is_mgv")
1526    }
1527
1528    /// [142] wenn im DE3155 in demselben COM der Code EM vorhanden ist
1529    fn evaluate_142(&self, ctx: &EvaluationContext) -> ConditionResult {
1530        let coms = ctx.find_segments("COM");
1531        ConditionResult::from(coms.iter().any(|s| {
1532            s.elements
1533                .first()
1534                .and_then(|e| e.get(1))
1535                .is_some_and(|v| v == "EM")
1536        }))
1537    }
1538
1539    /// [143] wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
1540    fn evaluate_143(&self, ctx: &EvaluationContext) -> ConditionResult {
1541        let coms = ctx.find_segments("COM");
1542        ConditionResult::from(coms.iter().any(|s| {
1543            s.elements
1544                .first()
1545                .and_then(|e| e.get(1))
1546                .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
1547        }))
1548    }
1549
1550    /// [144] Wenn Wert in SG7 RFF+AGK DE1154 (Konfigurations-ID) vorhanden
1551    fn evaluate_144(&self, ctx: &EvaluationContext) -> ConditionResult {
1552        let rffs = ctx.find_segments_with_qualifier("RFF", 0, "AGK");
1553        match rffs.first() {
1554            None => ConditionResult::False,
1555            Some(rff) => {
1556                let has_value = rff
1557                    .elements
1558                    .first()
1559                    .and_then(|e| e.get(1))
1560                    .map(|v| !v.is_empty())
1561                    .unwrap_or(false);
1562                ConditionResult::from(has_value)
1563            }
1564        }
1565    }
1566
1567    /// [145] Wenn in derselben S9 LIN das SG10 DTM+163 (Beginn Messperiode) nicht vorhanden ist.
1568    // REVIEW: Checks absence of DTM+163 (Beginn Messperiode) in the message. The condition references the same SG10 context within the enclosing LIN group; without the exact MSCONS group path this is a message-wide approximation. Note: DTM+163 also appears in SG6 (Übertragungszeitraum), so this may yield false-negatives for the SG10-scoped intent. (medium confidence)
1569    fn evaluate_145(&self, ctx: &EvaluationContext) -> ConditionResult {
1570        ctx.lacks_qualifier("DTM", 0, "163")
1571    }
1572
1573    /// [146] Wenn es bei dem zu übermittelnden Wert um einen Wert zu einem Zeitpunkt handelt.
1574    // REVIEW: A point-in-time value (Wert zu einem Zeitpunkt) is signalled by the presence of DTM+7 (Nutzungszeitpunkt) in SG10. If this DTM qualifier is present, the transmitted value represents a single point in time rather than an interval. (medium confidence)
1575    fn evaluate_146(&self, ctx: &EvaluationContext) -> ConditionResult {
1576        ctx.has_qualifier("DTM", 0, "7")
1577    }
1578
1579    /// [147] Wenn in derselben S9 LIN das SG10 DTM+7 (Nutzungszeitpunkt) nicht vorhanden ist.
1580    // REVIEW: Checks absence of DTM+7 (Nutzungszeitpunkt) in the message, approximating the SG10-scoped check within the enclosing LIN group. Absence means the value is not a point-in-time value. (medium confidence)
1581    fn evaluate_147(&self, ctx: &EvaluationContext) -> ConditionResult {
1582        ctx.lacks_qualifier("DTM", 0, "7")
1583    }
1584
1585    /// [148] Wenn es bei dem zu übermittelnden Wert um einen Wert in einem Zeitintervall handelt.
1586    // REVIEW: An interval value (Wert in einem Zeitintervall) is signalled by the presence of DTM+163 (Beginn Messperiode) in SG10. If Beginn Messperiode is present, the value spans a time interval rather than being a single point. (medium confidence)
1587    fn evaluate_148(&self, ctx: &EvaluationContext) -> ConditionResult {
1588        ctx.has_qualifier("DTM", 0, "163")
1589    }
1590
1591    /// [149] Wenn in derselben S9 LIN das SG10 DTM+163 (Beginn Messperiode) vorhanden ist.
1592    // REVIEW: Checks presence of DTM+163 (Beginn Messperiode) — the direct inverse of condition 145. Message-wide approximation of the SG10-scoped intent within the enclosing LIN group. (medium confidence)
1593    fn evaluate_149(&self, ctx: &EvaluationContext) -> ConditionResult {
1594        ctx.has_qualifier("DTM", 0, "163")
1595    }
1596
1597    /// [150] Wenn BGM+Z69 (Redispatch tägliche Ausfallarbeitsüberführungszeitreihe) vorhanden.
1598    fn evaluate_150(&self, ctx: &EvaluationContext) -> ConditionResult {
1599        ctx.has_qualifier("BGM", 0, "Z69")
1600    }
1601
1602    /// [490] wenn Wert in diesem DE, an der Stelle CCYYMMDDHHMM ein Zeitpunkt aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Übersicht gesetzliche deutsche Sommerzeit (MESZ)“ der Spalten: „Sommerzei...
1603    fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
1604        let dtm_segs = ctx.find_segments("DTM");
1605        match dtm_segs
1606            .first()
1607            .and_then(|s| s.elements.first())
1608            .and_then(|e| e.get(1))
1609        {
1610            Some(val) => is_mesz_utc(val),
1611            None => ConditionResult::False, // segment absent → condition not applicable
1612        }
1613    }
1614
1615    /// [491] wenn Wert in diesem DE, an der Stelle CCYYMMDDHHMM ein Zeitpunkt aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Übersicht gesetzliche deutsche Zeit (MEZ)“ der Spalten: „Winterzeit (MEZ)...
1616    // REVIEW: Checks whether the datetime value falls within German winter time (MEZ, UTC+1) using the is_mez_utc helper. Medium confidence because 'diesem DE' is context-dependent — without knowing the exact field the condition is applied to in the AHB table, we check the first DTM value available. The is_mez_utc helper applies the EU DST rule to determine MEZ vs MESZ. (medium confidence)
1617    fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1618        let dtm_segs = ctx.find_segments("DTM");
1619        match dtm_segs
1620            .first()
1621            .and_then(|s| s.elements.first())
1622            .and_then(|e| e.get(1))
1623        {
1624            Some(val) => is_mez_utc(val),
1625            None => ConditionResult::False, // segment absent → condition not applicable
1626        }
1627    }
1628
1629    /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
1630    /// EXTERNAL: Requires context from outside the message.
1631    fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1632        ctx.external.evaluate("recipient_is_electricity_sector")
1633    }
1634
1635    /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
1636    /// EXTERNAL: Requires context from outside the message.
1637    fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1638        ctx.external.evaluate("recipient_is_gas_sector")
1639    }
1640
1641    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt.
1642    // REVIEW: 'Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt' — the date in this DE must be the document creation time or earlier. Implemented by comparing the target DTM value to DTM+137 (Nachrichtendatum / document creation date). Medium confidence because 'this DE' is context-dependent without the full AHB table cross-reference. String comparison on CCYYMMDDHHMM format is lexicographically valid for datetime ordering. (medium confidence)
1643    fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
1644        // The date in this DE must be the document creation time or a point before it
1645        // Verify by checking that the relevant DTM value <= DTM+137 (document creation date)
1646        let dtm_137_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1647        let creation_val = match dtm_137_segs
1648            .first()
1649            .and_then(|s| s.elements.first())
1650            .and_then(|e| e.get(1))
1651        {
1652            Some(v) if !v.is_empty() => v.clone(),
1653            _ => return ConditionResult::Unknown,
1654        };
1655        // Find the first non-137 DTM in the message to compare against creation date
1656        let all_dtms = ctx.find_segments("DTM");
1657        for dtm in &all_dtms {
1658            let qual = dtm
1659                .elements
1660                .first()
1661                .and_then(|e| e.first())
1662                .map(|s| s.as_str());
1663            if qual != Some("137") {
1664                if let Some(val) = dtm.elements.first().and_then(|e| e.get(1)) {
1665                    if !val.is_empty() {
1666                        return ConditionResult::from(val.as_str() <= creation_val.as_str());
1667                    }
1668                }
1669            }
1670        }
1671        ConditionResult::Unknown
1672    }
1673
1674    /// [495] Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein
1675    // REVIEW: 'Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein' — the timestamp must be ≤ DTM+137's DE2380 value. The reference segment is explicitly DTM+137 (elements[0][0]=137, value at elements[0][1]). Implemented by extracting DTM+137's value and comparing the target timestamp (DTM+163 as the most likely MSCONS candidate for this constraint) using lexicographic string ordering, which is valid for CCYYMMDDHHMM format 303 strings. Medium confidence because the specific source DE this condition is attached to in the AHB is ambiguous without the full AHB table. (medium confidence)
1676    fn evaluate_495(&self, ctx: &EvaluationContext) -> ConditionResult {
1677        let dtm_137_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1678        let threshold = match dtm_137_segs
1679            .first()
1680            .and_then(|s| s.elements.first())
1681            .and_then(|e| e.get(1))
1682        {
1683            Some(v) if !v.is_empty() => v.clone(),
1684            _ => return ConditionResult::Unknown,
1685        };
1686        // The timestamp in this DE must be <= DE2380 of DTM+137
1687        // For MSCONS, the most likely candidate is DTM+163 (Beginn Messperiode)
1688        let dtm_163_segs = ctx.find_segments_with_qualifier("DTM", 0, "163");
1689        match dtm_163_segs
1690            .first()
1691            .and_then(|s| s.elements.first())
1692            .and_then(|e| e.get(1))
1693        {
1694            Some(val) if !val.is_empty() => {
1695                ConditionResult::from(val.as_str() <= threshold.as_str())
1696            }
1697            _ => ConditionResult::Unknown,
1698        }
1699    }
1700
1701    /// [501] Hinweis: Es sind nur die Werte erlaubt, die in der EDI@Energy Codeliste der OBIS-Kennzahlen und Medien mit dem entsprechenden Prüfidentifikator versehen sind.
1702    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1703        // Hinweis: Informational note about allowed OBIS codes — always applies unconditionally
1704        ConditionResult::True
1705    }
1706
1707    /// [502] Hinweis: Einmal für die Energiemenge von Beginn des Kalenderjahres bis zum Lieferbeginn und bis zu zweimal für die zwei höchsten Monatsleistungswerte (wegen KAV) von Beginn des Kalenderjahres bi...
1708    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1709        // Hinweis: Informational note about cardinality (once for energy from start of calendar year,
1710        // up to twice for two highest monthly peak values due to KAV) — always applies unconditionally
1711        ConditionResult::True
1712    }
1713
1714    /// [506] Hinweis: Nur bei Einspeisemengen und bei Gas zur stündlichen Energiedatenübermittlung
1715    fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
1716        // Hinweis: Informational note — only for feed-in quantities and gas hourly energy data transmission
1717        // This is an informational annotation, not a boolean predicate — always applies
1718        ConditionResult::True
1719    }
1720
1721    /// [509] Hinweis: Falls es sich um eine Korrekturenergiemenge handelt, ist hier die Referenz auf die MSCONS anzugeben, in der der Zählerstand vorab übermittelt wurde.
1722    fn evaluate_509(&self, _ctx: &EvaluationContext) -> ConditionResult {
1723        // Hinweis: Informational note — for correction energy amounts, reference the prior MSCONS
1724        // in which the meter reading was transmitted. Informational annotation, always applies.
1725        ConditionResult::True
1726    }
1727
1728    /// [510] Hinweis: Verwendung der ID der Messlokation
1729    fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
1730        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1731        ConditionResult::True
1732    }
1733
1734    /// [511] Hinweis: Verwendung der ID des MaBiS-ZP
1735    fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
1736        // Hinweis: Verwendung der ID des MaBiS-ZP — informational note, always applies
1737        ConditionResult::True
1738    }
1739
1740    /// [512] Hinweis: Verwendung der Bilanzkreisbezeichnung
1741    fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
1742        // Hinweis: Verwendung der Bilanzkreisbezeichnung — informational note, always applies
1743        ConditionResult::True
1744    }
1745
1746    /// [513] Hinweis: Verwendung der Bezeichnung des Bilanzierungsgebietes
1747    fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
1748        // Hinweis: Verwendung der Bezeichnung des Bilanzierungsgebietes — informational note, always applies
1749        ConditionResult::True
1750    }
1751
1752    /// [514] Hinweis: Verwendung der ID der Marktlokation
1753    fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
1754        // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1755        ConditionResult::True
1756    }
1757
1758    /// [515] Hinweis: Verwendung der Profilbezeichnung
1759    fn evaluate_515(&self, _ctx: &EvaluationContext) -> ConditionResult {
1760        // Hinweis: Verwendung der Profilbezeichnung — informational note, always applies
1761        ConditionResult::True
1762    }
1763
1764    /// [516] Hinweis: Verwendung der Bezeichnung der Profilschar
1765    fn evaluate_516(&self, _ctx: &EvaluationContext) -> ConditionResult {
1766        // Hinweis: Verwendung der Bezeichnung der Profilschar — informational note, always applies
1767        ConditionResult::True
1768    }
1769
1770    /// [518] Hinweis: Verwendung der ID der Tranche
1771    fn evaluate_518(&self, _ctx: &EvaluationContext) -> ConditionResult {
1772        // Hinweis: Verwendung der ID der Tranche — informational note, always applies
1773        ConditionResult::True
1774    }
1775
1776    /// [519] Hinweis: Nur wenn der gemessene Lastgang der Messlokation nicht dem Lastgang der Marktlokation 1:1 entspricht.
1777    fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
1778        // Hinweis: Nur wenn der gemessene Lastgang der Messlokation nicht dem Lastgang der Marktlokation 1:1 entspricht — informational note, always applies
1779        ConditionResult::True
1780    }
1781
1782    /// [520] Hinweis: Wenn es sich um eine 1:1 Beziehung zwischen Messlokation und Marktlokation handelt und der gemessene Lastgang der Messlokation dem Lastgang der Marktlokation 1:1 entspricht, oder wenn der ...
1783    fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
1784        // Hinweis: Wenn es sich um eine 1:1 Beziehung zwischen Messlokation und Marktlokation handelt... — informational note, always applies
1785        ConditionResult::True
1786    }
1787
1788    /// [522] Hinweis: Nur für die Übermittlung der Korrekturenergiemengen im Zeitintervall zwischen zwei Messwerten.
1789    fn evaluate_522(&self, _ctx: &EvaluationContext) -> ConditionResult {
1790        // Hinweis: Nur für die Übermittlung der Korrekturenergiemengen im Zeitintervall zwischen zwei Messwerten.
1791        ConditionResult::True
1792    }
1793
1794    /// [523] Hinweis: Nur für die Übermittlung der Energiemenge im Zeitintervall zwischen zwei Messwerten vor der Netznutzungsabrechnung.
1795    fn evaluate_523(&self, _ctx: &EvaluationContext) -> ConditionResult {
1796        // Hinweis: Nur für die Übermittlung der Energiemenge im Zeitintervall zwischen zwei Messwerten vor der Netznutzungsabrechnung.
1797        ConditionResult::True
1798    }
1799
1800    /// [524] Hinweis: Nur, wenn es sich um die Übermittlung von Abrechnungsbrennwert und Z-Zahl für den vom Lieferanten über eine Geschäftsdatenanfrage angeforderten Zeitraum handelt.
1801    fn evaluate_524(&self, _ctx: &EvaluationContext) -> ConditionResult {
1802        // Hinweis: Nur, wenn es sich um die Übermittlung von Abrechnungsbrennwert und Z-Zahl für den vom Lieferanten über eine Geschäftsdatenanfrage angeforderten Zeitraum handelt.
1803        ConditionResult::True
1804    }
1805
1806    /// [525] Hinweis: Nur für die Übermittlung der Energiemenge im Zeitintervall für eine Marktlokation ohne Messlokation (Pauschalanlage) wenn eines der Ereignisse aus Kapitel 4.2 eingetreten ist.
1807    fn evaluate_525(&self, _ctx: &EvaluationContext) -> ConditionResult {
1808        // Hinweis: Nur für die Übermittlung der Energiemenge im Zeitintervall für eine Marktlokation ohne Messlokation (Pauschalanlage) wenn eines der Ereignisse aus Kapitel 4.2 eingetreten ist.
1809        ConditionResult::True
1810    }
1811
1812    /// [526] Hinweis: Wert aus BGM+Z24 DE1004 der ORDERS mit der die Allokationsliste bestellt wurde.
1813    fn evaluate_526(&self, _ctx: &EvaluationContext) -> ConditionResult {
1814        // Hinweis: Wert aus BGM+Z24 DE1004 der ORDERS mit der die Allokationsliste bestellt wurde.
1815        ConditionResult::True
1816    }
1817
1818    /// [528] Hinweis: Wert aus BGM+Z28 DE1004 der ORDERS mit der die Anforderung von Messwerten erfolgt ist.
1819    fn evaluate_528(&self, _ctx: &EvaluationContext) -> ConditionResult {
1820        // Hinweis: Wert aus BGM+Z28 DE1004 der ORDERS mit der die Anforderung von Messwerten erfolgt ist.
1821        // Informational note, always applies
1822        ConditionResult::True
1823    }
1824
1825    /// [529] Hinweis: Wert aus BGM+7 DE1004 der ORDERS mit der die Anforderung von Messwerten erfolgt ist.
1826    fn evaluate_529(&self, _ctx: &EvaluationContext) -> ConditionResult {
1827        // Hinweis: Wert aus BGM+7 DE1004 der ORDERS mit der die Anforderung von Messwerten erfolgt ist.
1828        // Informational note, always applies
1829        ConditionResult::True
1830    }
1831
1832    /// [530] Hinweis: Wert aus SG4 IDE+24 DE7402 der UTILMD mit dem der Sender der MSCONS die vorherigen Stammdaten mittels UTILMD übermittelt hat.
1833    fn evaluate_530(&self, _ctx: &EvaluationContext) -> ConditionResult {
1834        // Hinweis: Wert aus SG4 IDE+24 DE7402 der UTILMD mit dem der Sender der MSCONS die vorherigen Stammdaten mittels UTILMD übermittelt hat.
1835        // Informational note, always applies
1836        ConditionResult::True
1837    }
1838
1839    /// [531] Hinweis: Wert aus BGM+7 DE1004 der MSCONS mit der der Zählerstand übermittelt wurde.
1840    fn evaluate_531(&self, _ctx: &EvaluationContext) -> ConditionResult {
1841        // Hinweis: Wert aus BGM+7 DE1004 der MSCONS mit der der Zählerstand übermittelt wurde.
1842        // Informational note, always applies
1843        ConditionResult::True
1844    }
1845
1846    /// [532] Hinweis: Wert aus BGM+7/Z27/Z28/270/Z41/Z42/Z85 DE1004 der MSCONS Nachricht, die storniert wird
1847    fn evaluate_532(&self, _ctx: &EvaluationContext) -> ConditionResult {
1848        // Hinweis: Wert aus BGM+7/Z27/Z28/270/Z41/Z42/Z85 DE1004 der MSCONS Nachricht, die storniert wird
1849        // Informational note, always applies
1850        ConditionResult::True
1851    }
1852
1853    /// [535] Hinweis: Verwendung der ID des Netzkoppelpunktes Strom/Gas
1854    fn evaluate_535(&self, _ctx: &EvaluationContext) -> ConditionResult {
1855        // Hinweis: Verwendung der ID des Netzkoppelpunktes Strom/Gas — informational note, always applies
1856        ConditionResult::True
1857    }
1858
1859    /// [538] Hinweis: Die Referenz auf die ORDERS ist nur dann anzugeben, wenn diese Werte vom Empfänger auch ursprünglich mittels ORDERS angefragt wurden.
1860    fn evaluate_538(&self, _ctx: &EvaluationContext) -> ConditionResult {
1861        // Hinweis: Die Referenz auf die ORDERS ist nur dann anzugeben, wenn diese Werte vom Empfänger auch ursprünglich mittels ORDERS angefragt wurden — informational note, always applies
1862        ConditionResult::True
1863    }
1864
1865    /// [541] Hinweis: Ein Korrekturgrund ist anzugeben, wenn: 1. ein bereits an den MP übermittelter vorläufiger Wert nach Stornierung durch einen Ersatzwert ersetzt wird, oder  2. ein bereits an den MP über...
1866    fn evaluate_541(&self, _ctx: &EvaluationContext) -> ConditionResult {
1867        // Hinweis: Ein Korrekturgrund ist anzugeben in bestimmten Ersatzwert/Stornierung-Szenarien — informational note, always applies
1868        ConditionResult::True
1869    }
1870
1871    /// [543] Hinweis: Wert aus BGM+Z23 DE1004 der ORDERS mit der die bilanzierte Menge bestellt wurde.
1872    fn evaluate_543(&self, _ctx: &EvaluationContext) -> ConditionResult {
1873        // Hinweis: Wert aus BGM+Z23 DE1004 der ORDERS mit der die bilanzierte Menge bestellt wurde — informational note, always applies
1874        ConditionResult::True
1875    }
1876
1877    /// [544] Hinweis: Bei einer Mengenaufteilung (z. B. Aufgrund einer Abgrenzung) für SG6 LOC+172 muss für den frühesten angegebenen Zeitpunkt zum Beginn des Zeitintervalls  (über alle Wiederholungen der L...
1878    fn evaluate_544(&self, _ctx: &EvaluationContext) -> ConditionResult {
1879        // Hinweis: Bei einer Mengenaufteilung für SG6 LOC+172 muss für den frühesten Zeitpunkt zu jeder OBIS-Kennziffer ein Zählerstand vorhanden sein — informational note, always applies
1880        ConditionResult::True
1881    }
1882
1883    /// [545] Hinweis: Bei einer Mengenaufteilung (z. B. Aufgrund einer Abgrenzung) für SG6 LOC+172 muss für den spätesten angegebenen Zeitpunkt zum Ende des Zeitintervalls (über alle Wiederholungen der LIN-...
1884    fn evaluate_545(&self, _ctx: &EvaluationContext) -> ConditionResult {
1885        // Hinweis: informational note about Mengenaufteilung and Zählerstand requirement — always applies
1886        ConditionResult::True
1887    }
1888
1889    /// [546] Hinweis: Eine Referenz auf die Stammdatenänderung des Gerätewechsels ist immer anzugeben, wenn diese dem Sender vorliegt.
1890    fn evaluate_546(&self, _ctx: &EvaluationContext) -> ConditionResult {
1891        // Hinweis: informational note about referencing master data changes for device replacements — always applies
1892        ConditionResult::True
1893    }
1894
1895    /// [547] Hinweis: Der Code 270 ist nur zu nutzen, wenn ein Lieferschein, der vor dem 1.4.2021 erstellt wurde, storniert wird.
1896    fn evaluate_547(&self, _ctx: &EvaluationContext) -> ConditionResult {
1897        // Hinweis: informational note — code 270 only for cancelling delivery notes created before 1.4.2021 — always applies
1898        ConditionResult::True
1899    }
1900
1901    /// [551] Hinweis: Ein Korrekturgrund ist anzugeben, wenn: 1. ein bereits an den MP übermittelter vorläufiger Wert durch einen Ersatzwert ersetzt wird, oder  2. ein bereits an den MP übermittelter Ersatzw...
1902    fn evaluate_551(&self, _ctx: &EvaluationContext) -> ConditionResult {
1903        // Hinweis: informational note describing when a Korrekturgrund must be provided — always applies
1904        ConditionResult::True
1905    }
1906
1907    /// [553] Hinweis: Wert aus BGM+Z34 DE1004 der ORDERS mit der die Reklamation von Werten erfolgt ist
1908    fn evaluate_553(&self, _ctx: &EvaluationContext) -> ConditionResult {
1909        // Hinweis: informational note — value from BGM+Z34 DE1004 of the ORDERS used for value complaint — always applies
1910        ConditionResult::True
1911    }
1912
1913    /// [554] Hinweis: Verwendung der ID der Technischen Ressource
1914    fn evaluate_554(&self, _ctx: &EvaluationContext) -> ConditionResult {
1915        // Hinweis: Verwendung der ID der Technischen Ressource — informational note, always applies
1916        ConditionResult::True
1917    }
1918
1919    /// [556] Hinweis: Wert aus BGM+Z45 DE1004 der ORDERS mit der die Anforderung der Ausfallarbeit durch den anfNB erfolgt ist.
1920    fn evaluate_556(&self, _ctx: &EvaluationContext) -> ConditionResult {
1921        // Hinweis: Wert aus BGM+Z45 DE1004 der ORDERS — informational note, always applies
1922        ConditionResult::True
1923    }
1924
1925    /// [557] Hinweis: Die Referenz auf die ursprüngliche MSCONS ist anzugeben, wenn es sich um die Übermittlung eines Gegenvorschlags durch den BTR handelt.
1926    fn evaluate_557(&self, _ctx: &EvaluationContext) -> ConditionResult {
1927        // Hinweis: Referenz auf ursprüngliche MSCONS bei Gegenvorschlag — informational note, always applies
1928        ConditionResult::True
1929    }
1930
1931    /// [558] Hinweis: Wert aus BGM+Z45 DE1004 der MSCONS auf die sich die Übermittlung des Gegenvorschlags durch den BTR bezieht.
1932    fn evaluate_558(&self, _ctx: &EvaluationContext) -> ConditionResult {
1933        // Hinweis: Wert aus BGM+Z45 DE1004 der MSCONS bei Gegenvorschlag — informational note, always applies
1934        ConditionResult::True
1935    }
1936
1937    /// [559] Hinweis: Ein Korrekturgrund ist anzugeben, wenn: 1. ein bereits an den MP übermittelter vorläufiger Wert nach Stornierung durch einen Ersatzwert ersetzt wird, oder  2. ein bereits an den MP über...
1938    fn evaluate_559(&self, _ctx: &EvaluationContext) -> ConditionResult {
1939        // Hinweis: Korrekturgrund bei Ersatz/Stornierung von Werten — informational note, always applies
1940        ConditionResult::True
1941    }
1942
1943    /// [560] Hinweis: Ein Korrekturgrund ist anzugeben, wenn: 1. ein bereits an den MP übermittelter vorläufiger Wert durch einen Ersatzwert ersetzt wird, oder  2. ein bereits an den MP übermittelter Ersatzw...
1944    fn evaluate_560(&self, _ctx: &EvaluationContext) -> ConditionResult {
1945        // Hinweis: Ein Korrekturgrund ist anzugeben, wenn ein vorläufiger/Ersatz/wahrer Wert ersetzt wird — informational note, always applies
1946        ConditionResult::True
1947    }
1948
1949    /// [566] Hinweis: Es sind nur die Werte erlaubt, die im vorherigen Stammdatenaustausch zu diesem Meldepunkt vom MSB zum Zeitpunkt übermittelt wurden.
1950    fn evaluate_566(&self, _ctx: &EvaluationContext) -> ConditionResult {
1951        // Hinweis: Es sind nur die Werte erlaubt, die im vorherigen Stammdatenaustausch übermittelt wurden — informational note, always applies
1952        ConditionResult::True
1953    }
1954
1955    /// [567] Hinweis: Es ist die Konfigurations-ID anzugeben, die im vorherigen Stammdatenaustausch kommuniziert wurde.
1956    fn evaluate_567(&self, _ctx: &EvaluationContext) -> ConditionResult {
1957        // Hinweis: Es ist die Konfigurations-ID anzugeben, die im vorherigen Stammdatenaustausch kommuniziert wurde — informational note, always applies
1958        ConditionResult::True
1959    }
1960
1961    /// [568] Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung zwischen Markt- und Messlokation handelt und auf Ebene der Messlokation unterschiedliche Ersatzwertbildungsverfahren verwendet u...
1962    fn evaluate_568(&self, _ctx: &EvaluationContext) -> ConditionResult {
1963        // Hinweis: Verwendung nur zulässig bei 1:n Beziehung zwischen Markt- und Messlokation mit unterschiedlichen Ersatzwertbildungsverfahren — informational note, always applies
1964        ConditionResult::True
1965    }
1966
1967    /// [569] Hinweis: Bei mehreren Zählerständen einer Messlokation (z. B. HT/NT) ist diese Zeitangabe zu nutzen und eine Wiederholung das SG9 LIN durchzuführen.
1968    fn evaluate_569(&self, _ctx: &EvaluationContext) -> ConditionResult {
1969        // Hinweis: Bei mehreren Zählerständen einer Messlokation (z. B. HT/NT) ist diese Zeitangabe zu nutzen und SG9 LIN zu wiederholen — informational note, always applies
1970        ConditionResult::True
1971    }
1972
1973    /// [570] Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung zwischen Markt- und Messlokation handelt und auf Ebene der Messlokation unterschiedliche Gründe für die Ersatzwertbildung vorl...
1974    fn evaluate_570(&self, _ctx: &EvaluationContext) -> ConditionResult {
1975        // Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung zwischen Markt- und Messlokation handelt und auf Ebene der Messlokation unterschiedliche Gründe für die Ersatzwertbildung vorliegen und kommuniziert wurden.
1976        ConditionResult::True
1977    }
1978
1979    /// [571] Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung handelt und auf Ebene der Netzkopplungspunkte unterschiedliche Gründe für die Ersatzwertbildung vorliegen und kommuniziert wur...
1980    fn evaluate_571(&self, _ctx: &EvaluationContext) -> ConditionResult {
1981        // Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung handelt und auf Ebene der Netzkopplungspunkte unterschiedliche Gründe für die Ersatzwertbildung vorliegen und kommuniziert wurden.
1982        ConditionResult::True
1983    }
1984
1985    /// [572] Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung handelt und auf Ebene der Netzkopplungspunkte unterschiedliche Ersatzwertbildungsverfahren vorliegen und kommuniziert wurden.
1986    fn evaluate_572(&self, _ctx: &EvaluationContext) -> ConditionResult {
1987        // Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung handelt und auf Ebene der Netzkopplungspunkte unterschiedliche Ersatzwertbildungsverfahren vorliegen und kommuniziert wurden.
1988        ConditionResult::True
1989    }
1990
1991    /// [573] Hinweis: Eine Energiemenge in der Sparte Gas ist gemäß DVGW G685 Arbeitsblatt 4 Kapitel 5.3 auf ganze Kilowattstunden zu runden.
1992    fn evaluate_573(&self, _ctx: &EvaluationContext) -> ConditionResult {
1993        // Hinweis: Eine Energiemenge in der Sparte Gas ist gemäß DVGW G685 Arbeitsblatt 4 Kapitel 5.3 auf ganze Kilowattstunden zu runden.
1994        ConditionResult::True
1995    }
1996
1997    /// [574] Hinweis: Wert aus BGM DE1004 der ORDERS mit der die Bestellung der Werte nach Typ 2 erfolg ist
1998    fn evaluate_574(&self, _ctx: &EvaluationContext) -> ConditionResult {
1999        // Hinweis: Wert aus BGM DE1004 der ORDERS mit der die Bestellung der Werte nach Typ 2 erfolg ist
2000        ConditionResult::True
2001    }
2002
2003    /// [575] Hinweis: Verwendung der ID der Netzlokation
2004    fn evaluate_575(&self, _ctx: &EvaluationContext) -> ConditionResult {
2005        // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
2006        ConditionResult::True
2007    }
2008
2009    /// [576] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
2010    fn evaluate_576(&self, _ctx: &EvaluationContext) -> ConditionResult {
2011        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
2012        ConditionResult::True
2013    }
2014
2015    /// [577] Hinweis: Dieser Code ist auch zu verwenden, wenn aufgrund der Beendigung einer Messlokation (Stilllegung) die Beendigung der Marktlokation (Stilllegung) zu unterschiedlichen Zeitpunkten erfolgt, da...
2016    fn evaluate_577(&self, _ctx: &EvaluationContext) -> ConditionResult {
2017        // Hinweis: Informational note about Messlokation/Marktlokation termination timing — always applies
2018        ConditionResult::True
2019    }
2020
2021    /// [902] Format: Möglicher Wert: ≥ 0
2022    fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
2023        // Format: Möglicher Wert: ≥ 0
2024        let segs = ctx.find_segments("QTY");
2025        match segs
2026            .first()
2027            .and_then(|s| s.elements.first())
2028            .and_then(|e| e.get(1))
2029        {
2030            Some(val) => validate_numeric(val, ">=", 0.0),
2031            None => ConditionResult::False, // segment absent → condition not applicable
2032        }
2033    }
2034
2035    /// [904] Format: genau 16 Stellen
2036    // REVIEW: Format condition requiring exactly 16 characters. Applied to QTY value (most common numeric format condition target in MSCONS). Confidence is medium because the exact target segment/element is not specified in the condition text — QTY is the most likely candidate in MSCONS context. (medium confidence)
2037    fn evaluate_904(&self, ctx: &EvaluationContext) -> ConditionResult {
2038        // Format: genau 16 Stellen
2039        let segs = ctx.find_segments("QTY");
2040        match segs
2041            .first()
2042            .and_then(|s| s.elements.first())
2043            .and_then(|e| e.get(1))
2044        {
2045            Some(val) => validate_exact_length(val, 16),
2046            None => ConditionResult::False, // segment absent → condition not applicable
2047        }
2048    }
2049
2050    /// [905] Format: max. 3 Stellen
2051    fn evaluate_905(&self, ctx: &EvaluationContext) -> ConditionResult {
2052        let segs = ctx.find_segments("QTY");
2053        match segs
2054            .first()
2055            .and_then(|s| s.elements.first())
2056            .and_then(|e| e.get(1))
2057        {
2058            Some(val) => validate_max_integer_digits(val, 3),
2059            None => ConditionResult::False, // segment absent → condition not applicable
2060        }
2061    }
2062
2063    /// [906] Format: max. 3 Nachkommastellen
2064    fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
2065        let segs = ctx.find_segments("QTY");
2066        match segs
2067            .first()
2068            .and_then(|s| s.elements.first())
2069            .and_then(|e| e.get(1))
2070        {
2071            Some(val) => validate_max_decimal_places(val, 3),
2072            None => ConditionResult::False, // segment absent → condition not applicable
2073        }
2074    }
2075
2076    /// [907] Format: max. 4 Nachkommastellen
2077    fn evaluate_907(&self, ctx: &EvaluationContext) -> ConditionResult {
2078        let segs = ctx.find_segments("QTY");
2079        match segs
2080            .first()
2081            .and_then(|s| s.elements.first())
2082            .and_then(|e| e.get(1))
2083        {
2084            Some(val) => validate_max_decimal_places(val, 4),
2085            None => ConditionResult::False, // segment absent → condition not applicable
2086        }
2087    }
2088
2089    /// [908] Format: Mögliche Werte: 1 bis n
2090    fn evaluate_908(&self, ctx: &EvaluationContext) -> ConditionResult {
2091        let segs = ctx.find_segments("QTY");
2092        match segs
2093            .first()
2094            .and_then(|s| s.elements.first())
2095            .and_then(|e| e.get(1))
2096        {
2097            Some(val) => validate_numeric(val, ">=", 1.0),
2098            None => ConditionResult::False, // segment absent → condition not applicable
2099        }
2100    }
2101
2102    /// [909] Format: Mögliche Werte: 0 bis n
2103    fn evaluate_909(&self, ctx: &EvaluationContext) -> ConditionResult {
2104        let segs = ctx.find_segments("QTY");
2105        match segs
2106            .first()
2107            .and_then(|s| s.elements.first())
2108            .and_then(|e| e.get(1))
2109        {
2110            Some(val) => validate_numeric(val, ">=", 0.0),
2111            None => ConditionResult::False, // segment absent → condition not applicable
2112        }
2113    }
2114
2115    /// [910] Format: Möglicher Wert: &lt; 0 oder ≥ 0
2116    fn evaluate_910(&self, ctx: &EvaluationContext) -> ConditionResult {
2117        let qty_segments = ctx.find_segments("QTY");
2118        let val = qty_segments
2119            .first()
2120            .and_then(|s| s.elements.first())
2121            .and_then(|e| e.get(1))
2122            .map(|s| s.as_str())
2123            .unwrap_or("");
2124        if val.is_empty() {
2125            return ConditionResult::Unknown;
2126        }
2127        match val.parse::<f64>() {
2128            Ok(_) => ConditionResult::True,
2129            Err(_) => ConditionResult::False,
2130        }
2131    }
2132
2133    /// [917] Format: max. 4 Vorkommastellen
2134    fn evaluate_917(&self, ctx: &EvaluationContext) -> ConditionResult {
2135        let qty_segments = ctx.find_segments("QTY");
2136        match qty_segments
2137            .first()
2138            .and_then(|s| s.elements.first())
2139            .and_then(|e| e.get(1))
2140        {
2141            Some(val) => validate_max_integer_digits(val, 4),
2142            None => ConditionResult::False, // segment absent → condition not applicable
2143        }
2144    }
2145
2146    /// [918] Format: Zeichen aus dem über UNOC definierten Zeichensatz, wobei von den Buchstaben nur Großbuchstaben erlaubt sind.
2147    // REVIEW: UNOC charset requires uppercase-only alphabetics. Checks all alphabetic characters in FTX text fields (C108, elements[3], components 0..4) are uppercase. Non-alphabetic characters (digits, punctuation) are permitted. Medium confidence — UNOC validation approximated as uppercase-only alphabetic check without full ISO 8859-1 codepoint enumeration. (medium confidence)
2148    fn evaluate_918(&self, ctx: &EvaluationContext) -> ConditionResult {
2149        let ftx_segments = ctx.find_segments("FTX");
2150        for seg in &ftx_segments {
2151            if let Some(c108) = seg.elements.get(3) {
2152                for component in c108 {
2153                    let s = component.as_str();
2154                    if s.is_empty() {
2155                        continue;
2156                    }
2157                    if !s.chars().all(|c| !c.is_alphabetic() || c.is_uppercase()) {
2158                        return ConditionResult::False;
2159                    }
2160                }
2161            }
2162        }
2163        ConditionResult::True
2164    }
2165
2166    /// [922] Format: TR-ID
2167    // REVIEW: Format: TR-ID — validates that the value is a valid Transaktionsreferenz-ID (1–35 alphanumeric characters) using validate_tr_id. In MSCONS, the document/transaction ID is typically found in BGM elements[1][0] (the document number composite). Medium confidence because the exact DE this format condition is applied to depends on the AHB field context, which could alternatively be RFF or another reference segment. (medium confidence)
2168    fn evaluate_922(&self, ctx: &EvaluationContext) -> ConditionResult {
2169        let bgm_segs = ctx.find_segments("BGM");
2170        match bgm_segs
2171            .first()
2172            .and_then(|s| s.elements.get(1))
2173            .and_then(|e| e.first())
2174        {
2175            Some(val) => validate_tr_id(val),
2176            None => ConditionResult::False, // segment absent → condition not applicable
2177        }
2178    }
2179
2180    /// [925] Format: max. 5 Nachkommastellen
2181    fn evaluate_925(&self, ctx: &EvaluationContext) -> ConditionResult {
2182        let qty_segments = ctx.find_segments("QTY");
2183        match qty_segments
2184            .first()
2185            .and_then(|s| s.elements.first())
2186            .and_then(|e| e.get(1))
2187        {
2188            Some(val) => validate_max_decimal_places(val, 5),
2189            None => ConditionResult::False, // segment absent → condition not applicable
2190        }
2191    }
2192
2193    /// [931] Format: ZZZ = +00
2194    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
2195        let dtm_segs = ctx.find_segments("DTM");
2196        match dtm_segs
2197            .first()
2198            .and_then(|s| s.elements.first())
2199            .and_then(|e| e.get(1))
2200        {
2201            Some(val) => validate_timezone_utc(val),
2202            None => ConditionResult::False, // segment absent → condition not applicable
2203        }
2204    }
2205
2206    /// [932] Format: HHMM = 2200
2207    fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
2208        let dtm_segs = ctx.find_segments("DTM");
2209        match dtm_segs
2210            .first()
2211            .and_then(|s| s.elements.first())
2212            .and_then(|e| e.get(1))
2213        {
2214            Some(val) => validate_hhmm_equals(val, "2200"),
2215            None => ConditionResult::False, // segment absent → condition not applicable
2216        }
2217    }
2218
2219    /// [933] Format: HHMM = 2300
2220    fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
2221        let dtm_segs = ctx.find_segments("DTM");
2222        match dtm_segs
2223            .first()
2224            .and_then(|s| s.elements.first())
2225            .and_then(|e| e.get(1))
2226        {
2227            Some(val) => validate_hhmm_equals(val, "2300"),
2228            None => ConditionResult::False, // segment absent → condition not applicable
2229        }
2230    }
2231
2232    /// [934] Format: HHMM = 0400
2233    fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
2234        let dtm_segs = ctx.find_segments("DTM");
2235        match dtm_segs
2236            .first()
2237            .and_then(|s| s.elements.first())
2238            .and_then(|e| e.get(1))
2239        {
2240            Some(val) => validate_hhmm_equals(val, "0400"),
2241            None => ConditionResult::False, // segment absent → condition not applicable
2242        }
2243    }
2244
2245    /// [935] Format: HHMM = 0500
2246    fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
2247        let dtm_segs = ctx.find_segments("DTM");
2248        match dtm_segs
2249            .first()
2250            .and_then(|s| s.elements.first())
2251            .and_then(|e| e.get(1))
2252        {
2253            Some(val) => validate_hhmm_equals(val, "0500"),
2254            None => ConditionResult::False, // segment absent → condition not applicable
2255        }
2256    }
2257
2258    /// [937] Format: keine Nachkommastelle
2259    fn evaluate_937(&self, ctx: &EvaluationContext) -> ConditionResult {
2260        let segs = ctx.find_segments("QTY");
2261        match segs
2262            .first()
2263            .and_then(|s| s.elements.first())
2264            .and_then(|e| e.get(1))
2265        {
2266            Some(val) => validate_max_decimal_places(val, 0),
2267            None => ConditionResult::False, // segment absent → condition not applicable
2268        }
2269    }
2270
2271    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
2272    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
2273        let segs = ctx.find_segments("COM");
2274        match segs
2275            .first()
2276            .and_then(|s| s.elements.first())
2277            .and_then(|e| e.first())
2278        {
2279            Some(val) => validate_email(val),
2280            None => ConditionResult::False, // segment absent → condition not applicable
2281        }
2282    }
2283
2284    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
2285    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
2286        let segs = ctx.find_segments("COM");
2287        match segs
2288            .first()
2289            .and_then(|s| s.elements.first())
2290            .and_then(|e| e.first())
2291        {
2292            Some(val) => validate_phone(val),
2293            None => ConditionResult::False, // segment absent → condition not applicable
2294        }
2295    }
2296
2297    /// [950] Format: Marktlokations-ID
2298    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
2299        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z16");
2300        match segs
2301            .first()
2302            .and_then(|s| s.elements.get(1))
2303            .and_then(|e| e.first())
2304        {
2305            Some(val) => validate_malo_id(val),
2306            None => ConditionResult::False, // segment absent → condition not applicable
2307        }
2308    }
2309
2310    /// [951] Format: Zählpunktbezeichnung
2311    // REVIEW: Format: Zählpunktbezeichnung — 33 alphanumeric characters. In MSCONS, Zählpunktbezeichnung typically appears in LOC+Z19. Using validate_zahlpunkt helper. (medium confidence)
2312    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
2313        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z19");
2314        match segs
2315            .first()
2316            .and_then(|s| s.elements.get(1))
2317            .and_then(|e| e.first())
2318        {
2319            Some(val) => validate_zahlpunkt(val),
2320            None => ConditionResult::False, // segment absent → condition not applicable
2321        }
2322    }
2323
2324    /// [960] Format: Netzlokations-ID
2325    // REVIEW: Netzlokations-ID uses the same 11-digit Luhn-check format as Marktlokations-ID (validate_malo_id). In MSCONS, Netzlokation is typically referenced via LOC+Z17. Segment structure reference does not include LOC for MSCONS, so Z17 qualifier is assumed from German energy market convention. (medium confidence)
2326    fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
2327        // Format: Netzlokations-ID — 11-digit ID with Luhn check digit (same format as MaLo-ID)
2328        // In MSCONS, Netzlokation is identified by LOC+Z17
2329        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z17");
2330        match segs
2331            .first()
2332            .and_then(|s| s.elements.get(1))
2333            .and_then(|e| e.first())
2334        {
2335            Some(val) => validate_malo_id(val),
2336            None => ConditionResult::False, // segment absent → condition not applicable
2337        }
2338    }
2339
2340    /// [2001] Segmentgruppe ist nur einmal je UNH anzugeben
2341    fn evaluate_2001(&self, _ctx: &EvaluationContext) -> ConditionResult {
2342        // Hinweis: Segmentgruppe ist nur einmal je UNH anzugeben — cardinality annotation, always applies
2343        ConditionResult::True
2344    }
2345
2346    /// [2002] Segmentgruppe ist bis zu drei Mal je SG5 NAD+DP anzugeben
2347    fn evaluate_2002(&self, _ctx: &EvaluationContext) -> ConditionResult {
2348        // Hinweis: Segmentgruppe ist bis zu drei Mal je SG5 NAD+DP anzugeben — cardinality annotation, always applies
2349        ConditionResult::True
2350    }
2351
2352    /// [2003] Segmentgruppe ist genau zwei Mal je SG9 LIN anzugeben
2353    fn evaluate_2003(&self, _ctx: &EvaluationContext) -> ConditionResult {
2354        // Hinweis: Segmentgruppe ist genau zwei Mal je SG9 LIN anzugeben — cardinality annotation, always applies
2355        ConditionResult::True
2356    }
2357}