Skip to main content

automapper_validation/generated/fv2510/
mscons_conditions_fv2510.rs

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