Skip to main content

automapper_validation/generated/fv2510/
quotes_conditions_fv2510.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2510/QUOTES_AHB_1_1_20250401.xml
4// Generated: 2026-03-12T11:11:29Z
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 QUOTES FV2510.
17pub struct QuotesConditionEvaluatorFV2510 {
18    // External condition IDs that require runtime context.
19    external_conditions: std::collections::HashSet<u32>,
20}
21
22impl Default for QuotesConditionEvaluatorFV2510 {
23    fn default() -> Self {
24        let mut external_conditions = std::collections::HashSet::new();
25        external_conditions.insert(1);
26        external_conditions.insert(4);
27        external_conditions.insert(13);
28        external_conditions.insert(29);
29        external_conditions.insert(31);
30        external_conditions.insert(39);
31        external_conditions.insert(44);
32        external_conditions.insert(54);
33        external_conditions.insert(56);
34        external_conditions.insert(57);
35        external_conditions.insert(58);
36        external_conditions.insert(59);
37        external_conditions.insert(60);
38        external_conditions.insert(61);
39        external_conditions.insert(62);
40        external_conditions.insert(63);
41        external_conditions.insert(69);
42        external_conditions.insert(70);
43        external_conditions.insert(71);
44        external_conditions.insert(74);
45        external_conditions.insert(75);
46        external_conditions.insert(77);
47        external_conditions.insert(78);
48        external_conditions.insert(90);
49        external_conditions.insert(91);
50        external_conditions.insert(92);
51        external_conditions.insert(492);
52        external_conditions.insert(493);
53        external_conditions.insert(494);
54        external_conditions.insert(2066);
55        Self {
56            external_conditions,
57        }
58    }
59}
60
61impl ConditionEvaluator for QuotesConditionEvaluatorFV2510 {
62    fn message_type(&self) -> &str {
63        "QUOTES"
64    }
65
66    fn format_version(&self) -> &str {
67        "FV2510"
68    }
69
70    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
71        match condition {
72            1 => self.evaluate_1(ctx),
73            2 => self.evaluate_2(ctx),
74            3 => self.evaluate_3(ctx),
75            4 => self.evaluate_4(ctx),
76            5 => self.evaluate_5(ctx),
77            8 => self.evaluate_8(ctx),
78            9 => self.evaluate_9(ctx),
79            10 => self.evaluate_10(ctx),
80            11 => self.evaluate_11(ctx),
81            12 => self.evaluate_12(ctx),
82            13 => self.evaluate_13(ctx),
83            15 => self.evaluate_15(ctx),
84            16 => self.evaluate_16(ctx),
85            17 => self.evaluate_17(ctx),
86            18 => self.evaluate_18(ctx),
87            20 => self.evaluate_20(ctx),
88            21 => self.evaluate_21(ctx),
89            22 => self.evaluate_22(ctx),
90            25 => self.evaluate_25(ctx),
91            26 => self.evaluate_26(ctx),
92            27 => self.evaluate_27(ctx),
93            28 => self.evaluate_28(ctx),
94            29 => self.evaluate_29(ctx),
95            30 => self.evaluate_30(ctx),
96            31 => self.evaluate_31(ctx),
97            39 => self.evaluate_39(ctx),
98            44 => self.evaluate_44(ctx),
99            49 => self.evaluate_49(ctx),
100            50 => self.evaluate_50(ctx),
101            51 => self.evaluate_51(ctx),
102            52 => self.evaluate_52(ctx),
103            53 => self.evaluate_53(ctx),
104            54 => self.evaluate_54(ctx),
105            55 => self.evaluate_55(ctx),
106            56 => self.evaluate_56(ctx),
107            57 => self.evaluate_57(ctx),
108            58 => self.evaluate_58(ctx),
109            59 => self.evaluate_59(ctx),
110            60 => self.evaluate_60(ctx),
111            61 => self.evaluate_61(ctx),
112            62 => self.evaluate_62(ctx),
113            63 => self.evaluate_63(ctx),
114            65 => self.evaluate_65(ctx),
115            66 => self.evaluate_66(ctx),
116            67 => self.evaluate_67(ctx),
117            68 => self.evaluate_68(ctx),
118            69 => self.evaluate_69(ctx),
119            70 => self.evaluate_70(ctx),
120            71 => self.evaluate_71(ctx),
121            72 => self.evaluate_72(ctx),
122            73 => self.evaluate_73(ctx),
123            74 => self.evaluate_74(ctx),
124            75 => self.evaluate_75(ctx),
125            77 => self.evaluate_77(ctx),
126            78 => self.evaluate_78(ctx),
127            79 => self.evaluate_79(ctx),
128            80 => self.evaluate_80(ctx),
129            81 => self.evaluate_81(ctx),
130            82 => self.evaluate_82(ctx),
131            83 => self.evaluate_83(ctx),
132            84 => self.evaluate_84(ctx),
133            85 => self.evaluate_85(ctx),
134            86 => self.evaluate_86(ctx),
135            87 => self.evaluate_87(ctx),
136            88 => self.evaluate_88(ctx),
137            90 => self.evaluate_90(ctx),
138            91 => self.evaluate_91(ctx),
139            92 => self.evaluate_92(ctx),
140            93 => self.evaluate_93(ctx),
141            94 => self.evaluate_94(ctx),
142            490 => self.evaluate_490(ctx),
143            491 => self.evaluate_491(ctx),
144            492 => self.evaluate_492(ctx),
145            493 => self.evaluate_493(ctx),
146            494 => self.evaluate_494(ctx),
147            500 => self.evaluate_500(ctx),
148            501 => self.evaluate_501(ctx),
149            502 => self.evaluate_502(ctx),
150            503 => self.evaluate_503(ctx),
151            504 => self.evaluate_504(ctx),
152            505 => self.evaluate_505(ctx),
153            506 => self.evaluate_506(ctx),
154            507 => self.evaluate_507(ctx),
155            511 => self.evaluate_511(ctx),
156            512 => self.evaluate_512(ctx),
157            513 => self.evaluate_513(ctx),
158            514 => self.evaluate_514(ctx),
159            516 => self.evaluate_516(ctx),
160            517 => self.evaluate_517(ctx),
161            518 => self.evaluate_518(ctx),
162            519 => self.evaluate_519(ctx),
163            520 => self.evaluate_520(ctx),
164            521 => self.evaluate_521(ctx),
165            903 => self.evaluate_903(ctx),
166            906 => self.evaluate_906(ctx),
167            908 => self.evaluate_908(ctx),
168            911 => self.evaluate_911(ctx),
169            912 => self.evaluate_912(ctx),
170            914 => self.evaluate_914(ctx),
171            931 => self.evaluate_931(ctx),
172            932 => self.evaluate_932(ctx),
173            933 => self.evaluate_933(ctx),
174            934 => self.evaluate_934(ctx),
175            935 => self.evaluate_935(ctx),
176            939 => self.evaluate_939(ctx),
177            940 => self.evaluate_940(ctx),
178            942 => self.evaluate_942(ctx),
179            950 => self.evaluate_950(ctx),
180            951 => self.evaluate_951(ctx),
181            959 => self.evaluate_959(ctx),
182            960 => self.evaluate_960(ctx),
183            961 => self.evaluate_961(ctx),
184            962 => self.evaluate_962(ctx),
185            2042 => self.evaluate_2042(ctx),
186            2060 => self.evaluate_2060(ctx),
187            2061 => self.evaluate_2061(ctx),
188            2062 => self.evaluate_2062(ctx),
189            2063 => self.evaluate_2063(ctx),
190            2064 => self.evaluate_2064(ctx),
191            2066 => self.evaluate_2066(ctx),
192            2068 => self.evaluate_2068(ctx),
193            2069 => self.evaluate_2069(ctx),
194            2070 => self.evaluate_2070(ctx),
195            2071 => self.evaluate_2071(ctx),
196            2072 => self.evaluate_2072(ctx),
197            2073 => self.evaluate_2073(ctx),
198            2074 => self.evaluate_2074(ctx),
199            2075 => self.evaluate_2075(ctx),
200            2076 => self.evaluate_2076(ctx),
201            _ => ConditionResult::Unknown,
202        }
203    }
204
205    fn is_external(&self, condition: u32) -> bool {
206        self.external_conditions.contains(&condition)
207    }
208    fn is_known(&self, condition: u32) -> bool {
209        matches!(
210            condition,
211            1 | 2
212                | 3
213                | 4
214                | 5
215                | 8
216                | 9
217                | 10
218                | 11
219                | 12
220                | 13
221                | 15
222                | 16
223                | 17
224                | 18
225                | 20
226                | 21
227                | 22
228                | 25
229                | 26
230                | 27
231                | 28
232                | 29
233                | 30
234                | 31
235                | 39
236                | 44
237                | 49
238                | 50
239                | 51
240                | 52
241                | 53
242                | 54
243                | 55
244                | 56
245                | 57
246                | 58
247                | 59
248                | 60
249                | 61
250                | 62
251                | 63
252                | 65
253                | 66
254                | 67
255                | 68
256                | 69
257                | 70
258                | 71
259                | 72
260                | 73
261                | 74
262                | 75
263                | 77
264                | 78
265                | 79
266                | 80
267                | 81
268                | 82
269                | 83
270                | 84
271                | 85
272                | 86
273                | 87
274                | 88
275                | 90
276                | 91
277                | 92
278                | 93
279                | 94
280                | 490
281                | 491
282                | 492
283                | 493
284                | 494
285                | 500
286                | 501
287                | 502
288                | 503
289                | 504
290                | 505
291                | 506
292                | 507
293                | 511
294                | 512
295                | 513
296                | 514
297                | 516
298                | 517
299                | 518
300                | 519
301                | 520
302                | 521
303                | 903
304                | 906
305                | 908
306                | 911
307                | 912
308                | 914
309                | 931
310                | 932
311                | 933
312                | 934
313                | 935
314                | 939
315                | 940
316                | 942
317                | 950
318                | 951
319                | 959
320                | 960
321                | 961
322                | 962
323                | 2042
324                | 2060
325                | 2061
326                | 2062
327                | 2063
328                | 2064
329                | 2066
330                | 2068
331                | 2069
332                | 2070
333                | 2071
334                | 2072
335                | 2073
336                | 2074
337                | 2075
338                | 2076
339        )
340    }
341}
342
343impl QuotesConditionEvaluatorFV2510 {
344    /// [31] Es sind nur die Artikelnummern erlaubt, die in der Codeliste der Artikelnummern des BDEW mit dem entsprechenden Prüfidentifikator versehen sind.
345    /// EXTERNAL: Requires context from outside the message.
346    fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
347        ctx.external.evaluate("valid_artikel_nummer_for_pid")
348    }
349
350    /// [59] Es sind nur die Konfigurations-Produkte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.1 „Konfigurationsprodukte Schaltzeitdefinition“ enthalten sind.
351    /// EXTERNAL: Requires context from outside the message.
352    fn evaluate_59(&self, ctx: &EvaluationContext) -> ConditionResult {
353        ctx.external
354            .evaluate("valid_konfigurations_produkt_schaltzeitdefinition")
355    }
356
357    /// [70] Es sind nur die Messprodukt-Position-Codes erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.7 „Art der Werte für Messprodukte nach Typ 2“ enthalten sind.
358    /// EXTERNAL: Requires context from outside the message.
359    fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
360        ctx.external
361            .evaluate("valid_messprodukt_position_code_typ2")
362    }
363
364    /// [90] Es sind nur die Produkt-Codes erlaubt, die in der Codeliste der Konfigurationen im Kapitel 7 "Produkte zur Bestellung einer Änderung an einer Lokation" die in der Spalte "Ebene" mit dem Wert "Mess...
365    /// EXTERNAL: Requires context from outside the message.
366    fn evaluate_90(&self, ctx: &EvaluationContext) -> ConditionResult {
367        ctx.external
368            .evaluate("valid_produkt_code_aenderung_lokation_messlokation")
369    }
370
371    /// [91] Es sind nur die Produkt-Codes erlaubt, die in der Codeliste der Konfigurationen im Kapitel 7 "Produkte zur Bestellung einer Änderung an einer Lokation" die in der Spalte "Ebene" mit dem Wert "Netz...
372    /// EXTERNAL: Requires context from outside the message.
373    fn evaluate_91(&self, ctx: &EvaluationContext) -> ConditionResult {
374        ctx.external
375            .evaluate("valid_produkt_code_aenderung_lokation_netzlokation")
376    }
377
378    /// [92] Es sind nur die Produkt-Codes erlaubt, die in der Codeliste der Konfigurationen im Kapitel 7 "Produkte zur Bestellung einer Änderung an einer Lokation" die in der Spalte "Ebene" mit dem Wert "Steu...
379    /// EXTERNAL: Requires context from outside the message.
380    fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
381        ctx.external
382            .evaluate("product_code_allowed_steuerbare_ressource")
383    }
384
385    /// [93] Wenn RNG+Z03 (verbindliche Mengenangabe) in dieser SG31 nicht vorhanden
386    fn evaluate_93(&self, ctx: &EvaluationContext) -> ConditionResult {
387        ConditionResult::from(ctx.count_qualified_in_group("RNG", 0, "Z03", &["SG4", "SG31"]) == 0)
388    }
389
390    /// [94] Wenn RNG+Z04 (unverbindliche Mengenangabe) in dieser SG31 nicht vorhanden
391    fn evaluate_94(&self, ctx: &EvaluationContext) -> ConditionResult {
392        ConditionResult::from(ctx.count_qualified_in_group("RNG", 0, "Z04", &["SG4", "SG31"]) == 0)
393    }
394
395    /// [517] Hinweis: Wert aus BGM+Z93 (Bestellung eines Angebots Änderung der Technik der Lokation) DE1004 der REQOTE mit der die Anfrage Angebot Änderung Technik erfolgt ist.
396    fn evaluate_517(&self, _ctx: &EvaluationContext) -> ConditionResult {
397        // Hinweis: Wert aus BGM+Z93 DE1004 der REQOTE — informational note about value origin, always applies
398        ConditionResult::True
399    }
400
401    /// [518] Hinweis: Angabe gemäß Preisblatt B des MSB.
402    fn evaluate_518(&self, _ctx: &EvaluationContext) -> ConditionResult {
403        // Hinweis: Angabe gemäß Preisblatt B des MSB — informational note, always applies
404        ConditionResult::True
405    }
406
407    /// [519] Hinweis: Wert aus BGM DE1004 der PRICAT mit der das Preisblatt B des MSB, auf dem dieses Angebot basiert, übermittelt wurde.
408    fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
409        // Hinweis: Wert aus BGM DE1004 der PRICAT mit der das Preisblatt B des MSB, auf dem dieses Angebot basiert, übermittelt wurde.
410        ConditionResult::True
411    }
412
413    /// [520] Hinweis: Angabe des Beginnzeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann.
414    fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
415        // Hinweis: Angabe des Beginnzeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann.
416        ConditionResult::True
417    }
418
419    /// [521] Hinweis: Angabe des Endezeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann..
420    fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
421        // Hinweis: Angabe des Endezeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann.
422        ConditionResult::True
423    }
424
425    /// [959] Format: n13-n2
426    fn evaluate_959(&self, ctx: &EvaluationContext) -> ConditionResult {
427        ctx.format_check_qualified("PIA", 0, "Z02", 1, 0, |val| {
428            validate_artikel_pattern(val, &[13, 2])
429        })
430    }
431
432    /// [2066] Diese SG28 ist so oft zu wiederholen, wie zu den unterschiedlichen Messprodukt-Position-Codes zu dem innerhalb derselben SG27 LIN im PIA+5 DE7140 (Erforderliches Produkt Konfigurationserlaubnis fü...
433    /// EXTERNAL: Requires context from outside the message.
434    fn evaluate_2066(&self, ctx: &EvaluationContext) -> ConditionResult {
435        ctx.external
436            .evaluate("smgw_schwellwert_config_product_count")
437    }
438
439    /// [2074] Pro Nachricht ist diese SG27 so oft zu wiederholen, bis für jedes angefragte Produkt derselben SG27 aus der Anfrage eine Angebotsposition angegeben wurde.
440    fn evaluate_2074(&self, _ctx: &EvaluationContext) -> ConditionResult {
441        // Hinweis: Pro Nachricht ist diese SG27 so oft zu wiederholen, bis für jedes angefragte Produkt
442        // derselben SG27 aus der Anfrage eine Angebotsposition angegeben wurde.
443        // Informational cardinality rule about SG27 repetition — always applies.
444        ConditionResult::True
445    }
446
447    /// [2075] Pro SG27 LIN kann die SG27 PIA+Z02 (Artikel-ID) bis zu zweimal angegeben werden. Einmal für die Artikel-ID gemäß Preisblatt B des MSB und einmal für die Artikel-ID "9991000003030-01" (Pauschale...
448    fn evaluate_2075(&self, _ctx: &EvaluationContext) -> ConditionResult {
449        // Hinweis: Pro SG27 LIN kann die SG27 PIA+Z02 (Artikel-ID) bis zu zweimal angegeben werden —
450        // einmal für die reguläre Artikel-ID gemäß Preisblatt B und einmal für die Pauschale Kosten-ID
451        // '9991000003030-01'. Ob die zweite Angabe erlaubt ist, hängt vom Preisblatt B des MSB ab (extern).
452        // Informational cardinality rule about max 2 PIA+Z02 occurrences per SG27.
453        ConditionResult::True
454    }
455
456    /// [2076] Pro SG27 LIN kann die SG31 Preisangabe zur Position nur einmal angegeben werden. Die Angabe der Preisangaben zur Position bezieht sich auf die Artikel-ID bei der es sich nicht um die Artikel-ID "99...
457    fn evaluate_2076(&self, _ctx: &EvaluationContext) -> ConditionResult {
458        // Hinweis: Pro SG27 LIN kann die SG31 Preisangabe zur Position nur einmal angegeben werden.
459        // Die Preisangabe bezieht sich auf die Artikel-ID, die nicht die '9991000003030-01'
460        // (Pauschale Kosten für das Scheitern der Änderung der Technik) ist.
461        // Informational cardinality rule — SG31 appears at most once per SG27 LIN.
462        ConditionResult::True
463    }
464
465    /// [1] Wenn Position nicht angeboten werden kann, weil rechtliche Regelungen oder Rechte Dritter dem entgegenstehen
466    /// EXTERNAL: Requires context from outside the message.
467    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
468        ctx.external.evaluate("position_legally_blocked")
469    }
470
471    /// [2] Wenn in derselben SG27 LIN IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Code Z09 (Kann nicht angeboten werden) nicht vorhanden.
472    // REVIEW: Negates presence of IMD with DE7081=Z09 (Kann nicht angeboten werden) at elements[1][0] in any SG27 group instance. 'nicht vorhanden' means condition is True when Z09 is absent. any_group_has_qualifier falls back to message-wide if no group navigator is available. (medium confidence)
473    fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
474        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
475        match ctx.this_group_has_qualifier("IMD", 1, "Z09", &["SG27"]) {
476            ConditionResult::True => ConditionResult::False,
477            ConditionResult::False => ConditionResult::True,
478            ConditionResult::Unknown => ConditionResult::Unknown,
479        }
480    }
481
482    /// [3] Wenn CCI+++Z64 vorhanden ist
483    fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
484        ctx.has_qualifier("CCI", 2, "Z64")
485    }
486
487    /// [4] Wenn am Gerät vorhanden und abweichend von Gerätenummer
488    /// EXTERNAL: Requires context from outside the message.
489    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
490        ctx.external
491            .evaluate("device_attribute_differs_from_device_number")
492    }
493
494    /// [5] Wenn in derselben SG27 LIN die Artikelnummer 9990001000649 vorhanden ist
495    // REVIEW: Checks if any SG27 group has LIN with article number 9990001000649 at elements[2][0] (C212 item number identification, first component). LIN is the entry segment of SG27 so each rep has exactly one LIN. (medium confidence)
496    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
497        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
498        ctx.this_group_has_qualifier("LIN", 2, "9990001000649", &["SG27"])
499    }
500
501    /// [8] Wenn SG28 CCI+++E13 CAV+EHZ vorhanden
502    // REVIEW: CCI+++E13 maps to elements[2][0]=E13; CAV+EHZ maps to elements[0][0]=EHZ. Checks that in the same SG28 group instance, CCI with characteristic code E13 AND CAV with code EHZ both occur. Group path SG28 may need parent prefix depending on MIG structure. (medium confidence)
503    fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
504        ctx.any_group_has_co_occurrence("CCI", 2, &["E13"], "CAV", 0, 0, &["EHZ"], &["SG28"])
505    }
506
507    /// [9] Wenn in derselben SG27 LIN die Artikelnummer 9990001000657 vorhanden ist
508    // REVIEW: Checks if any SG27 group has LIN with article number 9990001000657 at elements[2][0] (C212 first component). Same pattern as condition 5. (medium confidence)
509    fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
510        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
511        ctx.this_group_has_qualifier("LIN", 2, "9990001000657", &["SG27"])
512    }
513
514    /// [10] Wenn SG28 CAV+MIW/MPW/MBW vorhanden
515    fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
516        ctx.any_group_has_any_qualifier("CAV", 0, &["MIW", "MPW", "MBW"], &["SG28"])
517    }
518
519    /// [11] Wenn in derselben SG27 LIN die Artikelnummer 9990001000665 vorhanden ist
520    // REVIEW: Checks if any SG27 group has LIN with article number 9990001000665 at elements[2][0]. Same pattern as condition 5. (medium confidence)
521    fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
522        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
523        ctx.this_group_has_qualifier("LIN", 2, "9990001000665", &["SG27"])
524    }
525
526    /// [12] Wenn in derselben SG27 LIN die Artikelnummer 9990001000673 vorhanden ist
527    // REVIEW: Checks if any SG27 group has LIN with article number 9990001000673 at elements[2][0]. Same pattern as condition 5. (medium confidence)
528    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
529        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
530        ctx.this_group_has_qualifier("LIN", 2, "9990001000673", &["SG27"])
531    }
532
533    /// [13] Wenn am Gerät vorhanden
534    /// EXTERNAL: Requires context from outside the message.
535    fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
536        ctx.external.evaluate("device_attribute_present")
537    }
538
539    /// [15] Wenn in derselben SG27 LIN die Artikelnummer 9990001000772 vorhanden ist
540    // REVIEW: Checks if any SG27 group has LIN with article number 9990001000772 at elements[2][0]. Same pattern as condition 5. (medium confidence)
541    fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
542        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
543        ctx.this_group_has_qualifier("LIN", 2, "9990001000772", &["SG27"])
544    }
545
546    /// [16] Wenn in derselben SG27 LIN die Artikelnummer 9990001000780 vorhanden ist
547    // REVIEW: Checks if any SG27 group has LIN with article number 9990001000780 at elements[2][0]. Same pattern as condition 5. (medium confidence)
548    fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
549        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
550        ctx.this_group_has_qualifier("LIN", 2, "9990001000780", &["SG27"])
551    }
552
553    /// [17] Wenn das Angebot per REQOTE angefragt wurde
554    // REVIEW: A QUOTES message triggered by a REQOTE contains a reference RFF+AAV in SG1 (Nachrichtennummer der Anfrage, qualifier AAV at elements[0][0]). Presence of RFF+AAV indicates the quote was requested via REQOTE. (medium confidence)
555    fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
556        ctx.has_qualifier("RFF", 0, "AAV")
557    }
558
559    /// [18] Wenn Angebot mehrere Marktlokationen abdeckt, an welchen der identische Anschlussnutzer vorhanden ist und deren messtechnische Einordnung iMS ist (Marktlokation aus SG11 LOC+172 und alle Marktlokat...
560    // REVIEW: RFF+Z18 ('Referenz auf ID weiterer Marktlokationen') is the direct EDIFACT indicator that additional market locations are covered, which is the core structural requirement. However, the additional qualifiers — identical Anschlussnutzer and messtechnische Einordnung iMS — are business attributes about the referenced locations not determinable from message content alone. The RFF+Z18 check is the best message-derivable approximation. (medium confidence)
561    fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
562        // Check if offer references additional market locations via RFF+Z18
563        // RFF+Z18 = 'Referenz auf ID weiterer Marktlokationen' — presence indicates multiple locations covered
564        // Note: the 'identical connection user' and 'iMS classification' sub-conditions require external context
565        ctx.has_qualifier("RFF", 0, "Z18")
566    }
567
568    /// [20] Wenn IMD++Z33 vorhanden
569    fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
570        ctx.has_qualifier("IMD", 1, "Z33")
571    }
572
573    /// [21] Wenn IMD++Z34 vorhanden
574    fn evaluate_21(&self, ctx: &EvaluationContext) -> ConditionResult {
575        ctx.has_qualifier("IMD", 1, "Z34")
576    }
577
578    /// [22] Wenn CCI+++Z75 vorhanden ist
579    fn evaluate_22(&self, ctx: &EvaluationContext) -> ConditionResult {
580        ctx.has_qualifier("CCI", 2, "Z75")
581    }
582
583    /// [25] Wenn SG28 CCI+++E13 CAV+MME vorhanden
584    fn evaluate_25(&self, ctx: &EvaluationContext) -> ConditionResult {
585        ctx.any_group_has_co_occurrence("CCI", 2, &["E13"], "CAV", 0, 0, &["MME"], &["SG28"])
586    }
587
588    /// [26] Wenn DTM+203 nicht vorhanden
589    fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
590        ctx.lacks_qualifier("DTM", 0, "203")
591    }
592
593    /// [27] Wenn DTM+469 nicht vorhanden
594    fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
595        ctx.lacks_qualifier("DTM", 0, "469")
596    }
597
598    /// [28] Wenn RFF+AAV vorhanden
599    fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
600        ctx.has_qualifier("RFF", 0, "AAV")
601    }
602
603    /// [29] Wenn DTM+469 in Anfrage (REQOTE) vorhanden war
604    /// EXTERNAL: Requires context from outside the message.
605    fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
606        ctx.external.evaluate("reqote_had_dtm_469")
607    }
608
609    /// [30] Wenn SG27 IMD+Z09 vorhanden
610    fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
611        ctx.any_group_has_qualifier("IMD", 0, "Z09", &["SG27"])
612    }
613
614    /// [39] MP-ID nur aus Sparte Strom
615    /// EXTERNAL: Requires context from outside the message.
616    fn evaluate_39(&self, ctx: &EvaluationContext) -> ConditionResult {
617        ctx.external.evaluate("mp_id_is_strom")
618    }
619
620    /// [44] Wenn MSBA nicht Eigentümer des Gerätes/der Geräte ist
621    /// EXTERNAL: Requires context from outside the message.
622    fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
623        ctx.external.evaluate("msba_is_not_device_owner")
624    }
625
626    /// [49] Wenn SG27 LIN++Z64 (Erforderliches Produkt Schaltzeitdefinitionen) vorhanden
627    fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
628        ctx.any_group_has_qualifier("LIN", 1, "Z64", &["SG27"])
629    }
630
631    /// [50] Wenn SG27 LIN++Z65 (Erforderliches Produkt Leistungskurvendefinitionen) vorhanden
632    fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
633        ctx.any_group_has_qualifier("LIN", 1, "Z65", &["SG27"])
634    }
635
636    /// [51] Wenn SG27 LIN++Z66 (Erforderliches Produkt Ad-hoc-Steuerkanal) vorhanden
637    fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
638        ctx.any_group_has_qualifier("LIN", 1, "Z66", &["SG27"])
639    }
640
641    /// [52] Wenn SG27 LIN++Z67 (Erforderliches Messprodukt für Werte nach Typ 2 aus Backend) vorhanden
642    fn evaluate_52(&self, ctx: &EvaluationContext) -> ConditionResult {
643        ctx.has_qualifier("LIN", 1, "Z67")
644    }
645
646    /// [53] Wenn SG27 LIN++Z68 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ2 aus SMGW) vorhanden
647    fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
648        ctx.has_qualifier("LIN", 1, "Z68")
649    }
650
651    /// [54] Wenn in der Anfrage zum Angebot einer Konfiguration vorhanden
652    /// EXTERNAL: Requires context from outside the message.
653    fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
654        ctx.external.evaluate("configuration_in_request")
655    }
656
657    /// [55] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Marktlokation angegeben ist.
658    fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
659        // Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Marktlokation angegeben ist.
660        // LOC elements[0][0] == "172" (qualifier), elements[1][0] == DE3225 (Identifikator)
661        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
662        match locs
663            .first()
664            .and_then(|s| s.elements.get(1))
665            .and_then(|e| e.first())
666        {
667            Some(val) => validate_malo_id(val),
668            None => ConditionResult::False, // segment absent → condition not applicable
669        }
670    }
671
672    /// [56] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Messlokation angegeben ist.
673    /// EXTERNAL: Requires context from outside the message.
674    // REVIEW: Condition asks whether the ID in LOC+172 DE3225 (elements[1][0]) belongs to a Messlokation. While Messlokation IDs have a known 33-char format in Germany, the semantic type assignment (Messlokation vs other location types) ultimately requires external business context or a type registry lookup. Marked external. (medium confidence)
675    fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
676        ctx.external.evaluate("loc_172_id_is_messlokation")
677    }
678
679    /// [57] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Netzlokation angegeben ist.
680    /// EXTERNAL: Requires context from outside the message.
681    // REVIEW: Condition asks whether the ID in LOC+172 DE3225 (elements[1][0]) belongs to a Netzlokation. The ID type cannot be determined from EDIFACT message content alone — requires external type registry or format-based classification beyond the available validators. (medium confidence)
682    fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
683        ctx.external.evaluate("loc_172_id_is_netzlokation")
684    }
685
686    /// [58] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Steuerbaren Ressource angegeben ist.
687    /// EXTERNAL: Requires context from outside the message.
688    // REVIEW: Condition asks whether the ID in LOC+172 DE3225 (elements[1][0]) belongs to a Steuerbare Ressource. Same reasoning as [56] and [57] — semantic type assignment requires external business context. (medium confidence)
689    fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
690        ctx.external.evaluate("loc_172_id_is_steuerbare_ressource")
691    }
692
693    /// [60] Es sind nur die Konfigurations-Produkte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.2 „Konfigurationsprodukte Leistungskurvendefinition“ enthalten sind.
694    /// EXTERNAL: Requires context from outside the message.
695    fn evaluate_60(&self, ctx: &EvaluationContext) -> ConditionResult {
696        ctx.external.evaluate("config_product_leistungskurve")
697    }
698
699    /// [61] Es sind nur die Konfigurations-Produkte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.3 „Konfigurationsprodukte Ad-hoc-Steuerkanal“ enthalten sind.
700    /// EXTERNAL: Requires context from outside the message.
701    fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
702        ctx.external.evaluate("config_product_adhoc_steuerkanal")
703    }
704
705    /// [62] Es sind nur die Messprodukte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.5 „Messprodukte für Werte nach Typ 2 aus Backend für LF und NB“ enthalten sind.
706    /// EXTERNAL: Requires context from outside the message.
707    fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
708        ctx.external.evaluate("messprodukts_typ2_backend_lf_nb")
709    }
710
711    /// [63] Es sind nur die Messprodukte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.4 „Messprodukte mit Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW“ enthalten sind.
712    /// EXTERNAL: Requires context from outside the message.
713    fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
714        ctx.external.evaluate("messprodukts_typ2_smgw")
715    }
716
717    /// [65] Wenn DTM+203 (Ausführungsdatum) im DE2380 &lt; 202312312300?+00
718    // REVIEW: DTM+203 DE2380 is at elements[0][1]. Lexicographic comparison works for YYYYMMDDHHMI+ZZ format since all values use the same timezone offset and format. The EDIFACT escape `?+` in the threshold represents a literal `+`, giving the threshold string "202312312300+00". (medium confidence)
719    fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
720        let segs = ctx.find_segments_with_qualifier("DTM", 0, "203");
721        match segs.first() {
722            Some(dtm) => {
723                match dtm
724                    .elements
725                    .first()
726                    .and_then(|e| e.get(1))
727                    .map(|s| s.as_str())
728                {
729                    Some(value) if !value.is_empty() => {
730                        ConditionResult::from(value < "202312312300+00")
731                    }
732                    _ => ConditionResult::Unknown,
733                }
734            }
735            None => ConditionResult::False, // segment absent → condition not applicable
736        }
737    }
738
739    /// [66] Wenn DTM+469 (Beginn zum (nächstmöglichen Termin)) im DE2380 &lt; 202312312300?+00
740    // REVIEW: DTM+469 DE2380 is at elements[0][1]. Same lexicographic comparison as condition 65, applied to the Beginn zum nächstmöglichen Termin qualifier 469. (medium confidence)
741    fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
742        let segs = ctx.find_segments_with_qualifier("DTM", 0, "469");
743        match segs.first() {
744            Some(dtm) => {
745                match dtm
746                    .elements
747                    .first()
748                    .and_then(|e| e.get(1))
749                    .map(|s| s.as_str())
750                {
751                    Some(value) if !value.is_empty() => {
752                        ConditionResult::from(value < "202312312300+00")
753                    }
754                    _ => ConditionResult::Unknown,
755                }
756            }
757            None => ConditionResult::False, // segment absent → condition not applicable
758        }
759    }
760
761    /// [67] Wenn DTM+203 (Ausführungsdatum) im DE2380 ≥ 202312312300?+00
762    // REVIEW: DTM+203 DE2380 is at elements[0][1]. Logical complement of condition 65 — True when Ausführungsdatum >= 2023-12-31 23:00 UTC. Lexicographic comparison is valid for this date format. (medium confidence)
763    fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
764        let segs = ctx.find_segments_with_qualifier("DTM", 0, "203");
765        match segs.first() {
766            Some(dtm) => {
767                match dtm
768                    .elements
769                    .first()
770                    .and_then(|e| e.get(1))
771                    .map(|s| s.as_str())
772                {
773                    Some(value) if !value.is_empty() => {
774                        ConditionResult::from(value >= "202312312300+00")
775                    }
776                    _ => ConditionResult::Unknown,
777                }
778            }
779            None => ConditionResult::False, // segment absent → condition not applicable
780        }
781    }
782
783    /// [68] Wenn DTM+469 (Beginn zum (nächstmöglichen Termin)) im DE2380 ≥ 202312312300?+00
784    // REVIEW: DTM+469 DE2380 (elements[0][1]) compared lexicographically — works for ISO datetime strings with fixed-width format. EDIFACT ?+ escape resolves to literal + in parsed value. (medium confidence)
785    fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
786        let segs = ctx.find_segments_with_qualifier("DTM", 0, "469");
787        if segs.is_empty() {
788            return ConditionResult::Unknown;
789        }
790        for seg in segs {
791            let val = seg
792                .elements
793                .get(0)
794                .and_then(|e| e.get(1))
795                .map(|s| s.as_str())
796                .unwrap_or("");
797            if val >= "202312312300+00" {
798                return ConditionResult::True;
799            }
800        }
801        ConditionResult::False
802    }
803
804    /// [69] Es sind nur Werte aus der EDI@Energy Codeliste der Artikelnummern und Artikel-ID erlaubt, die im Kapitel 3.5 "Abrechnung Messstellenbetrieb für die Sparte Strom" genannt sind.
805    /// EXTERNAL: Requires context from outside the message.
806    fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
807        ctx.external
808            .evaluate("artikel_id_abrechnung_messstellenbetrieb_strom")
809    }
810
811    /// [71] Wenn innerhalb derselben SG27 LIN im PIA+5 DE7140 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW) ein Produkt angegeben ist, das in der Codeliste der Konfigurationen...
812    /// EXTERNAL: Requires context from outside the message.
813    fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
814        ctx.external.evaluate("pia5_product_is_threshold_trigger")
815    }
816
817    /// [72] wenn im DE3155 in demselben COM der Code EM vorhanden ist
818    fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
819        let segs = ctx.find_segments("COM");
820        if segs.is_empty() {
821            return ConditionResult::Unknown;
822        }
823        ConditionResult::from(segs.iter().any(|seg| {
824            seg.elements
825                .first()
826                .and_then(|e| e.get(1))
827                .is_some_and(|v| v == "EM")
828        }))
829    }
830
831    /// [73] wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
832    fn evaluate_73(&self, ctx: &EvaluationContext) -> ConditionResult {
833        let segs = ctx.find_segments("COM");
834        if segs.is_empty() {
835            return ConditionResult::Unknown;
836        }
837        ConditionResult::from(segs.iter().any(|seg| {
838            seg.elements
839                .first()
840                .and_then(|e| e.get(1))
841                .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
842        }))
843    }
844
845    /// [74] Es sind nur die Messprodukte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.6.1 "Werte nach Typ 2 aus Backend" enthalten sind.
846    /// EXTERNAL: Requires context from outside the message.
847    fn evaluate_74(&self, ctx: &EvaluationContext) -> ConditionResult {
848        ctx.external.evaluate("product_in_typ2_backend_codelist")
849    }
850
851    /// [75] Es sind nur die Messprodukte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.6.2 "Werte nach Typ 2 aus SMGW" enthalten sind.
852    /// EXTERNAL: Requires context from outside the message.
853    fn evaluate_75(&self, ctx: &EvaluationContext) -> ConditionResult {
854        ctx.external.evaluate("product_in_typ2_smgw_codelist")
855    }
856
857    /// [77] Wenn in der Anfrage nach Werten ein Messprodukt enthalten war, das in der Codeliste der Konfigurationen im Kapitel 4.6.1 "Werte nach Typ 2 aus Backend" enthalten ist.
858    /// EXTERNAL: Requires context from outside the message.
859    fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
860        ctx.external
861            .evaluate("request_contained_typ2_backend_product")
862    }
863
864    /// [78] Wenn in der Anfrage nach Werten ein Messprodukt enthalten war, das in der Codeliste der Konfigurationen im Kapitel 4.6.2 "Werte nach Typ 2 aus SMWG" enthalten ist.
865    /// EXTERNAL: Requires context from outside the message.
866    fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
867        ctx.external.evaluate("request_contained_typ2_smgw_product")
868    }
869
870    /// [79] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z07 (Kauf) vorhanden.
871    fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
872        let segs = ctx.find_segments("IMD");
873        if segs.is_empty() {
874            return ConditionResult::Unknown;
875        }
876        ConditionResult::from(segs.iter().any(|seg| {
877            seg.elements
878                .get(1)
879                .and_then(|e| e.first())
880                .is_some_and(|v| v == "Z07")
881        }))
882    }
883
884    /// [80] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z08 (Nutzungsüberlassung) vorhanden.
885    fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
886        let segs = ctx.find_segments("IMD");
887        if segs.is_empty() {
888            return ConditionResult::Unknown;
889        }
890        ConditionResult::from(segs.iter().any(|seg| {
891            seg.elements
892                .get(1)
893                .and_then(|e| e.first())
894                .is_some_and(|v| v == "Z08")
895        }))
896    }
897
898    /// [81] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z33 (Angebot auf Basis Preisblatt) vorhanden.
899    fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
900        let segs = ctx.find_segments("IMD");
901        if segs.is_empty() {
902            return ConditionResult::Unknown;
903        }
904        ConditionResult::from(segs.iter().any(|seg| {
905            seg.elements
906                .get(1)
907                .and_then(|e| e.first())
908                .is_some_and(|v| v == "Z33")
909        }))
910    }
911
912    /// [82] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z34 (Individuelles Angebot) vorhanden.
913    fn evaluate_82(&self, ctx: &EvaluationContext) -> ConditionResult {
914        let segs = ctx.find_segments("IMD");
915        if segs.is_empty() {
916            return ConditionResult::Unknown;
917        }
918        ConditionResult::from(segs.iter().any(|seg| {
919            seg.elements
920                .get(1)
921                .and_then(|e| e.first())
922                .is_some_and(|v| v == "Z34")
923        }))
924    }
925
926    /// [83] Wenn in derselben SG27 LIN ein PIA+Z02 (Artikel-ID) mit einer Artikel-ID im DE7140 bei der die letzten beiden Stellen mit dem Wert "01" (Kosten für das Einrichten des Messprodukts) vorhanden ist.
927    // REVIEW: PIA+Z02 where elements[0][0]=="Z02" (Artikel-ID qualifier) and elements[1][0] ends with "01" (Kosten für Einrichten). Group-scoped to SG27 but falls back to message-wide search; medium confidence due to lack of SG27 navigator API for flat groups. (medium confidence)
928    fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
929        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
930        let segs = ctx.scoped_find_segments_in("SG27", "PIA");
931        if segs.is_empty() {
932            return ConditionResult::Unknown;
933        }
934        ConditionResult::from(segs.iter().any(|seg| {
935            let is_z02 = seg
936                .elements
937                .first()
938                .and_then(|e| e.first())
939                .is_some_and(|v| v == "Z02");
940            let ends_with_01 = seg
941                .elements
942                .get(1)
943                .and_then(|e| e.first())
944                .is_some_and(|v| v.ends_with("01"));
945            is_z02 && ends_with_01
946        }))
947    }
948
949    /// [84] Wenn in derselben SG27 LIN ein PIA+Z02 (Artikel-ID) mit einer Artikel-ID im DE7140 bei der die letzten beiden Stellen mit dem Wert "02" (Kosten für den Betrieb des Messprodukts) vorhanden ist.
950    // REVIEW: Checks for PIA+Z02 where DE7140 (elements[1][0]) ends with '02' (Betriebskosten). Falls back to message-wide search since group-scoped navigator API has no direct find_segments_in_group for top-level SG27 — semantically equivalent for this condition. (medium confidence)
951    fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
952        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
953        let pias = ctx.scoped_find_segments_in_with_qualifier("SG27", "PIA", 0, "Z02");
954        ConditionResult::from(pias.iter().any(|s| {
955            s.elements
956                .get(1)
957                .and_then(|e| e.first())
958                .map(|v| v.ends_with("02"))
959                .unwrap_or(false)
960        }))
961    }
962
963    /// [85] Wenn in derselben SG27 LIN ein PIA+Z02 (Artikel-ID) mit einer Artikel-ID im DE7140 bei der die letzten beiden Stellen mit dem Wert "03" (Kosten für die anfallenden Transaktionen des Messprodukts) ...
964    // REVIEW: Checks for PIA+Z02 where DE7140 (elements[1][0]) ends with '03' (Transaktionskosten). Same pattern as condition 84. (medium confidence)
965    fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
966        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
967        let pias = ctx.scoped_find_segments_in_with_qualifier("SG27", "PIA", 0, "Z02");
968        ConditionResult::from(pias.iter().any(|s| {
969            s.elements
970                .get(1)
971                .and_then(|e| e.first())
972                .map(|v| v.ends_with("03"))
973                .unwrap_or(false)
974        }))
975    }
976
977    /// [86] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z01 (Einrichtungspreis) vorhanden ist.
978    fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
979        // HAND-EDITED: "in dieser/derselben SG31" — scoped to the enclosing group.
980        ctx.this_group_has_qualifier("PRI", 0, "Z01", &["SG31"])
981    }
982
983    /// [87] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z02 (Transaktionspreis) vorhanden ist.
984    fn evaluate_87(&self, ctx: &EvaluationContext) -> ConditionResult {
985        // HAND-EDITED: "in dieser/derselben SG31" — scoped to the enclosing group.
986        ctx.this_group_has_qualifier("PRI", 0, "Z02", &["SG31"])
987    }
988
989    /// [88] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z03 (Betriebspreis) vorhanden ist.
990    fn evaluate_88(&self, ctx: &EvaluationContext) -> ConditionResult {
991        // HAND-EDITED: "in dieser/derselben SG31" — scoped to the enclosing group.
992        ctx.this_group_has_qualifier("PRI", 0, "Z03", &["SG31"])
993    }
994
995    /// [490] wenn Wert in diesem DE, an der Stelle CCYYMMDDHHMM ein Zeitpunkt aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Übersicht gesetzliche deutsche Sommerzeit (MESZ)“ der Spalten: „Sommerzei...
996    fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
997        match ctx.resolved_value {
998            Some(val) => is_mesz_utc(val),
999            None => ConditionResult::False,
1000        }
1001    }
1002
1003    /// [491] wenn Wert in diesem DE, an der Stelle CCYYMMDDHHMM ein Zeitpunkt aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Übersicht gesetzliche deutsche Zeit (MEZ)“ der Spalten: „Winterzeit (MEZ)...
1004    fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1005        match ctx.resolved_value {
1006            Some(val) => is_mez_utc(val),
1007            None => ConditionResult::False,
1008        }
1009    }
1010
1011    /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
1012    /// EXTERNAL: Requires context from outside the message.
1013    fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1014        ctx.external.evaluate("recipient_is_electricity_sector")
1015    }
1016
1017    /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
1018    /// EXTERNAL: Requires context from outside the message.
1019    fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1020        ctx.external.evaluate("recipient_is_gas_sector")
1021    }
1022
1023    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt.
1024    /// EXTERNAL: Requires context from outside the message.
1025    // REVIEW: Validating that a date is the document creation date or earlier requires knowing the actual current/creation timestamp at validation time. This is a temporal constraint that depends on runtime context outside the message. (medium confidence)
1026    fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
1027        ctx.external.evaluate("document_date_is_creation_or_past")
1028    }
1029
1030    /// [500] Hinweis: Angabe eines technischen Ansprechpartners für die Geräteübernahme
1031    fn evaluate_500(&self, _ctx: &EvaluationContext) -> ConditionResult {
1032        // Hinweis: Angabe eines technischen Ansprechpartners für die Geräteübernahme — informational note, always applies
1033        ConditionResult::True
1034    }
1035
1036    /// [501] Hinweis: Verwendung der ID der Marktlokation
1037    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1038        // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1039        ConditionResult::True
1040    }
1041
1042    /// [502] Hinweis: Verwendung der ID der Messlokation
1043    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1044        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1045        ConditionResult::True
1046    }
1047
1048    /// [503] Hinweis: Es ist die MP-ID des Eigentümers (MSB) zur bilateralen Klärung anzugeben, wenn z. B. der MSBA das Gerät / die Geräte selbst gepachtet hat.
1049    fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
1050        // Hinweis: Es ist die MP-ID des Eigentümers (MSB) zur bilateralen Klärung anzugeben, wenn z. B. der MSBA das Gerät / die Geräte selbst gepachtet hat.
1051        ConditionResult::True
1052    }
1053
1054    /// [504] Hinweis: Wert aus BGM+311 DE1004 der REQOTE mit der die Angebotsanfrage erfolgt ist.
1055    fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1056        // Hinweis: Wert aus BGM+311 DE1004 der REQOTE mit der die Angebotsanfrage erfolgt ist.
1057        ConditionResult::True
1058    }
1059
1060    /// [505] Hinweis: Wert aus BGM+Z29 DE1004 der REQOTE, mit der die Anfrage Rechnungsabwicklung erfolgt ist.
1061    fn evaluate_505(&self, _ctx: &EvaluationContext) -> ConditionResult {
1062        // Hinweis: Wert aus BGM+Z29 DE1004 der REQOTE, mit der die Anfrage Rechnungsabwicklung erfolgt ist.
1063        ConditionResult::True
1064    }
1065
1066    /// [506] Hinweis: Wenn zu einer Position (z. B. Messwandlersatz) mehrere Gerätenummern existieren, sind die Gerätenummern in derselben Position (LIN-Segment) mittels Wiederholung der SG32 RFF+Z09 anzugeben
1067    fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
1068        // Hinweis: Wenn zu einer Position mehrere Gerätenummern existieren, sind die Gerätenummern in derselben Position mittels Wiederholung der SG32 RFF+Z09 anzugeben.
1069        ConditionResult::True
1070    }
1071
1072    /// [507] Hinweis: Verwendung der ID der Tranche
1073    fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
1074        // Hinweis: Verwendung der ID der Tranche
1075        ConditionResult::True
1076    }
1077
1078    /// [511] Hinweis: Wert aus BGM+Z74 (Bestellung eines Angebots einer Konfiguration) DE1004 der REQOTE mit der die Anfrage einer Konfiguration erfolgt ist.
1079    fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
1080        // Hinweis: Wert aus BGM+Z74 DE1004 der REQOTE — informational note, always applies
1081        ConditionResult::True
1082    }
1083
1084    /// [512] Hinweis: Verwendung der ID der Netzlokation
1085    fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
1086        // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
1087        ConditionResult::True
1088    }
1089
1090    /// [513] Hinweis: Verwendung der ID der Steuerbaren Ressource
1091    fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
1092        // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
1093        ConditionResult::True
1094    }
1095
1096    /// [514] Hinweis: Angabe gemäß Preisblatt des MSB. Bis zu einmal für die Parametrierung, bis zu einmal für den Betrieb und bis zu einmal für die Transaktion, sofern im Preisblatt des MSB vorhanden.
1097    fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
1098        // Hinweis: Angabe gemäß Preisblatt des MSB — informational note, always applies
1099        ConditionResult::True
1100    }
1101
1102    /// [516] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
1103    fn evaluate_516(&self, _ctx: &EvaluationContext) -> ConditionResult {
1104        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
1105        ConditionResult::True
1106    }
1107
1108    /// [903] Format: Möglicher Wert: 1
1109    // REVIEW: Format condition requiring the value to equal exactly 1. Applies to a numeric field — most likely QTY[0][1] in QUOTES context. validate_numeric with == 1.0 covers this. Medium confidence because the exact segment binding isn't specified in the condition text alone. (medium confidence)
1110    fn evaluate_903(&self, ctx: &EvaluationContext) -> ConditionResult {
1111        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "==", 1.0))
1112    }
1113
1114    /// [906] Format: max. 3 Nachkommastellen
1115    // REVIEW: Format condition for maximum 3 decimal places. Uses validate_max_decimal_places helper. Targeting QTY[0][1] as the most likely numeric value field in QUOTES. Medium confidence because the specific data element binding is not stated in the condition text. (medium confidence)
1116    fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
1117        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 3))
1118    }
1119
1120    /// [908] Format: Mögliche Werte: 1 bis n
1121    // REVIEW: Format condition for values in range 1 to n (any positive integer). validate_numeric with >= 1.0 captures the lower bound; the upper bound n is open-ended. Targeting QTY as the most common numeric field. Medium confidence due to unspecified exact segment. (medium confidence)
1122    fn evaluate_908(&self, ctx: &EvaluationContext) -> ConditionResult {
1123        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">=", 1.0))
1124    }
1125
1126    /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend und fortlaufend aufsteigend
1127    fn evaluate_911(&self, _ctx: &EvaluationContext) -> ConditionResult {
1128        // Hinweis: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend
1129        // und fortlaufend aufsteigend — informational annotation about sequential numbering,
1130        // always applies unconditionally
1131        ConditionResult::True
1132    }
1133
1134    /// [912] Format: max. 6 Nachkommastellen
1135    // REVIEW: Format condition for maximum 6 decimal places. Uses validate_max_decimal_places helper with limit 6. Targeting QTY[0][1] as the primary numeric value field in QUOTES. Medium confidence because the exact data element binding is not given in the condition text. (medium confidence)
1136    fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
1137        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 6))
1138    }
1139
1140    /// [914] Format: Möglicher Wert: &gt; 0
1141    // REVIEW: Format condition requiring value > 0. In QUOTES context this most likely applies to QTY element[0][1] (the quantity value). Medium confidence because the exact segment is not specified in the condition text — it depends on which data element this condition is attached to in the AHB. (medium confidence)
1142    fn evaluate_914(&self, ctx: &EvaluationContext) -> ConditionResult {
1143        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">", 0.0))
1144    }
1145
1146    /// [931] Format: ZZZ = +00
1147    // REVIEW: Format condition validating that the timezone portion (ZZZ) of a DTM value equals +00 (UTC). Uses validate_timezone_utc helper. Medium confidence because the specific DTM qualifier this applies to is not stated in the condition text — the AHB attaches this to a specific DTM element. (medium confidence)
1148    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1149        ctx.format_check("DTM", 0, 1, validate_timezone_utc)
1150    }
1151
1152    /// [932] Format: HHMM = 2200
1153    // REVIEW: Format condition requiring the HHMM portion of a DTM value to equal 2200. Uses validate_hhmm_equals helper. Medium confidence because the specific DTM qualifier (e.g., 163, 164, 137) this applies to is not stated in the condition text alone — it depends on which DTM element in the QUOTES AHB carries this constraint. (medium confidence)
1154    fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
1155        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2200"))
1156    }
1157
1158    /// [933] Format: HHMM = 2300
1159    // REVIEW: Format condition requiring the HHMM portion of a DTM value to equal 2300. Uses validate_hhmm_equals helper. Medium confidence for the same reason as 932 — specific DTM qualifier not identified from condition text alone. (medium confidence)
1160    fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
1161        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2300"))
1162    }
1163
1164    /// [934] Format: HHMM = 0400
1165    // REVIEW: Format condition requiring the HHMM portion of a DTM value to equal 0400. Uses validate_hhmm_equals helper. Medium confidence for the same reason as 932/933 — specific DTM qualifier not identified from condition text alone. In QUOTES context, 0400 likely represents a gas day start time. (medium confidence)
1166    fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
1167        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0400"))
1168    }
1169
1170    /// [935] Format: HHMM = 0500
1171    // REVIEW: Format condition checking HHMM = 0500 on a DTM segment. Without a specific qualifier reference provided for this QUOTES condition, falls back to the first DTM segment. validate_hhmm_equals extracts the HHMM portion of the datetime value and compares it to '0500'. (medium confidence)
1172    fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
1173        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0500"))
1174    }
1175
1176    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
1177    // REVIEW: Checks COM segments for email-format values (must contain '@' and '.'). COM is the standard EDIFACT segment for communication channels where email addresses appear (qualifier EM). Scans all COM values since context doesn't specify which occurrence to validate. (medium confidence)
1178    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
1179        for seg in ctx.find_segments("COM") {
1180            if let Some(elem) = seg.elements.first() {
1181                if let Some(value) = elem.first() {
1182                    if value.contains('@') && value.contains('.') {
1183                        return ConditionResult::True;
1184                    }
1185                }
1186            }
1187        }
1188        ConditionResult::False
1189    }
1190
1191    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
1192    // REVIEW: Checks COM segments for international phone number format (starts with '+' followed only by digits). COM is the standard segment for telephone contacts. Scans all COM values since context doesn't specify which occurrence to validate. (medium confidence)
1193    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
1194        for seg in ctx.find_segments("COM") {
1195            if let Some(elem) = seg.elements.first() {
1196                if let Some(value) = elem.first() {
1197                    if value.starts_with('+') && value.chars().skip(1).all(|c| c.is_ascii_digit()) {
1198                        return ConditionResult::True;
1199                    }
1200                }
1201            }
1202        }
1203        ConditionResult::False
1204    }
1205
1206    /// [942] Format: n1-n2-n1-n3
1207    fn evaluate_942(&self, ctx: &EvaluationContext) -> ConditionResult {
1208        ctx.format_check("PIA", 1, 0, |val| {
1209            validate_artikel_pattern(val, &[1, 2, 1, 3])
1210        })
1211    }
1212
1213    /// [950] Format: Marktlokations-ID
1214    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
1215        ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
1216    }
1217
1218    /// [951] Format: Zählpunktbezeichnung
1219    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
1220        ctx.format_check_qualified("LOC", 0, "Z17", 1, 0, validate_zahlpunkt)
1221    }
1222
1223    /// [960] Format: Netzlokations-ID
1224    // HAND-EDITED: a Netzlokations-ID is "E" + ten alphanumerics (`E1688117482`),
1225    // not MaLo-shaped; `validate_malo_id` rejected every real one (#173).
1226    fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
1227        ctx.format_check_qualified("LOC", 0, "Z18", 1, 0, validate_nelo_id)
1228    }
1229
1230    /// [961] Format: SR-ID
1231    // REVIEW: Format: SR-ID — 11-digit Luhn check digit number. In QUOTES context, Steuerbare Ressource IDs typically appear in LOC+Z19 at elements[1][0]. validate_sr_id checks 11 digits with Luhn validation. Medium confidence because QUOTES segment structure may differ from UTILMD. (medium confidence)
1232    fn evaluate_961(&self, ctx: &EvaluationContext) -> ConditionResult {
1233        ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_sr_id)
1234    }
1235
1236    /// [962] Format: max. 6 Vorkommastellen
1237    // REVIEW: Uses validate_max_integer_digits(value, 6) on the PRI price amount value. Standard EDIFACT PRI C509 places DE5118 (price amount) at elements[0][1]. PRI segment structure is not explicitly listed in the provided MIG reference so medium confidence on the element index. (medium confidence)
1238    fn evaluate_962(&self, ctx: &EvaluationContext) -> ConditionResult {
1239        ctx.format_check("PRI", 0, 1, |val| validate_max_integer_digits(val, 6))
1240    }
1241
1242    /// [2042] Innerhalb dieser LIN-Position muss das PIA+Z02 (Artikel-ID) mindestens ein Mal angegeben werden und kann bis zu drei Mal angegeben werden.
1243    // REVIEW: Counts PIA segments where elements[0][0] == 'Z02' (DE4347 Artikel-ID qualifier per segment structure reference) and verifies the count is between 1 and 3 inclusive. Medium confidence because the condition is scoped to 'within this LIN position' (SG27) but we count message-wide; in REQOTE the PIA+Z02 segments appear within SG27 position groups. (medium confidence)
1244    fn evaluate_2042(&self, ctx: &EvaluationContext) -> ConditionResult {
1245        let count = ctx
1246            .find_segments("PIA")
1247            .iter()
1248            .filter(|s| {
1249                s.elements
1250                    .first()
1251                    .and_then(|e| e.first())
1252                    .is_some_and(|v| v == "Z02")
1253            })
1254            .count();
1255        ConditionResult::from(count >= 1 && count <= 3)
1256    }
1257
1258    /// [2060] Pro Nachricht ist die SG27 LIN+Z64 (Erforderliches Produkt Schaltzeitdefinitionen) maximal einmal anzugeben
1259    fn evaluate_2060(&self, ctx: &EvaluationContext) -> ConditionResult {
1260        let count = ctx
1261            .find_segments("LIN")
1262            .iter()
1263            .filter(|s| {
1264                s.elements
1265                    .get(1)
1266                    .and_then(|e| e.first())
1267                    .is_some_and(|v| v == "Z64")
1268            })
1269            .count();
1270        ConditionResult::from(count <= 1)
1271    }
1272
1273    /// [2061] Pro Nachricht ist die SG27 LIN++Z65 (Erforderliches Produkt Leistungskurvendefinitionen) maximal einmal anzugeben
1274    fn evaluate_2061(&self, ctx: &EvaluationContext) -> ConditionResult {
1275        let count = ctx
1276            .find_segments("LIN")
1277            .iter()
1278            .filter(|s| {
1279                s.elements
1280                    .get(1)
1281                    .and_then(|e| e.first())
1282                    .is_some_and(|v| v == "Z65")
1283            })
1284            .count();
1285        ConditionResult::from(count <= 1)
1286    }
1287
1288    /// [2062] Pro Nachricht ist die SG27 LIN++Z66 (Erforderliches Produkt Ad-hoc-Steuerkanal) maximal einmal anzugeben
1289    fn evaluate_2062(&self, ctx: &EvaluationContext) -> ConditionResult {
1290        let count = ctx.find_segments_with_qualifier("LIN", 1, "Z66").len();
1291        ConditionResult::from(count <= 1)
1292    }
1293
1294    /// [2063] Pro Nachricht ist die SG27 LIN++Z67 (Erforderliches Messprodukt für Werte nach Typ 2 aus Backend) maximal einmal anzugeben
1295    fn evaluate_2063(&self, ctx: &EvaluationContext) -> ConditionResult {
1296        let count = ctx.find_segments_with_qualifier("LIN", 1, "Z67").len();
1297        ConditionResult::from(count <= 1)
1298    }
1299
1300    /// [2064] Pro Nachricht ist die SG27 LIN++Z68 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW) maximal einmal anzugeben
1301    fn evaluate_2064(&self, ctx: &EvaluationContext) -> ConditionResult {
1302        let count = ctx.find_segments_with_qualifier("LIN", 1, "Z68").len();
1303        ConditionResult::from(count <= 1)
1304    }
1305
1306    /// [2068] Pro SG27 LIN ist die SG31 PRI genau einmal anzugeben.
1307    // REVIEW: Checks that every SG27 instance has exactly one SG31 child group (PRI). Uses group navigator to count SG31 children per SG27 instance. Returns True only when all SG27s have exactly one SG31. (medium confidence)
1308    fn evaluate_2068(&self, ctx: &EvaluationContext) -> ConditionResult {
1309        let nav = match ctx.navigator() {
1310            Some(n) => n,
1311            None => return ConditionResult::Unknown,
1312        };
1313        let sg27_count = nav.group_instance_count(&["SG27"]);
1314        if sg27_count == 0 {
1315            return ConditionResult::Unknown;
1316        }
1317        for i in 0..sg27_count {
1318            let sg31_count = nav.child_group_instance_count(&["SG27"], i, "SG31");
1319            if sg31_count != 1 {
1320                return ConditionResult::False;
1321            }
1322        }
1323        ConditionResult::True
1324    }
1325
1326    /// [2069] Pro SG27 LIN ist die SG31 PRI genau einmal anzugeben. Dabei muss in der SG31 PRI im DE5284 eine Mengenangabe mit dem Code H87 (Stück) im DE6411 angegeben sein.
1327    // REVIEW: Checks that count_in_group for PRI in SG31 equals exactly 1 and that the PRI contains H87 in DE6411 (C509 index 4 = elements[0][1] is price amount; index 4 is unit of measure per standard EDIFACT C509 layout: DE5125, DE5118, DE5375, DE5419, DE6411). PRI structure not in the provided MIG reference so medium confidence on the DE6411 index. (medium confidence)
1328    fn evaluate_2069(&self, ctx: &EvaluationContext) -> ConditionResult {
1329        // Pro SG27 LIN: SG31 PRI must appear exactly once with H87 (Stück) in DE6411
1330        let pri_count = ctx.count_in_group("PRI", &["SG27", "SG31"]);
1331        if pri_count != 1 {
1332            return ConditionResult::False;
1333        }
1334        // DE6411 is the 5th component of C509 = elements[0][4]
1335        let segs = ctx.find_segments("PRI");
1336        match segs.first() {
1337            Some(pri) => {
1338                let unit = pri
1339                    .elements
1340                    .first()
1341                    .and_then(|e| e.get(4))
1342                    .map(|s| s.as_str());
1343                ConditionResult::from(unit == Some("H87"))
1344            }
1345            None => ConditionResult::False, // segment absent → condition not applicable
1346        }
1347    }
1348
1349    /// [2070] Pro SG27 LIN ist die SG31 PRI zweimal anzugeben. Dabei muss genau eine SG31 PRI mit Preis- und Mengenangabe und dem Code H87 (Stück) im DE6411 und genau eine SG31 PRI mit Preis- und Mengenangabe u...
1350    // REVIEW: Checks that SG31 PRI appears exactly twice and that among those two entries one has H87 and one has DAY in DE6411 (C509 index 4 = elements[0][4]). Same caveat as 2069 — PRI structure not in the provided MIG reference so medium confidence on the component index for DE6411. (medium confidence)
1351    fn evaluate_2070(&self, ctx: &EvaluationContext) -> ConditionResult {
1352        // Pro SG27 LIN: SG31 PRI must appear exactly twice — one with H87 (Stück), one with DAY (Tag) in DE6411
1353        let pri_count = ctx.count_in_group("PRI", &["SG27", "SG31"]);
1354        if pri_count != 2 {
1355            return ConditionResult::False;
1356        }
1357        // DE6411 is elements[0][4] in standard EDIFACT C509
1358        let segs = ctx.find_segments("PRI");
1359        let has_h87 = segs.iter().any(|pri| {
1360            pri.elements
1361                .first()
1362                .and_then(|e| e.get(4))
1363                .is_some_and(|v| v == "H87")
1364        });
1365        let has_day = segs.iter().any(|pri| {
1366            pri.elements
1367                .first()
1368                .and_then(|e| e.get(4))
1369                .is_some_and(|v| v == "DAY")
1370        });
1371        ConditionResult::from(has_h87 && has_day)
1372    }
1373
1374    /// [2071] Diese SG31 PRI (Preisangabe zur Position) ist bis zu dreimal anzugeben. Es ist so oft anzugeben, wie innerhalb derselben SG27 LIN (Erforderliches Messprodukt für Werte nach Typ2 aus Backend) das P...
1375    fn evaluate_2071(&self, _ctx: &EvaluationContext) -> ConditionResult {
1376        // Hinweis: Cardinality rule — SG31 PRI (Preisangabe) is given up to three times,
1377        // as often as PIA+Z02 (Artikel-ID) appears in the same SG27 LIN (Erforderliches Messprodukt
1378        // für Werte nach Typ2 aus Backend). Informational cardinality annotation, always applies.
1379        ConditionResult::True
1380    }
1381
1382    /// [2072] Diese SG31 PRI (Preisangabe zur Position) ist bis zu dreimal anzugeben. Es ist so oft anzugeben, wie innerhalb derselben SG27 LIN (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ...
1383    fn evaluate_2072(&self, _ctx: &EvaluationContext) -> ConditionResult {
1384        // Hinweis: Cardinality rule — SG31 PRI (Preisangabe) is given up to three times,
1385        // as often as PIA+Z02 (Artikel-ID) appears in the same SG27 LIN (Erforderliches Produkt
1386        // Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW). Informational cardinality annotation, always applies.
1387        ConditionResult::True
1388    }
1389
1390    /// [2073] Das PIA (OBIS-Kennzahl für Werte nach Typ 2 Backend) ist mindestens einmal anzugeben. Es kann in Abhängigkeit zu den anderen PIA Segmenten innerhalb derselben SG27 LIN (Erforderliches Messprodukt...
1391    fn evaluate_2073(&self, _ctx: &EvaluationContext) -> ConditionResult {
1392        // Hinweis: Cardinality rule — PIA (OBIS-Kennzahl für Werte nach Typ 2 Backend) must appear
1393        // at least once. It can appear up to 23 times if the other PIA segments in the same SG27 LIN
1394        // appear at most once each. The maximum total number of PIA segments per SG27 LIN is 25.
1395        // Informational cardinality annotation, always applies.
1396        ConditionResult::True
1397    }
1398}