Skip to main content

automapper_validation/generated/fv2504/
invoic_conditions_fv2504.rs

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