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