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