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