Skip to main content

automapper_validation/generated/fv2510/
iftsta_conditions_fv2510.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2510/IFTSTA_AHB_2_0h_Fehlerkorrektur_20250623.xml
4// Generated: 2026-03-12T10:40:40Z
5// </auto-generated>
6
7#[allow(unused_imports)]
8use crate::eval::format_validators::*;
9use crate::eval::{ConditionEvaluator, ConditionResult, EvaluationContext};
10
11/// Generated condition evaluator for IFTSTA FV2510.
12pub struct IftstaConditionEvaluatorFV2510 {
13    // External condition IDs that require runtime context.
14    external_conditions: std::collections::HashSet<u32>,
15}
16
17impl Default for IftstaConditionEvaluatorFV2510 {
18    fn default() -> Self {
19        let mut external_conditions = std::collections::HashSet::new();
20        external_conditions.insert(1);
21        external_conditions.insert(8);
22        external_conditions.insert(10);
23        external_conditions.insert(16);
24        external_conditions.insert(17);
25        external_conditions.insert(20);
26        external_conditions.insert(26);
27        external_conditions.insert(27);
28        external_conditions.insert(28);
29        external_conditions.insert(29);
30        external_conditions.insert(72);
31        external_conditions.insert(76);
32        external_conditions.insert(86);
33        external_conditions.insert(114);
34        external_conditions.insert(115);
35        external_conditions.insert(117);
36        external_conditions.insert(492);
37        external_conditions.insert(493);
38        Self {
39            external_conditions,
40        }
41    }
42}
43
44impl ConditionEvaluator for IftstaConditionEvaluatorFV2510 {
45    fn message_type(&self) -> &str {
46        "IFTSTA"
47    }
48
49    fn format_version(&self) -> &str {
50        "FV2510"
51    }
52
53    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
54        match condition {
55            1 => self.evaluate_1(ctx),
56            3 => self.evaluate_3(ctx),
57            4 => self.evaluate_4(ctx),
58            5 => self.evaluate_5(ctx),
59            6 => self.evaluate_6(ctx),
60            7 => self.evaluate_7(ctx),
61            8 => self.evaluate_8(ctx),
62            10 => self.evaluate_10(ctx),
63            16 => self.evaluate_16(ctx),
64            17 => self.evaluate_17(ctx),
65            18 => self.evaluate_18(ctx),
66            19 => self.evaluate_19(ctx),
67            20 => self.evaluate_20(ctx),
68            23 => self.evaluate_23(ctx),
69            26 => self.evaluate_26(ctx),
70            27 => self.evaluate_27(ctx),
71            28 => self.evaluate_28(ctx),
72            29 => self.evaluate_29(ctx),
73            30 => self.evaluate_30(ctx),
74            31 => self.evaluate_31(ctx),
75            32 => self.evaluate_32(ctx),
76            33 => self.evaluate_33(ctx),
77            43 => self.evaluate_43(ctx),
78            44 => self.evaluate_44(ctx),
79            45 => self.evaluate_45(ctx),
80            46 => self.evaluate_46(ctx),
81            47 => self.evaluate_47(ctx),
82            48 => self.evaluate_48(ctx),
83            49 => self.evaluate_49(ctx),
84            50 => self.evaluate_50(ctx),
85            51 => self.evaluate_51(ctx),
86            52 => self.evaluate_52(ctx),
87            53 => self.evaluate_53(ctx),
88            54 => self.evaluate_54(ctx),
89            55 => self.evaluate_55(ctx),
90            56 => self.evaluate_56(ctx),
91            57 => self.evaluate_57(ctx),
92            58 => self.evaluate_58(ctx),
93            59 => self.evaluate_59(ctx),
94            60 => self.evaluate_60(ctx),
95            61 => self.evaluate_61(ctx),
96            62 => self.evaluate_62(ctx),
97            63 => self.evaluate_63(ctx),
98            64 => self.evaluate_64(ctx),
99            65 => self.evaluate_65(ctx),
100            66 => self.evaluate_66(ctx),
101            67 => self.evaluate_67(ctx),
102            68 => self.evaluate_68(ctx),
103            69 => self.evaluate_69(ctx),
104            70 => self.evaluate_70(ctx),
105            71 => self.evaluate_71(ctx),
106            72 => self.evaluate_72(ctx),
107            76 => self.evaluate_76(ctx),
108            77 => self.evaluate_77(ctx),
109            78 => self.evaluate_78(ctx),
110            79 => self.evaluate_79(ctx),
111            80 => self.evaluate_80(ctx),
112            83 => self.evaluate_83(ctx),
113            84 => self.evaluate_84(ctx),
114            85 => self.evaluate_85(ctx),
115            86 => self.evaluate_86(ctx),
116            91 => self.evaluate_91(ctx),
117            92 => self.evaluate_92(ctx),
118            93 => self.evaluate_93(ctx),
119            94 => self.evaluate_94(ctx),
120            95 => self.evaluate_95(ctx),
121            96 => self.evaluate_96(ctx),
122            97 => self.evaluate_97(ctx),
123            98 => self.evaluate_98(ctx),
124            99 => self.evaluate_99(ctx),
125            100 => self.evaluate_100(ctx),
126            101 => self.evaluate_101(ctx),
127            103 => self.evaluate_103(ctx),
128            107 => self.evaluate_107(ctx),
129            114 => self.evaluate_114(ctx),
130            115 => self.evaluate_115(ctx),
131            117 => self.evaluate_117(ctx),
132            118 => self.evaluate_118(ctx),
133            119 => self.evaluate_119(ctx),
134            120 => self.evaluate_120(ctx),
135            121 => self.evaluate_121(ctx),
136            129 => self.evaluate_129(ctx),
137            130 => self.evaluate_130(ctx),
138            131 => self.evaluate_131(ctx),
139            132 => self.evaluate_132(ctx),
140            133 => self.evaluate_133(ctx),
141            134 => self.evaluate_134(ctx),
142            135 => self.evaluate_135(ctx),
143            136 => self.evaluate_136(ctx),
144            137 => self.evaluate_137(ctx),
145            138 => self.evaluate_138(ctx),
146            139 => self.evaluate_139(ctx),
147            140 => self.evaluate_140(ctx),
148            141 => self.evaluate_141(ctx),
149            142 => self.evaluate_142(ctx),
150            143 => self.evaluate_143(ctx),
151            144 => self.evaluate_144(ctx),
152            145 => self.evaluate_145(ctx),
153            146 => self.evaluate_146(ctx),
154            147 => self.evaluate_147(ctx),
155            148 => self.evaluate_148(ctx),
156            149 => self.evaluate_149(ctx),
157            150 => self.evaluate_150(ctx),
158            151 => self.evaluate_151(ctx),
159            152 => self.evaluate_152(ctx),
160            153 => self.evaluate_153(ctx),
161            154 => self.evaluate_154(ctx),
162            490 => self.evaluate_490(ctx),
163            491 => self.evaluate_491(ctx),
164            492 => self.evaluate_492(ctx),
165            493 => self.evaluate_493(ctx),
166            494 => self.evaluate_494(ctx),
167            495 => self.evaluate_495(ctx),
168            496 => self.evaluate_496(ctx),
169            501 => self.evaluate_501(ctx),
170            502 => self.evaluate_502(ctx),
171            503 => self.evaluate_503(ctx),
172            504 => self.evaluate_504(ctx),
173            505 => self.evaluate_505(ctx),
174            506 => self.evaluate_506(ctx),
175            510 => self.evaluate_510(ctx),
176            512 => self.evaluate_512(ctx),
177            519 => self.evaluate_519(ctx),
178            520 => self.evaluate_520(ctx),
179            521 => self.evaluate_521(ctx),
180            522 => self.evaluate_522(ctx),
181            523 => self.evaluate_523(ctx),
182            524 => self.evaluate_524(ctx),
183            525 => self.evaluate_525(ctx),
184            530 => self.evaluate_530(ctx),
185            531 => self.evaluate_531(ctx),
186            532 => self.evaluate_532(ctx),
187            533 => self.evaluate_533(ctx),
188            534 => self.evaluate_534(ctx),
189            535 => self.evaluate_535(ctx),
190            537 => self.evaluate_537(ctx),
191            538 => self.evaluate_538(ctx),
192            902 => self.evaluate_902(ctx),
193            903 => self.evaluate_903(ctx),
194            906 => self.evaluate_906(ctx),
195            911 => self.evaluate_911(ctx),
196            931 => self.evaluate_931(ctx),
197            932 => self.evaluate_932(ctx),
198            933 => self.evaluate_933(ctx),
199            934 => self.evaluate_934(ctx),
200            935 => self.evaluate_935(ctx),
201            939 => self.evaluate_939(ctx),
202            940 => self.evaluate_940(ctx),
203            950 => self.evaluate_950(ctx),
204            951 => self.evaluate_951(ctx),
205            960 => self.evaluate_960(ctx),
206            961 => self.evaluate_961(ctx),
207            _ => ConditionResult::Unknown,
208        }
209    }
210
211    fn is_external(&self, condition: u32) -> bool {
212        self.external_conditions.contains(&condition)
213    }
214    fn is_known(&self, condition: u32) -> bool {
215        matches!(
216            condition,
217            1 | 3
218                | 4
219                | 5
220                | 6
221                | 7
222                | 8
223                | 10
224                | 16
225                | 17
226                | 18
227                | 19
228                | 20
229                | 23
230                | 26
231                | 27
232                | 28
233                | 29
234                | 30
235                | 31
236                | 32
237                | 33
238                | 43
239                | 44
240                | 45
241                | 46
242                | 47
243                | 48
244                | 49
245                | 50
246                | 51
247                | 52
248                | 53
249                | 54
250                | 55
251                | 56
252                | 57
253                | 58
254                | 59
255                | 60
256                | 61
257                | 62
258                | 63
259                | 64
260                | 65
261                | 66
262                | 67
263                | 68
264                | 69
265                | 70
266                | 71
267                | 72
268                | 76
269                | 77
270                | 78
271                | 79
272                | 80
273                | 83
274                | 84
275                | 85
276                | 86
277                | 91
278                | 92
279                | 93
280                | 94
281                | 95
282                | 96
283                | 97
284                | 98
285                | 99
286                | 100
287                | 101
288                | 103
289                | 107
290                | 114
291                | 115
292                | 117
293                | 118
294                | 119
295                | 120
296                | 121
297                | 129
298                | 130
299                | 131
300                | 132
301                | 133
302                | 134
303                | 135
304                | 136
305                | 137
306                | 138
307                | 139
308                | 140
309                | 141
310                | 142
311                | 143
312                | 144
313                | 145
314                | 146
315                | 147
316                | 148
317                | 149
318                | 150
319                | 151
320                | 152
321                | 153
322                | 154
323                | 490
324                | 491
325                | 492
326                | 493
327                | 494
328                | 495
329                | 496
330                | 501
331                | 502
332                | 503
333                | 504
334                | 505
335                | 506
336                | 510
337                | 512
338                | 519
339                | 520
340                | 521
341                | 522
342                | 523
343                | 524
344                | 525
345                | 530
346                | 531
347                | 532
348                | 533
349                | 534
350                | 535
351                | 537
352                | 538
353                | 902
354                | 903
355                | 906
356                | 911
357                | 931
358                | 932
359                | 933
360                | 934
361                | 935
362                | 939
363                | 940
364                | 950
365                | 951
366                | 960
367                | 961
368        )
369    }
370}
371
372impl IftstaConditionEvaluatorFV2510 {
373    /// [6] Wenn für das 3-Tupel (MaBiS-ZP, Betrachtungszeitraum, Version der Summenzeitreihe) dem BIKO der Prüfstatus vorliegt, so ist dieser immer zusammen mit dem Datenstatus zu übertragen.
374    // REVIEW: The condition states that when BIKO has a Prüfstatus for the 3-tuple (MaBiS-ZP, Betrachtungszeitraum, Version), it must always be transmitted together with the Datenstatus. Interpreted as: this condition is True when STS with Z03 category (Prüfstatus zur Summenzeitreihe) is present in the message, triggering the co-transmission requirement. The BIKO role context is implicit in the message structure. (medium confidence)
375    fn evaluate_6(&self, ctx: &EvaluationContext) -> ConditionResult {
376        // Condition is True when STS+Z03 (Prüfstatus zur Summenzeitreihe) is present,
377        // signalling that Datenstatus must also be transmitted alongside it.
378        ctx.has_qualifier("STS", 0, "Z03")
379    }
380
381    /// [7] Wenn der Datenstatus "Abgerechnete Daten" bzw. "Abgerechnete Daten Korrektur-BKA" nicht vorhanden.
382    fn evaluate_7(&self, _ctx: &EvaluationContext) -> ConditionResult {
383        // TODO: Condition [7] requires manual implementation
384        // Reason: The condition checks for absence of Datenstatus values 'Abgerechnete Daten' and 'Abgerechnete Daten Korrektur-BKA'. These are specific code values in STS+Z04 at elements[1][1] (DE1131), but the exact BDEW code list values (e.g. E_0042, E_0043 are plausible guesses) cannot be confirmed from the MIG segment structure reference alone. Requires the full AHB code table to implement correctly.
385        ConditionResult::Unknown
386    }
387
388    /// [8] Wenn der Datenstatus einer NZR vom BIKO an NB nicht vorhanden.
389    /// EXTERNAL: Requires context from outside the message.
390    fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
391        ctx.external.evaluate("datenstatus_nzr_biko_to_nb_missing")
392    }
393
394    /// [23] Wenn SG15 STS+Z25+Z31+A06 / A09 / A10 / A11 / A13 / A14 / A15 / A16 vorhanden.
395    fn evaluate_23(&self, ctx: &EvaluationContext) -> ConditionResult {
396        // STS+Z25+Z31+A06/A09/A10/A11/A13/A14/A15/A16
397        // Per segment reference for 'Status des Lieferscheins':
398        //   elements[0][0] = Z25 (Statuskategorie)
399        //   elements[1][0] = Z31 (Status, Code)
400        //   elements[2][0] = Statusanlaß code (A06 etc.)
401        let target_causes = ["A06", "A09", "A10", "A11", "A13", "A14", "A15", "A16"];
402        let segs = ctx.find_segments("STS");
403        if segs.is_empty() {
404            return ConditionResult::Unknown;
405        }
406        let found = segs.iter().any(|seg| {
407            seg.elements
408                .first()
409                .and_then(|e| e.first())
410                .is_some_and(|v| v == "Z25")
411                && seg
412                    .elements
413                    .get(1)
414                    .and_then(|e| e.first())
415                    .is_some_and(|v| v == "Z31")
416                && seg
417                    .elements
418                    .get(2)
419                    .and_then(|e| e.first())
420                    .is_some_and(|v| target_causes.contains(&v.as_str()))
421        });
422        ConditionResult::from(found)
423    }
424
425    /// [103] Wenn in diesem STS DE1131 nicht vorhanden
426    // REVIEW: DE1131 (Codeliste, Code) appears at elements[2][1] in the majority of STS variants per the segment structure reference (C556, second component). Condition is True when that position is absent or empty. The 'in diesem STS' scope cannot be fully resolved without a current-segment concept in EvaluationContext, so this checks all STS segments. Note: STS+Z04 has DE1131 at elements[1][1] instead — this edge case is not covered. (medium confidence)
427    fn evaluate_103(&self, ctx: &EvaluationContext) -> ConditionResult {
428        // DE1131 (Codeliste, Code) is at elements[2][1] (C556 component [1]) in most STS variants.
429        // Condition is True when DE1131 is absent or empty in the relevant STS.
430        let segs = ctx.find_segments("STS");
431        if segs.is_empty() {
432            return ConditionResult::Unknown;
433        }
434        for seg in &segs {
435            let de1131 = seg.elements.get(2).and_then(|e| e.get(1));
436            if de1131.map(|v| v.is_empty()).unwrap_or(true) {
437                return ConditionResult::True;
438            }
439        }
440        ConditionResult::False
441    }
442
443    /// [149] Wenn in dieser SG14 STS DE1131 &lt;&gt; E_0278
444    // REVIEW: Checks DE1131 (elements[2][1]) in STS segments within SG14. Returns True if any STS has DE1131 present and not equal to E_0278, False if all present values equal E_0278, Unknown if no STS has DE1131 set. Uses navigator for group-scoped check with message-wide fallback. (medium confidence)
445    fn evaluate_149(&self, ctx: &EvaluationContext) -> ConditionResult {
446        let nav = match ctx.navigator() {
447            Some(n) => n,
448            None => {
449                let segs = ctx.find_segments("STS");
450                let mut found_any = false;
451                for seg in &segs {
452                    if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
453                        if !val.is_empty() {
454                            found_any = true;
455                            if val != "E_0278" {
456                                return ConditionResult::True;
457                            }
458                        }
459                    }
460                }
461                return if found_any {
462                    ConditionResult::False
463                } else {
464                    ConditionResult::Unknown
465                };
466            }
467        };
468        let sg14_count = nav.group_instance_count(&["SG14"]);
469        let mut found_any = false;
470        for i in 0..sg14_count {
471            let sts_segs = nav.find_segments_in_group("STS", &["SG14"], i);
472            for seg in &sts_segs {
473                if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
474                    if !val.is_empty() {
475                        found_any = true;
476                        if val != "E_0278" {
477                            return ConditionResult::True;
478                        }
479                    }
480                }
481            }
482        }
483        if found_any {
484            ConditionResult::False
485        } else {
486            ConditionResult::Unknown
487        }
488    }
489
490    /// [150] Wenn in dieser SG14 STS DE1131 &lt;&gt; E_0281
491    // REVIEW: Identical pattern to condition 149 but checks DE1131 != E_0281. Returns True if any STS in SG14 has DE1131 present and not equal to E_0281, False if all equal E_0281, Unknown if absent. (medium confidence)
492    fn evaluate_150(&self, ctx: &EvaluationContext) -> ConditionResult {
493        let nav = match ctx.navigator() {
494            Some(n) => n,
495            None => {
496                let segs = ctx.find_segments("STS");
497                let mut found_any = false;
498                for seg in &segs {
499                    if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
500                        if !val.is_empty() {
501                            found_any = true;
502                            if val != "E_0281" {
503                                return ConditionResult::True;
504                            }
505                        }
506                    }
507                }
508                return if found_any {
509                    ConditionResult::False
510                } else {
511                    ConditionResult::Unknown
512                };
513            }
514        };
515        let sg14_count = nav.group_instance_count(&["SG14"]);
516        let mut found_any = false;
517        for i in 0..sg14_count {
518            let sts_segs = nav.find_segments_in_group("STS", &["SG14"], i);
519            for seg in &sts_segs {
520                if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
521                    if !val.is_empty() {
522                        found_any = true;
523                        if val != "E_0281" {
524                            return ConditionResult::True;
525                        }
526                    }
527                }
528            }
529        }
530        if found_any {
531            ConditionResult::False
532        } else {
533            ConditionResult::Unknown
534        }
535    }
536
537    /// [151] Wenn STS+Z20+Z32+A99:E_0278 in dieser SG14 vorhanden
538    fn evaluate_151(&self, ctx: &EvaluationContext) -> ConditionResult {
539        // STS+Z20+Z32+A99:E_0278: elements[0][0]=Z20, elements[1][0]=Z32, elements[2][0]=A99, elements[2][1]=E_0278
540        ctx.has_segment_matching_in_group(
541            "STS",
542            &[
543                (0, 0, "Z20"),
544                (1, 0, "Z32"),
545                (2, 0, "A99"),
546                (2, 1, "E_0278"),
547            ],
548            &["SG14"],
549        )
550    }
551
552    /// [152] Wenn STS+Z20+Z32+A99:E_0281 in dieser SG14 vorhanden
553    fn evaluate_152(&self, ctx: &EvaluationContext) -> ConditionResult {
554        // STS+Z20+Z32+A99:E_0281: elements[0][0]=Z20, elements[1][0]=Z32, elements[2][0]=A99, elements[2][1]=E_0281
555        ctx.has_segment_matching_in_group(
556            "STS",
557            &[
558                (0, 0, "Z20"),
559                (1, 0, "Z32"),
560                (2, 0, "A99"),
561                (2, 1, "E_0281"),
562            ],
563            &["SG14"],
564        )
565    }
566
567    /// [153] Wenn in diesem STS DE1131 = E_0286
568    fn evaluate_153(&self, ctx: &EvaluationContext) -> ConditionResult {
569        // DE1131 is at elements[2][1] in STS (C556 Statusanlaß component 1)
570        ctx.has_segment_matching("STS", &[(2, 1, "E_0286")])
571    }
572
573    /// [154] Wenn STS+Z15+Z13+A99:E_0286 in dieser SG14 vorhanden
574    // REVIEW: Checks for STS in SG14/SG15 with Statuskategorie Z15 (Status des Umbaus der Messlokation), Status Z13, and Statusanlaß A99:E_0286. Uses has_segment_matching_in_group with authoritative element indices from the segment reference: elements[0][0]=Z15, elements[1][0]=Z13, elements[2][0]=A99 (DE9013 Prüfschritt), elements[2][1]=E_0286 (DE1131 Codeliste). Medium confidence because A99 is not listed in the abbreviated reference for this STS variant (reference shows Z74/Z75), so it may be an additional valid code from the full BDEW codelist. (medium confidence)
575    fn evaluate_154(&self, ctx: &EvaluationContext) -> ConditionResult {
576        ctx.has_segment_matching_in_group(
577            "STS",
578            &[
579                (0, 0, "Z15"),
580                (1, 0, "Z13"),
581                (2, 0, "A99"),
582                (2, 1, "E_0286"),
583            ],
584            &["SG14", "SG15"],
585        )
586    }
587
588    /// [537] Hinweis: Der Code ist nötig, da dieser Anwendungsfall auch in allen Use-Cases zur Anwendung kommt, in denen sich die in diesem Segment zu übertragende Information nicht anhand eines Entscheidungs...
589    fn evaluate_537(&self, _ctx: &EvaluationContext) -> ConditionResult {
590        // Hinweis: Der Code ist nötig, da dieser Anwendungsfall auch in allen Use-Cases zur Anwendung kommt, in denen sich die in diesem Segment zu übertragende Information nicht anhand eines Entscheidungsbaus ergibt.
591        ConditionResult::True
592    }
593
594    /// [538] Hinweis: An dieser Stelle wird der Code aus dem EBD übermittelt
595    fn evaluate_538(&self, _ctx: &EvaluationContext) -> ConditionResult {
596        // Hinweis: An dieser Stelle wird der Code aus dem EBD übermittelt
597        ConditionResult::True
598    }
599
600    /// [1] Wenn die Übermittlung nicht codierbarer Informationen nötig ist.
601    /// EXTERNAL: Requires context from outside the message.
602    // REVIEW: Whether transmission of non-codeable information is necessary is a business context decision that cannot be determined from the EDIFACT message content alone. It depends on whether the sending system has information that cannot be expressed via defined code lists. (medium confidence)
603    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
604        ctx.external.evaluate("non_codeable_info_needed")
605    }
606
607    /// [3] Wenn SG7 STS+Z01 nicht vorhanden.
608    fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
609        ctx.lacks_qualifier("STS", 0, "Z01")
610    }
611
612    /// [4] Wenn SG7 STS+Z02 nicht vorhanden.
613    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
614        ctx.lacks_qualifier("STS", 0, "Z02")
615    }
616
617    /// [5] Wenn SG7 STS+Z03 nicht vorhanden.
618    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
619        ctx.lacks_qualifier("STS", 0, "Z03")
620    }
621
622    /// [10] Wenn Meldung des BKV/NB/ÜNB nach Frist eingeht.
623    /// EXTERNAL: Requires context from outside the message.
624    fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
625        ctx.external.evaluate("message_after_deadline")
626    }
627
628    /// [16] Wenn MP-ID in SG1 NAD+MR in der Rolle BKV
629    /// EXTERNAL: Requires context from outside the message.
630    fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
631        ctx.external.evaluate("recipient_is_bkv")
632    }
633
634    /// [17] Wenn Meldung des BKV auf falscher Aggregationsebene eingeht.
635    /// EXTERNAL: Requires context from outside the message.
636    fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
637        ctx.external.evaluate("bkv_wrong_aggregation_level")
638    }
639
640    /// [18] Wenn SG15 STS+Z19 nicht vorhanden.
641    fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
642        ctx.lacks_qualifier("STS", 0, "Z19")
643    }
644
645    /// [19] Wenn SG15 STS+Z24 nicht vorhanden.
646    fn evaluate_19(&self, ctx: &EvaluationContext) -> ConditionResult {
647        ctx.lacks_qualifier("STS", 0, "Z24")
648    }
649
650    /// [20] Wenn MP-ID in SG1 NAD+MR in der Rolle LF
651    /// EXTERNAL: Requires context from outside the message.
652    fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
653        ctx.external.evaluate("recipient_is_lf")
654    }
655
656    /// [26] Wenn MP-ID in SG1 NAD+MR in der Rolle NB
657    /// EXTERNAL: Requires context from outside the message.
658    fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
659        ctx.external.evaluate("recipient_is_nb")
660    }
661
662    /// [27] Nur MP-ID aus Sparte Strom
663    /// EXTERNAL: Requires context from outside the message.
664    fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
665        ctx.external.evaluate("mp_id_is_electricity_sector")
666    }
667
668    /// [28] Nur MP-ID aus Sparte Gas
669    /// EXTERNAL: Requires context from outside the message.
670    fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
671        ctx.external.evaluate("mp_id_is_gas_sector")
672    }
673
674    /// [29] Wenn MP-ID in SG1 NAD+MR in der Rolle ÜNB
675    /// EXTERNAL: Requires context from outside the message.
676    fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
677        ctx.external.evaluate("recipient_is_uenb")
678    }
679
680    /// [30] Wenn in dieser SG15 STS+Z20+Z32+A07:E_0207 vorhanden.
681    // REVIEW: Checks STS segment for Statuskategorie Z20 (elements[0][0]), Status Z32 (elements[1][0]), Prüfschritt A07 (elements[2][0]), and Codeliste E_0207 (elements[2][1]). Uses message-wide low-level access since no group-scoped helper supports simultaneous multi-field checks on a single segment. The 'A07' code is taken from the AHB shorthand notation at DE9013 position per the segment structure reference. (medium confidence)
682    fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
683        let found = ctx.find_segments("STS").into_iter().any(|s| {
684            s.elements
685                .first()
686                .and_then(|e| e.first())
687                .is_some_and(|v| v == "Z20")
688                && s.elements
689                    .get(1)
690                    .and_then(|e| e.first())
691                    .is_some_and(|v| v == "Z32")
692                && s.elements
693                    .get(2)
694                    .and_then(|e| e.first())
695                    .is_some_and(|v| v == "A07")
696                && s.elements
697                    .get(2)
698                    .and_then(|e| e.get(1))
699                    .is_some_and(|v| v == "E_0207")
700        });
701        ConditionResult::from(found)
702    }
703
704    /// [31] Wenn in dieser SG16 in QTY in DE6411 KWH/K3 vorhanden.
705    fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
706        let found = ctx.find_segments("QTY").into_iter().any(|s| {
707            let unit = s
708                .elements
709                .first()
710                .and_then(|e| e.get(2))
711                .map(|v| v.as_str());
712            unit == Some("KWH") || unit == Some("K3")
713        });
714        ConditionResult::from(found)
715    }
716
717    /// [32] Wenn in dieser SG16 in QTY in DE6411 KWT/K5 vorhanden.
718    fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
719        let found = ctx.find_segments("QTY").into_iter().any(|s| {
720            let unit = s
721                .elements
722                .first()
723                .and_then(|e| e.get(2))
724                .map(|v| v.as_str());
725            unit == Some("KWT") || unit == Some("K5")
726        });
727        ConditionResult::from(found)
728    }
729
730    /// [33] Wenn in dieser SG16 DTM+163 vorhanden.
731    fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
732        ctx.any_group_has_qualifier("DTM", 0, "163", &["SG16"])
733    }
734
735    /// [43] Wenn STS+Z01+Z07 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
736    fn evaluate_43(&self, ctx: &EvaluationContext) -> ConditionResult {
737        ctx.has_qualified_value("STS", 0, "Z01", 1, 0, &["Z07"])
738    }
739
740    /// [44] Wenn STS+Z01+Z08 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
741    fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
742        ctx.has_qualified_value("STS", 0, "Z01", 1, 0, &["Z08"])
743    }
744
745    /// [45] Wenn STS+Z03+Z07 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
746    fn evaluate_45(&self, ctx: &EvaluationContext) -> ConditionResult {
747        ctx.has_qualified_value("STS", 0, "Z03", 1, 0, &["Z07"])
748    }
749
750    /// [46] Wenn STS+Z03+Z08 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
751    fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
752        ctx.has_qualified_value("STS", 0, "Z03", 1, 0, &["Z08"])
753    }
754
755    /// [47] Es sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
756    fn evaluate_47(&self, _ctx: &EvaluationContext) -> ConditionResult {
757        ConditionResult::True
758    }
759
760    /// [48] Es sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
761    fn evaluate_48(&self, _ctx: &EvaluationContext) -> ConditionResult {
762        ConditionResult::True
763    }
764
765    /// [49] Wenn STS+Z25+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
766    fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
767        ctx.has_qualified_value("STS", 0, "Z25", 1, 0, &["Z30"])
768    }
769
770    /// [50] Wenn STS+Z25+Z31 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
771    fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
772        ctx.has_qualified_value("STS", 0, "Z25", 1, 0, &["Z31"])
773    }
774
775    /// [51] Es sind nur Codes aus dem EBD-Cluster "Abweisung" möglich.
776    fn evaluate_51(&self, _ctx: &EvaluationContext) -> ConditionResult {
777        ConditionResult::True
778    }
779
780    /// [52] Wenn STS+Z27+Z32 vorhanden
781    fn evaluate_52(&self, ctx: &EvaluationContext) -> ConditionResult {
782        ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z32"])
783    }
784
785    /// [53] Wenn STS+Z28+Z32 vorhanden
786    fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
787        ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z32"])
788    }
789
790    /// [54] Wenn STS+Z29+Z32 vorhanden
791    fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
792        ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"])
793    }
794
795    /// [55] Wenn STS+Z30+Z32 vorhanden
796    fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
797        ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"])
798    }
799
800    /// [56] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z27 ist
801    // REVIEW: Cross-SG15 comparison: find SG15 instances with STS+Z27, collect their RFF+ACW values, then check if any SG15's RFF+ACW differs. Requires low-level navigator access. Medium confidence because the exact group path (SG14→SG15) is inferred from IFTSTA Family B structure. (medium confidence)
802    fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
803        // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z27
804        let nav = match ctx.navigator() {
805            Some(n) => n,
806            None => return ConditionResult::Unknown,
807        };
808        let sg15_path: &[&str] = &["SG14", "SG15"];
809        let sg15_count = nav.group_instance_count(sg15_path);
810        // Collect ACW reference values from SG15 instances that have STS with Z27
811        let mut ref_acw: Vec<String> = Vec::new();
812        for i in 0..sg15_count {
813            let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
814            let has_z27 = sts_segs.iter().any(|s| {
815                s.elements
816                    .first()
817                    .and_then(|e| e.first())
818                    .is_some_and(|v| v == "Z27")
819            });
820            if has_z27 {
821                let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
822                for rff in &rff_segs {
823                    if rff
824                        .elements
825                        .first()
826                        .and_then(|e| e.first())
827                        .is_some_and(|v| v == "ACW")
828                    {
829                        if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
830                            if !val.is_empty() {
831                                ref_acw.push(val.clone());
832                            }
833                        }
834                    }
835                }
836            }
837        }
838        if ref_acw.is_empty() {
839            return ConditionResult::Unknown;
840        }
841        // Check if any SG15 has an ACW value not present in the Z27 SG15 ACW values
842        for i in 0..sg15_count {
843            let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
844            for rff in &rff_segs {
845                if rff
846                    .elements
847                    .first()
848                    .and_then(|e| e.first())
849                    .is_some_and(|v| v == "ACW")
850                {
851                    if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
852                        if !val.is_empty() && !ref_acw.contains(val) {
853                            return ConditionResult::True;
854                        }
855                    }
856                }
857            }
858        }
859        ConditionResult::False
860    }
861
862    /// [57] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z28 ist
863    // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z28 (Status des Fahrplananteils) instead of Z27. (medium confidence)
864    fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
865        // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z28
866        let nav = match ctx.navigator() {
867            Some(n) => n,
868            None => return ConditionResult::Unknown,
869        };
870        let sg15_path: &[&str] = &["SG14", "SG15"];
871        let sg15_count = nav.group_instance_count(sg15_path);
872        let mut ref_acw: Vec<String> = Vec::new();
873        for i in 0..sg15_count {
874            let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
875            let has_z28 = sts_segs.iter().any(|s| {
876                s.elements
877                    .first()
878                    .and_then(|e| e.first())
879                    .is_some_and(|v| v == "Z28")
880            });
881            if has_z28 {
882                let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
883                for rff in &rff_segs {
884                    if rff
885                        .elements
886                        .first()
887                        .and_then(|e| e.first())
888                        .is_some_and(|v| v == "ACW")
889                    {
890                        if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
891                            if !val.is_empty() {
892                                ref_acw.push(val.clone());
893                            }
894                        }
895                    }
896                }
897            }
898        }
899        if ref_acw.is_empty() {
900            return ConditionResult::Unknown;
901        }
902        for i in 0..sg15_count {
903            let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
904            for rff in &rff_segs {
905                if rff
906                    .elements
907                    .first()
908                    .and_then(|e| e.first())
909                    .is_some_and(|v| v == "ACW")
910                {
911                    if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
912                        if !val.is_empty() && !ref_acw.contains(val) {
913                            return ConditionResult::True;
914                        }
915                    }
916                }
917            }
918        }
919        ConditionResult::False
920    }
921
922    /// [58] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z29 ist
923    // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z29 (Status des Gegenvorschlags der Ausfallarbeit). (medium confidence)
924    fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
925        // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z29
926        let nav = match ctx.navigator() {
927            Some(n) => n,
928            None => return ConditionResult::Unknown,
929        };
930        let sg15_path: &[&str] = &["SG14", "SG15"];
931        let sg15_count = nav.group_instance_count(sg15_path);
932        let mut ref_acw: Vec<String> = Vec::new();
933        for i in 0..sg15_count {
934            let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
935            let has_z29 = sts_segs.iter().any(|s| {
936                s.elements
937                    .first()
938                    .and_then(|e| e.first())
939                    .is_some_and(|v| v == "Z29")
940            });
941            if has_z29 {
942                let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
943                for rff in &rff_segs {
944                    if rff
945                        .elements
946                        .first()
947                        .and_then(|e| e.first())
948                        .is_some_and(|v| v == "ACW")
949                    {
950                        if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
951                            if !val.is_empty() {
952                                ref_acw.push(val.clone());
953                            }
954                        }
955                    }
956                }
957            }
958        }
959        if ref_acw.is_empty() {
960            return ConditionResult::Unknown;
961        }
962        for i in 0..sg15_count {
963            let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
964            for rff in &rff_segs {
965                if rff
966                    .elements
967                    .first()
968                    .and_then(|e| e.first())
969                    .is_some_and(|v| v == "ACW")
970                {
971                    if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
972                        if !val.is_empty() && !ref_acw.contains(val) {
973                            return ConditionResult::True;
974                        }
975                    }
976                }
977            }
978        }
979        ConditionResult::False
980    }
981
982    /// [59] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z30 ist
983    // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z30 (Status des Gegenvorschlags des Fahrplananteils). (medium confidence)
984    fn evaluate_59(&self, ctx: &EvaluationContext) -> ConditionResult {
985        // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z30
986        let nav = match ctx.navigator() {
987            Some(n) => n,
988            None => return ConditionResult::Unknown,
989        };
990        let sg15_path: &[&str] = &["SG14", "SG15"];
991        let sg15_count = nav.group_instance_count(sg15_path);
992        let mut ref_acw: Vec<String> = Vec::new();
993        for i in 0..sg15_count {
994            let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
995            let has_z30 = sts_segs.iter().any(|s| {
996                s.elements
997                    .first()
998                    .and_then(|e| e.first())
999                    .is_some_and(|v| v == "Z30")
1000            });
1001            if has_z30 {
1002                let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1003                for rff in &rff_segs {
1004                    if rff
1005                        .elements
1006                        .first()
1007                        .and_then(|e| e.first())
1008                        .is_some_and(|v| v == "ACW")
1009                    {
1010                        if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1011                            if !val.is_empty() {
1012                                ref_acw.push(val.clone());
1013                            }
1014                        }
1015                    }
1016                }
1017            }
1018        }
1019        if ref_acw.is_empty() {
1020            return ConditionResult::Unknown;
1021        }
1022        for i in 0..sg15_count {
1023            let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1024            for rff in &rff_segs {
1025                if rff
1026                    .elements
1027                    .first()
1028                    .and_then(|e| e.first())
1029                    .is_some_and(|v| v == "ACW")
1030                {
1031                    if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1032                        if !val.is_empty() && !ref_acw.contains(val) {
1033                            return ConditionResult::True;
1034                        }
1035                    }
1036                }
1037            }
1038        }
1039        ConditionResult::False
1040    }
1041
1042    /// [60] Wenn zusätzlich zum Fahrplananteil auch die Ausfallarbeit zu identischem Wert aus RFF+ACW nicht o.k. ist
1043    // REVIEW: Checks whether any SG15 with STS+Z27+Z32 (Ausfallarbeit rejected) shares the same RFF+ACW reference value as an SG15 with STS+Z28+Z32 (Fahrplananteil rejected), within the same SG14 transaction group. Assumes SG15 is a child group of SG14 (consistent with IFTSTA Family B tx_group=SG14). (medium confidence)
1044    fn evaluate_60(&self, ctx: &EvaluationContext) -> ConditionResult {
1045        {
1046            let nav = match ctx.navigator() {
1047                Some(n) => n,
1048                None => return ConditionResult::Unknown,
1049            };
1050            let sg14_count = nav.group_instance_count(&["SG14"]);
1051            for i in 0..sg14_count {
1052                let sg15_count = nav.child_group_instance_count(&["SG14"], i, "SG15");
1053                let mut z27_z32_acw: Vec<String> = Vec::new();
1054                let mut z28_z32_acw: Vec<String> = Vec::new();
1055                for j in 0..sg15_count {
1056                    let stses = nav.find_segments_in_child_group("STS", &["SG14"], i, "SG15", j);
1057                    let rffs = nav.find_segments_in_child_group("RFF", &["SG14"], i, "SG15", j);
1058                    let acw_val = rffs
1059                        .iter()
1060                        .find(|s| {
1061                            s.elements
1062                                .first()
1063                                .and_then(|e| e.first())
1064                                .is_some_and(|v| v == "ACW")
1065                        })
1066                        .and_then(|s| s.elements.first().and_then(|e| e.get(1)).cloned());
1067                    let Some(acw) = acw_val else { continue };
1068                    for sts in &stses {
1069                        let cat = sts
1070                            .elements
1071                            .first()
1072                            .and_then(|e| e.first())
1073                            .map(|s| s.as_str());
1074                        let status = sts
1075                            .elements
1076                            .get(1)
1077                            .and_then(|e| e.first())
1078                            .map(|s| s.as_str());
1079                        match (cat, status) {
1080                            (Some("Z27"), Some("Z32")) => z27_z32_acw.push(acw.clone()),
1081                            (Some("Z28"), Some("Z32")) => z28_z32_acw.push(acw.clone()),
1082                            _ => {}
1083                        }
1084                    }
1085                }
1086                if z27_z32_acw.iter().any(|v| z28_z32_acw.contains(v)) {
1087                    return ConditionResult::True;
1088                }
1089            }
1090            ConditionResult::False
1091        }
1092    }
1093
1094    /// [61] Wenn zusätzlich zur Ausfallarbeit auch der Fahrplananteil zu identischem Wert aus RFF+ACW nicht o.k. ist
1095    // REVIEW: Mirror of condition 60: checks whether any SG15 with STS+Z28+Z32 (Fahrplananteil rejected) shares the same RFF+ACW as an SG15 with STS+Z27+Z32 (Ausfallarbeit rejected). The two conditions are semantically equivalent but expressed from different perspectives (the AHB uses them on different fields). Same SG14→SG15 navigator approach. (medium confidence)
1096    fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
1097        {
1098            let nav = match ctx.navigator() {
1099                Some(n) => n,
1100                None => return ConditionResult::Unknown,
1101            };
1102            let sg14_count = nav.group_instance_count(&["SG14"]);
1103            for i in 0..sg14_count {
1104                let sg15_count = nav.child_group_instance_count(&["SG14"], i, "SG15");
1105                let mut z27_z32_acw: Vec<String> = Vec::new();
1106                let mut z28_z32_acw: Vec<String> = Vec::new();
1107                for j in 0..sg15_count {
1108                    let stses = nav.find_segments_in_child_group("STS", &["SG14"], i, "SG15", j);
1109                    let rffs = nav.find_segments_in_child_group("RFF", &["SG14"], i, "SG15", j);
1110                    let acw_val = rffs
1111                        .iter()
1112                        .find(|s| {
1113                            s.elements
1114                                .first()
1115                                .and_then(|e| e.first())
1116                                .is_some_and(|v| v == "ACW")
1117                        })
1118                        .and_then(|s| s.elements.first().and_then(|e| e.get(1)).cloned());
1119                    let Some(acw) = acw_val else { continue };
1120                    for sts in &stses {
1121                        let cat = sts
1122                            .elements
1123                            .first()
1124                            .and_then(|e| e.first())
1125                            .map(|s| s.as_str());
1126                        let status = sts
1127                            .elements
1128                            .get(1)
1129                            .and_then(|e| e.first())
1130                            .map(|s| s.as_str());
1131                        match (cat, status) {
1132                            (Some("Z27"), Some("Z32")) => z27_z32_acw.push(acw.clone()),
1133                            (Some("Z28"), Some("Z32")) => z28_z32_acw.push(acw.clone()),
1134                            _ => {}
1135                        }
1136                    }
1137                }
1138                if z28_z32_acw.iter().any(|v| z27_z32_acw.contains(v)) {
1139                    return ConditionResult::True;
1140                }
1141            }
1142            ConditionResult::False
1143        }
1144    }
1145
1146    /// [62] Wenn STS+Z27+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1147    fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
1148        ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z30"])
1149    }
1150
1151    /// [63] Wenn STS+Z27+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1152    fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
1153        ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z32"])
1154    }
1155
1156    /// [64] Wenn STS+Z28+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1157    fn evaluate_64(&self, ctx: &EvaluationContext) -> ConditionResult {
1158        ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z30"])
1159    }
1160
1161    /// [65] Wenn STS+Z28+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1162    fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
1163        ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z32"])
1164    }
1165
1166    /// [66] Wenn STS+Z29+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1167    fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
1168        ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z30"])
1169    }
1170
1171    /// [67] Wenn STS+Z29+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1172    fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
1173        ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"])
1174    }
1175
1176    /// [68] Wenn STS+Z30+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1177    fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
1178        ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z30"])
1179    }
1180
1181    /// [69] Wenn STS+Z30+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1182    fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
1183        ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"])
1184    }
1185
1186    /// [70] Wenn zusätzlich zum Gegenvorschlag des Fahrplananteils auch der Gegenvorschlag der Ausfallarbeit zu identischem Wert aus RFF+ACW nicht o.k. ist
1187    // REVIEW: Condition 70 means: additionally to Fahrplananteil counter-proposal (STS Z30 Z32) being rejected, the Ausfallarbeit counter-proposal (STS Z29 Z32) is also rejected. The 'identischem Wert aus RFF+ACW' (same RFF+ACW reference) cross-group correlation is simplified — checks that both rejection STS types are present in the message. Full cross-SG14 value matching would require navigator group enumeration not fully supported by current helpers. (medium confidence)
1188    fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
1189        match ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"]) {
1190            ConditionResult::True => ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"]),
1191            ConditionResult::False => ConditionResult::False,
1192            ConditionResult::Unknown => ConditionResult::Unknown,
1193        }
1194    }
1195
1196    /// [71] Wenn zusätzlich zum Gegenvorschlag der Ausfallarbeit auch der Gegenvorschlag des Fahrplananteils zu identischem Wert aus RFF+ACW nicht o.k. ist
1197    // REVIEW: Symmetric to condition 70: additionally to Ausfallarbeit counter-proposal (STS Z29 Z32) being rejected, the Fahrplananteil counter-proposal (STS Z30 Z32) is also rejected. Same simplification of the RFF+ACW cross-group correlation applies. (medium confidence)
1198    fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
1199        match ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"]) {
1200            ConditionResult::True => ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"]),
1201            ConditionResult::False => ConditionResult::False,
1202            ConditionResult::Unknown => ConditionResult::Unknown,
1203        }
1204    }
1205
1206    /// [72] Wenn Gegenvorschlag erstellt werden kann
1207    /// EXTERNAL: Requires context from outside the message.
1208    fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
1209        ctx.external.evaluate("counter_proposal_possible")
1210    }
1211
1212    /// [76] Wenn MP-ID in SG1 NAD+MR in der Rolle ESA
1213    /// EXTERNAL: Requires context from outside the message.
1214    fn evaluate_76(&self, ctx: &EvaluationContext) -> ConditionResult {
1215        ctx.external.evaluate("recipient_is_esa")
1216    }
1217
1218    /// [77] Wenn STS+Z37+Z14 in dieser SG14 vorhanden
1219    fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
1220        ctx.any_group_has_qualified_value("STS", 0, "Z37", 1, 0, &["Z14"], &["SG14"])
1221    }
1222
1223    /// [78] Wenn STS+Z38 in dieser SG14 nicht vorhanden
1224    // REVIEW: STS+Z38 (Entsperren) is absent in the SG14 that contains STS+Z37 (Sperren). Uses any_group_has_qualifier_without to find an SG14 instance where STS+Z37 is present but STS+Z38 is not, covering the intra-SG14 scoping requirement. (medium confidence)
1225    fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
1226        ctx.any_group_has_qualifier_without("STS", 0, "Z37", "STS", 0, "Z38", &["SG14"])
1227    }
1228
1229    /// [79] Wenn STS+Z37 in dieser SG14 nicht vorhanden
1230    // REVIEW: STS+Z37 (Sperren) is absent in the SG14 that contains STS+Z38 (Entsperren). Symmetric to condition 78 — finds SG14 where STS+Z38 is present but STS+Z37 is not. (medium confidence)
1231    fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
1232        ctx.any_group_has_qualifier_without("STS", 0, "Z38", "STS", 0, "Z37", &["SG14"])
1233    }
1234
1235    /// [80] Wenn STS+Z38+Z14 in dieser SG14 vorhanden
1236    fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
1237        ctx.any_group_has_qualified_value("STS", 0, "Z38", 1, 0, &["Z14"], &["SG14"])
1238    }
1239
1240    /// [83] Wenn STS+Z37+Z13+A04/A05/A06:E_0472 in dieser SG14 vorhanden
1241    // REVIEW: STS+Z37 (Sperren) with Status Z13 and Statusanlass (elements[2][0], DE9013) in {A04, A05, A06}. The Codeliste code E_0472 in elements[2][1] is not checked as the primary discriminator is the DE9013 Prüfschritt code. Message-wide search is used as group-scoped SG15 traversal is not available via documented helpers. (medium confidence)
1242    fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
1243        let sts_segs = ctx.find_segments("STS");
1244        let found = sts_segs.iter().any(|s| {
1245            s.elements
1246                .first()
1247                .and_then(|e| e.first())
1248                .is_some_and(|v| v == "Z37")
1249                && s.elements
1250                    .get(1)
1251                    .and_then(|e| e.first())
1252                    .is_some_and(|v| v == "Z13")
1253                && s.elements
1254                    .get(2)
1255                    .and_then(|e| e.first())
1256                    .is_some_and(|v| matches!(v.as_str(), "A04" | "A05" | "A06"))
1257        });
1258        ConditionResult::from(found)
1259    }
1260
1261    /// [84] Wenn STS+Z38+Z13+A02:E_0499 in dieser SG14 vorhanden
1262    // REVIEW: STS+Z38 (Entsperren) with Status Z13 and Statusanlass DE9013=A02 (Codeliste E_0499). Checks elements[0][0]=Z38, elements[1][0]=Z13, elements[2][0]=A02. Message-wide search used as SG14/SG15 child group traversal is not in documented helpers. (medium confidence)
1263    fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
1264        let sts_segs = ctx.find_segments("STS");
1265        let found = sts_segs.iter().any(|s| {
1266            s.elements
1267                .first()
1268                .and_then(|e| e.first())
1269                .is_some_and(|v| v == "Z38")
1270                && s.elements
1271                    .get(1)
1272                    .and_then(|e| e.first())
1273                    .is_some_and(|v| v == "Z13")
1274                && s.elements
1275                    .get(2)
1276                    .and_then(|e| e.first())
1277                    .is_some_and(|v| v == "A02")
1278        });
1279        ConditionResult::from(found)
1280    }
1281
1282    /// [85] Wenn STS+Z37+Z32+A01:E_0501 in dieser SG14 vorhanden
1283    // REVIEW: STS+Z37 (Sperren) with Status Z32 (Ablehnung) and Statusanlass DE9013=A01 (Codeliste E_0501). Checks elements[0][0]=Z37, elements[1][0]=Z32, elements[2][0]=A01. Message-wide search used as SG14/SG15 child group traversal is not in documented helpers. (medium confidence)
1284    fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
1285        let sts_segs = ctx.find_segments("STS");
1286        let found = sts_segs.iter().any(|s| {
1287            s.elements
1288                .first()
1289                .and_then(|e| e.first())
1290                .is_some_and(|v| v == "Z37")
1291                && s.elements
1292                    .get(1)
1293                    .and_then(|e| e.first())
1294                    .is_some_and(|v| v == "Z32")
1295                && s.elements
1296                    .get(2)
1297                    .and_then(|e| e.first())
1298                    .is_some_and(|v| v == "A01")
1299        });
1300        ConditionResult::from(found)
1301    }
1302
1303    /// [86] Wenn MP-ID in SG1 NAD+MS in der Rolle LF
1304    /// EXTERNAL: Requires context from outside the message.
1305    fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
1306        ctx.external.evaluate("sender_is_lf")
1307    }
1308
1309    /// [91] Wenn in diesem STS DE1131 = E_0472
1310    fn evaluate_91(&self, ctx: &EvaluationContext) -> ConditionResult {
1311        let segments = ctx.find_segments("STS");
1312        ConditionResult::from(segments.iter().any(|s| {
1313            s.elements
1314                .get(2)
1315                .and_then(|e| e.get(1))
1316                .map(|v| v == "E_0472")
1317                .unwrap_or(false)
1318        }))
1319    }
1320
1321    /// [92] Wenn in diesem STS DE1131 = E_0501
1322    fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
1323        let segments = ctx.find_segments("STS");
1324        ConditionResult::from(segments.iter().any(|s| {
1325            s.elements
1326                .get(2)
1327                .and_then(|e| e.get(1))
1328                .map(|v| v == "E_0501")
1329                .unwrap_or(false)
1330        }))
1331    }
1332
1333    /// [93] Wenn STS+Z37+Z13 vorhanden, dann sind nur Codes aus dem EBD-Cluster "gescheitert" möglich.
1334    fn evaluate_93(&self, ctx: &EvaluationContext) -> ConditionResult {
1335        ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z13"])
1336    }
1337
1338    /// [94] Wenn STS+Z37+Z14 vorhanden, dann sind nur Codes aus dem EBD-Cluster "erfolgreich" möglich.
1339    fn evaluate_94(&self, ctx: &EvaluationContext) -> ConditionResult {
1340        ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z14"])
1341    }
1342
1343    /// [95] Wenn in diesem STS DE1131 = E_0499
1344    fn evaluate_95(&self, ctx: &EvaluationContext) -> ConditionResult {
1345        let segments = ctx.find_segments("STS");
1346        ConditionResult::from(segments.iter().any(|s| {
1347            s.elements
1348                .get(2)
1349                .and_then(|e| e.get(1))
1350                .map(|v| v == "E_0499")
1351                .unwrap_or(false)
1352        }))
1353    }
1354
1355    /// [96] Wenn in diesem STS DE1131 = E_0487
1356    fn evaluate_96(&self, ctx: &EvaluationContext) -> ConditionResult {
1357        let segments = ctx.find_segments("STS");
1358        ConditionResult::from(segments.iter().any(|s| {
1359            s.elements
1360                .get(2)
1361                .and_then(|e| e.get(1))
1362                .map(|v| v == "E_0487")
1363                .unwrap_or(false)
1364        }))
1365    }
1366
1367    /// [97] Wenn STS+Z38+Z13 vorhanden, dann sind nur Codes aus dem EBD-Cluster "gescheitert" möglich.
1368    fn evaluate_97(&self, ctx: &EvaluationContext) -> ConditionResult {
1369        ctx.has_qualified_value("STS", 0, "Z38", 1, 0, &["Z13"])
1370    }
1371
1372    /// [98] Wenn STS+Z38+Z14 vorhanden, dann sind nur Codes aus dem EBD-Cluster "erfolgreich" möglich.
1373    fn evaluate_98(&self, ctx: &EvaluationContext) -> ConditionResult {
1374        ctx.has_qualified_value("STS", 0, "Z38", 1, 0, &["Z14"])
1375    }
1376
1377    /// [99] Wenn in diesem STS DE1131 = E_0487, dann ist nur der Code A01 möglich.
1378    fn evaluate_99(&self, ctx: &EvaluationContext) -> ConditionResult {
1379        let segments = ctx.find_segments("STS");
1380        ConditionResult::from(segments.iter().any(|s| {
1381            s.elements
1382                .get(2)
1383                .and_then(|e| e.get(1))
1384                .map(|v| v == "E_0487")
1385                .unwrap_or(false)
1386        }))
1387    }
1388
1389    /// [100] Wenn STS+Z43+Z47 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Änderung der Daten" möglich.
1390    fn evaluate_100(&self, ctx: &EvaluationContext) -> ConditionResult {
1391        ctx.has_qualified_value("STS", 0, "Z43", 1, 0, &["Z47"])
1392    }
1393
1394    /// [101] Wenn STS+Z43+Z48 vorhanden, dann sind nur Codes aus dem EBD-Cluster "keine Änderung der Daten" möglich.
1395    fn evaluate_101(&self, ctx: &EvaluationContext) -> ConditionResult {
1396        ctx.has_qualified_value("STS", 0, "Z43", 1, 0, &["Z48"])
1397    }
1398
1399    /// [107] Wenn STS+Z37+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1400    fn evaluate_107(&self, ctx: &EvaluationContext) -> ConditionResult {
1401        ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z32"])
1402    }
1403
1404    /// [114] Wenn MP-ID in SG1 NAD+MS in der Rolle MSB
1405    /// EXTERNAL: Requires context from outside the message.
1406    fn evaluate_114(&self, ctx: &EvaluationContext) -> ConditionResult {
1407        ctx.external.evaluate("sender_is_msb")
1408    }
1409
1410    /// [115] Wenn MP-ID in SG1 NAD+MS in der Rolle NB
1411    /// EXTERNAL: Requires context from outside the message.
1412    fn evaluate_115(&self, ctx: &EvaluationContext) -> ConditionResult {
1413        ctx.external.evaluate("sender_is_nb")
1414    }
1415
1416    /// [117] Wenn MP-ID in SG1 NAD+MR in der Rolle MSB
1417    /// EXTERNAL: Requires context from outside the message.
1418    fn evaluate_117(&self, ctx: &EvaluationContext) -> ConditionResult {
1419        ctx.external.evaluate("recipient_is_msb")
1420    }
1421
1422    /// [118] Wenn in diesem STS DE1131 = E_0526
1423    fn evaluate_118(&self, ctx: &EvaluationContext) -> ConditionResult {
1424        ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0526"])
1425    }
1426
1427    /// [119] Wenn in diesem STS DE1131 = E_0528
1428    fn evaluate_119(&self, ctx: &EvaluationContext) -> ConditionResult {
1429        ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0528"])
1430    }
1431
1432    /// [120] Wenn in diesem STS DE1131 = E_0529
1433    fn evaluate_120(&self, ctx: &EvaluationContext) -> ConditionResult {
1434        ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0529"])
1435    }
1436
1437    /// [121] Wenn in diesem STS DE1131 = E_0536
1438    fn evaluate_121(&self, ctx: &EvaluationContext) -> ConditionResult {
1439        ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0536"])
1440    }
1441
1442    /// [129] Wenn STS+Z20+Z32+A99:E_0524 in dieser SG14 vorhanden
1443    // REVIEW: Check STS with Statuskategorie Z20, Status Z32, Prüfschritt A99 (elements[2][0]), and DE1131 E_0524 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide search. (medium confidence)
1444    fn evaluate_129(&self, ctx: &EvaluationContext) -> ConditionResult {
1445        let segments = ctx.find_segments("STS");
1446        ConditionResult::from(segments.iter().any(|s| {
1447            s.elements
1448                .first()
1449                .and_then(|e| e.first())
1450                .map(|v| v == "Z20")
1451                .unwrap_or(false)
1452                && s.elements
1453                    .get(1)
1454                    .and_then(|e| e.first())
1455                    .map(|v| v == "Z32")
1456                    .unwrap_or(false)
1457                && s.elements
1458                    .get(2)
1459                    .and_then(|e| e.first())
1460                    .map(|v| v == "A99")
1461                    .unwrap_or(false)
1462                && s.elements
1463                    .get(2)
1464                    .and_then(|e| e.get(1))
1465                    .map(|v| v == "E_0524")
1466                    .unwrap_or(false)
1467        }))
1468    }
1469
1470    /// [130] Wenn STS+Z20+Z32+A99:E_0531 in dieser SG14 vorhanden
1471    // REVIEW: Check STS with Statuskategorie Z20, Status Z32, Prüfschritt A99 (elements[2][0]), and DE1131 E_0531 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide search. (medium confidence)
1472    fn evaluate_130(&self, ctx: &EvaluationContext) -> ConditionResult {
1473        let segments = ctx.find_segments("STS");
1474        ConditionResult::from(segments.iter().any(|s| {
1475            s.elements
1476                .first()
1477                .and_then(|e| e.first())
1478                .map(|v| v == "Z20")
1479                .unwrap_or(false)
1480                && s.elements
1481                    .get(1)
1482                    .and_then(|e| e.first())
1483                    .map(|v| v == "Z32")
1484                    .unwrap_or(false)
1485                && s.elements
1486                    .get(2)
1487                    .and_then(|e| e.first())
1488                    .map(|v| v == "A99")
1489                    .unwrap_or(false)
1490                && s.elements
1491                    .get(2)
1492                    .and_then(|e| e.get(1))
1493                    .map(|v| v == "E_0531")
1494                    .unwrap_or(false)
1495        }))
1496    }
1497
1498    /// [131] Wenn STS+Z37+Z13+A04/A05/A06:E_1003 in dieser SG14 vorhanden
1499    // REVIEW: Check STS with Statuskategorie Z37 (Auftragstatus Sperren), Status Z13, DE9013 in {A04,A05,A06} (elements[2][0]), and DE1131 E_1003 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide. (medium confidence)
1500    fn evaluate_131(&self, ctx: &EvaluationContext) -> ConditionResult {
1501        let segments = ctx.find_segments("STS");
1502        ConditionResult::from(segments.iter().any(|s| {
1503            s.elements
1504                .first()
1505                .and_then(|e| e.first())
1506                .map(|v| v == "Z37")
1507                .unwrap_or(false)
1508                && s.elements
1509                    .get(1)
1510                    .and_then(|e| e.first())
1511                    .map(|v| v == "Z13")
1512                    .unwrap_or(false)
1513                && s.elements
1514                    .get(2)
1515                    .and_then(|e| e.first())
1516                    .map(|v| matches!(v.as_str(), "A04" | "A05" | "A06"))
1517                    .unwrap_or(false)
1518                && s.elements
1519                    .get(2)
1520                    .and_then(|e| e.get(1))
1521                    .map(|v| v == "E_1003")
1522                    .unwrap_or(false)
1523        }))
1524    }
1525
1526    /// [132] Wenn STS+Z37+Z32+A01:E_1002 in dieser SG14 vorhanden
1527    // REVIEW: Check STS with Statuskategorie Z37 (Auftragstatus Sperren), Status Z32, Prüfschritt A01 (elements[2][0]), and DE1131 E_1002 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide. (medium confidence)
1528    fn evaluate_132(&self, ctx: &EvaluationContext) -> ConditionResult {
1529        let segments = ctx.find_segments("STS");
1530        ConditionResult::from(segments.iter().any(|s| {
1531            s.elements
1532                .first()
1533                .and_then(|e| e.first())
1534                .map(|v| v == "Z37")
1535                .unwrap_or(false)
1536                && s.elements
1537                    .get(1)
1538                    .and_then(|e| e.first())
1539                    .map(|v| v == "Z32")
1540                    .unwrap_or(false)
1541                && s.elements
1542                    .get(2)
1543                    .and_then(|e| e.first())
1544                    .map(|v| v == "A01")
1545                    .unwrap_or(false)
1546                && s.elements
1547                    .get(2)
1548                    .and_then(|e| e.get(1))
1549                    .map(|v| v == "E_1002")
1550                    .unwrap_or(false)
1551        }))
1552    }
1553
1554    /// [133] Wenn in diesem STS DE1131 = E_1003
1555    fn evaluate_133(&self, ctx: &EvaluationContext) -> ConditionResult {
1556        ctx.has_qualified_value("STS", 0, "Z37", 2, 1, &["E_1003"])
1557    }
1558
1559    /// [134] Wenn in diesem STS DE1131 = E_1002
1560    fn evaluate_134(&self, ctx: &EvaluationContext) -> ConditionResult {
1561        ctx.has_qualified_value("STS", 0, "Z37", 2, 1, &["E_1002"])
1562    }
1563
1564    /// [135] Wenn in dieser SG15 in STS+Z21 DE9013 = A99
1565    fn evaluate_135(&self, ctx: &EvaluationContext) -> ConditionResult {
1566        ctx.has_qualified_value("STS", 0, "Z21", 2, 0, &["A99"])
1567    }
1568
1569    /// [136] Wenn STS+Z38+Z13+A02:E_1005 in dieser SG14 vorhanden
1570    // REVIEW: Check STS with Statuskategorie Z38 (Auftragstatus Entsperren), Status Z13, Prüfschritt A02 (elements[2][0]), and DE1131 E_1005 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide. (medium confidence)
1571    fn evaluate_136(&self, ctx: &EvaluationContext) -> ConditionResult {
1572        let segments = ctx.find_segments("STS");
1573        ConditionResult::from(segments.iter().any(|s| {
1574            s.elements
1575                .first()
1576                .and_then(|e| e.first())
1577                .map(|v| v == "Z38")
1578                .unwrap_or(false)
1579                && s.elements
1580                    .get(1)
1581                    .and_then(|e| e.first())
1582                    .map(|v| v == "Z13")
1583                    .unwrap_or(false)
1584                && s.elements
1585                    .get(2)
1586                    .and_then(|e| e.first())
1587                    .map(|v| v == "A02")
1588                    .unwrap_or(false)
1589                && s.elements
1590                    .get(2)
1591                    .and_then(|e| e.get(1))
1592                    .map(|v| v == "E_1005")
1593                    .unwrap_or(false)
1594        }))
1595    }
1596
1597    /// [137] Wenn in diesem STS DE1131 = E_1005
1598    fn evaluate_137(&self, ctx: &EvaluationContext) -> ConditionResult {
1599        ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1005"])
1600    }
1601
1602    /// [138] Wenn in diesem STS DE1131 = E_1020
1603    fn evaluate_138(&self, ctx: &EvaluationContext) -> ConditionResult {
1604        ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1020"])
1605    }
1606
1607    /// [139] Wenn in diesem STS DE1131 = E_1020, dann ist nur der Code A01 möglich.
1608    // REVIEW: Condition checks if DE1131=E_1020 in STS Z38 (Auftragstatus Entsperren). The clause 'dann ist nur der Code A01 möglich' documents that when this condition is true, only code A01 is valid for the dependent field — this is a downstream validation note, not an additional condition predicate. Implementation is identical to condition 138. (medium confidence)
1609    fn evaluate_139(&self, ctx: &EvaluationContext) -> ConditionResult {
1610        ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1020"])
1611    }
1612
1613    /// [140] Wenn in diesem STS DE9013 &lt;&gt; A01
1614    // REVIEW: Checks if any STS segment has DE9013 (elements[2][0]) with a non-empty value that is not 'A01'. Medium confidence because 'in diesem STS' implies a segment-level context that may not be fully captured by message-wide search. (medium confidence)
1615    fn evaluate_140(&self, ctx: &EvaluationContext) -> ConditionResult {
1616        let sts_segments = ctx.find_segments("STS");
1617        ConditionResult::from(sts_segments.iter().any(|s| {
1618            s.elements
1619                .get(2)
1620                .and_then(|e| e.first())
1621                .is_some_and(|v| !v.is_empty() && v != "A01")
1622        }))
1623    }
1624
1625    /// [141] Wenn in diesem STS DE9013 = A01
1626    // REVIEW: Checks if any STS segment has DE9013 (elements[2][0]) equal to 'A01'. Medium confidence due to segment-level context ambiguity. (medium confidence)
1627    fn evaluate_141(&self, ctx: &EvaluationContext) -> ConditionResult {
1628        let sts_segments = ctx.find_segments("STS");
1629        ConditionResult::from(sts_segments.iter().any(|s| {
1630            s.elements
1631                .get(2)
1632                .and_then(|e| e.first())
1633                .is_some_and(|v| v == "A01")
1634        }))
1635    }
1636
1637    /// [142] Wenn in diesem STS DE1131 = E_0535
1638    fn evaluate_142(&self, ctx: &EvaluationContext) -> ConditionResult {
1639        let sts_segments = ctx.find_segments("STS");
1640        ConditionResult::from(sts_segments.iter().any(|s| {
1641            s.elements
1642                .get(2)
1643                .and_then(|e| e.get(1))
1644                .is_some_and(|v| v == "E_0535")
1645        }))
1646    }
1647
1648    /// [143] Wenn mehr als ein Grund vorliegt und die voranstehenden DE9013 dieses STS nicht ausreichen
1649    // REVIEW: STS Z41 (Grund der Privilegierung nach EnFG) supports up to 5 C556 groups (elements[2]-[6]). 'mehr als ein Grund' is indicated when elements[3][0] (second C556 DE9013) is non-empty, meaning the first slot alone was insufficient. Medium confidence because the exact threshold depends on which positional slot this condition guards. (medium confidence)
1650    fn evaluate_143(&self, ctx: &EvaluationContext) -> ConditionResult {
1651        let sts_segments = ctx.find_segments("STS");
1652        ConditionResult::from(sts_segments.iter().any(|s| {
1653            s.elements
1654                .get(3)
1655                .and_then(|e| e.first())
1656                .is_some_and(|v| !v.is_empty())
1657        }))
1658    }
1659
1660    /// [144] Wenn RFF+Z45 in dieser SG15 nicht vorhanden
1661    // REVIEW: Checks that in some SG15 instance (inside SG14 for IFTSTA Family B), RFF+Z13 (Prüfidentifikator, always present) is present but RFF+Z45 (Artikel-ID reference) is absent. Uses RFF+Z13 as the anchor since it is mandatory in every SG15. (medium confidence)
1662    fn evaluate_144(&self, ctx: &EvaluationContext) -> ConditionResult {
1663        ctx.any_group_has_qualifier_without("RFF", 0, "Z13", "RFF", 0, "Z45", &["SG14", "SG15"])
1664    }
1665
1666    /// [145] Wenn RFF+Z17 in dieser SG15 nicht vorhanden
1667    // REVIEW: Checks that in some SG15 instance, RFF+Z13 (mandatory Prüfidentifikator) is present but RFF+Z17 (Preisschlüsselstamm reference) is absent. (medium confidence)
1668    fn evaluate_145(&self, ctx: &EvaluationContext) -> ConditionResult {
1669        ctx.any_group_has_qualifier_without("RFF", 0, "Z13", "RFF", 0, "Z17", &["SG14", "SG15"])
1670    }
1671
1672    /// [146] Wenn im DE3155 in demselben COM der Code EM vorhanden ist
1673    fn evaluate_146(&self, ctx: &EvaluationContext) -> ConditionResult {
1674        let com_segs = ctx.find_segments("COM");
1675        ConditionResult::from(com_segs.iter().any(|s| {
1676            s.elements
1677                .first()
1678                .and_then(|e| e.get(1))
1679                .is_some_and(|v| v == "EM")
1680        }))
1681    }
1682
1683    /// [147] Wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
1684    fn evaluate_147(&self, ctx: &EvaluationContext) -> ConditionResult {
1685        let com_segs = ctx.find_segments("COM");
1686        ConditionResult::from(com_segs.iter().any(|s| {
1687            s.elements
1688                .first()
1689                .and_then(|e| e.get(1))
1690                .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
1691        }))
1692    }
1693
1694    /// [148] Wenn in dieser SG15 STS+Z43+Z48+A99:E_0595 vorhanden
1695    fn evaluate_148(&self, ctx: &EvaluationContext) -> ConditionResult {
1696        let sts_segs = ctx.find_segments("STS");
1697        ConditionResult::from(sts_segs.iter().any(|s| {
1698            s.elements
1699                .first()
1700                .and_then(|e| e.first())
1701                .is_some_and(|v| v == "Z43")
1702                && s.elements
1703                    .get(1)
1704                    .and_then(|e| e.first())
1705                    .is_some_and(|v| v == "Z48")
1706                && s.elements
1707                    .get(2)
1708                    .and_then(|e| e.first())
1709                    .is_some_and(|v| v == "A99")
1710                && s.elements
1711                    .get(2)
1712                    .and_then(|e| e.get(1))
1713                    .is_some_and(|v| v == "E_0595")
1714        }))
1715    }
1716
1717    /// [490] wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Prozesszeitpunkt bei MESZ mit UTC“ ist
1718    fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
1719        let dtm_segs = ctx.find_segments("DTM");
1720        match dtm_segs
1721            .first()
1722            .and_then(|s| s.elements.first())
1723            .and_then(|e| e.get(1))
1724        {
1725            Some(val) => is_mesz_utc(val),
1726            None => ConditionResult::False, // segment absent → condition not applicable
1727        }
1728    }
1729
1730    /// [491] wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Prozesszeitpunkt bei MEZ mit UTC“ ist
1731    fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1732        let dtm_segs = ctx.find_segments("DTM");
1733        match dtm_segs
1734            .first()
1735            .and_then(|s| s.elements.first())
1736            .and_then(|e| e.get(1))
1737        {
1738            Some(val) => is_mez_utc(val),
1739            None => ConditionResult::False, // segment absent → condition not applicable
1740        }
1741    }
1742
1743    /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
1744    /// EXTERNAL: Requires context from outside the message.
1745    fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1746        ctx.external.evaluate("recipient_is_strom")
1747    }
1748
1749    /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
1750    /// EXTERNAL: Requires context from outside the message.
1751    fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1752        ctx.external.evaluate("recipient_is_gas")
1753    }
1754
1755    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
1756    // REVIEW: The date must be the document creation time (DTM+137) or earlier. Applied to DTM+334 (Zeitpunkt der Statusvergabe) which is the most natural target in IFTSTA. Format 303 timestamps are lexicographically ordered so string comparison is valid. (medium confidence)
1757    fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
1758        // The referenced date must be the document creation time or earlier (≤ DTM+137)
1759        let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1760        let doc_val = match doc_segs
1761            .first()
1762            .and_then(|s| s.elements.first())
1763            .and_then(|e| e.get(1))
1764        {
1765            Some(v) => v.as_str(),
1766            None => return ConditionResult::Unknown,
1767        };
1768        let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
1769        match status_segs
1770            .first()
1771            .and_then(|s| s.elements.first())
1772            .and_then(|e| e.get(1))
1773        {
1774            Some(val) => ConditionResult::from(val.as_str() <= doc_val),
1775            None => ConditionResult::False, // segment absent → condition not applicable
1776        }
1777    }
1778
1779    /// [495] Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein
1780    // REVIEW: The timestamp must be ≤ DE2380 of DTM+137 (document creation date). Compares DTM+334 against DTM+137 using lexicographic ordering, valid for format 303 (YYYYMMDDhhmm) timestamps. (medium confidence)
1781    fn evaluate_495(&self, ctx: &EvaluationContext) -> ConditionResult {
1782        // The timestamp must be ≤ the value in DE2380 of DTM+137
1783        let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1784        let doc_val = match doc_segs
1785            .first()
1786            .and_then(|s| s.elements.first())
1787            .and_then(|e| e.get(1))
1788        {
1789            Some(v) => v.as_str(),
1790            None => return ConditionResult::Unknown,
1791        };
1792        let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
1793        match status_segs
1794            .first()
1795            .and_then(|s| s.elements.first())
1796            .and_then(|e| e.get(1))
1797        {
1798            Some(val) => ConditionResult::from(val.as_str() <= doc_val),
1799            None => ConditionResult::False, // segment absent → condition not applicable
1800        }
1801    }
1802
1803    /// [496] Der Zeitpunkt muss &gt; dem Wert im DE2380 des DTM+137 sein
1804    // REVIEW: The timestamp must be strictly greater than DE2380 of DTM+137. Inverse of condition 495. Applied to DTM+334 which must be after the document creation date in this usage context. (medium confidence)
1805    fn evaluate_496(&self, ctx: &EvaluationContext) -> ConditionResult {
1806        // The timestamp must be > the value in DE2380 of DTM+137
1807        let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1808        let doc_val = match doc_segs
1809            .first()
1810            .and_then(|s| s.elements.first())
1811            .and_then(|e| e.get(1))
1812        {
1813            Some(v) => v.as_str(),
1814            None => return ConditionResult::Unknown,
1815        };
1816        let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
1817        match status_segs
1818            .first()
1819            .and_then(|s| s.elements.first())
1820            .and_then(|e| e.get(1))
1821        {
1822            Some(val) => ConditionResult::from(val.as_str() > doc_val),
1823            None => ConditionResult::False, // segment absent → condition not applicable
1824        }
1825    }
1826
1827    /// [501] Hinweis: Aus QUOTES BGM DE1004
1828    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1829        // Hinweis: Aus QUOTES BGM DE1004 — informational note about field origin, always applies
1830        ConditionResult::True
1831    }
1832
1833    /// [502] Hinweis: Aus REQOTE BGM DE1004
1834    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1835        // Hinweis: Aus REQOTE BGM DE1004 — informational note about field origin, always applies
1836        ConditionResult::True
1837    }
1838
1839    /// [503] Hinweis: Auf Selbsteinbau eines iMS oder einer mME wird verzichtet
1840    fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
1841        // Hinweis: Auf Selbsteinbau eines iMS oder einer mME wird verzichtet — informational note, always applies
1842        ConditionResult::True
1843    }
1844
1845    /// [504] Hinweis: Verwendung der ID des MaBiS-ZP
1846    fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1847        // Hinweis: Verwendung der ID des MaBiS-ZP — informational note, always applies
1848        ConditionResult::True
1849    }
1850
1851    /// [505] Hinweis: Verwendung der ID der Messlokation
1852    fn evaluate_505(&self, _ctx: &EvaluationContext) -> ConditionResult {
1853        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1854        ConditionResult::True
1855    }
1856
1857    /// [506] Hinweis: Verwendung der ID der Marktlokation
1858    fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
1859        // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1860        ConditionResult::True
1861    }
1862
1863    /// [510] Hinweis: Es ist neben der Information über die Ablehnung auch der unverändert gebliebene Datenstatus informell mitzugeben.
1864    fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
1865        // Hinweis: Es ist neben der Information über die Ablehnung auch der unverändert gebliebene Datenstatus informell mitzugeben — informational note, always applies
1866        ConditionResult::True
1867    }
1868
1869    /// [512] Hinweis: Aus MSCONS BGM DE1004
1870    fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
1871        // Hinweis: Aus MSCONS BGM DE1004 — informational note, always applies
1872        ConditionResult::True
1873    }
1874
1875    /// [519] Hinweis: Aus ORDERS BGM DE1004
1876    fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
1877        // Hinweis: Aus ORDERS BGM DE1004 — informational note, always applies
1878        ConditionResult::True
1879    }
1880
1881    /// [520] Hinweis: Zeitpunkt, zu dem der Wechsel erfolgt, falls er zustande kommt
1882    fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
1883        // Hinweis: Zeitpunkt, zu dem der Wechsel erfolgt, falls er zustande kommt — informational note, always applies
1884        ConditionResult::True
1885    }
1886
1887    /// [521] Hinweis: Zeitpunkt, ab dem der MSBN tatsächlich den Messstellenbetrieb übernimmt
1888    fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
1889        // Hinweis: Zeitpunkt, ab dem der MSBN tatsächlich den Messstellenbetrieb übernimmt — informational note, always applies
1890        ConditionResult::True
1891    }
1892
1893    /// [522] Hinweis: Zeitpunkt, ab dem der gMSB den Messstellenbetrieb übernimmt
1894    fn evaluate_522(&self, _ctx: &EvaluationContext) -> ConditionResult {
1895        // Hinweis: Zeitpunkt, ab dem der gMSB den Messstellenbetrieb übernimmt — informational note, always applies
1896        ConditionResult::True
1897    }
1898
1899    /// [523] Hinweis: Wert aus BGM DE1004 der MSCONS, auf die sich die Statusangabe bezieht
1900    fn evaluate_523(&self, _ctx: &EvaluationContext) -> ConditionResult {
1901        // Hinweis: Wert aus BGM DE1004 der MSCONS, auf die sich die Statusangabe bezieht — informational note, always applies
1902        ConditionResult::True
1903    }
1904
1905    /// [524] Hinweis: Wert aus BGM DE1004 der MSCONS, die den Gegenvorschlag enthält
1906    fn evaluate_524(&self, _ctx: &EvaluationContext) -> ConditionResult {
1907        // Hinweis: Wert aus BGM DE1004 der MSCONS, die den Gegenvorschlag enthält — informational note, always applies
1908        ConditionResult::True
1909    }
1910
1911    /// [525] Hinweis: Je SG14 sind nur Statusinformationen zu einer MSCONS enthalten
1912    fn evaluate_525(&self, _ctx: &EvaluationContext) -> ConditionResult {
1913        // Hinweis: Je SG14 sind nur Statusinformationen zu einer MSCONS enthalten — informational note, always applies
1914        ConditionResult::True
1915    }
1916
1917    /// [530] Hinweis: Hier ist die Arbeit bzw. Leistung anzugeben, die der Sender der IFTSTA im Lieferschein für den von ihm genannten Zeitraum / Leistungsperiode erwartet hätte.
1918    fn evaluate_530(&self, _ctx: &EvaluationContext) -> ConditionResult {
1919        // Hinweis: Hier ist die Arbeit bzw. Leistung anzugeben, die der Sender der IFTSTA im Lieferschein für den von ihm genannten Zeitraum / Leistungsperiode erwartet hätte — informational note, always applies
1920        ConditionResult::True
1921    }
1922
1923    /// [531] Vom MSBN in Schritt 1 des SD verwendete Vorgangsnummer, damit der LF diese bei der Bestellung einer Konfiguration beim MSBN, unter dem Vorbehalt, dass der MSB-Wechsel an der Messlokation erfolgreic...
1924    fn evaluate_531(&self, _ctx: &EvaluationContext) -> ConditionResult {
1925        // Hinweis: Vom MSBN in Schritt 1 des SD verwendete Vorgangsnummer, damit der LF diese
1926        // bei der Bestellung einer Konfiguration beim MSBN verwenden kann — informational note
1927        // describing field semantics and usage context, always applies unconditionally
1928        ConditionResult::True
1929    }
1930
1931    /// [532] Hinweis: Verwendung der ID der Netzlokation
1932    fn evaluate_532(&self, _ctx: &EvaluationContext) -> ConditionResult {
1933        // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
1934        ConditionResult::True
1935    }
1936
1937    /// [533] Hinweis: Verwendung der ID der Steuerbaren Ressource
1938    fn evaluate_533(&self, _ctx: &EvaluationContext) -> ConditionResult {
1939        // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
1940        ConditionResult::True
1941    }
1942
1943    /// [534] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
1944    fn evaluate_534(&self, _ctx: &EvaluationContext) -> ConditionResult {
1945        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
1946        ConditionResult::True
1947    }
1948
1949    /// [535] Hinweis: Aus UTILMD IDE DE7402
1950    fn evaluate_535(&self, _ctx: &EvaluationContext) -> ConditionResult {
1951        // Hinweis: Aus UTILMD IDE DE7402 — informational note about value origin, always applies
1952        ConditionResult::True
1953    }
1954
1955    /// [902] Format: Wert darf nur positiv oder 0 sein
1956    // REVIEW: Format condition requiring value >= 0. Applied to QTY segment value (elements[0][1]) which is the standard location for quantity values in EDIFACT. Uses validate_numeric helper with >= 0.0. (medium confidence)
1957    fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
1958        // Format: Wert darf nur positiv oder 0 sein — value must be >= 0
1959        let segs = ctx.find_segments("QTY");
1960        match segs
1961            .first()
1962            .and_then(|s| s.elements.first())
1963            .and_then(|e| e.get(1))
1964        {
1965            Some(val) => validate_numeric(val, ">=", 0.0),
1966            None => ConditionResult::False, // segment absent → condition not applicable
1967        }
1968    }
1969
1970    /// [903] Format: Möglicher Wert: 1
1971    // REVIEW: Format condition requiring value == 1. Uses validate_numeric with == operator. Targets QTY segment value (elements[0][1]) as most common numeric value field in IFTSTA context. (medium confidence)
1972    fn evaluate_903(&self, ctx: &EvaluationContext) -> ConditionResult {
1973        let segs = ctx.find_segments("QTY");
1974        match segs
1975            .first()
1976            .and_then(|s| s.elements.first())
1977            .and_then(|e| e.get(1))
1978        {
1979            Some(val) => validate_numeric(val, "==", 1.0),
1980            None => ConditionResult::False, // segment absent → condition not applicable
1981        }
1982    }
1983
1984    /// [906] Format: max. 3 Nachkommastellen
1985    // REVIEW: Format condition: max 3 decimal places. Applies to QTY segment value field (elements[0][1]). (medium confidence)
1986    fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
1987        let segs = ctx.find_segments("QTY");
1988        match segs
1989            .first()
1990            .and_then(|s| s.elements.first())
1991            .and_then(|e| e.get(1))
1992        {
1993            Some(val) => validate_max_decimal_places(val, 3),
1994            None => ConditionResult::False, // segment absent → condition not applicable
1995        }
1996    }
1997
1998    /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht bei 1 beginnend und fortlaufend aufsteigend
1999    fn evaluate_911(&self, _ctx: &EvaluationContext) -> ConditionResult {
2000        // Hinweis: Mögliche Werte 1 bis n, je Nachricht bei 1 beginnend und fortlaufend aufsteigend
2001        // Informational note about sequence numbering — always applies
2002        ConditionResult::True
2003    }
2004
2005    /// [931] Format: ZZZ = +00
2006    // REVIEW: Format condition: timezone must be UTC (+00). Uses validate_timezone_utc on the DTM value field. Targets first DTM segment found. (medium confidence)
2007    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
2008        let dtm_segs = ctx.find_segments("DTM");
2009        match dtm_segs
2010            .first()
2011            .and_then(|s| s.elements.first())
2012            .and_then(|e| e.get(1))
2013        {
2014            Some(val) => validate_timezone_utc(val),
2015            None => ConditionResult::False, // segment absent → condition not applicable
2016        }
2017    }
2018
2019    /// [932] Format: HHMM = 2200
2020    // REVIEW: Format condition: HHMM must equal 2200. Uses validate_hhmm_equals. DTM+163 is a common time qualifier in IFTSTA for delivery end times. (medium confidence)
2021    fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
2022        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "163");
2023        match dtm_segs
2024            .first()
2025            .and_then(|s| s.elements.first())
2026            .and_then(|e| e.get(1))
2027        {
2028            Some(val) => validate_hhmm_equals(val, "2200"),
2029            None => ConditionResult::False, // segment absent → condition not applicable
2030        }
2031    }
2032
2033    /// [933] Format: HHMM = 2300
2034    fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
2035        let dtm_segs = ctx.find_segments("DTM");
2036        match dtm_segs
2037            .first()
2038            .and_then(|s| s.elements.first())
2039            .and_then(|e| e.get(1))
2040        {
2041            Some(val) => validate_hhmm_equals(val, "2300"),
2042            None => ConditionResult::False, // segment absent → condition not applicable
2043        }
2044    }
2045
2046    /// [934] Format: HHMM = 0400
2047    fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
2048        let dtm_segs = ctx.find_segments("DTM");
2049        match dtm_segs
2050            .first()
2051            .and_then(|s| s.elements.first())
2052            .and_then(|e| e.get(1))
2053        {
2054            Some(val) => validate_hhmm_equals(val, "0400"),
2055            None => ConditionResult::False, // segment absent → condition not applicable
2056        }
2057    }
2058
2059    /// [935] Format: HHMM = 0500
2060    fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
2061        let dtm_segs = ctx.find_segments("DTM");
2062        match dtm_segs
2063            .first()
2064            .and_then(|s| s.elements.first())
2065            .and_then(|e| e.get(1))
2066        {
2067            Some(val) => validate_hhmm_equals(val, "0500"),
2068            None => ConditionResult::False, // segment absent → condition not applicable
2069        }
2070    }
2071
2072    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
2073    // REVIEW: Checks if any COM segment value contains both '@' and '.' characters, validating email address format. COM segments carry communication details; email format requires both characters. This is the standard way to detect email COM values in EDIFACT. (medium confidence)
2074    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
2075        let com_segments = ctx.find_segments("COM");
2076        for seg in &com_segments {
2077            if let Some(value) = seg.elements.first().and_then(|e| e.first()) {
2078                if value.contains('@') && value.contains('.') {
2079                    return ConditionResult::True;
2080                }
2081            }
2082        }
2083        ConditionResult::False
2084    }
2085
2086    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
2087    // REVIEW: Checks if any COM segment value starts with '+' and is followed only by digits — the international phone number format (E.164). This is the standard pattern for phone number validation in German energy market EDIFACT messages. (medium confidence)
2088    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
2089        let com_segments = ctx.find_segments("COM");
2090        for seg in &com_segments {
2091            if let Some(value) = seg.elements.first().and_then(|e| e.first()) {
2092                if value.starts_with('+')
2093                    && value.len() > 1
2094                    && value[1..].chars().all(|c| c.is_ascii_digit())
2095                {
2096                    return ConditionResult::True;
2097                }
2098            }
2099        }
2100        ConditionResult::False
2101    }
2102
2103    /// [950] Format: Marktlokations-ID
2104    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
2105        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z16");
2106        match segs
2107            .first()
2108            .and_then(|s| s.elements.get(1))
2109            .and_then(|e| e.first())
2110        {
2111            Some(val) => validate_malo_id(val),
2112            None => ConditionResult::False, // segment absent → condition not applicable
2113        }
2114    }
2115
2116    /// [951] Format: Zählpunktbezeichnung
2117    // REVIEW: Format condition validating Zählpunktbezeichnung (33 alphanumeric chars). In IFTSTA, Zählpunkt is typically associated with LOC+Z19 (Messlokation/Zählpunkt). Uses validate_zahlpunkt helper. Medium confidence because the exact LOC qualifier for Zählpunkt in IFTSTA may vary — could also be a different qualifier depending on context. (medium confidence)
2118    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
2119        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z19");
2120        match segs
2121            .first()
2122            .and_then(|s| s.elements.get(1))
2123            .and_then(|e| e.first())
2124        {
2125            Some(val) => validate_zahlpunkt(val),
2126            None => ConditionResult::False, // segment absent → condition not applicable
2127        }
2128    }
2129
2130    /// [960] Format: Netzlokations-ID
2131    // REVIEW: Netzlokations-ID uses the same 11-digit Luhn check digit format as Marktlokations-ID. LOC+Z18 is the standard qualifier for Netzlokation in IFTSTA. Element 1, component 0 holds the location ID. (medium confidence)
2132    fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
2133        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z18");
2134        match segs
2135            .first()
2136            .and_then(|s| s.elements.get(1))
2137            .and_then(|e| e.first())
2138        {
2139            Some(val) => validate_malo_id(val),
2140            None => ConditionResult::False, // segment absent → condition not applicable
2141        }
2142    }
2143
2144    /// [961] Format: SR-ID
2145    // REVIEW: SR-ID (Steuerbare Ressource ID) uses the same 11-digit Luhn check digit format as Marktlokations-ID. LOC+Z19 is the standard qualifier for SteuerbareRessource. Element 1, component 0 holds the location ID. (medium confidence)
2146    fn evaluate_961(&self, ctx: &EvaluationContext) -> ConditionResult {
2147        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z19");
2148        match segs
2149            .first()
2150            .and_then(|s| s.elements.get(1))
2151            .and_then(|e| e.first())
2152        {
2153            Some(val) => validate_malo_id(val),
2154            None => ConditionResult::False, // segment absent → condition not applicable
2155        }
2156    }
2157}