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