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