Skip to main content

automapper_validation/generated/fv2510/
invoic_conditions_fv2510.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2510/INVOIC_AHB_1_0_Fehlerkorrektur_20250623.xml
4// Generated: 2026-03-12T11:09:04Z
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 INVOIC FV2510.
17pub struct InvoicConditionEvaluatorFV2510 {
18    // External condition IDs that require runtime context.
19    external_conditions: std::collections::HashSet<u32>,
20}
21
22impl Default for InvoicConditionEvaluatorFV2510 {
23    fn default() -> Self {
24        let mut external_conditions = std::collections::HashSet::new();
25        external_conditions.insert(1);
26        external_conditions.insert(2);
27        external_conditions.insert(3);
28        external_conditions.insert(7);
29        external_conditions.insert(8);
30        external_conditions.insert(11);
31        external_conditions.insert(13);
32        external_conditions.insert(15);
33        external_conditions.insert(16);
34        external_conditions.insert(24);
35        external_conditions.insert(25);
36        external_conditions.insert(28);
37        external_conditions.insert(30);
38        external_conditions.insert(31);
39        external_conditions.insert(34);
40        external_conditions.insert(37);
41        external_conditions.insert(40);
42        external_conditions.insert(45);
43        external_conditions.insert(67);
44        external_conditions.insert(68);
45        external_conditions.insert(69);
46        external_conditions.insert(72);
47        external_conditions.insert(76);
48        external_conditions.insert(492);
49        external_conditions.insert(493);
50        Self {
51            external_conditions,
52        }
53    }
54}
55
56impl ConditionEvaluator for InvoicConditionEvaluatorFV2510 {
57    fn message_type(&self) -> &str {
58        "INVOIC"
59    }
60
61    fn format_version(&self) -> &str {
62        "FV2510"
63    }
64
65    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
66        match condition {
67            1 => self.evaluate_1(ctx),
68            2 => self.evaluate_2(ctx),
69            3 => self.evaluate_3(ctx),
70            4 => self.evaluate_4(ctx),
71            5 => self.evaluate_5(ctx),
72            6 => self.evaluate_6(ctx),
73            7 => self.evaluate_7(ctx),
74            8 => self.evaluate_8(ctx),
75            9 => self.evaluate_9(ctx),
76            10 => self.evaluate_10(ctx),
77            11 => self.evaluate_11(ctx),
78            12 => self.evaluate_12(ctx),
79            13 => self.evaluate_13(ctx),
80            14 => self.evaluate_14(ctx),
81            15 => self.evaluate_15(ctx),
82            16 => self.evaluate_16(ctx),
83            17 => self.evaluate_17(ctx),
84            18 => self.evaluate_18(ctx),
85            19 => self.evaluate_19(ctx),
86            20 => self.evaluate_20(ctx),
87            21 => self.evaluate_21(ctx),
88            22 => self.evaluate_22(ctx),
89            23 => self.evaluate_23(ctx),
90            24 => self.evaluate_24(ctx),
91            25 => self.evaluate_25(ctx),
92            26 => self.evaluate_26(ctx),
93            27 => self.evaluate_27(ctx),
94            28 => self.evaluate_28(ctx),
95            29 => self.evaluate_29(ctx),
96            30 => self.evaluate_30(ctx),
97            31 => self.evaluate_31(ctx),
98            32 => self.evaluate_32(ctx),
99            34 => self.evaluate_34(ctx),
100            36 => self.evaluate_36(ctx),
101            37 => self.evaluate_37(ctx),
102            40 => self.evaluate_40(ctx),
103            41 => self.evaluate_41(ctx),
104            42 => self.evaluate_42(ctx),
105            44 => self.evaluate_44(ctx),
106            45 => self.evaluate_45(ctx),
107            47 => self.evaluate_47(ctx),
108            48 => self.evaluate_48(ctx),
109            49 => self.evaluate_49(ctx),
110            50 => self.evaluate_50(ctx),
111            51 => self.evaluate_51(ctx),
112            53 => self.evaluate_53(ctx),
113            54 => self.evaluate_54(ctx),
114            55 => self.evaluate_55(ctx),
115            56 => self.evaluate_56(ctx),
116            57 => self.evaluate_57(ctx),
117            58 => self.evaluate_58(ctx),
118            59 => self.evaluate_59(ctx),
119            60 => self.evaluate_60(ctx),
120            61 => self.evaluate_61(ctx),
121            64 => self.evaluate_64(ctx),
122            65 => self.evaluate_65(ctx),
123            66 => self.evaluate_66(ctx),
124            67 => self.evaluate_67(ctx),
125            68 => self.evaluate_68(ctx),
126            69 => self.evaluate_69(ctx),
127            70 => self.evaluate_70(ctx),
128            71 => self.evaluate_71(ctx),
129            72 => self.evaluate_72(ctx),
130            73 => self.evaluate_73(ctx),
131            74 => self.evaluate_74(ctx),
132            75 => self.evaluate_75(ctx),
133            76 => self.evaluate_76(ctx),
134            77 => self.evaluate_77(ctx),
135            78 => self.evaluate_78(ctx),
136            79 => self.evaluate_79(ctx),
137            80 => self.evaluate_80(ctx),
138            81 => self.evaluate_81(ctx),
139            82 => self.evaluate_82(ctx),
140            83 => self.evaluate_83(ctx),
141            84 => self.evaluate_84(ctx),
142            85 => self.evaluate_85(ctx),
143            86 => self.evaluate_86(ctx),
144            87 => self.evaluate_87(ctx),
145            88 => self.evaluate_88(ctx),
146            490 => self.evaluate_490(ctx),
147            491 => self.evaluate_491(ctx),
148            492 => self.evaluate_492(ctx),
149            493 => self.evaluate_493(ctx),
150            501 => self.evaluate_501(ctx),
151            502 => self.evaluate_502(ctx),
152            503 => self.evaluate_503(ctx),
153            504 => self.evaluate_504(ctx),
154            507 => self.evaluate_507(ctx),
155            508 => self.evaluate_508(ctx),
156            509 => self.evaluate_509(ctx),
157            510 => self.evaluate_510(ctx),
158            512 => self.evaluate_512(ctx),
159            513 => self.evaluate_513(ctx),
160            514 => self.evaluate_514(ctx),
161            515 => self.evaluate_515(ctx),
162            516 => self.evaluate_516(ctx),
163            517 => self.evaluate_517(ctx),
164            518 => self.evaluate_518(ctx),
165            519 => self.evaluate_519(ctx),
166            520 => self.evaluate_520(ctx),
167            521 => self.evaluate_521(ctx),
168            522 => self.evaluate_522(ctx),
169            523 => self.evaluate_523(ctx),
170            524 => self.evaluate_524(ctx),
171            525 => self.evaluate_525(ctx),
172            526 => self.evaluate_526(ctx),
173            902 => self.evaluate_902(ctx),
174            906 => self.evaluate_906(ctx),
175            908 => self.evaluate_908(ctx),
176            910 => self.evaluate_910(ctx),
177            911 => self.evaluate_911(ctx),
178            912 => self.evaluate_912(ctx),
179            914 => self.evaluate_914(ctx),
180            927 => self.evaluate_927(ctx),
181            930 => self.evaluate_930(ctx),
182            931 => self.evaluate_931(ctx),
183            932 => self.evaluate_932(ctx),
184            933 => self.evaluate_933(ctx),
185            934 => self.evaluate_934(ctx),
186            935 => self.evaluate_935(ctx),
187            937 => self.evaluate_937(ctx),
188            939 => self.evaluate_939(ctx),
189            940 => self.evaluate_940(ctx),
190            946 => self.evaluate_946(ctx),
191            950 => self.evaluate_950(ctx),
192            951 => self.evaluate_951(ctx),
193            960 => self.evaluate_960(ctx),
194            961 => self.evaluate_961(ctx),
195            _ => ConditionResult::Unknown,
196        }
197    }
198
199    fn is_external(&self, condition: u32) -> bool {
200        self.external_conditions.contains(&condition)
201    }
202    fn is_known(&self, condition: u32) -> bool {
203        matches!(
204            condition,
205            1 | 2
206                | 3
207                | 4
208                | 5
209                | 6
210                | 7
211                | 8
212                | 9
213                | 10
214                | 11
215                | 12
216                | 13
217                | 14
218                | 15
219                | 16
220                | 17
221                | 18
222                | 19
223                | 20
224                | 21
225                | 22
226                | 23
227                | 24
228                | 25
229                | 26
230                | 27
231                | 28
232                | 29
233                | 30
234                | 31
235                | 32
236                | 34
237                | 36
238                | 37
239                | 40
240                | 41
241                | 42
242                | 44
243                | 45
244                | 47
245                | 48
246                | 49
247                | 50
248                | 51
249                | 53
250                | 54
251                | 55
252                | 56
253                | 57
254                | 58
255                | 59
256                | 60
257                | 61
258                | 64
259                | 65
260                | 66
261                | 67
262                | 68
263                | 69
264                | 70
265                | 71
266                | 72
267                | 73
268                | 74
269                | 75
270                | 76
271                | 77
272                | 78
273                | 79
274                | 80
275                | 81
276                | 82
277                | 83
278                | 84
279                | 85
280                | 86
281                | 87
282                | 88
283                | 490
284                | 491
285                | 492
286                | 493
287                | 501
288                | 502
289                | 503
290                | 504
291                | 507
292                | 508
293                | 509
294                | 510
295                | 512
296                | 513
297                | 514
298                | 515
299                | 516
300                | 517
301                | 518
302                | 519
303                | 520
304                | 521
305                | 522
306                | 523
307                | 524
308                | 525
309                | 526
310                | 902
311                | 906
312                | 908
313                | 910
314                | 911
315                | 912
316                | 914
317                | 927
318                | 930
319                | 931
320                | 932
321                | 933
322                | 934
323                | 935
324                | 937
325                | 939
326                | 940
327                | 946
328                | 950
329                | 951
330                | 960
331                | 961
332        )
333    }
334}
335
336impl InvoicConditionEvaluatorFV2510 {
337    /// [1] Wenn in zu stornierender Rechnung gefüllt
338    /// EXTERNAL: Requires context from outside the message.
339    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
340        ctx.external.evaluate("original_invoice_field_filled")
341    }
342
343    /// [2] Wenn es sich um eine Nutzungsüberlassung (Pacht) eines Gerätes handelt
344    /// EXTERNAL: Requires context from outside the message.
345    fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
346        ctx.external.evaluate("device_is_leased")
347    }
348
349    /// [3] Wenn es sich um einen Kauf eines Gerätes handelt
350    /// EXTERNAL: Requires context from outside the message.
351    fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
352        ctx.external.evaluate("device_is_purchased")
353    }
354
355    /// [7] Sofern keine Großkundenpostleitzahl verwendet wird
356    /// EXTERNAL: Requires context from outside the message.
357    fn evaluate_7(&self, ctx: &EvaluationContext) -> ConditionResult {
358        ctx.external.evaluate("no_grosskundenpzl_used")
359    }
360
361    /// [8] Bei zeitabhängigen Preisen
362    /// EXTERNAL: Requires context from outside the message.
363    fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
364        ctx.external.evaluate("time_dependent_prices")
365    }
366
367    /// [24] Wert muss mindestens 10 WT nach Wert aus DTM+137 DE2380 liegen
368    /// EXTERNAL: Requires context from outside the message.
369    fn evaluate_24(&self, ctx: &EvaluationContext) -> ConditionResult {
370        ctx.external
371            .evaluate("value_at_least_10_werktage_after_nachrichtendatum")
372    }
373
374    /// [25] Wert darf maximal 10 WT nach Wert aus DTM+137 DE2380 liegen
375    /// EXTERNAL: Requires context from outside the message.
376    fn evaluate_25(&self, ctx: &EvaluationContext) -> ConditionResult {
377        ctx.external
378            .evaluate("value_at_most_10_werktage_after_nachrichtendatum")
379    }
380
381    /// [67] Es sind nur die Artikelnummern erlaubt, die im Kapitel 2 „Codeliste der Artikelnummer“ in der Codeliste der Artikelnummern und Artikel-ID ein „X“ für den entsprechenden Prüfidentifikator ...
382    /// EXTERNAL: Requires context from outside the message.
383    fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
384        ctx.external
385            .evaluate("invoic_artikelnummer_allowed_for_pid")
386    }
387
388    /// [68] Es sind nur die Artikel-ID erlaubt, die im Kapitel 3 „Codeliste der Gruppenartikel-ID und Artikel-ID“  in der Codeliste der Artikelnummern und Artikel-ID ein „X“ in der Spalte „INVOIC Cod...
389    /// EXTERNAL: Requires context from outside the message.
390    fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
391        ctx.external.evaluate("invoic_artikel_id_x_codeverwendung")
392    }
393
394    /// [69] Es sind nur die Artikel-ID erlaubt, die im Kapitel 3 „Codeliste der Gruppenartikel-ID und Artikel-ID“  in der Codeliste der Artikelnummern und Artikel-ID ein „SOR“ in der Spalte „INVOIC C...
395    /// EXTERNAL: Requires context from outside the message.
396    fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
397        ctx.external
398            .evaluate("invoic_artikel_id_sor_codeverwendung")
399    }
400
401    /// [83] Wenn GEI+Z10 nicht vorhanden
402    fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
403        ctx.lacks_qualifier("GEI", 0, "Z10")
404    }
405
406    /// [87] Wenn IMD++TEC vorhanden
407    fn evaluate_87(&self, ctx: &EvaluationContext) -> ConditionResult {
408        ctx.has_qualifier("IMD", 1, "TEC")
409    }
410
411    /// [4] Wenn Steuerschuldnerschaft des Leistungsempfängers vorliegt
412    // REVIEW: "Wenn Steuerschuldnerschaft des Leistungsempfängers vorliegt" is the reverse charge mechanism. In UN/EDIFACT INVOIC, TAX category code "AE" (VAT Reverse Charge) signals this. The TAX segment composite C241 component 3 holds the tax category code. Medium confidence because element indices are derived from standard UN/EDIFACT TAX structure without a specific MIG segment structure reference to confirm the exact index for this INVOIC variant. (medium confidence)
413    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
414        // Steuerschuldnerschaft des Leistungsempfängers = reverse charge (VAT category AE)
415        // In EDIFACT INVOIC the TAX segment carries duty/tax type category code.
416        // UN/EDIFACT code "AE" = VAT Reverse Charge (Steuerschuldnerschaft des Leistungsempfängers).
417        // TAX+7+VAT+++:::AE — elements[4][3] holds the tax category code.
418        let tax_segs = ctx.find_segments("TAX");
419        for seg in &tax_segs {
420            // elements[4] is the duty/tax account detail composite (C241 in UN/EDIFACT TAX)
421            // component 3 (index 3) is the duty/tax rate category code
422            if let Some(code) = seg.elements.get(4).and_then(|e| e.get(3)) {
423                if code == "AE" {
424                    return ConditionResult::True;
425                }
426            }
427        }
428        ConditionResult::False
429    }
430
431    /// [5] Wenn NAD+MR DE3207 &lt;&gt; „DE“
432    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
433        let nads = ctx.find_segments_with_qualifier("NAD", 0, "MR");
434        match nads.first() {
435            Some(nad) => {
436                match nad
437                    .elements
438                    .get(8)
439                    .and_then(|e| e.first())
440                    .map(|s| s.as_str())
441                {
442                    Some(country) => ConditionResult::from(country != "DE"),
443                    None => ConditionResult::False, // segment absent → condition not applicable
444                }
445            }
446            None => ConditionResult::False, // segment absent → condition not applicable
447        }
448    }
449
450    /// [6] Wenn NAD+MR DE3207 = „DE“
451    fn evaluate_6(&self, ctx: &EvaluationContext) -> ConditionResult {
452        let nads = ctx.find_segments_with_qualifier("NAD", 0, "MR");
453        match nads.first() {
454            Some(nad) => {
455                match nad
456                    .elements
457                    .get(8)
458                    .and_then(|e| e.first())
459                    .map(|s| s.as_str())
460                {
461                    Some(country) => ConditionResult::from(country == "DE"),
462                    None => ConditionResult::False, // segment absent → condition not applicable
463                }
464            }
465            None => ConditionResult::False, // segment absent → condition not applicable
466        }
467    }
468
469    /// [9] Wenn SG26 DTM+203 nicht vorhanden
470    // REVIEW: Checks whether any SG26 instance lacks DTM+203 (Ausführungsdatum). Uses group navigator when available, falling back to message-wide absence check. DTM qualifier at elements[0][0]. (medium confidence)
471    fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
472        // DTM+203 (Ausführungsdatum) absence in the current SG26 context.
473        // Falls back to message-wide check when no group navigator available.
474        let nav = match ctx.navigator() {
475            Some(n) => n,
476            None => return ctx.lacks_qualifier("DTM", 0, "203"),
477        };
478        let sg26_count = nav.group_instance_count(&["SG26"]);
479        for i in 0..sg26_count {
480            let dtms = nav.find_segments_in_group("DTM", &["SG26"], i);
481            let has_203 = dtms.iter().any(|s| {
482                s.elements
483                    .first()
484                    .and_then(|e| e.first())
485                    .is_some_and(|v| v == "203")
486            });
487            if !has_203 {
488                return ConditionResult::True;
489            }
490        }
491        if sg26_count == 0 {
492            return ctx.lacks_qualifier("DTM", 0, "203");
493        }
494        ConditionResult::False
495    }
496
497    /// [10] Wenn SG26 DTM+155/156 nicht vorhanden
498    // REVIEW: Both DTM+155 (Abrechnungszeitraum Beginn) and DTM+156 (Ende) must be absent in the SG26. Uses group navigator; both must be absent simultaneously for the condition to be True. (medium confidence)
499    fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
500        // DTM+155 (Abrechnungszeitraum Beginn) and DTM+156 (Ende) both absent in SG26.
501        // Falls back to message-wide check when no group navigator available.
502        let nav = match ctx.navigator() {
503            Some(n) => n,
504            None => {
505                let no_155 = ctx.lacks_qualifier("DTM", 0, "155");
506                let no_156 = ctx.lacks_qualifier("DTM", 0, "156");
507                return match (no_155, no_156) {
508                    (ConditionResult::True, ConditionResult::True) => ConditionResult::True,
509                    (ConditionResult::False, _) | (_, ConditionResult::False) => {
510                        ConditionResult::False
511                    }
512                    _ => ConditionResult::Unknown,
513                };
514            }
515        };
516        let sg26_count = nav.group_instance_count(&["SG26"]);
517        for i in 0..sg26_count {
518            let dtms = nav.find_segments_in_group("DTM", &["SG26"], i);
519            let has_155 = dtms.iter().any(|s| {
520                s.elements
521                    .first()
522                    .and_then(|e| e.first())
523                    .is_some_and(|v| v == "155")
524            });
525            let has_156 = dtms.iter().any(|s| {
526                s.elements
527                    .first()
528                    .and_then(|e| e.first())
529                    .is_some_and(|v| v == "156")
530            });
531            if !has_155 && !has_156 {
532                return ConditionResult::True;
533            }
534        }
535        if sg26_count == 0 {
536            let no_155 = ctx.lacks_qualifier("DTM", 0, "155");
537            let no_156 = ctx.lacks_qualifier("DTM", 0, "156");
538            return match (no_155, no_156) {
539                (ConditionResult::True, ConditionResult::True) => ConditionResult::True,
540                (ConditionResult::False, _) | (_, ConditionResult::False) => ConditionResult::False,
541                _ => ConditionResult::Unknown,
542            };
543        }
544        ConditionResult::False
545    }
546
547    /// [11] Wenn Abschlag anfällt
548    /// EXTERNAL: Requires context from outside the message.
549    // REVIEW: Whether a deposit/advance payment (Abschlag) applies is business context that cannot be determined from the EDIFACT message structure alone — it depends on the contract type and billing arrangement. (medium confidence)
550    fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
551        ctx.external.evaluate("advance_payment_applies")
552    }
553
554    /// [12] Wenn SG26 QTY+136 vorhanden
555    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
556        ctx.has_qualifier("QTY", 0, "136")
557    }
558
559    /// [13] Wenn vorausbezahlter Betrag vorliegt
560    /// EXTERNAL: Requires context from outside the message.
561    // REVIEW: Whether a prepaid amount (vorausbezahlter Betrag) is present is billing business context that cannot be derived solely from the EDIFACT segments in the message. (medium confidence)
562    fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
563        ctx.external.evaluate("prepaid_amount_present")
564    }
565
566    /// [14] Wenn in selben SG26 LIN DE7140 = "9990001000748" (Mehrmenge)
567    fn evaluate_14(&self, ctx: &EvaluationContext) -> ConditionResult {
568        // Check if any SG26 LIN has DE7140 (Produkt-/Leistungsnummer) = "9990001000748" (Mehrmenge).
569        // LIN is the entry segment of SG26; elements[2][0] = DE7140.
570        let lins = ctx.find_segments("LIN");
571        ConditionResult::from(lins.iter().any(|s| {
572            s.elements
573                .get(2)
574                .and_then(|e| e.first())
575                .is_some_and(|v| v == "9990001000748")
576        }))
577    }
578
579    /// [15] Wenn eine Bilanzierung erfolgt ist
580    /// EXTERNAL: Requires context from outside the message.
581    // REVIEW: Whether a balancing (Bilanzierung) has occurred is operational/business context external to the EDIFACT message itself. (medium confidence)
582    fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
583        ctx.external.evaluate("balancing_performed")
584    }
585
586    /// [16] Wenn eine Netznutzung erfolgt ist
587    /// EXTERNAL: Requires context from outside the message.
588    // REVIEW: Whether network usage (Netznutzung) has occurred is business context that cannot be determined solely from the EDIFACT message content. (medium confidence)
589    fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
590        ctx.external.evaluate("network_usage_performed")
591    }
592
593    /// [17] Wenn DTM+Z11 vorhanden
594    fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
595        ctx.has_qualifier("DTM", 0, "Z11")
596    }
597
598    /// [18] Wenn IMD++WIM nicht vorhanden
599    fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
600        ctx.lacks_qualifier("IMD", 1, "WIM")
601    }
602
603    /// [19] Wenn IMD++WIM vorhanden
604    fn evaluate_19(&self, ctx: &EvaluationContext) -> ConditionResult {
605        ctx.has_qualifier("IMD", 1, "WIM")
606    }
607
608    /// [20] Wenn fälliger Betrag (SG50 MOA+9) ≥ 0
609    // REVIEW: MOA+9 is fälliger Betrag (due amount): elements[0][0]='9', elements[0][1]=amount. Parses the numeric value and checks >= 0. (medium confidence)
610    fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
611        let moas = ctx.find_segments_with_qualifier("MOA", 0, "9");
612        match moas.first() {
613            Some(moa) => match moa.elements.first().and_then(|e| e.get(1)) {
614                Some(val) => match val.parse::<f64>() {
615                    Ok(n) => ConditionResult::from(n >= 0.0),
616                    Err(_) => ConditionResult::Unknown,
617                },
618                None => ConditionResult::False, // segment absent → condition not applicable
619            },
620            None => ConditionResult::False, // segment absent → condition not applicable
621        }
622    }
623
624    /// [21] Wenn fälliger Betrag (SG50 MOA+9) &lt; 0
625    // REVIEW: MOA+9 is fälliger Betrag. Parses numeric value and checks < 0. Inverse of condition 20. (medium confidence)
626    fn evaluate_21(&self, ctx: &EvaluationContext) -> ConditionResult {
627        let moas = ctx.find_segments_with_qualifier("MOA", 0, "9");
628        match moas.first() {
629            Some(moa) => match moa.elements.first().and_then(|e| e.get(1)) {
630                Some(val) => match val.parse::<f64>() {
631                    Ok(n) => ConditionResult::from(n < 0.0),
632                    Err(_) => ConditionResult::Unknown,
633                },
634                None => ConditionResult::False, // segment absent → condition not applicable
635            },
636            None => ConditionResult::False, // segment absent → condition not applicable
637        }
638    }
639
640    /// [22] Wenn vorhanden
641    fn evaluate_22(&self, _ctx: &EvaluationContext) -> ConditionResult {
642        // Wenn vorhanden — self-referential presence condition, always True when evaluated
643        ConditionResult::True
644    }
645
646    /// [23] Wenn im selben NAD DE3124 nicht vorhanden
647    // REVIEW: DE3124 resides in C058 at elements[2][0] for all NAD variants. 'Im selben NAD' means within the same NAD instance; evaluated message-wide as: any NAD where this component is absent or empty. (medium confidence)
648    fn evaluate_23(&self, ctx: &EvaluationContext) -> ConditionResult {
649        let nads = ctx.find_segments("NAD");
650        if nads.is_empty() {
651            return ConditionResult::Unknown;
652        }
653        // True if any NAD has DE3124 (elements[2][0] from C058) absent or empty
654        ConditionResult::from(nads.iter().any(|nad| {
655            nad.elements
656                .get(2)
657                .and_then(|e| e.first())
658                .map_or(true, |v| v.is_empty())
659        }))
660    }
661
662    /// [26] Wenn SG39 ALC+A+:Z04 vorhanden
663    fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
664        let alcs = ctx.find_segments_with_qualifier("ALC", 0, "A");
665        ConditionResult::from(alcs.iter().any(|alc| {
666            alc.elements
667                .get(1)
668                .and_then(|e| e.get(1))
669                .map_or(false, |v| v == "Z04")
670        }))
671    }
672
673    /// [27] Wenn SG39 ALC+C vorhanden
674    fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
675        ctx.has_qualifier("ALC", 0, "C")
676    }
677
678    /// [28] Wenn Zuschlag anfällt
679    /// EXTERNAL: Requires context from outside the message.
680    // REVIEW: 'Wenn Zuschlag anfällt' (if a surcharge applies) is a business-context determination that cannot be derived from EDIFACT segment content alone. It depends on contract terms or tariff configuration held outside the message. (medium confidence)
681    fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
682        ctx.external.evaluate("surcharge_applies")
683    }
684
685    /// [29] [Wenn DTM+155 (Abrechnungszeitraum Beginn) nicht größer 31.12.2015
686    // REVIEW: DTM+155 is Abrechnungszeitraum Beginn with format 303 (starts YYYYMMDD). 'nicht größer 31.12.2015' means <= 31.12.2015. Lexicographic comparison of 8-char ISO prefix is correct for this date format. (medium confidence)
687    fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
688        let dtms = ctx.find_segments_with_qualifier("DTM", 0, "155");
689        match dtms.first() {
690            Some(dtm) => {
691                match dtm.elements.first().and_then(|e| e.get(1)) {
692                    Some(val) if val.len() >= 8 => {
693                        // Format 303: CCYYMMDDHHMMSSZZZ — compare YYYYMMDD prefix
694                        let date_prefix = &val[..8];
695                        ConditionResult::from(date_prefix <= "20151231")
696                    }
697                    _ => ConditionResult::Unknown,
698                }
699            }
700            None => ConditionResult::False, // segment absent → condition not applicable
701        }
702    }
703
704    /// [30] Wenn MP-ID in NAD+MR nicht in der Rolle MGV oder KN
705    /// EXTERNAL: Requires context from outside the message.
706    // REVIEW: 'Wenn MP-ID in NAD+MR nicht in der Rolle MGV oder KN' requires knowledge of which market-participant IDs correspond to the MGV (Messstellenbetreiber Gasvertrieb) or KN (Kunde/Netz) roles. Role assignment is maintained in an external registry and cannot be determined from the NAD segment alone. (medium confidence)
707    fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
708        ctx.external.evaluate("recipient_not_mgv_or_kn")
709    }
710
711    /// [31] Wenn MP-ID in NAD+MR in der Rolle MGV
712    /// EXTERNAL: Requires context from outside the message.
713    fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
714        ctx.external.evaluate("recipient_is_mgv")
715    }
716
717    /// [32] Wenn SG39 ALC+A+:Z01 (Gemeinderabatt) vorhanden
718    fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
719        ctx.has_qualified_value("ALC", 0, "A", 1, 1, &["Z01"])
720    }
721
722    /// [34] Wenn in Ursprungsrechnung vorhanden
723    /// EXTERNAL: Requires context from outside the message.
724    // REVIEW: "Wenn in Ursprungsrechnung vorhanden" means the condition is met when the corresponding value was present in the referenced original invoice. That original document is not available inside the current EDIFACT message, so this is an external business-context check. (medium confidence)
725    fn evaluate_34(&self, ctx: &EvaluationContext) -> ConditionResult {
726        ctx.external.evaluate("in_original_invoice")
727    }
728
729    /// [36] Wenn DTM+156 (Abrechnungszeitraum Ende) ≥ 01.12.2019
730    // REVIEW: DTM+156 (Abrechnungszeitraum Ende) uses format 303 (CCYYMMDDHHMM), so lexicographic string comparison is correct. German date 01.12.2019 = December 1, 2019 = "201912010000". (medium confidence)
731    fn evaluate_36(&self, ctx: &EvaluationContext) -> ConditionResult {
732        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "156");
733        match dtm_segs.first() {
734            Some(dtm) => {
735                match dtm
736                    .elements
737                    .first()
738                    .and_then(|e| e.get(1))
739                    .map(|s| s.as_str())
740                {
741                    Some(value) if !value.is_empty() => {
742                        // Format 303 = CCYYMMDDHHMM; lexicographic comparison works for zero-padded timestamps.
743                        // 01.12.2019 (DD.MM.YYYY) = December 1, 2019 = "201912010000"
744                        ConditionResult::from(value >= "201912010000")
745                    }
746                    _ => ConditionResult::Unknown,
747                }
748            }
749            None => ConditionResult::False, // segment absent → condition not applicable
750        }
751    }
752
753    /// [37] Wenn Lieferschein zuvor ausgetauscht wurde
754    /// EXTERNAL: Requires context from outside the message.
755    // REVIEW: "Wenn Lieferschein zuvor ausgetauscht wurde" depends on whether a delivery note (Lieferschein) was exchanged in a prior business transaction. This cannot be determined from the current EDIFACT message alone; it requires external business context. (medium confidence)
756    fn evaluate_37(&self, ctx: &EvaluationContext) -> ConditionResult {
757        ctx.external.evaluate("delivery_note_previously_exchanged")
758    }
759
760    /// [40] Es sind nur die Artikelnummern erlaubt, die in der Codeliste der Artikelnummern und Artikel-ID mit dem entsprechenden Prüfidentifikator versehen sind oder Artikel-ID aus der Codeliste der Artikeln...
761    /// EXTERNAL: Requires context from outside the message.
762    // REVIEW: Condition requires validating that the article number/ID appears in the external "Codeliste der Artikelnummern und Artikel-ID" for the given Prüfidentifikator. Code-list membership cannot be verified from the EDIFACT message structure alone; it is an external reference-data check. (medium confidence)
763    fn evaluate_40(&self, ctx: &EvaluationContext) -> ConditionResult {
764        ctx.external.evaluate("artikel_id_in_codeliste")
765    }
766
767    /// [41] Wenn IMD++SOR vorhanden
768    fn evaluate_41(&self, ctx: &EvaluationContext) -> ConditionResult {
769        let imds = ctx.find_segments("IMD");
770        ConditionResult::from(imds.iter().any(|s| {
771            s.elements
772                .get(1)
773                .and_then(|e| e.first())
774                .is_some_and(|v| v == "SOR")
775        }))
776    }
777
778    /// [42] Wenn DTM+155 (Abrechnungszeitraum Beginn) ≥ 1.1.2023 0:00 gesetzlicher deutscher Zeit
779    // REVIEW: DTM+155 (Abrechnungszeitraum Beginn) uses format 303 (CCYYMMDDHHMM). January 1, 2023 at 00:00 = "202301010000". Lexicographic comparison is correct for zero-padded CCYYMMDDHHMM. (medium confidence)
780    fn evaluate_42(&self, ctx: &EvaluationContext) -> ConditionResult {
781        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "155");
782        match dtm_segs.first() {
783            Some(dtm) => {
784                match dtm
785                    .elements
786                    .first()
787                    .and_then(|e| e.get(1))
788                    .map(|s| s.as_str())
789                {
790                    Some(value) if !value.is_empty() => {
791                        // Format 303 = CCYYMMDDHHMM; 1.1.2023 0:00 gesetzlicher Zeit = "202301010000"
792                        ConditionResult::from(value >= "202301010000")
793                    }
794                    _ => ConditionResult::Unknown,
795                }
796            }
797            None => ConditionResult::False, // segment absent → condition not applicable
798        }
799    }
800
801    /// [44] Wenn IMD++ABS nicht vorhanden
802    fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
803        let imds = ctx.find_segments("IMD");
804        ConditionResult::from(!imds.iter().any(|s| {
805            s.elements
806                .get(1)
807                .and_then(|e| e.first())
808                .is_some_and(|v| v == "ABS")
809        }))
810    }
811
812    /// [45] Nur MP-ID aus Sparte Strom
813    /// EXTERNAL: Requires context from outside the message.
814    // REVIEW: "Nur MP-ID aus Sparte Strom" means only market-participant IDs that belong to the electricity sector are allowed. Determining which IDs are registered for Sparte Strom requires an external code list or market master data lookup and cannot be inferred from the EDIFACT message content. (medium confidence)
815    fn evaluate_45(&self, ctx: &EvaluationContext) -> ConditionResult {
816        ctx.external.evaluate("mp_id_sparte_strom")
817    }
818
819    /// [47] Wenn IMD++Z43 vorhanden
820    fn evaluate_47(&self, ctx: &EvaluationContext) -> ConditionResult {
821        let imds = ctx.find_segments("IMD");
822        ConditionResult::from(imds.iter().any(|s| {
823            s.elements
824                .get(1)
825                .and_then(|e| e.first())
826                .is_some_and(|v| v == "Z43")
827        }))
828    }
829
830    /// [48] Wenn IMD++Z44 vorhanden
831    fn evaluate_48(&self, ctx: &EvaluationContext) -> ConditionResult {
832        let imds = ctx.find_segments("IMD");
833        ConditionResult::from(imds.iter().any(|s| {
834            s.elements
835                .get(1)
836                .and_then(|e| e.first())
837                .is_some_and(|v| v == "Z44")
838        }))
839    }
840
841    /// [49] Wenn IMD++Z43 und IMD+Z44 nicht vorhanden
842    fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
843        let imds = ctx.find_segments("IMD");
844        let has_z43 = imds.iter().any(|s| {
845            s.elements
846                .get(1)
847                .and_then(|e| e.first())
848                .is_some_and(|v| v == "Z43")
849        });
850        let has_z44 = imds.iter().any(|s| {
851            s.elements
852                .get(1)
853                .and_then(|e| e.first())
854                .is_some_and(|v| v == "Z44")
855        });
856        ConditionResult::from(!has_z43 && !has_z44)
857    }
858
859    /// [50] Wenn IMD++SOR nicht vorhanden
860    fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
861        let imds = ctx.find_segments("IMD");
862        ConditionResult::from(!imds.iter().any(|s| {
863            s.elements
864                .get(1)
865                .and_then(|e| e.first())
866                .is_some_and(|v| v == "SOR")
867        }))
868    }
869
870    /// [51] Wenn SG26 DTM+156 (Positionsbezogener Abrechnungszeitraum Ende) ≤ 1.1.2023 0:00 gesetzlicher deutscher Zeit
871    // REVIEW: DTM+156 in SG26 (Positionsbezogener Abrechnungszeitraum Ende) uses format 303 (CCYYMMDDHHMM). January 1, 2023 at 00:00 = "202301010000". Without SG26-scoped group navigation, message-wide search is used; qualifier 156 in the SG26 context is distinct from the message-level 156, but without group navigation this is the best available approximation. (medium confidence)
872    fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
873        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "156");
874        match dtm_segs.first() {
875            Some(dtm) => {
876                match dtm
877                    .elements
878                    .first()
879                    .and_then(|e| e.get(1))
880                    .map(|s| s.as_str())
881                {
882                    Some(value) if !value.is_empty() => {
883                        // Format 303 = CCYYMMDDHHMM; 1.1.2023 0:00 = "202301010000"
884                        ConditionResult::from(value <= "202301010000")
885                    }
886                    _ => ConditionResult::Unknown,
887                }
888            }
889            None => ConditionResult::False, // segment absent → condition not applicable
890        }
891    }
892
893    /// [53] Wenn IMD++Z45 vorhanden
894    fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
895        ctx.has_qualifier("IMD", 1, "Z45")
896    }
897
898    /// [54] Wenn IMD++ABR/JVR/ZVR vorhanden
899    fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
900        {
901            let segs = ctx.find_segments("IMD");
902            if segs.is_empty() {
903                return ConditionResult::False;
904            }
905            ConditionResult::from(segs.iter().any(|s| {
906                s.elements
907                    .get(1)
908                    .and_then(|e| e.first())
909                    .is_some_and(|v| matches!(v.as_str(), "ABR" | "JVR" | "ZVR"))
910            }))
911        }
912    }
913
914    /// [55] Wenn IMD++ABS vorhanden
915    fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
916        ctx.has_qualifier("IMD", 1, "ABS")
917    }
918
919    /// [56] Wenn DTM+137 (Nachrichtendatum ≥ 1.1.2023 0:00 gesetzlicher deutscher Zeit
920    // REVIEW: DTM+137 Nachrichtendatum >= 2023-01-01. Parses the date value (format 303 = YYYYMMDDHHmm) and compares the first 8 characters against '20230101'. String comparison is valid for ISO date format. (medium confidence)
921    fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
922        {
923            let segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
924            match segs.first() {
925                Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
926                    Some(val) if val.len() >= 8 => {
927                        ConditionResult::from(val[..8].cmp("20230101") != std::cmp::Ordering::Less)
928                    }
929                    _ => ConditionResult::Unknown,
930                },
931                None => ConditionResult::False, // segment absent → condition not applicable
932            }
933        }
934    }
935
936    /// [57] Wenn DTM+156 (Abrechnungszeitraum Ende) ≤ 1.1.2023 0:00 gesetzlicher deutscher Zeit
937    // REVIEW: DTM+156 (Abrechnungszeitraum Ende) <= 2023-01-01 0:00. Compares first 8 chars of date value against '20230101'. Returns True when the end date is on or before Jan 1 2023. (medium confidence)
938    fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
939        {
940            let segs = ctx.find_segments_with_qualifier("DTM", 0, "156");
941            match segs.first() {
942                Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
943                    Some(val) if val.len() >= 8 => ConditionResult::from(
944                        val[..8].cmp("20230101") != std::cmp::Ordering::Greater,
945                    ),
946                    _ => ConditionResult::Unknown,
947                },
948                None => ConditionResult::False, // segment absent → condition not applicable
949            }
950        }
951    }
952
953    /// [58] Wenn in dieser SG52 MOA+113 vorhanden
954    fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
955        // HAND-EDITED: "in dieser/derselben SG52" — scoped to the enclosing group.
956        ctx.this_group_has_qualifier("MOA", 0, "113", &["SG52"])
957    }
958
959    /// [59] Wenn SG26 DTM+155 (Positionsbezogener Abrechnungszeitraum Beginn) ≥ 1.1.2023 0:00 gesetzlicher deutscher Zeit
960    // REVIEW: SG26 DTM+155 (Positionsbezogener Abrechnungszeitraum Beginn) >= 2023-01-01 0:00. Finds DTM with qualifier 155 and compares first 8 chars of the date value against '20230101'. (medium confidence)
961    fn evaluate_59(&self, ctx: &EvaluationContext) -> ConditionResult {
962        {
963            let segs = ctx.find_segments_with_qualifier("DTM", 0, "155");
964            match segs.first() {
965                Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
966                    Some(val) if val.len() >= 8 => {
967                        ConditionResult::from(val[..8].cmp("20230101") != std::cmp::Ordering::Less)
968                    }
969                    _ => ConditionResult::Unknown,
970                },
971                None => ConditionResult::False, // segment absent → condition not applicable
972            }
973        }
974    }
975
976    /// [60] Wenn DTM+203 DE2379 in demselben Segment mit Wert 303 vorhanden
977    fn evaluate_60(&self, ctx: &EvaluationContext) -> ConditionResult {
978        ConditionResult::from(ctx.self_segment_value_equals("DTM", 0, 2, "303"))
979    }
980
981    /// [61] Wenn DTM+203 DE2379 in demselben Segment mit Wert 102 vorhanden
982    fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
983        ConditionResult::from(ctx.self_segment_value_equals("DTM", 0, 2, "102"))
984    }
985
986    /// [64] Wenn DTM+156 (Abrechnungszeitraum Ende) ≤ 1.1.2024 0:00 gesetzlicher deutscher Zeit
987    // REVIEW: DTM+156 (Abrechnungszeitraum Ende) <= 2024-01-01 0:00. Same pattern as condition 57 but with threshold '20240101'. (medium confidence)
988    fn evaluate_64(&self, ctx: &EvaluationContext) -> ConditionResult {
989        {
990            let segs = ctx.find_segments_with_qualifier("DTM", 0, "156");
991            match segs.first() {
992                Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
993                    Some(val) if val.len() >= 8 => ConditionResult::from(
994                        val[..8].cmp("20240101") != std::cmp::Ordering::Greater,
995                    ),
996                    _ => ConditionResult::Unknown,
997                },
998                None => ConditionResult::False, // segment absent → condition not applicable
999            }
1000        }
1001    }
1002
1003    /// [65] Wenn SG26 DTM+155 (Positionsbezogener Abrechnungszeitraum Beginn) ≥ 1.1.2024 0:00 gesetzlicher deutscher Zeit
1004    // REVIEW: SG26 DTM+155 (Positionsbezogener Abrechnungszeitraum Beginn) >= 2024-01-01 0:00. Same pattern as condition 59 but with threshold '20240101'. (medium confidence)
1005    fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
1006        {
1007            let segs = ctx.find_segments_with_qualifier("DTM", 0, "155");
1008            match segs.first() {
1009                Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1010                    Some(val) if val.len() >= 8 => {
1011                        ConditionResult::from(val[..8].cmp("20240101") != std::cmp::Ordering::Less)
1012                    }
1013                    _ => ConditionResult::Unknown,
1014                },
1015                None => ConditionResult::False, // segment absent → condition not applicable
1016            }
1017        }
1018    }
1019
1020    /// [66] Wenn IMD++KON vorhanden
1021    fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
1022        ctx.has_qualifier("IMD", 1, "KON")
1023    }
1024
1025    /// [70] Wenn IMD++Z45 nicht vorhanden
1026    fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
1027        let imd_segments = ctx.find_segments("IMD");
1028        let found = imd_segments.iter().any(|s| {
1029            s.elements
1030                .get(1)
1031                .and_then(|e| e.first())
1032                .is_some_and(|v| v == "Z45")
1033        });
1034        ConditionResult::from(!found)
1035    }
1036
1037    /// [71] Wenn DTM+155 (Abrechnungszeitraum Beginn) ≥ 1.1.2024 0:00 gesetzlicher deutscher Zeit
1038    // REVIEW: DTM+155 means elements[0][0]=="155". Value is at elements[0][1] in 303 format (YYYYMMDDhhmm). String comparison works for chronological ordering. Timezone nuance (German legal time) ignored — medium confidence. (medium confidence)
1039    fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
1040        let dtm_segments = ctx.find_segments_with_qualifier("DTM", 0, "155");
1041        match dtm_segments.first() {
1042            Some(dtm) => {
1043                match dtm
1044                    .elements
1045                    .first()
1046                    .and_then(|e| e.get(1))
1047                    .map(|s| s.as_str())
1048                {
1049                    Some(value) if !value.is_empty() => {
1050                        // 303 format: YYYYMMDDhhmm — lexicographic comparison is chronological for zero-padded strings
1051                        ConditionResult::from(value >= "202401010000")
1052                    }
1053                    _ => ConditionResult::Unknown,
1054                }
1055            }
1056            None => ConditionResult::False, // segment absent → condition not applicable
1057        }
1058    }
1059
1060    /// [72] Wenn MP-ID in NAD+MR in der Rolle LF
1061    /// EXTERNAL: Requires context from outside the message.
1062    // REVIEW: Whether the MP-ID in NAD+MR has the market role LF (Lieferant) cannot be determined from the EDIFACT message alone — it requires a business registry lookup. The NAD+MR segment only provides the party identifier, not the assigned market role. (medium confidence)
1063    fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
1064        ctx.external.evaluate("recipient_is_lf")
1065    }
1066
1067    /// [73] Wenn IMD++MSB vorhanden
1068    fn evaluate_73(&self, ctx: &EvaluationContext) -> ConditionResult {
1069        let imd_segments = ctx.find_segments("IMD");
1070        let found = imd_segments.iter().any(|s| {
1071            s.elements
1072                .get(1)
1073                .and_then(|e| e.first())
1074                .is_some_and(|v| v == "MSB")
1075        });
1076        ConditionResult::from(found)
1077    }
1078
1079    /// [74] wenn im DE3155 in demselben COM der Code EM vorhanden ist
1080    fn evaluate_74(&self, ctx: &EvaluationContext) -> ConditionResult {
1081        let com_segments = ctx.find_segments("COM");
1082        let found = com_segments.iter().any(|s| {
1083            s.elements
1084                .first()
1085                .and_then(|e| e.get(1))
1086                .is_some_and(|v| v == "EM")
1087        });
1088        ConditionResult::from(found)
1089    }
1090
1091    /// [75] wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
1092    fn evaluate_75(&self, ctx: &EvaluationContext) -> ConditionResult {
1093        let com_segments = ctx.find_segments("COM");
1094        let found = com_segments.iter().any(|s| {
1095            s.elements
1096                .first()
1097                .and_then(|e| e.get(1))
1098                .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
1099        });
1100        ConditionResult::from(found)
1101    }
1102
1103    /// [76] Wenn MP-ID in NAD+MR in der Rolle NB
1104    /// EXTERNAL: Requires context from outside the message.
1105    // REVIEW: Whether the MP-ID in NAD+MR has the market role NB (Netzbetreiber) cannot be determined from the EDIFACT message alone — it requires a business registry lookup. The NAD+MR segment only provides the party identifier, not the assigned market role. (medium confidence)
1106    fn evaluate_76(&self, ctx: &EvaluationContext) -> ConditionResult {
1107        ctx.external.evaluate("recipient_is_nb")
1108    }
1109
1110    /// [77] Wenn SG26 DTM+156 (Positionsbezogener Abrechnungszeitraum Ende) ≤ 1.1.2024 0:00 gesetzlicher deutscher Zeit
1111    // REVIEW: SG26 DTM+156 means elements[0][0]=="156". Value at elements[0][1] in 303 format. Compare <= "202401010000" (Jan 1 2024 00:00). Medium confidence due to timezone nuance. (medium confidence)
1112    fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
1113        let dtm_segments = ctx.find_segments_with_qualifier("DTM", 0, "156");
1114        match dtm_segments.first() {
1115            Some(dtm) => {
1116                match dtm
1117                    .elements
1118                    .first()
1119                    .and_then(|e| e.get(1))
1120                    .map(|s| s.as_str())
1121                {
1122                    Some(value) if !value.is_empty() => {
1123                        // 303 format: YYYYMMDDhhmm — lexicographic comparison is chronological
1124                        ConditionResult::from(value <= "202401010000")
1125                    }
1126                    _ => ConditionResult::Unknown,
1127                }
1128            }
1129            None => ConditionResult::False, // segment absent → condition not applicable
1130        }
1131    }
1132
1133    /// [78] Wenn GEI+Z08 nicht vorhanden
1134    // REVIEW: GEI schema shows elements[0] qualifier is always Z01, while the specification code is at elements[1][0] (DE7365) with codes Z01-Z06 +3 more. 'GEI+Z08' in AHB shorthand refers to elements[1][0]=="Z08". Absence check returns True when not found. (medium confidence)
1135    fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
1136        let found = ctx.find_segments("GEI").iter().any(|s| {
1137            s.elements
1138                .get(1)
1139                .and_then(|e| e.first())
1140                .is_some_and(|v| v == "Z08")
1141        });
1142        ConditionResult::from(!found)
1143    }
1144
1145    /// [79] Wenn GEI+Z09 nicht vorhanden
1146    // REVIEW: Same pattern as condition 78. 'GEI+Z09' in AHB shorthand refers to elements[1][0]=="Z09". (medium confidence)
1147    fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
1148        let found = ctx.find_segments("GEI").iter().any(|s| {
1149            s.elements
1150                .get(1)
1151                .and_then(|e| e.first())
1152                .is_some_and(|v| v == "Z09")
1153        });
1154        ConditionResult::from(!found)
1155    }
1156
1157    /// [80] Wenn GEI+Z01 nicht vorhanden
1158    // REVIEW: 'GEI+Z01' refers to elements[1][0]=="Z01" (the specification code, not the fixed qualifier at elements[0]). Absence check. (medium confidence)
1159    fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
1160        let found = ctx.find_segments("GEI").iter().any(|s| {
1161            s.elements
1162                .get(1)
1163                .and_then(|e| e.first())
1164                .is_some_and(|v| v == "Z01")
1165        });
1166        ConditionResult::from(!found)
1167    }
1168
1169    /// [81] Wenn GEI+Z04 nicht vorhanden
1170    // REVIEW: Same pattern as condition 78. 'GEI+Z04' refers to elements[1][0]=="Z04". (medium confidence)
1171    fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
1172        let found = ctx.find_segments("GEI").iter().any(|s| {
1173            s.elements
1174                .get(1)
1175                .and_then(|e| e.first())
1176                .is_some_and(|v| v == "Z04")
1177        });
1178        ConditionResult::from(!found)
1179    }
1180
1181    /// [82] Wenn GEI+Z05 nicht vorhanden
1182    // REVIEW: Same pattern as condition 78. 'GEI+Z05' refers to elements[1][0]=="Z05". (medium confidence)
1183    fn evaluate_82(&self, ctx: &EvaluationContext) -> ConditionResult {
1184        let found = ctx.find_segments("GEI").iter().any(|s| {
1185            s.elements
1186                .get(1)
1187                .and_then(|e| e.first())
1188                .is_some_and(|v| v == "Z05")
1189        });
1190        ConditionResult::from(!found)
1191    }
1192
1193    /// [84] Wenn in dieser SG39 ALC+C+:Z02 / Z03 / Z04 vorhanden
1194    fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
1195        // HAND-EDITED: "in dieser/derselben SG39" — scoped to the enclosing group.
1196        ctx.this_group_has_qualified_value("ALC", 0, "C", 1, 1, &["Z02", "Z03", "Z04"], &["SG39"])
1197    }
1198
1199    /// [85] Wenn in diesem Segment DE6411 = DAY
1200    // REVIEW: DE6411 is the measurement unit code, appearing in QTY segments as C186 composite: elements[0][0]=DE6063 (qualifier), elements[0][1]=DE6060 (quantity), elements[0][2]=DE6411 (unit code). 'DAY' is the unit code for daily quantities. Medium confidence as the specific segment context ('in diesem Segment') may require group scoping. (medium confidence)
1201    fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
1202        let qty_segments = ctx.find_segments("QTY");
1203        let found = qty_segments.iter().any(|s| {
1204            s.elements
1205                .first()
1206                .and_then(|e| e.get(2))
1207                .is_some_and(|v| v == "DAY")
1208        });
1209        ConditionResult::from(found)
1210    }
1211
1212    /// [86] Wenn in diesem Segment DE6411 = MON/ANN
1213    // REVIEW: DE6411 is the unit of measure code in QTY segments at elements[0][2] (C186 component 2). 'In diesem Segment' means within the current QTY context. Check message-wide for QTY with MON or ANN unit code. (medium confidence)
1214    fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
1215        let qty_segments = ctx.find_segments("QTY");
1216        ConditionResult::from(qty_segments.iter().any(|s| {
1217            s.elements
1218                .first()
1219                .and_then(|e| e.get(2))
1220                .map(|v| v == "MON" || v == "ANN")
1221                .unwrap_or(false)
1222        }))
1223    }
1224
1225    /// [88] Wenn DTM+155 (Abrechnungszeitraum Beginn) ≥ 1.1.2026 0:00 gesetzlicher deutscher Zeit
1226    fn evaluate_88(&self, ctx: &EvaluationContext) -> ConditionResult {
1227        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "155");
1228        match dtm_segs.first() {
1229            Some(dtm) => {
1230                match dtm
1231                    .elements
1232                    .first()
1233                    .and_then(|e| e.get(1))
1234                    .map(|s| s.as_str())
1235                {
1236                    Some(value) if value.len() >= 8 => {
1237                        let date_part = &value[..8];
1238                        ConditionResult::from(date_part >= "20260101")
1239                    }
1240                    _ => ConditionResult::Unknown,
1241                }
1242            }
1243            None => ConditionResult::False, // segment absent → condition not applicable
1244        }
1245    }
1246
1247    /// [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
1248    fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
1249        match ctx.resolved_value {
1250            Some(val) => is_mesz_utc(val),
1251            None => ConditionResult::False,
1252        }
1253    }
1254
1255    /// [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
1256    fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1257        match ctx.resolved_value {
1258            Some(val) => is_mez_utc(val),
1259            None => ConditionResult::False,
1260        }
1261    }
1262
1263    /// [492] Wenn MP-ID in NAD+MR (Nachrichtenempfänger) aus Sparte Strom
1264    /// EXTERNAL: Requires context from outside the message.
1265    // REVIEW: Checks whether the NAD+MR (message recipient) MP-ID belongs to the electricity (Strom) sector. The NAD+MR element structure is known (elements[0]=MR, elements[1][0]=MP-ID), but sector membership cannot be determined from the EDIFACT message content alone — it requires an external market participant registry. Delegated to external provider. (medium confidence)
1266    fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1267        ctx.external.evaluate("recipient_is_strom")
1268    }
1269
1270    /// [493] Wenn MP-ID in NAD+MR (Nachrichtenempfänger) aus Sparte Gas
1271    /// EXTERNAL: Requires context from outside the message.
1272    // REVIEW: Checks whether the NAD+MR (message recipient) MP-ID belongs to the gas (Gas) sector. Same reasoning as 492 — sector membership cannot be determined from the EDIFACT message alone; requires external market participant registry. Delegated to external provider. (medium confidence)
1273    fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1274        ctx.external.evaluate("recipient_is_gas")
1275    }
1276
1277    /// [501] Hinweis: Dokumentennummer der ORDERS
1278    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1279        // Hinweis: Dokumentennummer der ORDERS — informational note, always applies
1280        ConditionResult::True
1281    }
1282
1283    /// [502] Hinweis: Dokumentennummer der Bilanzierungs-MSCONS
1284    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1285        // Hinweis: Dokumentennummer der Bilanzierungs-MSCONS — informational note, always applies
1286        ConditionResult::True
1287    }
1288
1289    /// [503] Hinweis: Ein positiver Betrag ist eine Forderung des Rechnungsstellers.
1290    fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
1291        // Hinweis: Ein positiver Betrag ist eine Forderung des Rechnungsstellers — informational note, always applies
1292        ConditionResult::True
1293    }
1294
1295    /// [504] Hinweis: Ein positiver Betrag ist eine Forderung des Rechnungsempfängers.
1296    fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1297        // Hinweis: Ein positiver Betrag ist eine Forderung des Rechnungsempfängers — informational note, always applies
1298        ConditionResult::True
1299    }
1300
1301    /// [507] Hinweis: Dokumentennummer der SSQNOT
1302    fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
1303        // Hinweis: Dokumentennummer der SSQNOT — informational note, always applies
1304        ConditionResult::True
1305    }
1306
1307    /// [508] Hinweis: Dokumentennummer der QUOTES
1308    fn evaluate_508(&self, _ctx: &EvaluationContext) -> ConditionResult {
1309        // Hinweis: Dokumentennummer der QUOTES — informational note, always applies
1310        ConditionResult::True
1311    }
1312
1313    /// [509] Hinweis: Verwendung der ID der Marktlokation
1314    fn evaluate_509(&self, _ctx: &EvaluationContext) -> ConditionResult {
1315        // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1316        ConditionResult::True
1317    }
1318
1319    /// [510] Hinweis: Verwendung der ID der Messlokation
1320    fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
1321        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1322        ConditionResult::True
1323    }
1324
1325    /// [512] Hinweis: Hier ist entweder der Betrag aus MOA+203 oder der um den gültigen Steuerbetrag erhöhte Betrag aus MOA+203 anzugeben.
1326    fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
1327        // Hinweis: Hier ist entweder der Betrag aus MOA+203 oder der um den gültigen Steuerbetrag erhöhte Betrag aus MOA+203 anzugeben — informational note, always applies
1328        ConditionResult::True
1329    }
1330
1331    /// [513] Hinweis: Hier ist das Ergebnis der Multiplikation von MOA+25 mit PCD+3 anzugeben.
1332    fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
1333        // Hinweis: Hier ist das Ergebnis der Multiplikation von MOA+25 mit PCD+3 anzugeben — informational note, always applies
1334        ConditionResult::True
1335    }
1336
1337    /// [514] Hinweis: Dokumentennummer der Lieferschein-MSCONS
1338    fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
1339        // Hinweis: Dokumentennummer der Lieferschein-MSCONS — informational note, always applies
1340        ConditionResult::True
1341    }
1342
1343    /// [515] Hinweis: BGM DE1004 aus INVOIC-Nachricht, die storniert werden soll
1344    fn evaluate_515(&self, _ctx: &EvaluationContext) -> ConditionResult {
1345        // Hinweis: BGM DE1004 aus INVOIC-Nachricht, die storniert werden soll — informational note, always applies
1346        ConditionResult::True
1347    }
1348
1349    /// [516] Hinweis: Ein Lieferschein zu einer Rechnung ist für alle Abrechnungszeiträume, die erstmals nach dem 1.12.2019 abgerechnet werden und für alle Abrechnungszeiträume, für die sich nach dem 1.12....
1350    fn evaluate_516(&self, _ctx: &EvaluationContext) -> ConditionResult {
1351        // Hinweis: Ein Lieferschein zu einer Rechnung ist für alle Abrechnungszeiträume, die erstmals nach dem 1.12.2019 abgerechnet werden und für alle Abrechnungszeiträume, für die sich nach dem 1.12.2019 geänderte Mengen oder Leistungswerte ergeben, nötig.
1352        ConditionResult::True
1353    }
1354
1355    /// [517] Hinweis: Dokumentennummer der PDF-Kapazitätsrechnung
1356    fn evaluate_517(&self, _ctx: &EvaluationContext) -> ConditionResult {
1357        // Hinweis: Dokumentennummer der PDF-Kapazitätsrechnung — informational annotation, always applies
1358        ConditionResult::True
1359    }
1360
1361    /// [518] Hinweis:  Im Fall der Stornierung des Auftrags der Unterbrechung: Der Tag an dem der NB die Stornierung empfangen hat Bei erfolgreicher Sperrung: Tag der durchgeführten Sperrung Bei erfolgloser Sp...
1362    fn evaluate_518(&self, _ctx: &EvaluationContext) -> ConditionResult {
1363        // Hinweis: Date semantics for cancellation/blocking scenarios — informational annotation, always applies
1364        ConditionResult::True
1365    }
1366
1367    /// [519] Hinweis: Stornierte Abschlagsrechnungen sind nicht aufzuführen
1368    fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
1369        // Hinweis: Stornierte Abschlagsrechnungen sind nicht aufzuführen — informational annotation, always applies
1370        ConditionResult::True
1371    }
1372
1373    /// [520] Hinweis: Es sind nur die Artikel-IDs aus dem Preisblatt erlaubt
1374    fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
1375        // Hinweis: Es sind nur die Artikel-IDs aus dem Preisblatt erlaubt — informational annotation, always applies
1376        ConditionResult::True
1377    }
1378
1379    /// [521] Hinweis: BGM DE1004 aus der INVOIC-Nachricht, für die Verzugskosten erhoben werden
1380    fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
1381        // Hinweis: BGM DE1004 aus der INVOIC-Nachricht, für die Verzugskosten erhoben werden — informational note, always applies
1382        ConditionResult::True
1383    }
1384
1385    /// [522] Hinweis: Verwendung der ID der Netzlokation
1386    fn evaluate_522(&self, _ctx: &EvaluationContext) -> ConditionResult {
1387        // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
1388        ConditionResult::True
1389    }
1390
1391    /// [523] Hinweis: Verwendung der ID der Steuerbaren Ressource
1392    fn evaluate_523(&self, _ctx: &EvaluationContext) -> ConditionResult {
1393        // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
1394        ConditionResult::True
1395    }
1396
1397    /// [524] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
1398    fn evaluate_524(&self, _ctx: &EvaluationContext) -> ConditionResult {
1399        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
1400        ConditionResult::True
1401    }
1402
1403    /// [525] Hinweis: Verwendung, sofern Netzentgelte geringer als die Pauschale Netzentgeltreduzierung
1404    fn evaluate_525(&self, _ctx: &EvaluationContext) -> ConditionResult {
1405        // Hinweis: Verwendung, sofern Netzentgelte geringer als die Pauschale Netzentgeltreduzierung — informational note, always applies
1406        ConditionResult::True
1407    }
1408
1409    /// [526] Hinweis: Der hier angegebene Geldbetrag muss mit dem identisch sein, der in SG50 MOA+77 im DE5004 der Abschlagsrechnung steht, die die Rechnungsnummer hat, die in dieser SG50 in SG51-RFF+AFL in DE1...
1410    fn evaluate_526(&self, _ctx: &EvaluationContext) -> ConditionResult {
1411        // Hinweis: Der hier angegebene Geldbetrag muss mit dem identisch sein, der in SG50 MOA+77
1412        // im DE5004 der Abschlagsrechnung steht, die die Rechnungsnummer hat, die in dieser SG50
1413        // in SG51-RFF+AFL in DE1154 genannt ist.
1414        // Informational note — always applies
1415        ConditionResult::True
1416    }
1417
1418    /// [902] Format: Möglicher Wert: ≥ 0
1419    // REVIEW: Format condition requiring value >= 0, applied to MOA amount (DE5004 at elements[0][1]). Medium confidence because exact segment/group context is inferred from INVOIC structure — the full AHB table row would clarify which specific MOA qualifier this applies to. (medium confidence)
1420    fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
1421        ctx.format_check("MOA", 0, 1, |val| validate_numeric(val, ">=", 0.0))
1422    }
1423
1424    /// [906] Format: max. 3 Nachkommastellen
1425    // REVIEW: Format condition requiring at most 3 decimal places. Most likely applies to MOA DE5004 (Geldbetrag) in INVOIC. Medium confidence — the exact MOA qualifier or group (SG27/SG42/SG50/SG52) depends on the AHB table row context not included in the condition description alone. (medium confidence)
1426    fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
1427        ctx.format_check("MOA", 0, 1, |val| validate_max_decimal_places(val, 3))
1428    }
1429
1430    /// [908] Format: Mögliche Werte: 1 bis n
1431    // REVIEW: Format condition requiring value in range 1..n (strictly positive). Applied to QTY quantity values in INVOIC (DE6060 at elements[0][1]). Medium confidence — the exact segment context depends on the full AHB table row. (medium confidence)
1432    fn evaluate_908(&self, ctx: &EvaluationContext) -> ConditionResult {
1433        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">=", 1.0))
1434    }
1435
1436    /// [910] Format: Möglicher Wert: &lt; 0 oder ≥ 0
1437    // REVIEW: Format condition accepting any numeric value (< 0 OR >= 0 is universally true). This documents that the field accepts negative values (unlike condition 902 which restricts to >= 0). Validates the value is a parseable number. Medium confidence on the exact segment — likely a MOA that can be a credit/deduction amount. (medium confidence)
1438    fn evaluate_910(&self, ctx: &EvaluationContext) -> ConditionResult {
1439        // Format: Möglicher Wert: < 0 oder ≥ 0 — any numeric value is valid (no sign constraint)
1440        // Applied to MOA amounts that may be negative (e.g., credits, deductions)
1441        let segs = ctx.find_segments("MOA");
1442        match segs
1443            .first()
1444            .and_then(|s| s.elements.first())
1445            .and_then(|e| e.get(1))
1446        {
1447            Some(val) if !val.is_empty() => {
1448                // < 0 or >= 0 covers all real numbers — validate it is a parseable number
1449                match val.trim_start_matches('-').parse::<f64>() {
1450                    Ok(_) => ConditionResult::True,
1451                    Err(_) => ConditionResult::False,
1452                }
1453            }
1454            _ => ConditionResult::Unknown,
1455        }
1456    }
1457
1458    /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend und fortlaufend aufsteigend
1459    // REVIEW: Sequential numbering validation (1 to n, ascending) — only the >= 1 integer constraint can be checked on a single value. Full sequential ordering cannot be verified without stateful iteration across all instances. Applies to LIN line item numbers in INVOIC SG26. Medium confidence due to segment assumption and inability to verify sequential ordering in isolation. (medium confidence)
1460    fn evaluate_911(&self, ctx: &EvaluationContext) -> ConditionResult {
1461        // Format: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend und fortlaufend aufsteigend
1462        // Validates that the value is a positive integer >= 1 (sequential enforcement requires external state)
1463        let segs = ctx.find_segments("LIN");
1464        match segs
1465            .first()
1466            .and_then(|s| s.elements.first())
1467            .and_then(|e| e.first())
1468        {
1469            Some(val) => match val.parse::<i64>() {
1470                Ok(n) if n >= 1 => ConditionResult::True,
1471                Ok(_) => ConditionResult::False,
1472                Err(_) => ConditionResult::False,
1473            },
1474            None => ConditionResult::False, // segment absent → condition not applicable
1475        }
1476    }
1477
1478    /// [912] Format: max. 6 Nachkommastellen
1479    fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
1480        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 6))
1481    }
1482
1483    /// [914] Format: Möglicher Wert: &gt; 0
1484    fn evaluate_914(&self, ctx: &EvaluationContext) -> ConditionResult {
1485        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">", 0.0))
1486    }
1487
1488    /// [927] Format: Möglicher Wert: -1
1489    // REVIEW: Value must be exactly -1. Used for special indicator values (e.g., a reversal or placeholder quantity). validate_numeric with == operator handles exact match. Medium confidence due to segment assumption — the -1 constraint is unusual and the exact segment context (QTY vs MOA) depends on which AHB data element references this condition. (medium confidence)
1490    fn evaluate_927(&self, ctx: &EvaluationContext) -> ConditionResult {
1491        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "==", -1.0))
1492    }
1493
1494    /// [930] Format: max. 2 Nachkommastellen
1495    fn evaluate_930(&self, ctx: &EvaluationContext) -> ConditionResult {
1496        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 2))
1497    }
1498
1499    /// [931] Format: ZZZ = +00
1500    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1501        ctx.format_check("DTM", 0, 1, validate_timezone_utc)
1502    }
1503
1504    /// [932] Format: HHMM = 2200
1505    fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
1506        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2200"))
1507    }
1508
1509    /// [933] Format: HHMM = 2300
1510    fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
1511        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2300"))
1512    }
1513
1514    /// [934] Format: HHMM = 0400
1515    fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
1516        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0400"))
1517    }
1518
1519    /// [935] Format: HHMM = 0500
1520    fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
1521        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0500"))
1522    }
1523
1524    /// [937] Format: keine Nachkommastelle
1525    fn evaluate_937(&self, ctx: &EvaluationContext) -> ConditionResult {
1526        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 0))
1527    }
1528
1529    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
1530    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
1531        ctx.format_check("COM", 0, 0, validate_email)
1532    }
1533
1534    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
1535    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
1536        ctx.format_check("COM", 0, 0, validate_phone)
1537    }
1538
1539    /// [946] Format: max. 11 Nachkommastellen
1540    fn evaluate_946(&self, ctx: &EvaluationContext) -> ConditionResult {
1541        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 11))
1542    }
1543
1544    /// [950] Format: Marktlokations-ID
1545    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
1546        ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
1547    }
1548
1549    /// [951] Format: Zählpunktbezeichnung
1550    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
1551        ctx.format_check_qualified("LOC", 0, "Z17", 1, 0, validate_zahlpunkt)
1552    }
1553
1554    /// [960] Format: Netzlokations-ID
1555    // HAND-EDITED: a Netzlokations-ID is "E" + ten alphanumerics (`E1688117482`),
1556    // not MaLo-shaped; `validate_malo_id` rejected every real one (#173).
1557    fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
1558        ctx.format_check_qualified("LOC", 0, "Z18", 1, 0, validate_nelo_id)
1559    }
1560
1561    /// [961] Format: SR-ID
1562    fn evaluate_961(&self, ctx: &EvaluationContext) -> ConditionResult {
1563        ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_malo_id)
1564    }
1565}