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