Skip to main content

automapper_validation/generated/fv2504/
mscons_conditions_fv2504.rs

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