Skip to main content

automapper_validation/generated/fv2504/
mscons_conditions_fv2504.rs

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