Skip to main content

automapper_validation/generated/fv2504/
quotes_conditions_fv2504.rs

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