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        // HAND-EDITED: "in dieser SG14 STS DE1131 <> E_0278; Unknown without any DE1131, as before" — the enclosing SG14 only, when the scope
453        // reaches one; the message as before otherwise.
454        if let Some(scope) = ctx.enclosing_scope("SG14") {
455            let codes: Vec<String> = ctx
456                .find_segments_at(scope, "STS")
457                .iter()
458                .map(|s| s.get_component(2, 1).to_string())
459                .filter(|v| !v.is_empty())
460                .collect();
461            if codes.is_empty() {
462                return ConditionResult::Unknown;
463            }
464            return ConditionResult::from(codes.iter().any(|v| v != "E_0278"));
465        }
466        let nav = match ctx.navigator() {
467            Some(n) => n,
468            None => {
469                let segs = ctx.find_segments("STS");
470                let mut found_any = false;
471                for seg in &segs {
472                    if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
473                        if !val.is_empty() {
474                            found_any = true;
475                            if val != "E_0278" {
476                                return ConditionResult::True;
477                            }
478                        }
479                    }
480                }
481                return if found_any {
482                    ConditionResult::False
483                } else {
484                    ConditionResult::Unknown
485                };
486            }
487        };
488        let sg14_count = nav.group_instance_count(&["SG14"]);
489        let mut found_any = false;
490        for i in 0..sg14_count {
491            let sts_segs = nav.find_segments_in_group("STS", &["SG14"], i);
492            for seg in &sts_segs {
493                if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
494                    if !val.is_empty() {
495                        found_any = true;
496                        if val != "E_0278" {
497                            return ConditionResult::True;
498                        }
499                    }
500                }
501            }
502        }
503        if found_any {
504            ConditionResult::False
505        } else {
506            ConditionResult::Unknown
507        }
508    }
509
510    /// [150] Wenn in dieser SG14 STS DE1131 &lt;&gt; E_0281
511    // 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)
512    fn evaluate_150(&self, ctx: &EvaluationContext) -> ConditionResult {
513        // HAND-EDITED: "in dieser SG14 STS DE1131 <> E_0281; Unknown without any DE1131, as before" — the enclosing SG14 only, when the scope
514        // reaches one; the message as before otherwise.
515        if let Some(scope) = ctx.enclosing_scope("SG14") {
516            let codes: Vec<String> = ctx
517                .find_segments_at(scope, "STS")
518                .iter()
519                .map(|s| s.get_component(2, 1).to_string())
520                .filter(|v| !v.is_empty())
521                .collect();
522            if codes.is_empty() {
523                return ConditionResult::Unknown;
524            }
525            return ConditionResult::from(codes.iter().any(|v| v != "E_0281"));
526        }
527        let nav = match ctx.navigator() {
528            Some(n) => n,
529            None => {
530                let segs = ctx.find_segments("STS");
531                let mut found_any = false;
532                for seg in &segs {
533                    if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
534                        if !val.is_empty() {
535                            found_any = true;
536                            if val != "E_0281" {
537                                return ConditionResult::True;
538                            }
539                        }
540                    }
541                }
542                return if found_any {
543                    ConditionResult::False
544                } else {
545                    ConditionResult::Unknown
546                };
547            }
548        };
549        let sg14_count = nav.group_instance_count(&["SG14"]);
550        let mut found_any = false;
551        for i in 0..sg14_count {
552            let sts_segs = nav.find_segments_in_group("STS", &["SG14"], i);
553            for seg in &sts_segs {
554                if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
555                    if !val.is_empty() {
556                        found_any = true;
557                        if val != "E_0281" {
558                            return ConditionResult::True;
559                        }
560                    }
561                }
562            }
563        }
564        if found_any {
565            ConditionResult::False
566        } else {
567            ConditionResult::Unknown
568        }
569    }
570
571    /// [151] Wenn STS+Z20+Z32+A99:E_0278 in dieser SG14 vorhanden
572    fn evaluate_151(&self, ctx: &EvaluationContext) -> ConditionResult {
573        // HAND-EDITED: "STS+Z20+Z32+A99:E_0278 in dieser SG14" — the enclosing SG14 only, when the scope
574        // reaches one; the message as before otherwise.
575        if let Some(scope) = ctx.enclosing_scope("SG14") {
576            return ConditionResult::from(ctx.find_segments_at(scope, "STS").iter().any(|s| {
577                s.get_element(0) == "Z20"
578                    && s.get_component(1, 0) == "Z32"
579                    && s.get_component(2, 0) == "A99"
580                    && s.get_component(2, 1) == "E_0278"
581            }));
582        }
583        // STS+Z20+Z32+A99:E_0278: elements[0][0]=Z20, elements[1][0]=Z32, elements[2][0]=A99, elements[2][1]=E_0278
584        ctx.has_segment_matching_in_group(
585            "STS",
586            &[
587                (0, 0, "Z20"),
588                (1, 0, "Z32"),
589                (2, 0, "A99"),
590                (2, 1, "E_0278"),
591            ],
592            &["SG14"],
593        )
594    }
595
596    /// [152] Wenn STS+Z20+Z32+A99:E_0281 in dieser SG14 vorhanden
597    fn evaluate_152(&self, ctx: &EvaluationContext) -> ConditionResult {
598        // HAND-EDITED: "STS+Z20+Z32+A99:E_0281 in dieser SG14" — the enclosing SG14 only, when the scope
599        // reaches one; the message as before otherwise.
600        if let Some(scope) = ctx.enclosing_scope("SG14") {
601            return ConditionResult::from(ctx.find_segments_at(scope, "STS").iter().any(|s| {
602                s.get_element(0) == "Z20"
603                    && s.get_component(1, 0) == "Z32"
604                    && s.get_component(2, 0) == "A99"
605                    && s.get_component(2, 1) == "E_0281"
606            }));
607        }
608        // STS+Z20+Z32+A99:E_0281: elements[0][0]=Z20, elements[1][0]=Z32, elements[2][0]=A99, elements[2][1]=E_0281
609        ctx.has_segment_matching_in_group(
610            "STS",
611            &[
612                (0, 0, "Z20"),
613                (1, 0, "Z32"),
614                (2, 0, "A99"),
615                (2, 1, "E_0281"),
616            ],
617            &["SG14"],
618        )
619    }
620
621    /// [153] Wenn in diesem STS DE1131 = E_0286
622    fn evaluate_153(&self, ctx: &EvaluationContext) -> ConditionResult {
623        // DE1131 is at elements[2][1] in STS (C556 Statusanlaß component 1)
624        ctx.has_segment_matching("STS", &[(2, 1, "E_0286")])
625    }
626
627    /// [154] Wenn STS+Z15+Z13+A99:E_0286 in dieser SG14 vorhanden
628    // 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)
629    fn evaluate_154(&self, ctx: &EvaluationContext) -> ConditionResult {
630        // HAND-EDITED: "STS+Z15+Z13+A99:E_0286 in dieser SG14" — the enclosing SG14 only, when the scope
631        // reaches one; the message as before otherwise.
632        if let Some(scope) = ctx.enclosing_scope("SG14") {
633            return ConditionResult::from(ctx.find_segments_at(scope, "STS").iter().any(|s| {
634                s.get_element(0) == "Z15"
635                    && s.get_component(1, 0) == "Z13"
636                    && s.get_component(2, 0) == "A99"
637                    && s.get_component(2, 1) == "E_0286"
638            }));
639        }
640        ctx.has_segment_matching_in_group(
641            "STS",
642            &[
643                (0, 0, "Z15"),
644                (1, 0, "Z13"),
645                (2, 0, "A99"),
646                (2, 1, "E_0286"),
647            ],
648            &["SG14", "SG15"],
649        )
650    }
651
652    /// [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...
653    fn evaluate_537(&self, _ctx: &EvaluationContext) -> ConditionResult {
654        // 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.
655        ConditionResult::True
656    }
657
658    /// [538] Hinweis: An dieser Stelle wird der Code aus dem EBD übermittelt
659    fn evaluate_538(&self, _ctx: &EvaluationContext) -> ConditionResult {
660        // Hinweis: An dieser Stelle wird der Code aus dem EBD übermittelt
661        ConditionResult::True
662    }
663
664    /// [1] Wenn die Übermittlung nicht codierbarer Informationen nötig ist.
665    /// EXTERNAL: Requires context from outside the message.
666    // 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)
667    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
668        ctx.external.evaluate("non_codeable_info_needed")
669    }
670
671    /// [3] Wenn SG7 STS+Z01 nicht vorhanden.
672    fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
673        ctx.lacks_qualifier("STS", 0, "Z01")
674    }
675
676    /// [4] Wenn SG7 STS+Z02 nicht vorhanden.
677    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
678        ctx.lacks_qualifier("STS", 0, "Z02")
679    }
680
681    /// [5] Wenn SG7 STS+Z03 nicht vorhanden.
682    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
683        ctx.lacks_qualifier("STS", 0, "Z03")
684    }
685
686    /// [10] Wenn Meldung des BKV/NB/ÜNB nach Frist eingeht.
687    /// EXTERNAL: Requires context from outside the message.
688    fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
689        ctx.external.evaluate("message_after_deadline")
690    }
691
692    /// [16] Wenn MP-ID in SG1 NAD+MR in der Rolle BKV
693    /// EXTERNAL: Requires context from outside the message.
694    fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
695        ctx.external.evaluate("recipient_is_bkv")
696    }
697
698    /// [17] Wenn Meldung des BKV auf falscher Aggregationsebene eingeht.
699    /// EXTERNAL: Requires context from outside the message.
700    fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
701        ctx.external.evaluate("bkv_wrong_aggregation_level")
702    }
703
704    /// [18] Wenn SG15 STS+Z19 nicht vorhanden.
705    fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
706        ctx.lacks_qualifier("STS", 0, "Z19")
707    }
708
709    /// [19] Wenn SG15 STS+Z24 nicht vorhanden.
710    fn evaluate_19(&self, ctx: &EvaluationContext) -> ConditionResult {
711        ctx.lacks_qualifier("STS", 0, "Z24")
712    }
713
714    /// [20] Wenn MP-ID in SG1 NAD+MR in der Rolle LF
715    /// EXTERNAL: Requires context from outside the message.
716    fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
717        ctx.external.evaluate("recipient_is_lf")
718    }
719
720    /// [26] Wenn MP-ID in SG1 NAD+MR in der Rolle NB
721    /// EXTERNAL: Requires context from outside the message.
722    fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
723        ctx.external.evaluate("recipient_is_nb")
724    }
725
726    /// [27] Nur MP-ID aus Sparte Strom
727    /// EXTERNAL: Requires context from outside the message.
728    fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
729        ctx.external.evaluate("mp_id_is_electricity_sector")
730    }
731
732    /// [28] Nur MP-ID aus Sparte Gas
733    /// EXTERNAL: Requires context from outside the message.
734    fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
735        ctx.external.evaluate("mp_id_is_gas_sector")
736    }
737
738    /// [29] Wenn MP-ID in SG1 NAD+MR in der Rolle ÜNB
739    /// EXTERNAL: Requires context from outside the message.
740    fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
741        ctx.external.evaluate("recipient_is_uenb")
742    }
743
744    /// [30] Wenn in dieser SG15 STS+Z20+Z32+A07:E_0207 vorhanden.
745    // 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)
746    fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
747        // HAND-EDITED: "in dieser/derselben SG15" — scoped to the enclosing group.
748        let found = ctx
749            .scoped_find_segments_in("SG15", "STS")
750            .into_iter()
751            .any(|s| {
752                s.elements
753                    .first()
754                    .and_then(|e| e.first())
755                    .is_some_and(|v| v == "Z20")
756                    && s.elements
757                        .get(1)
758                        .and_then(|e| e.first())
759                        .is_some_and(|v| v == "Z32")
760                    && s.elements
761                        .get(2)
762                        .and_then(|e| e.first())
763                        .is_some_and(|v| v == "A07")
764                    && s.elements
765                        .get(2)
766                        .and_then(|e| e.get(1))
767                        .is_some_and(|v| v == "E_0207")
768            });
769        ConditionResult::from(found)
770    }
771
772    /// [31] Wenn in dieser SG16 in QTY in DE6411 KWH/K3 vorhanden.
773    fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
774        // HAND-EDITED: "in dieser/derselben SG16" — scoped to the enclosing group.
775        let found = ctx
776            .scoped_find_segments_in("SG16", "QTY")
777            .into_iter()
778            .any(|s| {
779                let unit = s
780                    .elements
781                    .first()
782                    .and_then(|e| e.get(2))
783                    .map(|v| v.as_str());
784                unit == Some("KWH") || unit == Some("K3")
785            });
786        ConditionResult::from(found)
787    }
788
789    /// [32] Wenn in dieser SG16 in QTY in DE6411 KWT/K5 vorhanden.
790    fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
791        // HAND-EDITED: "in dieser/derselben SG16" — scoped to the enclosing group.
792        let found = ctx
793            .scoped_find_segments_in("SG16", "QTY")
794            .into_iter()
795            .any(|s| {
796                let unit = s
797                    .elements
798                    .first()
799                    .and_then(|e| e.get(2))
800                    .map(|v| v.as_str());
801                unit == Some("KWT") || unit == Some("K5")
802            });
803        ConditionResult::from(found)
804    }
805
806    /// [33] Wenn in dieser SG16 DTM+163 vorhanden.
807    fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
808        // HAND-EDITED: "in dieser/derselben SG16" — scoped to the enclosing group.
809        ctx.this_group_has_qualifier("DTM", 0, "163", &["SG16"])
810    }
811
812    /// [43] Wenn STS+Z01+Z07 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
813    fn evaluate_43(&self, ctx: &EvaluationContext) -> ConditionResult {
814        ctx.has_qualified_value("STS", 0, "Z01", 1, 0, &["Z07"])
815    }
816
817    /// [44] Wenn STS+Z01+Z08 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
818    fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
819        ctx.has_qualified_value("STS", 0, "Z01", 1, 0, &["Z08"])
820    }
821
822    /// [45] Wenn STS+Z03+Z07 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
823    fn evaluate_45(&self, ctx: &EvaluationContext) -> ConditionResult {
824        ctx.has_qualified_value("STS", 0, "Z03", 1, 0, &["Z07"])
825    }
826
827    /// [46] Wenn STS+Z03+Z08 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
828    fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
829        ctx.has_qualified_value("STS", 0, "Z03", 1, 0, &["Z08"])
830    }
831
832    /// [47] Es sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
833    fn evaluate_47(&self, _ctx: &EvaluationContext) -> ConditionResult {
834        ConditionResult::True
835    }
836
837    /// [48] Es sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
838    fn evaluate_48(&self, _ctx: &EvaluationContext) -> ConditionResult {
839        ConditionResult::True
840    }
841
842    /// [49] Wenn STS+Z25+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
843    fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
844        ctx.has_qualified_value("STS", 0, "Z25", 1, 0, &["Z30"])
845    }
846
847    /// [50] Wenn STS+Z25+Z31 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
848    fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
849        ctx.has_qualified_value("STS", 0, "Z25", 1, 0, &["Z31"])
850    }
851
852    /// [51] Es sind nur Codes aus dem EBD-Cluster "Abweisung" möglich.
853    fn evaluate_51(&self, _ctx: &EvaluationContext) -> ConditionResult {
854        ConditionResult::True
855    }
856
857    /// [52] Wenn STS+Z27+Z32 vorhanden
858    fn evaluate_52(&self, ctx: &EvaluationContext) -> ConditionResult {
859        ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z32"])
860    }
861
862    /// [53] Wenn STS+Z28+Z32 vorhanden
863    fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
864        ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z32"])
865    }
866
867    /// [54] Wenn STS+Z29+Z32 vorhanden
868    fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
869        ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"])
870    }
871
872    /// [55] Wenn STS+Z30+Z32 vorhanden
873    fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
874        ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"])
875    }
876
877    /// [56] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z27 ist
878    // 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)
879    fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
880        // HAND-EDITED: "nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z27" — compared with the other instances when the
881        // scope reaches an SG15; the message as before otherwise.
882        if let Some(scope) = ctx.enclosing_scope("SG15") {
883            let acw = |path: &[(&str, usize)]| -> Vec<String> {
884                ctx.find_segments_at(path, "RFF")
885                    .iter()
886                    .filter(|s| s.get_element(0) == "ACW")
887                    .map(|s| s.get_component(0, 1).to_string())
888                    .filter(|v| !v.is_empty())
889                    .collect()
890            };
891            let own = acw(scope);
892            // The STS+Z27 SG15 of the same SG14 (Vorgang).
893            let reference: Vec<String> = ctx
894                .scopes_under("SG14", "SG15")
895                .unwrap_or_default()
896                .iter()
897                .filter(|p| {
898                    ctx.find_segments_at(p, "STS")
899                        .iter()
900                        .any(|s| s.get_element(0) == "Z27")
901                })
902                .flat_map(|p| acw(p))
903                .collect();
904            if own.is_empty() || reference.is_empty() {
905                return ConditionResult::Unknown;
906            }
907            return ConditionResult::from(own.iter().any(|v| !reference.contains(v)));
908        }
909        // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z27
910        let nav = match ctx.navigator() {
911            Some(n) => n,
912            None => return ConditionResult::Unknown,
913        };
914        let sg15_path: &[&str] = &["SG14", "SG15"];
915        let sg15_count = nav.group_instance_count(sg15_path);
916        // Collect ACW reference values from SG15 instances that have STS with Z27
917        let mut ref_acw: Vec<String> = Vec::new();
918        for i in 0..sg15_count {
919            let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
920            let has_z27 = sts_segs.iter().any(|s| {
921                s.elements
922                    .first()
923                    .and_then(|e| e.first())
924                    .is_some_and(|v| v == "Z27")
925            });
926            if has_z27 {
927                let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
928                for rff in &rff_segs {
929                    if rff
930                        .elements
931                        .first()
932                        .and_then(|e| e.first())
933                        .is_some_and(|v| v == "ACW")
934                    {
935                        if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
936                            if !val.is_empty() {
937                                ref_acw.push(val.clone());
938                            }
939                        }
940                    }
941                }
942            }
943        }
944        if ref_acw.is_empty() {
945            return ConditionResult::Unknown;
946        }
947        // Check if any SG15 has an ACW value not present in the Z27 SG15 ACW values
948        for i in 0..sg15_count {
949            let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
950            for rff in &rff_segs {
951                if rff
952                    .elements
953                    .first()
954                    .and_then(|e| e.first())
955                    .is_some_and(|v| v == "ACW")
956                {
957                    if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
958                        if !val.is_empty() && !ref_acw.contains(val) {
959                            return ConditionResult::True;
960                        }
961                    }
962                }
963            }
964        }
965        ConditionResult::False
966    }
967
968    /// [57] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z28 ist
969    // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z28 (Status des Fahrplananteils) instead of Z27. (medium confidence)
970    fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
971        // HAND-EDITED: "nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z28" — compared with the other instances when the
972        // scope reaches an SG15; the message as before otherwise.
973        if let Some(scope) = ctx.enclosing_scope("SG15") {
974            let acw = |path: &[(&str, usize)]| -> Vec<String> {
975                ctx.find_segments_at(path, "RFF")
976                    .iter()
977                    .filter(|s| s.get_element(0) == "ACW")
978                    .map(|s| s.get_component(0, 1).to_string())
979                    .filter(|v| !v.is_empty())
980                    .collect()
981            };
982            let own = acw(scope);
983            // The STS+Z28 SG15 of the same SG14 (Vorgang).
984            let reference: Vec<String> = ctx
985                .scopes_under("SG14", "SG15")
986                .unwrap_or_default()
987                .iter()
988                .filter(|p| {
989                    ctx.find_segments_at(p, "STS")
990                        .iter()
991                        .any(|s| s.get_element(0) == "Z28")
992                })
993                .flat_map(|p| acw(p))
994                .collect();
995            if own.is_empty() || reference.is_empty() {
996                return ConditionResult::Unknown;
997            }
998            return ConditionResult::from(own.iter().any(|v| !reference.contains(v)));
999        }
1000        // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z28
1001        let nav = match ctx.navigator() {
1002            Some(n) => n,
1003            None => return ConditionResult::Unknown,
1004        };
1005        let sg15_path: &[&str] = &["SG14", "SG15"];
1006        let sg15_count = nav.group_instance_count(sg15_path);
1007        let mut ref_acw: Vec<String> = Vec::new();
1008        for i in 0..sg15_count {
1009            let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
1010            let has_z28 = sts_segs.iter().any(|s| {
1011                s.elements
1012                    .first()
1013                    .and_then(|e| e.first())
1014                    .is_some_and(|v| v == "Z28")
1015            });
1016            if has_z28 {
1017                let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1018                for rff in &rff_segs {
1019                    if rff
1020                        .elements
1021                        .first()
1022                        .and_then(|e| e.first())
1023                        .is_some_and(|v| v == "ACW")
1024                    {
1025                        if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1026                            if !val.is_empty() {
1027                                ref_acw.push(val.clone());
1028                            }
1029                        }
1030                    }
1031                }
1032            }
1033        }
1034        if ref_acw.is_empty() {
1035            return ConditionResult::Unknown;
1036        }
1037        for i in 0..sg15_count {
1038            let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1039            for rff in &rff_segs {
1040                if rff
1041                    .elements
1042                    .first()
1043                    .and_then(|e| e.first())
1044                    .is_some_and(|v| v == "ACW")
1045                {
1046                    if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1047                        if !val.is_empty() && !ref_acw.contains(val) {
1048                            return ConditionResult::True;
1049                        }
1050                    }
1051                }
1052            }
1053        }
1054        ConditionResult::False
1055    }
1056
1057    /// [58] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z29 ist
1058    // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z29 (Status des Gegenvorschlags der Ausfallarbeit). (medium confidence)
1059    fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
1060        // HAND-EDITED: "nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z29" — compared with the other instances when the
1061        // scope reaches an SG15; the message as before otherwise.
1062        if let Some(scope) = ctx.enclosing_scope("SG15") {
1063            let acw = |path: &[(&str, usize)]| -> Vec<String> {
1064                ctx.find_segments_at(path, "RFF")
1065                    .iter()
1066                    .filter(|s| s.get_element(0) == "ACW")
1067                    .map(|s| s.get_component(0, 1).to_string())
1068                    .filter(|v| !v.is_empty())
1069                    .collect()
1070            };
1071            let own = acw(scope);
1072            // The STS+Z29 SG15 of the same SG14 (Vorgang).
1073            let reference: Vec<String> = ctx
1074                .scopes_under("SG14", "SG15")
1075                .unwrap_or_default()
1076                .iter()
1077                .filter(|p| {
1078                    ctx.find_segments_at(p, "STS")
1079                        .iter()
1080                        .any(|s| s.get_element(0) == "Z29")
1081                })
1082                .flat_map(|p| acw(p))
1083                .collect();
1084            if own.is_empty() || reference.is_empty() {
1085                return ConditionResult::Unknown;
1086            }
1087            return ConditionResult::from(own.iter().any(|v| !reference.contains(v)));
1088        }
1089        // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z29
1090        let nav = match ctx.navigator() {
1091            Some(n) => n,
1092            None => return ConditionResult::Unknown,
1093        };
1094        let sg15_path: &[&str] = &["SG14", "SG15"];
1095        let sg15_count = nav.group_instance_count(sg15_path);
1096        let mut ref_acw: Vec<String> = Vec::new();
1097        for i in 0..sg15_count {
1098            let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
1099            let has_z29 = sts_segs.iter().any(|s| {
1100                s.elements
1101                    .first()
1102                    .and_then(|e| e.first())
1103                    .is_some_and(|v| v == "Z29")
1104            });
1105            if has_z29 {
1106                let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1107                for rff in &rff_segs {
1108                    if rff
1109                        .elements
1110                        .first()
1111                        .and_then(|e| e.first())
1112                        .is_some_and(|v| v == "ACW")
1113                    {
1114                        if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1115                            if !val.is_empty() {
1116                                ref_acw.push(val.clone());
1117                            }
1118                        }
1119                    }
1120                }
1121            }
1122        }
1123        if ref_acw.is_empty() {
1124            return ConditionResult::Unknown;
1125        }
1126        for i in 0..sg15_count {
1127            let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1128            for rff in &rff_segs {
1129                if rff
1130                    .elements
1131                    .first()
1132                    .and_then(|e| e.first())
1133                    .is_some_and(|v| v == "ACW")
1134                {
1135                    if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1136                        if !val.is_empty() && !ref_acw.contains(val) {
1137                            return ConditionResult::True;
1138                        }
1139                    }
1140                }
1141            }
1142        }
1143        ConditionResult::False
1144    }
1145
1146    /// [59] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z30 ist
1147    // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z30 (Status des Gegenvorschlags des Fahrplananteils). (medium confidence)
1148    fn evaluate_59(&self, ctx: &EvaluationContext) -> ConditionResult {
1149        // HAND-EDITED: "nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z30" — compared with the other instances when the
1150        // scope reaches an SG15; the message as before otherwise.
1151        if let Some(scope) = ctx.enclosing_scope("SG15") {
1152            let acw = |path: &[(&str, usize)]| -> Vec<String> {
1153                ctx.find_segments_at(path, "RFF")
1154                    .iter()
1155                    .filter(|s| s.get_element(0) == "ACW")
1156                    .map(|s| s.get_component(0, 1).to_string())
1157                    .filter(|v| !v.is_empty())
1158                    .collect()
1159            };
1160            let own = acw(scope);
1161            // The STS+Z30 SG15 of the same SG14 (Vorgang).
1162            let reference: Vec<String> = ctx
1163                .scopes_under("SG14", "SG15")
1164                .unwrap_or_default()
1165                .iter()
1166                .filter(|p| {
1167                    ctx.find_segments_at(p, "STS")
1168                        .iter()
1169                        .any(|s| s.get_element(0) == "Z30")
1170                })
1171                .flat_map(|p| acw(p))
1172                .collect();
1173            if own.is_empty() || reference.is_empty() {
1174                return ConditionResult::Unknown;
1175            }
1176            return ConditionResult::from(own.iter().any(|v| !reference.contains(v)));
1177        }
1178        // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z30
1179        let nav = match ctx.navigator() {
1180            Some(n) => n,
1181            None => return ConditionResult::Unknown,
1182        };
1183        let sg15_path: &[&str] = &["SG14", "SG15"];
1184        let sg15_count = nav.group_instance_count(sg15_path);
1185        let mut ref_acw: Vec<String> = Vec::new();
1186        for i in 0..sg15_count {
1187            let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
1188            let has_z30 = sts_segs.iter().any(|s| {
1189                s.elements
1190                    .first()
1191                    .and_then(|e| e.first())
1192                    .is_some_and(|v| v == "Z30")
1193            });
1194            if has_z30 {
1195                let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1196                for rff in &rff_segs {
1197                    if rff
1198                        .elements
1199                        .first()
1200                        .and_then(|e| e.first())
1201                        .is_some_and(|v| v == "ACW")
1202                    {
1203                        if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1204                            if !val.is_empty() {
1205                                ref_acw.push(val.clone());
1206                            }
1207                        }
1208                    }
1209                }
1210            }
1211        }
1212        if ref_acw.is_empty() {
1213            return ConditionResult::Unknown;
1214        }
1215        for i in 0..sg15_count {
1216            let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1217            for rff in &rff_segs {
1218                if rff
1219                    .elements
1220                    .first()
1221                    .and_then(|e| e.first())
1222                    .is_some_and(|v| v == "ACW")
1223                {
1224                    if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1225                        if !val.is_empty() && !ref_acw.contains(val) {
1226                            return ConditionResult::True;
1227                        }
1228                    }
1229                }
1230            }
1231        }
1232        ConditionResult::False
1233    }
1234
1235    /// [60] Wenn zusätzlich zum Fahrplananteil auch die Ausfallarbeit zu identischem Wert aus RFF+ACW nicht o.k. ist
1236    // 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)
1237    fn evaluate_60(&self, ctx: &EvaluationContext) -> ConditionResult {
1238        {
1239            let nav = match ctx.navigator() {
1240                Some(n) => n,
1241                None => return ConditionResult::Unknown,
1242            };
1243            let sg14_count = nav.group_instance_count(&["SG14"]);
1244            for i in 0..sg14_count {
1245                let sg15_count = nav.child_group_instance_count(&["SG14"], i, "SG15");
1246                let mut z27_z32_acw: Vec<String> = Vec::new();
1247                let mut z28_z32_acw: Vec<String> = Vec::new();
1248                for j in 0..sg15_count {
1249                    let stses = nav.find_segments_in_child_group("STS", &["SG14"], i, "SG15", j);
1250                    let rffs = nav.find_segments_in_child_group("RFF", &["SG14"], i, "SG15", j);
1251                    let acw_val = rffs
1252                        .iter()
1253                        .find(|s| {
1254                            s.elements
1255                                .first()
1256                                .and_then(|e| e.first())
1257                                .is_some_and(|v| v == "ACW")
1258                        })
1259                        .and_then(|s| s.elements.first().and_then(|e| e.get(1)).cloned());
1260                    let Some(acw) = acw_val else { continue };
1261                    for sts in &stses {
1262                        let cat = sts
1263                            .elements
1264                            .first()
1265                            .and_then(|e| e.first())
1266                            .map(|s| s.as_str());
1267                        let status = sts
1268                            .elements
1269                            .get(1)
1270                            .and_then(|e| e.first())
1271                            .map(|s| s.as_str());
1272                        match (cat, status) {
1273                            (Some("Z27"), Some("Z32")) => z27_z32_acw.push(acw.clone()),
1274                            (Some("Z28"), Some("Z32")) => z28_z32_acw.push(acw.clone()),
1275                            _ => {}
1276                        }
1277                    }
1278                }
1279                if z27_z32_acw.iter().any(|v| z28_z32_acw.contains(v)) {
1280                    return ConditionResult::True;
1281                }
1282            }
1283            ConditionResult::False
1284        }
1285    }
1286
1287    /// [61] Wenn zusätzlich zur Ausfallarbeit auch der Fahrplananteil zu identischem Wert aus RFF+ACW nicht o.k. ist
1288    // 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)
1289    fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
1290        {
1291            let nav = match ctx.navigator() {
1292                Some(n) => n,
1293                None => return ConditionResult::Unknown,
1294            };
1295            let sg14_count = nav.group_instance_count(&["SG14"]);
1296            for i in 0..sg14_count {
1297                let sg15_count = nav.child_group_instance_count(&["SG14"], i, "SG15");
1298                let mut z27_z32_acw: Vec<String> = Vec::new();
1299                let mut z28_z32_acw: Vec<String> = Vec::new();
1300                for j in 0..sg15_count {
1301                    let stses = nav.find_segments_in_child_group("STS", &["SG14"], i, "SG15", j);
1302                    let rffs = nav.find_segments_in_child_group("RFF", &["SG14"], i, "SG15", j);
1303                    let acw_val = rffs
1304                        .iter()
1305                        .find(|s| {
1306                            s.elements
1307                                .first()
1308                                .and_then(|e| e.first())
1309                                .is_some_and(|v| v == "ACW")
1310                        })
1311                        .and_then(|s| s.elements.first().and_then(|e| e.get(1)).cloned());
1312                    let Some(acw) = acw_val else { continue };
1313                    for sts in &stses {
1314                        let cat = sts
1315                            .elements
1316                            .first()
1317                            .and_then(|e| e.first())
1318                            .map(|s| s.as_str());
1319                        let status = sts
1320                            .elements
1321                            .get(1)
1322                            .and_then(|e| e.first())
1323                            .map(|s| s.as_str());
1324                        match (cat, status) {
1325                            (Some("Z27"), Some("Z32")) => z27_z32_acw.push(acw.clone()),
1326                            (Some("Z28"), Some("Z32")) => z28_z32_acw.push(acw.clone()),
1327                            _ => {}
1328                        }
1329                    }
1330                }
1331                if z28_z32_acw.iter().any(|v| z27_z32_acw.contains(v)) {
1332                    return ConditionResult::True;
1333                }
1334            }
1335            ConditionResult::False
1336        }
1337    }
1338
1339    /// [62] Wenn STS+Z27+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1340    fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
1341        ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z30"])
1342    }
1343
1344    /// [63] Wenn STS+Z27+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1345    fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
1346        ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z32"])
1347    }
1348
1349    /// [64] Wenn STS+Z28+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1350    fn evaluate_64(&self, ctx: &EvaluationContext) -> ConditionResult {
1351        ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z30"])
1352    }
1353
1354    /// [65] Wenn STS+Z28+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1355    fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
1356        ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z32"])
1357    }
1358
1359    /// [66] Wenn STS+Z29+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1360    fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
1361        ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z30"])
1362    }
1363
1364    /// [67] Wenn STS+Z29+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1365    fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
1366        ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"])
1367    }
1368
1369    /// [68] Wenn STS+Z30+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1370    fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
1371        ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z30"])
1372    }
1373
1374    /// [69] Wenn STS+Z30+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1375    fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
1376        ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"])
1377    }
1378
1379    /// [70] Wenn zusätzlich zum Gegenvorschlag des Fahrplananteils auch der Gegenvorschlag der Ausfallarbeit zu identischem Wert aus RFF+ACW nicht o.k. ist
1380    // 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)
1381    fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
1382        match ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"]) {
1383            ConditionResult::True => ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"]),
1384            ConditionResult::False => ConditionResult::False,
1385            ConditionResult::Unknown => ConditionResult::Unknown,
1386        }
1387    }
1388
1389    /// [71] Wenn zusätzlich zum Gegenvorschlag der Ausfallarbeit auch der Gegenvorschlag des Fahrplananteils zu identischem Wert aus RFF+ACW nicht o.k. ist
1390    // 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)
1391    fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
1392        match ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"]) {
1393            ConditionResult::True => ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"]),
1394            ConditionResult::False => ConditionResult::False,
1395            ConditionResult::Unknown => ConditionResult::Unknown,
1396        }
1397    }
1398
1399    /// [72] Wenn Gegenvorschlag erstellt werden kann
1400    /// EXTERNAL: Requires context from outside the message.
1401    fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
1402        ctx.external.evaluate("counter_proposal_possible")
1403    }
1404
1405    /// [76] Wenn MP-ID in SG1 NAD+MR in der Rolle ESA
1406    /// EXTERNAL: Requires context from outside the message.
1407    fn evaluate_76(&self, ctx: &EvaluationContext) -> ConditionResult {
1408        ctx.external.evaluate("recipient_is_esa")
1409    }
1410
1411    /// [77] Wenn STS+Z37+Z14 in dieser SG14 vorhanden
1412    fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
1413        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1414        ctx.this_group_has_qualified_value("STS", 0, "Z37", 1, 0, &["Z14"], &["SG14"])
1415    }
1416
1417    /// [78] Wenn STS+Z38 in dieser SG14 nicht vorhanden
1418    // 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)
1419    fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
1420        // HAND-EDITED: "STS+Z38 in dieser SG14 nicht vorhanden" — the enclosing SG14 only, when the scope
1421        // reaches one; the message as before otherwise.
1422        if let Some(scope) = ctx.enclosing_scope("SG14") {
1423            return ConditionResult::from(
1424                !ctx.find_segments_at(scope, "STS")
1425                    .iter()
1426                    .any(|s| s.get_element(0) == "Z38"),
1427            );
1428        }
1429        ctx.any_group_has_qualifier_without("STS", 0, "Z37", "STS", 0, "Z38", &["SG14"])
1430    }
1431
1432    /// [79] Wenn STS+Z37 in dieser SG14 nicht vorhanden
1433    // 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)
1434    fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
1435        // HAND-EDITED: "STS+Z37 in dieser SG14 nicht vorhanden" — the enclosing SG14 only, when the scope
1436        // reaches one; the message as before otherwise.
1437        if let Some(scope) = ctx.enclosing_scope("SG14") {
1438            return ConditionResult::from(
1439                !ctx.find_segments_at(scope, "STS")
1440                    .iter()
1441                    .any(|s| s.get_element(0) == "Z37"),
1442            );
1443        }
1444        ctx.any_group_has_qualifier_without("STS", 0, "Z38", "STS", 0, "Z37", &["SG14"])
1445    }
1446
1447    /// [80] Wenn STS+Z38+Z14 in dieser SG14 vorhanden
1448    fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
1449        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1450        ctx.this_group_has_qualified_value("STS", 0, "Z38", 1, 0, &["Z14"], &["SG14"])
1451    }
1452
1453    /// [83] Wenn STS+Z37+Z13+A04/A05/A06:E_0472 in dieser SG14 vorhanden
1454    // 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)
1455    fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
1456        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1457        let sts_segs = ctx.scoped_find_segments_in("SG14", "STS");
1458        let found = sts_segs.iter().any(|s| {
1459            s.elements
1460                .first()
1461                .and_then(|e| e.first())
1462                .is_some_and(|v| v == "Z37")
1463                && s.elements
1464                    .get(1)
1465                    .and_then(|e| e.first())
1466                    .is_some_and(|v| v == "Z13")
1467                && s.elements
1468                    .get(2)
1469                    .and_then(|e| e.first())
1470                    .is_some_and(|v| matches!(v.as_str(), "A04" | "A05" | "A06"))
1471        });
1472        ConditionResult::from(found)
1473    }
1474
1475    /// [84] Wenn STS+Z38+Z13+A02:E_0499 in dieser SG14 vorhanden
1476    // 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)
1477    fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
1478        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1479        let sts_segs = ctx.scoped_find_segments_in("SG14", "STS");
1480        let found = sts_segs.iter().any(|s| {
1481            s.elements
1482                .first()
1483                .and_then(|e| e.first())
1484                .is_some_and(|v| v == "Z38")
1485                && s.elements
1486                    .get(1)
1487                    .and_then(|e| e.first())
1488                    .is_some_and(|v| v == "Z13")
1489                && s.elements
1490                    .get(2)
1491                    .and_then(|e| e.first())
1492                    .is_some_and(|v| v == "A02")
1493        });
1494        ConditionResult::from(found)
1495    }
1496
1497    /// [85] Wenn STS+Z37+Z32+A01:E_0501 in dieser SG14 vorhanden
1498    // 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)
1499    fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
1500        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1501        let sts_segs = ctx.scoped_find_segments_in("SG14", "STS");
1502        let found = sts_segs.iter().any(|s| {
1503            s.elements
1504                .first()
1505                .and_then(|e| e.first())
1506                .is_some_and(|v| v == "Z37")
1507                && s.elements
1508                    .get(1)
1509                    .and_then(|e| e.first())
1510                    .is_some_and(|v| v == "Z32")
1511                && s.elements
1512                    .get(2)
1513                    .and_then(|e| e.first())
1514                    .is_some_and(|v| v == "A01")
1515        });
1516        ConditionResult::from(found)
1517    }
1518
1519    /// [86] Wenn MP-ID in SG1 NAD+MS in der Rolle LF
1520    /// EXTERNAL: Requires context from outside the message.
1521    fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
1522        ctx.external.evaluate("sender_is_lf")
1523    }
1524
1525    /// [91] Wenn in diesem STS DE1131 = E_0472
1526    fn evaluate_91(&self, ctx: &EvaluationContext) -> ConditionResult {
1527        let segments = ctx.find_segments("STS");
1528        ConditionResult::from(segments.iter().any(|s| {
1529            s.elements
1530                .get(2)
1531                .and_then(|e| e.get(1))
1532                .map(|v| v == "E_0472")
1533                .unwrap_or(false)
1534        }))
1535    }
1536
1537    /// [92] Wenn in diesem STS DE1131 = E_0501
1538    fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
1539        let segments = ctx.find_segments("STS");
1540        ConditionResult::from(segments.iter().any(|s| {
1541            s.elements
1542                .get(2)
1543                .and_then(|e| e.get(1))
1544                .map(|v| v == "E_0501")
1545                .unwrap_or(false)
1546        }))
1547    }
1548
1549    /// [93] Wenn STS+Z37+Z13 vorhanden, dann sind nur Codes aus dem EBD-Cluster "gescheitert" möglich.
1550    fn evaluate_93(&self, ctx: &EvaluationContext) -> ConditionResult {
1551        ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z13"])
1552    }
1553
1554    /// [94] Wenn STS+Z37+Z14 vorhanden, dann sind nur Codes aus dem EBD-Cluster "erfolgreich" möglich.
1555    fn evaluate_94(&self, ctx: &EvaluationContext) -> ConditionResult {
1556        ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z14"])
1557    }
1558
1559    /// [95] Wenn in diesem STS DE1131 = E_0499
1560    fn evaluate_95(&self, ctx: &EvaluationContext) -> ConditionResult {
1561        let segments = ctx.find_segments("STS");
1562        ConditionResult::from(segments.iter().any(|s| {
1563            s.elements
1564                .get(2)
1565                .and_then(|e| e.get(1))
1566                .map(|v| v == "E_0499")
1567                .unwrap_or(false)
1568        }))
1569    }
1570
1571    /// [96] Wenn in diesem STS DE1131 = E_0487
1572    fn evaluate_96(&self, ctx: &EvaluationContext) -> ConditionResult {
1573        let segments = ctx.find_segments("STS");
1574        ConditionResult::from(segments.iter().any(|s| {
1575            s.elements
1576                .get(2)
1577                .and_then(|e| e.get(1))
1578                .map(|v| v == "E_0487")
1579                .unwrap_or(false)
1580        }))
1581    }
1582
1583    /// [97] Wenn STS+Z38+Z13 vorhanden, dann sind nur Codes aus dem EBD-Cluster "gescheitert" möglich.
1584    fn evaluate_97(&self, ctx: &EvaluationContext) -> ConditionResult {
1585        ctx.has_qualified_value("STS", 0, "Z38", 1, 0, &["Z13"])
1586    }
1587
1588    /// [98] Wenn STS+Z38+Z14 vorhanden, dann sind nur Codes aus dem EBD-Cluster "erfolgreich" möglich.
1589    fn evaluate_98(&self, ctx: &EvaluationContext) -> ConditionResult {
1590        ctx.has_qualified_value("STS", 0, "Z38", 1, 0, &["Z14"])
1591    }
1592
1593    /// [99] Wenn in diesem STS DE1131 = E_0487, dann ist nur der Code A01 möglich.
1594    fn evaluate_99(&self, ctx: &EvaluationContext) -> ConditionResult {
1595        let segments = ctx.find_segments("STS");
1596        ConditionResult::from(segments.iter().any(|s| {
1597            s.elements
1598                .get(2)
1599                .and_then(|e| e.get(1))
1600                .map(|v| v == "E_0487")
1601                .unwrap_or(false)
1602        }))
1603    }
1604
1605    /// [100] Wenn STS+Z43+Z47 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Änderung der Daten" möglich.
1606    fn evaluate_100(&self, ctx: &EvaluationContext) -> ConditionResult {
1607        ctx.has_qualified_value("STS", 0, "Z43", 1, 0, &["Z47"])
1608    }
1609
1610    /// [101] Wenn STS+Z43+Z48 vorhanden, dann sind nur Codes aus dem EBD-Cluster "keine Änderung der Daten" möglich.
1611    fn evaluate_101(&self, ctx: &EvaluationContext) -> ConditionResult {
1612        ctx.has_qualified_value("STS", 0, "Z43", 1, 0, &["Z48"])
1613    }
1614
1615    /// [107] Wenn STS+Z37+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1616    fn evaluate_107(&self, ctx: &EvaluationContext) -> ConditionResult {
1617        ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z32"])
1618    }
1619
1620    /// [114] Wenn MP-ID in SG1 NAD+MS in der Rolle MSB
1621    /// EXTERNAL: Requires context from outside the message.
1622    fn evaluate_114(&self, ctx: &EvaluationContext) -> ConditionResult {
1623        ctx.external.evaluate("sender_is_msb")
1624    }
1625
1626    /// [115] Wenn MP-ID in SG1 NAD+MS in der Rolle NB
1627    /// EXTERNAL: Requires context from outside the message.
1628    fn evaluate_115(&self, ctx: &EvaluationContext) -> ConditionResult {
1629        ctx.external.evaluate("sender_is_nb")
1630    }
1631
1632    /// [117] Wenn MP-ID in SG1 NAD+MR in der Rolle MSB
1633    /// EXTERNAL: Requires context from outside the message.
1634    fn evaluate_117(&self, ctx: &EvaluationContext) -> ConditionResult {
1635        ctx.external.evaluate("recipient_is_msb")
1636    }
1637
1638    /// [118] Wenn in diesem STS DE1131 = E_0526
1639    fn evaluate_118(&self, ctx: &EvaluationContext) -> ConditionResult {
1640        ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0526"])
1641    }
1642
1643    /// [119] Wenn in diesem STS DE1131 = E_0528
1644    fn evaluate_119(&self, ctx: &EvaluationContext) -> ConditionResult {
1645        ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0528"])
1646    }
1647
1648    /// [120] Wenn in diesem STS DE1131 = E_0529
1649    fn evaluate_120(&self, ctx: &EvaluationContext) -> ConditionResult {
1650        ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0529"])
1651    }
1652
1653    /// [121] Wenn in diesem STS DE1131 = E_0536
1654    fn evaluate_121(&self, ctx: &EvaluationContext) -> ConditionResult {
1655        ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0536"])
1656    }
1657
1658    /// [129] Wenn STS+Z20+Z32+A99:E_0524 in dieser SG14 vorhanden
1659    // 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)
1660    fn evaluate_129(&self, ctx: &EvaluationContext) -> ConditionResult {
1661        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1662        let segments = ctx.scoped_find_segments_in("SG14", "STS");
1663        ConditionResult::from(segments.iter().any(|s| {
1664            s.elements
1665                .first()
1666                .and_then(|e| e.first())
1667                .map(|v| v == "Z20")
1668                .unwrap_or(false)
1669                && s.elements
1670                    .get(1)
1671                    .and_then(|e| e.first())
1672                    .map(|v| v == "Z32")
1673                    .unwrap_or(false)
1674                && s.elements
1675                    .get(2)
1676                    .and_then(|e| e.first())
1677                    .map(|v| v == "A99")
1678                    .unwrap_or(false)
1679                && s.elements
1680                    .get(2)
1681                    .and_then(|e| e.get(1))
1682                    .map(|v| v == "E_0524")
1683                    .unwrap_or(false)
1684        }))
1685    }
1686
1687    /// [130] Wenn STS+Z20+Z32+A99:E_0531 in dieser SG14 vorhanden
1688    // 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)
1689    fn evaluate_130(&self, ctx: &EvaluationContext) -> ConditionResult {
1690        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1691        let segments = ctx.scoped_find_segments_in("SG14", "STS");
1692        ConditionResult::from(segments.iter().any(|s| {
1693            s.elements
1694                .first()
1695                .and_then(|e| e.first())
1696                .map(|v| v == "Z20")
1697                .unwrap_or(false)
1698                && s.elements
1699                    .get(1)
1700                    .and_then(|e| e.first())
1701                    .map(|v| v == "Z32")
1702                    .unwrap_or(false)
1703                && s.elements
1704                    .get(2)
1705                    .and_then(|e| e.first())
1706                    .map(|v| v == "A99")
1707                    .unwrap_or(false)
1708                && s.elements
1709                    .get(2)
1710                    .and_then(|e| e.get(1))
1711                    .map(|v| v == "E_0531")
1712                    .unwrap_or(false)
1713        }))
1714    }
1715
1716    /// [131] Wenn STS+Z37+Z13+A04/A05/A06:E_1003 in dieser SG14 vorhanden
1717    // 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)
1718    fn evaluate_131(&self, ctx: &EvaluationContext) -> ConditionResult {
1719        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1720        let segments = ctx.scoped_find_segments_in("SG14", "STS");
1721        ConditionResult::from(segments.iter().any(|s| {
1722            s.elements
1723                .first()
1724                .and_then(|e| e.first())
1725                .map(|v| v == "Z37")
1726                .unwrap_or(false)
1727                && s.elements
1728                    .get(1)
1729                    .and_then(|e| e.first())
1730                    .map(|v| v == "Z13")
1731                    .unwrap_or(false)
1732                && s.elements
1733                    .get(2)
1734                    .and_then(|e| e.first())
1735                    .map(|v| matches!(v.as_str(), "A04" | "A05" | "A06"))
1736                    .unwrap_or(false)
1737                && s.elements
1738                    .get(2)
1739                    .and_then(|e| e.get(1))
1740                    .map(|v| v == "E_1003")
1741                    .unwrap_or(false)
1742        }))
1743    }
1744
1745    /// [132] Wenn STS+Z37+Z32+A01:E_1002 in dieser SG14 vorhanden
1746    // 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)
1747    fn evaluate_132(&self, ctx: &EvaluationContext) -> ConditionResult {
1748        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1749        let segments = ctx.scoped_find_segments_in("SG14", "STS");
1750        ConditionResult::from(segments.iter().any(|s| {
1751            s.elements
1752                .first()
1753                .and_then(|e| e.first())
1754                .map(|v| v == "Z37")
1755                .unwrap_or(false)
1756                && s.elements
1757                    .get(1)
1758                    .and_then(|e| e.first())
1759                    .map(|v| v == "Z32")
1760                    .unwrap_or(false)
1761                && s.elements
1762                    .get(2)
1763                    .and_then(|e| e.first())
1764                    .map(|v| v == "A01")
1765                    .unwrap_or(false)
1766                && s.elements
1767                    .get(2)
1768                    .and_then(|e| e.get(1))
1769                    .map(|v| v == "E_1002")
1770                    .unwrap_or(false)
1771        }))
1772    }
1773
1774    /// [133] Wenn in diesem STS DE1131 = E_1003
1775    fn evaluate_133(&self, ctx: &EvaluationContext) -> ConditionResult {
1776        ctx.has_qualified_value("STS", 0, "Z37", 2, 1, &["E_1003"])
1777    }
1778
1779    /// [134] Wenn in diesem STS DE1131 = E_1002
1780    fn evaluate_134(&self, ctx: &EvaluationContext) -> ConditionResult {
1781        ctx.has_qualified_value("STS", 0, "Z37", 2, 1, &["E_1002"])
1782    }
1783
1784    /// [135] Wenn in dieser SG15 in STS+Z21 DE9013 = A99
1785    fn evaluate_135(&self, ctx: &EvaluationContext) -> ConditionResult {
1786        // HAND-EDITED: "in dieser SG15 in STS+Z21 DE9013 = A99" — the enclosing SG15 only, when the scope
1787        // reaches one; the message as before otherwise.
1788        if let Some(scope) = ctx.enclosing_scope("SG15") {
1789            // As `has_qualified_value`: Unknown without any STS+Z21.
1790            let sts: Vec<_> = ctx
1791                .find_segments_at(scope, "STS")
1792                .into_iter()
1793                .filter(|s| s.get_element(0) == "Z21")
1794                .collect();
1795            if sts.is_empty() {
1796                return ConditionResult::Unknown;
1797            }
1798            return ConditionResult::from(sts.iter().any(|s| s.get_component(2, 0) == "A99"));
1799        }
1800        ctx.has_qualified_value("STS", 0, "Z21", 2, 0, &["A99"])
1801    }
1802
1803    /// [136] Wenn STS+Z38+Z13+A02:E_1005 in dieser SG14 vorhanden
1804    // 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)
1805    fn evaluate_136(&self, ctx: &EvaluationContext) -> ConditionResult {
1806        // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1807        let segments = ctx.scoped_find_segments_in("SG14", "STS");
1808        ConditionResult::from(segments.iter().any(|s| {
1809            s.elements
1810                .first()
1811                .and_then(|e| e.first())
1812                .map(|v| v == "Z38")
1813                .unwrap_or(false)
1814                && s.elements
1815                    .get(1)
1816                    .and_then(|e| e.first())
1817                    .map(|v| v == "Z13")
1818                    .unwrap_or(false)
1819                && s.elements
1820                    .get(2)
1821                    .and_then(|e| e.first())
1822                    .map(|v| v == "A02")
1823                    .unwrap_or(false)
1824                && s.elements
1825                    .get(2)
1826                    .and_then(|e| e.get(1))
1827                    .map(|v| v == "E_1005")
1828                    .unwrap_or(false)
1829        }))
1830    }
1831
1832    /// [137] Wenn in diesem STS DE1131 = E_1005
1833    fn evaluate_137(&self, ctx: &EvaluationContext) -> ConditionResult {
1834        ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1005"])
1835    }
1836
1837    /// [138] Wenn in diesem STS DE1131 = E_1020
1838    fn evaluate_138(&self, ctx: &EvaluationContext) -> ConditionResult {
1839        ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1020"])
1840    }
1841
1842    /// [139] Wenn in diesem STS DE1131 = E_1020, dann ist nur der Code A01 möglich.
1843    // 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)
1844    fn evaluate_139(&self, ctx: &EvaluationContext) -> ConditionResult {
1845        ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1020"])
1846    }
1847
1848    /// [140] Wenn in diesem STS DE9013 &lt;&gt; A01
1849    // 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)
1850    fn evaluate_140(&self, ctx: &EvaluationContext) -> ConditionResult {
1851        let sts_segments = ctx.find_segments("STS");
1852        ConditionResult::from(sts_segments.iter().any(|s| {
1853            s.elements
1854                .get(2)
1855                .and_then(|e| e.first())
1856                .is_some_and(|v| !v.is_empty() && v != "A01")
1857        }))
1858    }
1859
1860    /// [141] Wenn in diesem STS DE9013 = A01
1861    // REVIEW: Checks if any STS segment has DE9013 (elements[2][0]) equal to 'A01'. Medium confidence due to segment-level context ambiguity. (medium confidence)
1862    fn evaluate_141(&self, ctx: &EvaluationContext) -> ConditionResult {
1863        let sts_segments = ctx.find_segments("STS");
1864        ConditionResult::from(sts_segments.iter().any(|s| {
1865            s.elements
1866                .get(2)
1867                .and_then(|e| e.first())
1868                .is_some_and(|v| v == "A01")
1869        }))
1870    }
1871
1872    /// [142] Wenn in diesem STS DE1131 = E_0535
1873    fn evaluate_142(&self, ctx: &EvaluationContext) -> ConditionResult {
1874        let sts_segments = ctx.find_segments("STS");
1875        ConditionResult::from(sts_segments.iter().any(|s| {
1876            s.elements
1877                .get(2)
1878                .and_then(|e| e.get(1))
1879                .is_some_and(|v| v == "E_0535")
1880        }))
1881    }
1882
1883    /// [143] Wenn mehr als ein Grund vorliegt und die voranstehenden DE9013 dieses STS nicht ausreichen
1884    // 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)
1885    fn evaluate_143(&self, ctx: &EvaluationContext) -> ConditionResult {
1886        let sts_segments = ctx.find_segments("STS");
1887        ConditionResult::from(sts_segments.iter().any(|s| {
1888            s.elements
1889                .get(3)
1890                .and_then(|e| e.first())
1891                .is_some_and(|v| !v.is_empty())
1892        }))
1893    }
1894
1895    /// [144] Wenn RFF+Z45 in dieser SG15 nicht vorhanden
1896    // 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)
1897    fn evaluate_144(&self, ctx: &EvaluationContext) -> ConditionResult {
1898        // HAND-EDITED: "RFF+Z45 in dieser SG15 nicht vorhanden" — the enclosing SG15 only, when the scope
1899        // reaches one; the message as before otherwise.
1900        if let Some(scope) = ctx.enclosing_scope("SG15") {
1901            return ConditionResult::from(
1902                !ctx.find_segments_at(scope, "RFF")
1903                    .iter()
1904                    .any(|s| s.get_element(0) == "Z45"),
1905            );
1906        }
1907        ctx.any_group_has_qualifier_without("RFF", 0, "Z13", "RFF", 0, "Z45", &["SG14", "SG15"])
1908    }
1909
1910    /// [145] Wenn RFF+Z17 in dieser SG15 nicht vorhanden
1911    // REVIEW: Checks that in some SG15 instance, RFF+Z13 (mandatory Prüfidentifikator) is present but RFF+Z17 (Preisschlüsselstamm reference) is absent. (medium confidence)
1912    fn evaluate_145(&self, ctx: &EvaluationContext) -> ConditionResult {
1913        // HAND-EDITED: "RFF+Z17 in dieser SG15 nicht vorhanden" — the enclosing SG15 only, when the scope
1914        // reaches one; the message as before otherwise.
1915        if let Some(scope) = ctx.enclosing_scope("SG15") {
1916            return ConditionResult::from(
1917                !ctx.find_segments_at(scope, "RFF")
1918                    .iter()
1919                    .any(|s| s.get_element(0) == "Z17"),
1920            );
1921        }
1922        ctx.any_group_has_qualifier_without("RFF", 0, "Z13", "RFF", 0, "Z17", &["SG14", "SG15"])
1923    }
1924
1925    /// [146] Wenn im DE3155 in demselben COM der Code EM vorhanden ist
1926    fn evaluate_146(&self, ctx: &EvaluationContext) -> ConditionResult {
1927        let com_segs = ctx.find_segments("COM");
1928        ConditionResult::from(com_segs.iter().any(|s| {
1929            s.elements
1930                .first()
1931                .and_then(|e| e.get(1))
1932                .is_some_and(|v| v == "EM")
1933        }))
1934    }
1935
1936    /// [147] Wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
1937    fn evaluate_147(&self, ctx: &EvaluationContext) -> ConditionResult {
1938        let com_segs = ctx.find_segments("COM");
1939        ConditionResult::from(com_segs.iter().any(|s| {
1940            s.elements
1941                .first()
1942                .and_then(|e| e.get(1))
1943                .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
1944        }))
1945    }
1946
1947    /// [148] Wenn in dieser SG15 STS+Z43+Z48+A99:E_0595 vorhanden
1948    fn evaluate_148(&self, ctx: &EvaluationContext) -> ConditionResult {
1949        // HAND-EDITED: "in dieser/derselben SG15" — scoped to the enclosing group.
1950        let sts_segs = ctx.scoped_find_segments_in("SG15", "STS");
1951        ConditionResult::from(sts_segs.iter().any(|s| {
1952            s.elements
1953                .first()
1954                .and_then(|e| e.first())
1955                .is_some_and(|v| v == "Z43")
1956                && s.elements
1957                    .get(1)
1958                    .and_then(|e| e.first())
1959                    .is_some_and(|v| v == "Z48")
1960                && s.elements
1961                    .get(2)
1962                    .and_then(|e| e.first())
1963                    .is_some_and(|v| v == "A99")
1964                && s.elements
1965                    .get(2)
1966                    .and_then(|e| e.get(1))
1967                    .is_some_and(|v| v == "E_0595")
1968        }))
1969    }
1970
1971    /// [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
1972    fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
1973        match ctx.resolved_value {
1974            Some(val) => is_mesz_utc(val),
1975            None => ConditionResult::False,
1976        }
1977    }
1978
1979    /// [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
1980    fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1981        match ctx.resolved_value {
1982            Some(val) => is_mez_utc(val),
1983            None => ConditionResult::False,
1984        }
1985    }
1986
1987    /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
1988    /// EXTERNAL: Requires context from outside the message.
1989    fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1990        ctx.external.evaluate("recipient_is_strom")
1991    }
1992
1993    /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
1994    /// EXTERNAL: Requires context from outside the message.
1995    fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1996        ctx.external.evaluate("recipient_is_gas")
1997    }
1998
1999    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
2000    // 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)
2001    fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
2002        // The referenced date must be the document creation time or earlier (≤ DTM+137)
2003        let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
2004        let doc_val = match doc_segs
2005            .first()
2006            .and_then(|s| s.elements.first())
2007            .and_then(|e| e.get(1))
2008        {
2009            Some(v) => v.as_str(),
2010            None => return ConditionResult::Unknown,
2011        };
2012        let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
2013        match status_segs
2014            .first()
2015            .and_then(|s| s.elements.first())
2016            .and_then(|e| e.get(1))
2017        {
2018            Some(val) => ConditionResult::from(val.as_str() <= doc_val),
2019            None => ConditionResult::False, // segment absent → condition not applicable
2020        }
2021    }
2022
2023    /// [495] Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein
2024    // 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)
2025    fn evaluate_495(&self, ctx: &EvaluationContext) -> ConditionResult {
2026        // The timestamp must be ≤ the value in DE2380 of DTM+137
2027        let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
2028        let doc_val = match doc_segs
2029            .first()
2030            .and_then(|s| s.elements.first())
2031            .and_then(|e| e.get(1))
2032        {
2033            Some(v) => v.as_str(),
2034            None => return ConditionResult::Unknown,
2035        };
2036        let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
2037        match status_segs
2038            .first()
2039            .and_then(|s| s.elements.first())
2040            .and_then(|e| e.get(1))
2041        {
2042            Some(val) => ConditionResult::from(val.as_str() <= doc_val),
2043            None => ConditionResult::False, // segment absent → condition not applicable
2044        }
2045    }
2046
2047    /// [496] Der Zeitpunkt muss &gt; dem Wert im DE2380 des DTM+137 sein
2048    // 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)
2049    fn evaluate_496(&self, ctx: &EvaluationContext) -> ConditionResult {
2050        // The timestamp must be > the value in DE2380 of DTM+137
2051        let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
2052        let doc_val = match doc_segs
2053            .first()
2054            .and_then(|s| s.elements.first())
2055            .and_then(|e| e.get(1))
2056        {
2057            Some(v) => v.as_str(),
2058            None => return ConditionResult::Unknown,
2059        };
2060        let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
2061        match status_segs
2062            .first()
2063            .and_then(|s| s.elements.first())
2064            .and_then(|e| e.get(1))
2065        {
2066            Some(val) => ConditionResult::from(val.as_str() > doc_val),
2067            None => ConditionResult::False, // segment absent → condition not applicable
2068        }
2069    }
2070
2071    /// [501] Hinweis: Aus QUOTES BGM DE1004
2072    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
2073        // Hinweis: Aus QUOTES BGM DE1004 — informational note about field origin, always applies
2074        ConditionResult::True
2075    }
2076
2077    /// [502] Hinweis: Aus REQOTE BGM DE1004
2078    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
2079        // Hinweis: Aus REQOTE BGM DE1004 — informational note about field origin, always applies
2080        ConditionResult::True
2081    }
2082
2083    /// [503] Hinweis: Auf Selbsteinbau eines iMS oder einer mME wird verzichtet
2084    fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
2085        // Hinweis: Auf Selbsteinbau eines iMS oder einer mME wird verzichtet — informational note, always applies
2086        ConditionResult::True
2087    }
2088
2089    /// [504] Hinweis: Verwendung der ID des MaBiS-ZP
2090    fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
2091        // Hinweis: Verwendung der ID des MaBiS-ZP — informational note, always applies
2092        ConditionResult::True
2093    }
2094
2095    /// [505] Hinweis: Verwendung der ID der Messlokation
2096    fn evaluate_505(&self, _ctx: &EvaluationContext) -> ConditionResult {
2097        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
2098        ConditionResult::True
2099    }
2100
2101    /// [506] Hinweis: Verwendung der ID der Marktlokation
2102    fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
2103        // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
2104        ConditionResult::True
2105    }
2106
2107    /// [510] Hinweis: Es ist neben der Information über die Ablehnung auch der unverändert gebliebene Datenstatus informell mitzugeben.
2108    fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
2109        // Hinweis: Es ist neben der Information über die Ablehnung auch der unverändert gebliebene Datenstatus informell mitzugeben — informational note, always applies
2110        ConditionResult::True
2111    }
2112
2113    /// [512] Hinweis: Aus MSCONS BGM DE1004
2114    fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
2115        // Hinweis: Aus MSCONS BGM DE1004 — informational note, always applies
2116        ConditionResult::True
2117    }
2118
2119    /// [519] Hinweis: Aus ORDERS BGM DE1004
2120    fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
2121        // Hinweis: Aus ORDERS BGM DE1004 — informational note, always applies
2122        ConditionResult::True
2123    }
2124
2125    /// [520] Hinweis: Zeitpunkt, zu dem der Wechsel erfolgt, falls er zustande kommt
2126    fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
2127        // Hinweis: Zeitpunkt, zu dem der Wechsel erfolgt, falls er zustande kommt — informational note, always applies
2128        ConditionResult::True
2129    }
2130
2131    /// [521] Hinweis: Zeitpunkt, ab dem der MSBN tatsächlich den Messstellenbetrieb übernimmt
2132    fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
2133        // Hinweis: Zeitpunkt, ab dem der MSBN tatsächlich den Messstellenbetrieb übernimmt — informational note, always applies
2134        ConditionResult::True
2135    }
2136
2137    /// [522] Hinweis: Zeitpunkt, ab dem der gMSB den Messstellenbetrieb übernimmt
2138    fn evaluate_522(&self, _ctx: &EvaluationContext) -> ConditionResult {
2139        // Hinweis: Zeitpunkt, ab dem der gMSB den Messstellenbetrieb übernimmt — informational note, always applies
2140        ConditionResult::True
2141    }
2142
2143    /// [523] Hinweis: Wert aus BGM DE1004 der MSCONS, auf die sich die Statusangabe bezieht
2144    fn evaluate_523(&self, _ctx: &EvaluationContext) -> ConditionResult {
2145        // Hinweis: Wert aus BGM DE1004 der MSCONS, auf die sich die Statusangabe bezieht — informational note, always applies
2146        ConditionResult::True
2147    }
2148
2149    /// [524] Hinweis: Wert aus BGM DE1004 der MSCONS, die den Gegenvorschlag enthält
2150    fn evaluate_524(&self, _ctx: &EvaluationContext) -> ConditionResult {
2151        // Hinweis: Wert aus BGM DE1004 der MSCONS, die den Gegenvorschlag enthält — informational note, always applies
2152        ConditionResult::True
2153    }
2154
2155    /// [525] Hinweis: Je SG14 sind nur Statusinformationen zu einer MSCONS enthalten
2156    fn evaluate_525(&self, _ctx: &EvaluationContext) -> ConditionResult {
2157        // Hinweis: Je SG14 sind nur Statusinformationen zu einer MSCONS enthalten — informational note, always applies
2158        ConditionResult::True
2159    }
2160
2161    /// [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.
2162    fn evaluate_530(&self, _ctx: &EvaluationContext) -> ConditionResult {
2163        // 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
2164        ConditionResult::True
2165    }
2166
2167    /// [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...
2168    fn evaluate_531(&self, _ctx: &EvaluationContext) -> ConditionResult {
2169        // Hinweis: Vom MSBN in Schritt 1 des SD verwendete Vorgangsnummer, damit der LF diese
2170        // bei der Bestellung einer Konfiguration beim MSBN verwenden kann — informational note
2171        // describing field semantics and usage context, always applies unconditionally
2172        ConditionResult::True
2173    }
2174
2175    /// [532] Hinweis: Verwendung der ID der Netzlokation
2176    fn evaluate_532(&self, _ctx: &EvaluationContext) -> ConditionResult {
2177        // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
2178        ConditionResult::True
2179    }
2180
2181    /// [533] Hinweis: Verwendung der ID der Steuerbaren Ressource
2182    fn evaluate_533(&self, _ctx: &EvaluationContext) -> ConditionResult {
2183        // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
2184        ConditionResult::True
2185    }
2186
2187    /// [534] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
2188    fn evaluate_534(&self, _ctx: &EvaluationContext) -> ConditionResult {
2189        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
2190        ConditionResult::True
2191    }
2192
2193    /// [535] Hinweis: Aus UTILMD IDE DE7402
2194    fn evaluate_535(&self, _ctx: &EvaluationContext) -> ConditionResult {
2195        // Hinweis: Aus UTILMD IDE DE7402 — informational note about value origin, always applies
2196        ConditionResult::True
2197    }
2198
2199    /// [902] Format: Wert darf nur positiv oder 0 sein
2200    // 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)
2201    fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
2202        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">=", 0.0))
2203    }
2204
2205    /// [903] Format: Möglicher Wert: 1
2206    // 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)
2207    fn evaluate_903(&self, ctx: &EvaluationContext) -> ConditionResult {
2208        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "==", 1.0))
2209    }
2210
2211    /// [906] Format: max. 3 Nachkommastellen
2212    // REVIEW: Format condition: max 3 decimal places. Applies to QTY segment value field (elements[0][1]). (medium confidence)
2213    fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
2214        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 3))
2215    }
2216
2217    /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht bei 1 beginnend und fortlaufend aufsteigend
2218    fn evaluate_911(&self, _ctx: &EvaluationContext) -> ConditionResult {
2219        // Hinweis: Mögliche Werte 1 bis n, je Nachricht bei 1 beginnend und fortlaufend aufsteigend
2220        // Informational note about sequence numbering — always applies
2221        ConditionResult::True
2222    }
2223
2224    /// [931] Format: ZZZ = +00
2225    // REVIEW: Format condition: timezone must be UTC (+00). Uses validate_timezone_utc on the DTM value field. Targets first DTM segment found. (medium confidence)
2226    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
2227        ctx.format_check("DTM", 0, 1, validate_timezone_utc)
2228    }
2229
2230    /// [932] Format: HHMM = 2200
2231    // 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)
2232    fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
2233        ctx.format_check_qualified("DTM", 0, "163", 0, 1, |val| {
2234            validate_hhmm_equals(val, "2200")
2235        })
2236    }
2237
2238    /// [933] Format: HHMM = 2300
2239    fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
2240        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2300"))
2241    }
2242
2243    /// [934] Format: HHMM = 0400
2244    fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
2245        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0400"))
2246    }
2247
2248    /// [935] Format: HHMM = 0500
2249    fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
2250        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0500"))
2251    }
2252
2253    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
2254    // 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)
2255    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
2256        let com_segments = ctx.find_segments("COM");
2257        for seg in &com_segments {
2258            if let Some(value) = seg.elements.first().and_then(|e| e.first()) {
2259                if value.contains('@') && value.contains('.') {
2260                    return ConditionResult::True;
2261                }
2262            }
2263        }
2264        ConditionResult::False
2265    }
2266
2267    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
2268    // 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)
2269    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
2270        let com_segments = ctx.find_segments("COM");
2271        for seg in &com_segments {
2272            if let Some(value) = seg.elements.first().and_then(|e| e.first()) {
2273                if value.starts_with('+')
2274                    && value.len() > 1
2275                    && value[1..].chars().all(|c| c.is_ascii_digit())
2276                {
2277                    return ConditionResult::True;
2278                }
2279            }
2280        }
2281        ConditionResult::False
2282    }
2283
2284    /// [950] Format: Marktlokations-ID
2285    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
2286        ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
2287    }
2288
2289    /// [951] Format: Zählpunktbezeichnung
2290    // 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)
2291    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
2292        ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_zahlpunkt)
2293    }
2294
2295    /// [960] Format: Netzlokations-ID
2296    // HAND-EDITED: a Netzlokations-ID is "E" + ten alphanumerics (`E1688117482`),
2297    // not MaLo-shaped; `validate_malo_id` rejected every real one (#173).
2298    fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
2299        ctx.format_check_qualified("LOC", 0, "Z18", 1, 0, validate_nelo_id)
2300    }
2301
2302    /// [961] Format: SR-ID
2303    // 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)
2304    fn evaluate_961(&self, ctx: &EvaluationContext) -> ConditionResult {
2305        ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_malo_id)
2306    }
2307}