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        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
424        match ctx.this_group_has_qualifier("IMD", 1, "Z09", &["SG27"]) {
425            ConditionResult::True => ConditionResult::False,
426            ConditionResult::False => ConditionResult::True,
427            ConditionResult::Unknown => ConditionResult::Unknown,
428        }
429    }
430
431    /// [3] Wenn CCI+++Z64 vorhanden ist
432    fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
433        ctx.has_qualifier("CCI", 2, "Z64")
434    }
435
436    /// [4] Wenn am Gerät vorhanden und abweichend von Gerätenummer
437    /// EXTERNAL: Requires context from outside the message.
438    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
439        ctx.external
440            .evaluate("device_attribute_differs_from_device_number")
441    }
442
443    /// [5] Wenn in derselben SG27 LIN die Artikelnummer 9990001000649 vorhanden ist
444    // 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)
445    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
446        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
447        ctx.this_group_has_qualifier("LIN", 2, "9990001000649", &["SG27"])
448    }
449
450    /// [8] Wenn SG28 CCI+++E13 CAV+EHZ vorhanden
451    // 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)
452    fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
453        ctx.any_group_has_co_occurrence("CCI", 2, &["E13"], "CAV", 0, 0, &["EHZ"], &["SG28"])
454    }
455
456    /// [9] Wenn in derselben SG27 LIN die Artikelnummer 9990001000657 vorhanden ist
457    // 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)
458    fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
459        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
460        ctx.this_group_has_qualifier("LIN", 2, "9990001000657", &["SG27"])
461    }
462
463    /// [10] Wenn SG28 CAV+MIW/MPW/MBW vorhanden
464    fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
465        ctx.any_group_has_any_qualifier("CAV", 0, &["MIW", "MPW", "MBW"], &["SG28"])
466    }
467
468    /// [11] Wenn in derselben SG27 LIN die Artikelnummer 9990001000665 vorhanden ist
469    // REVIEW: Checks if any SG27 group has LIN with article number 9990001000665 at elements[2][0]. Same pattern as condition 5. (medium confidence)
470    fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
471        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
472        ctx.this_group_has_qualifier("LIN", 2, "9990001000665", &["SG27"])
473    }
474
475    /// [12] Wenn in derselben SG27 LIN die Artikelnummer 9990001000673 vorhanden ist
476    // REVIEW: Checks if any SG27 group has LIN with article number 9990001000673 at elements[2][0]. Same pattern as condition 5. (medium confidence)
477    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
478        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
479        ctx.this_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        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
492        ctx.this_group_has_qualifier("LIN", 2, "9990001000772", &["SG27"])
493    }
494
495    /// [16] Wenn in derselben SG27 LIN die Artikelnummer 9990001000780 vorhanden ist
496    // REVIEW: Checks if any SG27 group has LIN with article number 9990001000780 at elements[2][0]. Same pattern as condition 5. (medium confidence)
497    fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
498        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
499        ctx.this_group_has_qualifier("LIN", 2, "9990001000780", &["SG27"])
500    }
501
502    /// [17] Wenn das Angebot per REQOTE angefragt wurde
503    // 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)
504    fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
505        ctx.has_qualifier("RFF", 0, "AAV")
506    }
507
508    /// [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...
509    // 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)
510    fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
511        // Check if offer references additional market locations via RFF+Z18
512        // RFF+Z18 = 'Referenz auf ID weiterer Marktlokationen' — presence indicates multiple locations covered
513        // Note: the 'identical connection user' and 'iMS classification' sub-conditions require external context
514        ctx.has_qualifier("RFF", 0, "Z18")
515    }
516
517    /// [20] Wenn IMD++Z33 vorhanden
518    fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
519        ctx.has_qualifier("IMD", 1, "Z33")
520    }
521
522    /// [21] Wenn IMD++Z34 vorhanden
523    fn evaluate_21(&self, ctx: &EvaluationContext) -> ConditionResult {
524        ctx.has_qualifier("IMD", 1, "Z34")
525    }
526
527    /// [22] Wenn CCI+++Z75 vorhanden ist
528    fn evaluate_22(&self, ctx: &EvaluationContext) -> ConditionResult {
529        ctx.has_qualifier("CCI", 2, "Z75")
530    }
531
532    /// [25] Wenn SG28 CCI+++E13 CAV+MME vorhanden
533    fn evaluate_25(&self, ctx: &EvaluationContext) -> ConditionResult {
534        ctx.any_group_has_co_occurrence("CCI", 2, &["E13"], "CAV", 0, 0, &["MME"], &["SG28"])
535    }
536
537    /// [26] Wenn DTM+203 nicht vorhanden
538    fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
539        ctx.lacks_qualifier("DTM", 0, "203")
540    }
541
542    /// [27] Wenn DTM+469 nicht vorhanden
543    fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
544        ctx.lacks_qualifier("DTM", 0, "469")
545    }
546
547    /// [28] Wenn RFF+AAV vorhanden
548    fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
549        ctx.has_qualifier("RFF", 0, "AAV")
550    }
551
552    /// [29] Wenn DTM+469 in Anfrage (REQOTE) vorhanden war
553    /// EXTERNAL: Requires context from outside the message.
554    fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
555        ctx.external.evaluate("reqote_had_dtm_469")
556    }
557
558    /// [30] Wenn SG27 IMD+Z09 vorhanden
559    fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
560        ctx.any_group_has_qualifier("IMD", 0, "Z09", &["SG27"])
561    }
562
563    /// [39] MP-ID nur aus Sparte Strom
564    /// EXTERNAL: Requires context from outside the message.
565    fn evaluate_39(&self, ctx: &EvaluationContext) -> ConditionResult {
566        ctx.external.evaluate("mp_id_is_strom")
567    }
568
569    /// [44] Wenn MSBA nicht Eigentümer des Gerätes/der Geräte ist
570    /// EXTERNAL: Requires context from outside the message.
571    fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
572        ctx.external.evaluate("msba_is_not_device_owner")
573    }
574
575    /// [49] Wenn SG27 LIN++Z64 (Erforderliches Produkt Schaltzeitdefinitionen) vorhanden
576    fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
577        ctx.any_group_has_qualifier("LIN", 1, "Z64", &["SG27"])
578    }
579
580    /// [50] Wenn SG27 LIN++Z65 (Erforderliches Produkt Leistungskurvendefinitionen) vorhanden
581    fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
582        ctx.any_group_has_qualifier("LIN", 1, "Z65", &["SG27"])
583    }
584
585    /// [51] Wenn SG27 LIN++Z66 (Erforderliches Produkt Ad-hoc-Steuerkanal) vorhanden
586    fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
587        ctx.any_group_has_qualifier("LIN", 1, "Z66", &["SG27"])
588    }
589
590    /// [52] Wenn SG27 LIN++Z67 (Erforderliches Messprodukt für Werte nach Typ 2 aus Backend) vorhanden
591    fn evaluate_52(&self, ctx: &EvaluationContext) -> ConditionResult {
592        ctx.has_qualifier("LIN", 1, "Z67")
593    }
594
595    /// [53] Wenn SG27 LIN++Z68 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ2 aus SMGW) vorhanden
596    fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
597        ctx.has_qualifier("LIN", 1, "Z68")
598    }
599
600    /// [54] Wenn in der Anfrage zum Angebot einer Konfiguration vorhanden
601    /// EXTERNAL: Requires context from outside the message.
602    fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
603        ctx.external.evaluate("configuration_in_request")
604    }
605
606    /// [55] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Marktlokation angegeben ist.
607    fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
608        // Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Marktlokation angegeben ist.
609        // LOC elements[0][0] == "172" (qualifier), elements[1][0] == DE3225 (Identifikator)
610        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
611        match locs
612            .first()
613            .and_then(|s| s.elements.get(1))
614            .and_then(|e| e.first())
615        {
616            Some(val) => validate_malo_id(val),
617            None => ConditionResult::False, // segment absent → condition not applicable
618        }
619    }
620
621    /// [65] Wenn DTM+203 (Ausführungsdatum) im DE2380 &lt; 202312312300?+00
622    // 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)
623    fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
624        let segs = ctx.find_segments_with_qualifier("DTM", 0, "203");
625        match segs.first() {
626            Some(dtm) => {
627                match dtm
628                    .elements
629                    .first()
630                    .and_then(|e| e.get(1))
631                    .map(|s| s.as_str())
632                {
633                    Some(value) if !value.is_empty() => {
634                        ConditionResult::from(value < "202312312300+00")
635                    }
636                    _ => ConditionResult::Unknown,
637                }
638            }
639            None => ConditionResult::False, // segment absent → condition not applicable
640        }
641    }
642
643    /// [66] Wenn DTM+469 (Beginn zum (nächstmöglichen Termin)) im DE2380 &lt; 202312312300?+00
644    // 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)
645    fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
646        let segs = ctx.find_segments_with_qualifier("DTM", 0, "469");
647        match segs.first() {
648            Some(dtm) => {
649                match dtm
650                    .elements
651                    .first()
652                    .and_then(|e| e.get(1))
653                    .map(|s| s.as_str())
654                {
655                    Some(value) if !value.is_empty() => {
656                        ConditionResult::from(value < "202312312300+00")
657                    }
658                    _ => ConditionResult::Unknown,
659                }
660            }
661            None => ConditionResult::False, // segment absent → condition not applicable
662        }
663    }
664
665    /// [67] Wenn DTM+203 (Ausführungsdatum) im DE2380 ≥ 202312312300?+00
666    // 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)
667    fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
668        let segs = ctx.find_segments_with_qualifier("DTM", 0, "203");
669        match segs.first() {
670            Some(dtm) => {
671                match dtm
672                    .elements
673                    .first()
674                    .and_then(|e| e.get(1))
675                    .map(|s| s.as_str())
676                {
677                    Some(value) if !value.is_empty() => {
678                        ConditionResult::from(value >= "202312312300+00")
679                    }
680                    _ => ConditionResult::Unknown,
681                }
682            }
683            None => ConditionResult::False, // segment absent → condition not applicable
684        }
685    }
686
687    /// [68] Wenn DTM+469 (Beginn zum (nächstmöglichen Termin)) im DE2380 ≥ 202312312300?+00
688    // 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)
689    fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
690        let segs = ctx.find_segments_with_qualifier("DTM", 0, "469");
691        if segs.is_empty() {
692            return ConditionResult::Unknown;
693        }
694        for seg in segs {
695            let val = seg
696                .elements
697                .get(0)
698                .and_then(|e| e.get(1))
699                .map(|s| s.as_str())
700                .unwrap_or("");
701            if val >= "202312312300+00" {
702                return ConditionResult::True;
703            }
704        }
705        ConditionResult::False
706    }
707
708    /// [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...
709    /// EXTERNAL: Requires context from outside the message.
710    fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
711        ctx.external.evaluate("pia5_product_is_threshold_trigger")
712    }
713
714    /// [72] wenn im DE3155 in demselben COM der Code EM vorhanden ist
715    fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
716        let segs = ctx.find_segments("COM");
717        if segs.is_empty() {
718            return ConditionResult::Unknown;
719        }
720        ConditionResult::from(segs.iter().any(|seg| {
721            seg.elements
722                .first()
723                .and_then(|e| e.get(1))
724                .is_some_and(|v| v == "EM")
725        }))
726    }
727
728    /// [73] wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
729    fn evaluate_73(&self, ctx: &EvaluationContext) -> ConditionResult {
730        let segs = ctx.find_segments("COM");
731        if segs.is_empty() {
732            return ConditionResult::Unknown;
733        }
734        ConditionResult::from(segs.iter().any(|seg| {
735            seg.elements
736                .first()
737                .and_then(|e| e.get(1))
738                .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
739        }))
740    }
741
742    /// [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.
743    /// EXTERNAL: Requires context from outside the message.
744    fn evaluate_74(&self, ctx: &EvaluationContext) -> ConditionResult {
745        ctx.external.evaluate("product_in_typ2_backend_codelist")
746    }
747
748    /// [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.
749    /// EXTERNAL: Requires context from outside the message.
750    fn evaluate_75(&self, ctx: &EvaluationContext) -> ConditionResult {
751        ctx.external.evaluate("product_in_typ2_smgw_codelist")
752    }
753
754    /// [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.
755    /// EXTERNAL: Requires context from outside the message.
756    fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
757        ctx.external
758            .evaluate("request_contained_typ2_backend_product")
759    }
760
761    /// [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.
762    /// EXTERNAL: Requires context from outside the message.
763    fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
764        ctx.external.evaluate("request_contained_typ2_smgw_product")
765    }
766
767    /// [79] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z07 (Kauf) vorhanden.
768    fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
769        let segs = ctx.find_segments("IMD");
770        if segs.is_empty() {
771            return ConditionResult::Unknown;
772        }
773        ConditionResult::from(segs.iter().any(|seg| {
774            seg.elements
775                .get(1)
776                .and_then(|e| e.first())
777                .is_some_and(|v| v == "Z07")
778        }))
779    }
780
781    /// [80] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z08 (Nutzungsüberlassung) vorhanden.
782    fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
783        let segs = ctx.find_segments("IMD");
784        if segs.is_empty() {
785            return ConditionResult::Unknown;
786        }
787        ConditionResult::from(segs.iter().any(|seg| {
788            seg.elements
789                .get(1)
790                .and_then(|e| e.first())
791                .is_some_and(|v| v == "Z08")
792        }))
793    }
794
795    /// [81] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z33 (Angebot auf Basis Preisblatt) vorhanden.
796    fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
797        let segs = ctx.find_segments("IMD");
798        if segs.is_empty() {
799            return ConditionResult::Unknown;
800        }
801        ConditionResult::from(segs.iter().any(|seg| {
802            seg.elements
803                .get(1)
804                .and_then(|e| e.first())
805                .is_some_and(|v| v == "Z33")
806        }))
807    }
808
809    /// [82] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z34 (Individuelles Angebot) vorhanden.
810    fn evaluate_82(&self, ctx: &EvaluationContext) -> ConditionResult {
811        let segs = ctx.find_segments("IMD");
812        if segs.is_empty() {
813            return ConditionResult::Unknown;
814        }
815        ConditionResult::from(segs.iter().any(|seg| {
816            seg.elements
817                .get(1)
818                .and_then(|e| e.first())
819                .is_some_and(|v| v == "Z34")
820        }))
821    }
822
823    /// [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.
824    // 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)
825    fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
826        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
827        let segs = ctx.scoped_find_segments_in("SG27", "PIA");
828        if segs.is_empty() {
829            return ConditionResult::Unknown;
830        }
831        ConditionResult::from(segs.iter().any(|seg| {
832            let is_z02 = seg
833                .elements
834                .first()
835                .and_then(|e| e.first())
836                .is_some_and(|v| v == "Z02");
837            let ends_with_01 = seg
838                .elements
839                .get(1)
840                .and_then(|e| e.first())
841                .is_some_and(|v| v.ends_with("01"));
842            is_z02 && ends_with_01
843        }))
844    }
845
846    /// [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.
847    // 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)
848    fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
849        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
850        let pias = ctx.scoped_find_segments_in_with_qualifier("SG27", "PIA", 0, "Z02");
851        ConditionResult::from(pias.iter().any(|s| {
852            s.elements
853                .get(1)
854                .and_then(|e| e.first())
855                .map(|v| v.ends_with("02"))
856                .unwrap_or(false)
857        }))
858    }
859
860    /// [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) ...
861    // REVIEW: Checks for PIA+Z02 where DE7140 (elements[1][0]) ends with '03' (Transaktionskosten). Same pattern as condition 84. (medium confidence)
862    fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
863        // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
864        let pias = ctx.scoped_find_segments_in_with_qualifier("SG27", "PIA", 0, "Z02");
865        ConditionResult::from(pias.iter().any(|s| {
866            s.elements
867                .get(1)
868                .and_then(|e| e.first())
869                .map(|v| v.ends_with("03"))
870                .unwrap_or(false)
871        }))
872    }
873
874    /// [86] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z01 (Einrichtungspreis) vorhanden ist.
875    fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
876        // HAND-EDITED: "in dieser/derselben SG31" — scoped to the enclosing group.
877        ctx.this_group_has_qualifier("PRI", 0, "Z01", &["SG31"])
878    }
879
880    /// [87] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z02 (Transaktionspreis) vorhanden ist.
881    fn evaluate_87(&self, ctx: &EvaluationContext) -> ConditionResult {
882        // HAND-EDITED: "in dieser/derselben SG31" — scoped to the enclosing group.
883        ctx.this_group_has_qualifier("PRI", 0, "Z02", &["SG31"])
884    }
885
886    /// [88] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z03 (Betriebspreis) vorhanden ist.
887    fn evaluate_88(&self, ctx: &EvaluationContext) -> ConditionResult {
888        // HAND-EDITED: "in dieser/derselben SG31" — scoped to the enclosing group.
889        ctx.this_group_has_qualifier("PRI", 0, "Z03", &["SG31"])
890    }
891
892    /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
893    /// EXTERNAL: Requires context from outside the message.
894    fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
895        ctx.external.evaluate("recipient_is_electricity_sector")
896    }
897
898    /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
899    /// EXTERNAL: Requires context from outside the message.
900    fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
901        ctx.external.evaluate("recipient_is_gas_sector")
902    }
903
904    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt.
905    /// EXTERNAL: Requires context from outside the message.
906    // 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)
907    fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
908        ctx.external.evaluate("document_date_is_creation_or_past")
909    }
910
911    /// [500] Hinweis: Angabe eines technischen Ansprechpartners für die Geräteübernahme
912    fn evaluate_500(&self, _ctx: &EvaluationContext) -> ConditionResult {
913        // Hinweis: Angabe eines technischen Ansprechpartners für die Geräteübernahme — informational note, always applies
914        ConditionResult::True
915    }
916
917    /// [501] Hinweis: Verwendung der ID der Marktlokation
918    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
919        // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
920        ConditionResult::True
921    }
922
923    /// [502] Hinweis: Verwendung der ID der Messlokation
924    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
925        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
926        ConditionResult::True
927    }
928
929    /// [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.
930    fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
931        // 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.
932        ConditionResult::True
933    }
934
935    /// [504] Hinweis: Wert aus BGM+311 DE1004 der REQOTE mit der die Angebotsanfrage erfolgt ist.
936    fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
937        // Hinweis: Wert aus BGM+311 DE1004 der REQOTE mit der die Angebotsanfrage erfolgt ist.
938        ConditionResult::True
939    }
940
941    /// [505] Hinweis: Wert aus BGM+Z29 DE1004 der REQOTE, mit der die Anfrage Rechnungsabwicklung erfolgt ist.
942    fn evaluate_505(&self, _ctx: &EvaluationContext) -> ConditionResult {
943        // Hinweis: Wert aus BGM+Z29 DE1004 der REQOTE, mit der die Anfrage Rechnungsabwicklung erfolgt ist.
944        ConditionResult::True
945    }
946
947    /// [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
948    fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
949        // Hinweis: Wenn zu einer Position mehrere Gerätenummern existieren, sind die Gerätenummern in derselben Position mittels Wiederholung der SG32 RFF+Z09 anzugeben.
950        ConditionResult::True
951    }
952
953    /// [507] Hinweis: Verwendung der ID der Tranche
954    fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
955        // Hinweis: Verwendung der ID der Tranche
956        ConditionResult::True
957    }
958
959    /// [511] Hinweis: Wert aus BGM+Z74 (Bestellung eines Angebots einer Konfiguration) DE1004 der REQOTE mit der die Anfrage einer Konfiguration erfolgt ist.
960    fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
961        // Hinweis: Wert aus BGM+Z74 DE1004 der REQOTE — informational note, always applies
962        ConditionResult::True
963    }
964
965    /// [512] Hinweis: Verwendung der ID der Netzlokation
966    fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
967        // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
968        ConditionResult::True
969    }
970
971    /// [513] Hinweis: Verwendung der ID der Steuerbaren Ressource
972    fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
973        // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
974        ConditionResult::True
975    }
976
977    /// [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.
978    fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
979        // Hinweis: Angabe gemäß Preisblatt des MSB — informational note, always applies
980        ConditionResult::True
981    }
982
983    /// [516] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
984    fn evaluate_516(&self, _ctx: &EvaluationContext) -> ConditionResult {
985        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
986        ConditionResult::True
987    }
988
989    /// [903] Format: Möglicher Wert: 1
990    // 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)
991    fn evaluate_903(&self, ctx: &EvaluationContext) -> ConditionResult {
992        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "==", 1.0))
993    }
994
995    /// [906] Format: max. 3 Nachkommastellen
996    // 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)
997    fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
998        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 3))
999    }
1000
1001    /// [908] Format: Mögliche Werte: 1 bis n
1002    // 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)
1003    fn evaluate_908(&self, ctx: &EvaluationContext) -> ConditionResult {
1004        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">=", 1.0))
1005    }
1006
1007    /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend und fortlaufend aufsteigend
1008    fn evaluate_911(&self, _ctx: &EvaluationContext) -> ConditionResult {
1009        // Hinweis: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend
1010        // und fortlaufend aufsteigend — informational annotation about sequential numbering,
1011        // always applies unconditionally
1012        ConditionResult::True
1013    }
1014
1015    /// [912] Format: max. 6 Nachkommastellen
1016    // 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)
1017    fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
1018        ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 6))
1019    }
1020
1021    /// [914] Format: Möglicher Wert: &gt; 0
1022    // 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)
1023    fn evaluate_914(&self, ctx: &EvaluationContext) -> ConditionResult {
1024        ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">", 0.0))
1025    }
1026
1027    /// [931] Format: ZZZ = +00
1028    // 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)
1029    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1030        ctx.format_check("DTM", 0, 1, validate_timezone_utc)
1031    }
1032
1033    /// [932] Format: HHMM = 2200
1034    // 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)
1035    fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
1036        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2200"))
1037    }
1038
1039    /// [933] Format: HHMM = 2300
1040    // 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)
1041    fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
1042        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2300"))
1043    }
1044
1045    /// [934] Format: HHMM = 0400
1046    // 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)
1047    fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
1048        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0400"))
1049    }
1050
1051    /// [935] Format: HHMM = 0500
1052    // 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)
1053    fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
1054        ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0500"))
1055    }
1056
1057    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
1058    // 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)
1059    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
1060        for seg in ctx.find_segments("COM") {
1061            if let Some(elem) = seg.elements.first() {
1062                if let Some(value) = elem.first() {
1063                    if value.contains('@') && value.contains('.') {
1064                        return ConditionResult::True;
1065                    }
1066                }
1067            }
1068        }
1069        ConditionResult::False
1070    }
1071
1072    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
1073    // 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)
1074    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
1075        for seg in ctx.find_segments("COM") {
1076            if let Some(elem) = seg.elements.first() {
1077                if let Some(value) = elem.first() {
1078                    if value.starts_with('+') && value.chars().skip(1).all(|c| c.is_ascii_digit()) {
1079                        return ConditionResult::True;
1080                    }
1081                }
1082            }
1083        }
1084        ConditionResult::False
1085    }
1086
1087    /// [942] Format: n1-n2-n1-n3
1088    fn evaluate_942(&self, ctx: &EvaluationContext) -> ConditionResult {
1089        ctx.format_check("PIA", 1, 0, |val| {
1090            validate_artikel_pattern(val, &[1, 2, 1, 3])
1091        })
1092    }
1093
1094    /// [950] Format: Marktlokations-ID
1095    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
1096        ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
1097    }
1098
1099    /// [951] Format: Zählpunktbezeichnung
1100    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
1101        ctx.format_check_qualified("LOC", 0, "Z17", 1, 0, validate_zahlpunkt)
1102    }
1103
1104    /// [960] Format: Netzlokations-ID
1105    // HAND-EDITED: a Netzlokations-ID is "E" + ten alphanumerics (`E1688117482`),
1106    // not MaLo-shaped; `validate_malo_id` rejected every real one (#173).
1107    fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
1108        ctx.format_check_qualified("LOC", 0, "Z18", 1, 0, validate_nelo_id)
1109    }
1110
1111    /// [962] Format: max. 6 Vorkommastellen
1112    // 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)
1113    fn evaluate_962(&self, ctx: &EvaluationContext) -> ConditionResult {
1114        ctx.format_check("PRI", 0, 1, |val| validate_max_integer_digits(val, 6))
1115    }
1116
1117    /// [2042] Innerhalb dieser LIN-Position muss das PIA+Z02 (Artikel-ID) mindestens ein Mal angegeben werden und kann bis zu drei Mal angegeben werden.
1118    // 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)
1119    fn evaluate_2042(&self, ctx: &EvaluationContext) -> ConditionResult {
1120        let count = ctx
1121            .find_segments("PIA")
1122            .iter()
1123            .filter(|s| {
1124                s.elements
1125                    .first()
1126                    .and_then(|e| e.first())
1127                    .is_some_and(|v| v == "Z02")
1128            })
1129            .count();
1130        ConditionResult::from(count >= 1 && count <= 3)
1131    }
1132
1133    /// [2060] Pro Nachricht ist die SG27 LIN+Z64 (Erforderliches Produkt Schaltzeitdefinitionen) maximal einmal anzugeben
1134    fn evaluate_2060(&self, ctx: &EvaluationContext) -> ConditionResult {
1135        let count = ctx
1136            .find_segments("LIN")
1137            .iter()
1138            .filter(|s| {
1139                s.elements
1140                    .get(1)
1141                    .and_then(|e| e.first())
1142                    .is_some_and(|v| v == "Z64")
1143            })
1144            .count();
1145        ConditionResult::from(count <= 1)
1146    }
1147
1148    /// [2061] Pro Nachricht ist die SG27 LIN++Z65 (Erforderliches Produkt Leistungskurvendefinitionen) maximal einmal anzugeben
1149    fn evaluate_2061(&self, ctx: &EvaluationContext) -> ConditionResult {
1150        let count = ctx
1151            .find_segments("LIN")
1152            .iter()
1153            .filter(|s| {
1154                s.elements
1155                    .get(1)
1156                    .and_then(|e| e.first())
1157                    .is_some_and(|v| v == "Z65")
1158            })
1159            .count();
1160        ConditionResult::from(count <= 1)
1161    }
1162
1163    /// [2062] Pro Nachricht ist die SG27 LIN++Z66 (Erforderliches Produkt Ad-hoc-Steuerkanal) maximal einmal anzugeben
1164    fn evaluate_2062(&self, ctx: &EvaluationContext) -> ConditionResult {
1165        let count = ctx.find_segments_with_qualifier("LIN", 1, "Z66").len();
1166        ConditionResult::from(count <= 1)
1167    }
1168
1169    /// [2063] Pro Nachricht ist die SG27 LIN++Z67 (Erforderliches Messprodukt für Werte nach Typ 2 aus Backend) maximal einmal anzugeben
1170    fn evaluate_2063(&self, ctx: &EvaluationContext) -> ConditionResult {
1171        let count = ctx.find_segments_with_qualifier("LIN", 1, "Z67").len();
1172        ConditionResult::from(count <= 1)
1173    }
1174
1175    /// [2064] Pro Nachricht ist die SG27 LIN++Z68 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW) maximal einmal anzugeben
1176    fn evaluate_2064(&self, ctx: &EvaluationContext) -> ConditionResult {
1177        let count = ctx.find_segments_with_qualifier("LIN", 1, "Z68").len();
1178        ConditionResult::from(count <= 1)
1179    }
1180
1181    /// [2068] Pro SG27 LIN ist die SG31 PRI genau einmal anzugeben.
1182    // 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)
1183    fn evaluate_2068(&self, ctx: &EvaluationContext) -> ConditionResult {
1184        let nav = match ctx.navigator() {
1185            Some(n) => n,
1186            None => return ConditionResult::Unknown,
1187        };
1188        let sg27_count = nav.group_instance_count(&["SG27"]);
1189        if sg27_count == 0 {
1190            return ConditionResult::Unknown;
1191        }
1192        for i in 0..sg27_count {
1193            let sg31_count = nav.child_group_instance_count(&["SG27"], i, "SG31");
1194            if sg31_count != 1 {
1195                return ConditionResult::False;
1196            }
1197        }
1198        ConditionResult::True
1199    }
1200
1201    /// [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.
1202    // 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)
1203    fn evaluate_2069(&self, ctx: &EvaluationContext) -> ConditionResult {
1204        // Pro SG27 LIN: SG31 PRI must appear exactly once with H87 (Stück) in DE6411
1205        let pri_count = ctx.count_in_group("PRI", &["SG27", "SG31"]);
1206        if pri_count != 1 {
1207            return ConditionResult::False;
1208        }
1209        // DE6411 is the 5th component of C509 = elements[0][4]
1210        let segs = ctx.find_segments("PRI");
1211        match segs.first() {
1212            Some(pri) => {
1213                let unit = pri
1214                    .elements
1215                    .first()
1216                    .and_then(|e| e.get(4))
1217                    .map(|s| s.as_str());
1218                ConditionResult::from(unit == Some("H87"))
1219            }
1220            None => ConditionResult::False, // segment absent → condition not applicable
1221        }
1222    }
1223
1224    /// [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...
1225    // 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)
1226    fn evaluate_2070(&self, ctx: &EvaluationContext) -> ConditionResult {
1227        // Pro SG27 LIN: SG31 PRI must appear exactly twice — one with H87 (Stück), one with DAY (Tag) in DE6411
1228        let pri_count = ctx.count_in_group("PRI", &["SG27", "SG31"]);
1229        if pri_count != 2 {
1230            return ConditionResult::False;
1231        }
1232        // DE6411 is elements[0][4] in standard EDIFACT C509
1233        let segs = ctx.find_segments("PRI");
1234        let has_h87 = segs.iter().any(|pri| {
1235            pri.elements
1236                .first()
1237                .and_then(|e| e.get(4))
1238                .is_some_and(|v| v == "H87")
1239        });
1240        let has_day = segs.iter().any(|pri| {
1241            pri.elements
1242                .first()
1243                .and_then(|e| e.get(4))
1244                .is_some_and(|v| v == "DAY")
1245        });
1246        ConditionResult::from(has_h87 && has_day)
1247    }
1248
1249    /// [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...
1250    fn evaluate_2071(&self, _ctx: &EvaluationContext) -> ConditionResult {
1251        // Hinweis: Cardinality rule — SG31 PRI (Preisangabe) is given up to three times,
1252        // as often as PIA+Z02 (Artikel-ID) appears in the same SG27 LIN (Erforderliches Messprodukt
1253        // für Werte nach Typ2 aus Backend). Informational cardinality annotation, always applies.
1254        ConditionResult::True
1255    }
1256
1257    /// [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...
1258    fn evaluate_2072(&self, _ctx: &EvaluationContext) -> ConditionResult {
1259        // Hinweis: Cardinality rule — SG31 PRI (Preisangabe) is given up to three times,
1260        // as often as PIA+Z02 (Artikel-ID) appears in the same SG27 LIN (Erforderliches Produkt
1261        // Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW). Informational cardinality annotation, always applies.
1262        ConditionResult::True
1263    }
1264
1265    /// [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...
1266    fn evaluate_2073(&self, _ctx: &EvaluationContext) -> ConditionResult {
1267        // Hinweis: Cardinality rule — PIA (OBIS-Kennzahl für Werte nach Typ 2 Backend) must appear
1268        // at least once. It can appear up to 23 times if the other PIA segments in the same SG27 LIN
1269        // appear at most once each. The maximum total number of PIA segments per SG27 LIN is 25.
1270        // Informational cardinality annotation, always applies.
1271        ConditionResult::True
1272    }
1273}