Skip to main content

automapper_validation/generated/fv2504/
iftsta_conditions_fv2504.rs

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