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