Skip to main content

automapper_validation/generated/fv2504/
quotes_conditions_fv2504.rs

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