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