Skip to main content

automapper_validation/generated/fv2510/
iftsta_conditions_fv2510.rs

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