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