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