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