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