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