Skip to main content

automapper_validation/generated/fv2504/
utilmd_gas_conditions_fv2504.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2504/UTILMD_AHB_Gas_1_0a_außerordentliche_20240726.xml
4// Generated: 2026-03-12T10:28:10Z
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 UTILMD_Gas FV2504.
12pub struct UtilmdGasConditionEvaluatorFV2504 {
13    // External condition IDs that require runtime context.
14    external_conditions: std::collections::HashSet<u32>,
15}
16
17impl Default for UtilmdGasConditionEvaluatorFV2504 {
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(5);
23        external_conditions.insert(14);
24        external_conditions.insert(17);
25        external_conditions.insert(29);
26        external_conditions.insert(37);
27        external_conditions.insert(39);
28        external_conditions.insert(51);
29        external_conditions.insert(65);
30        external_conditions.insert(92);
31        external_conditions.insert(98);
32        external_conditions.insert(108);
33        external_conditions.insert(127);
34        external_conditions.insert(129);
35        external_conditions.insert(133);
36        external_conditions.insert(147);
37        external_conditions.insert(165);
38        external_conditions.insert(166);
39        external_conditions.insert(219);
40        external_conditions.insert(241);
41        external_conditions.insert(268);
42        external_conditions.insert(283);
43        external_conditions.insert(315);
44        external_conditions.insert(324);
45        external_conditions.insert(336);
46        external_conditions.insert(427);
47        external_conditions.insert(490);
48        external_conditions.insert(491);
49        Self {
50            external_conditions,
51        }
52    }
53}
54
55impl ConditionEvaluator for UtilmdGasConditionEvaluatorFV2504 {
56    fn message_type(&self) -> &str {
57        "UTILMD_Gas"
58    }
59
60    fn format_version(&self) -> &str {
61        "FV2504"
62    }
63
64    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
65        match condition {
66            1 => self.evaluate_1(ctx),
67            4 => self.evaluate_4(ctx),
68            5 => self.evaluate_5(ctx),
69            7 => self.evaluate_7(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            14 => self.evaluate_14(ctx),
76            15 => self.evaluate_15(ctx),
77            16 => self.evaluate_16(ctx),
78            17 => self.evaluate_17(ctx),
79            18 => self.evaluate_18(ctx),
80            19 => self.evaluate_19(ctx),
81            24 => self.evaluate_24(ctx),
82            25 => self.evaluate_25(ctx),
83            26 => self.evaluate_26(ctx),
84            28 => self.evaluate_28(ctx),
85            29 => self.evaluate_29(ctx),
86            32 => self.evaluate_32(ctx),
87            33 => self.evaluate_33(ctx),
88            35 => self.evaluate_35(ctx),
89            36 => self.evaluate_36(ctx),
90            37 => self.evaluate_37(ctx),
91            39 => self.evaluate_39(ctx),
92            46 => self.evaluate_46(ctx),
93            47 => self.evaluate_47(ctx),
94            48 => self.evaluate_48(ctx),
95            51 => self.evaluate_51(ctx),
96            58 => self.evaluate_58(ctx),
97            64 => self.evaluate_64(ctx),
98            65 => self.evaluate_65(ctx),
99            66 => self.evaluate_66(ctx),
100            68 => self.evaluate_68(ctx),
101            69 => self.evaluate_69(ctx),
102            70 => self.evaluate_70(ctx),
103            77 => self.evaluate_77(ctx),
104            78 => self.evaluate_78(ctx),
105            81 => self.evaluate_81(ctx),
106            84 => self.evaluate_84(ctx),
107            92 => self.evaluate_92(ctx),
108            98 => self.evaluate_98(ctx),
109            106 => self.evaluate_106(ctx),
110            108 => self.evaluate_108(ctx),
111            123 => self.evaluate_123(ctx),
112            127 => self.evaluate_127(ctx),
113            128 => self.evaluate_128(ctx),
114            129 => self.evaluate_129(ctx),
115            130 => self.evaluate_130(ctx),
116            133 => self.evaluate_133(ctx),
117            137 => self.evaluate_137(ctx),
118            138 => self.evaluate_138(ctx),
119            147 => self.evaluate_147(ctx),
120            165 => self.evaluate_165(ctx),
121            166 => self.evaluate_166(ctx),
122            200 => self.evaluate_200(ctx),
123            202 => self.evaluate_202(ctx),
124            203 => self.evaluate_203(ctx),
125            205 => self.evaluate_205(ctx),
126            209 => self.evaluate_209(ctx),
127            212 => self.evaluate_212(ctx),
128            213 => self.evaluate_213(ctx),
129            216 => self.evaluate_216(ctx),
130            219 => self.evaluate_219(ctx),
131            230 => self.evaluate_230(ctx),
132            241 => self.evaluate_241(ctx),
133            249 => self.evaluate_249(ctx),
134            252 => self.evaluate_252(ctx),
135            257 => self.evaluate_257(ctx),
136            268 => self.evaluate_268(ctx),
137            274 => self.evaluate_274(ctx),
138            276 => self.evaluate_276(ctx),
139            277 => self.evaluate_277(ctx),
140            283 => self.evaluate_283(ctx),
141            315 => self.evaluate_315(ctx),
142            324 => self.evaluate_324(ctx),
143            336 => self.evaluate_336(ctx),
144            345 => self.evaluate_345(ctx),
145            361 => self.evaluate_361(ctx),
146            362 => self.evaluate_362(ctx),
147            367 => self.evaluate_367(ctx),
148            368 => self.evaluate_368(ctx),
149            427 => self.evaluate_427(ctx),
150            442 => self.evaluate_442(ctx),
151            490 => self.evaluate_490(ctx),
152            491 => self.evaluate_491(ctx),
153            494 => self.evaluate_494(ctx),
154            500 => self.evaluate_500(ctx),
155            501 => self.evaluate_501(ctx),
156            502 => self.evaluate_502(ctx),
157            504 => self.evaluate_504(ctx),
158            507 => self.evaluate_507(ctx),
159            508 => self.evaluate_508(ctx),
160            510 => self.evaluate_510(ctx),
161            513 => self.evaluate_513(ctx),
162            527 => self.evaluate_527(ctx),
163            528 => self.evaluate_528(ctx),
164            530 => self.evaluate_530(ctx),
165            556 => self.evaluate_556(ctx),
166            558 => self.evaluate_558(ctx),
167            559 => self.evaluate_559(ctx),
168            560 => self.evaluate_560(ctx),
169            566 => self.evaluate_566(ctx),
170            567 => self.evaluate_567(ctx),
171            570 => self.evaluate_570(ctx),
172            571 => self.evaluate_571(ctx),
173            572 => self.evaluate_572(ctx),
174            581 => self.evaluate_581(ctx),
175            583 => self.evaluate_583(ctx),
176            584 => self.evaluate_584(ctx),
177            590 => self.evaluate_590(ctx),
178            601 => self.evaluate_601(ctx),
179            621 => self.evaluate_621(ctx),
180            622 => self.evaluate_622(ctx),
181            636 => self.evaluate_636(ctx),
182            637 => self.evaluate_637(ctx),
183            638 => self.evaluate_638(ctx),
184            643 => self.evaluate_643(ctx),
185            644 => self.evaluate_644(ctx),
186            651 => self.evaluate_651(ctx),
187            654 => self.evaluate_654(ctx),
188            655 => self.evaluate_655(ctx),
189            902 => self.evaluate_902(ctx),
190            907 => self.evaluate_907(ctx),
191            912 => self.evaluate_912(ctx),
192            930 => self.evaluate_930(ctx),
193            931 => self.evaluate_931(ctx),
194            934 => self.evaluate_934(ctx),
195            935 => self.evaluate_935(ctx),
196            937 => self.evaluate_937(ctx),
197            938 => self.evaluate_938(ctx),
198            950 => self.evaluate_950(ctx),
199            951 => self.evaluate_951(ctx),
200            952 => self.evaluate_952(ctx),
201            953 => self.evaluate_953(ctx),
202            2061 => self.evaluate_2061(ctx),
203            2119 => self.evaluate_2119(ctx),
204            2284 => self.evaluate_2284(ctx),
205            2286 => self.evaluate_2286(ctx),
206            2287 => self.evaluate_2287(ctx),
207            2335 => self.evaluate_2335(ctx),
208            2353 => self.evaluate_2353(ctx),
209            _ => ConditionResult::Unknown,
210        }
211    }
212
213    fn is_external(&self, condition: u32) -> bool {
214        self.external_conditions.contains(&condition)
215    }
216    fn is_known(&self, condition: u32) -> bool {
217        matches!(
218            condition,
219            1 | 4
220                | 5
221                | 7
222                | 9
223                | 10
224                | 11
225                | 12
226                | 13
227                | 14
228                | 15
229                | 16
230                | 17
231                | 18
232                | 19
233                | 24
234                | 25
235                | 26
236                | 28
237                | 29
238                | 32
239                | 33
240                | 35
241                | 36
242                | 37
243                | 39
244                | 46
245                | 47
246                | 48
247                | 51
248                | 58
249                | 64
250                | 65
251                | 66
252                | 68
253                | 69
254                | 70
255                | 77
256                | 78
257                | 81
258                | 84
259                | 92
260                | 98
261                | 106
262                | 108
263                | 123
264                | 127
265                | 128
266                | 129
267                | 130
268                | 133
269                | 137
270                | 138
271                | 147
272                | 165
273                | 166
274                | 200
275                | 202
276                | 203
277                | 205
278                | 209
279                | 212
280                | 213
281                | 216
282                | 219
283                | 230
284                | 241
285                | 249
286                | 252
287                | 257
288                | 268
289                | 274
290                | 276
291                | 277
292                | 283
293                | 315
294                | 324
295                | 336
296                | 345
297                | 361
298                | 362
299                | 367
300                | 368
301                | 427
302                | 442
303                | 490
304                | 491
305                | 494
306                | 500
307                | 501
308                | 502
309                | 504
310                | 507
311                | 508
312                | 510
313                | 513
314                | 527
315                | 528
316                | 530
317                | 556
318                | 558
319                | 559
320                | 560
321                | 566
322                | 567
323                | 570
324                | 571
325                | 572
326                | 581
327                | 583
328                | 584
329                | 590
330                | 601
331                | 621
332                | 622
333                | 636
334                | 637
335                | 638
336                | 643
337                | 644
338                | 651
339                | 654
340                | 655
341                | 902
342                | 907
343                | 912
344                | 930
345                | 931
346                | 934
347                | 935
348                | 937
349                | 938
350                | 950
351                | 951
352                | 952
353                | 953
354                | 2061
355                | 2119
356                | 2284
357                | 2286
358                | 2287
359                | 2335
360                | 2353
361        )
362    }
363}
364
365impl UtilmdGasConditionEvaluatorFV2504 {
366    /// [108] Wenn Kundenwertverfahren (z. B. TU München)
367    /// EXTERNAL: Requires context from outside the message.
368    // REVIEW: Kundenwertverfahren (e.g., TU München) is a specific billing methodology determined by contract terms, not derivable from the EDIFACT message content itself. (medium confidence)
369    fn evaluate_108(&self, ctx: &EvaluationContext) -> ConditionResult {
370        ctx.external.evaluate("kundenwertverfahren")
371    }
372
373    /// [127] Hat der Lieferant auf Grund seines Vertrags Kenntnis, dass der Kunde keine hohe KA hat so muss er dies dem NB mitteilen
374    /// EXTERNAL: Requires context from outside the message.
375    // REVIEW: Whether the supplier knows from their contract that the customer has no high KA (Kapazitätsanteil) is contract-based knowledge external to the EDIFACT message. Cannot be determined from segment data alone. (medium confidence)
376    fn evaluate_127(&self, ctx: &EvaluationContext) -> ConditionResult {
377        ctx.external.evaluate("supplier_knows_no_high_ka")
378    }
379
380    /// [129] Hat der Lieferant auf Grund seines Vertrags Kenntnis über die Höhe der Sonder-KA, so muss er diesen dem NB mitteilen
381    /// EXTERNAL: Requires context from outside the message.
382    // REVIEW: Whether the supplier has contract knowledge about the amount of a special KA (Sonder-Kapazitätsanteil) is contract-based knowledge. Cannot be derived from the EDIFACT message. (medium confidence)
383    fn evaluate_129(&self, ctx: &EvaluationContext) -> ConditionResult {
384        ctx.external.evaluate("supplier_knows_special_ka_amount")
385    }
386
387    /// [230] Sich ergebendes Datum/ bzw. Endedatum des Turnuszeitraums aus DTM+Z21 (Termin der Netznutzungsabrechnung) und DTM+Z09 (Nächste Netznutzungsabrechnung) muss &gt;= DTM+92 (Beginn zum) sein
388    // REVIEW: Compares the later of DTM+Z21 and DTM+Z09 (end of Turnuszeitraum) against DTM+92 (start date). Date formats differ across the three DTM qualifiers (Z21=CCYYMM/format 106, Z09=format 602, DTM+92=CCYYMMDDHHMM/format 303), so comparison is normalized to the CCYYMM prefix (first 6 chars). The exact definition of 'sich ergebendes Datum' from Z21+Z09 introduces some ambiguity — using max(Z21,Z09) as the best approximation. (medium confidence)
389    fn evaluate_230(&self, ctx: &EvaluationContext) -> ConditionResult {
390        {
391            // DTM+Z21 (Termin der Netznutzungsabrechnung): elements[0][1], format 106/104
392            let z21_val = ctx
393                .find_segments_with_qualifier("DTM", 0, "Z21")
394                .into_iter()
395                .find_map(|s| s.elements.first().and_then(|e| e.get(1)).cloned())
396                .unwrap_or_default();
397            // DTM+Z09 (Nächste Netznutzungsabrechnung): elements[0][1], format 602
398            let z09_val = ctx
399                .find_segments_with_qualifier("DTM", 0, "Z09")
400                .into_iter()
401                .find_map(|s| s.elements.first().and_then(|e| e.get(1)).cloned())
402                .unwrap_or_default();
403            // DTM+92 (Beginn zum): elements[0][1], format 303 (CCYYMMDDHHMM)
404            let dtm92_val = match ctx
405                .find_segments_with_qualifier("DTM", 0, "92")
406                .into_iter()
407                .find_map(|s| s.elements.first().and_then(|e| e.get(1)).cloned())
408            {
409                Some(v) if !v.is_empty() => v,
410                _ => return ConditionResult::Unknown,
411            };
412            // Use the later of Z21 and Z09 as the end date of the Turnuszeitraum
413            // Note: formats differ (Z21=106/104, Z09=602, DTM+92=303) — use first 6 chars (CCYYMM) for comparison
414            let end_date = match (z21_val.is_empty(), z09_val.is_empty()) {
415                (false, false) => {
416                    if z21_val >= z09_val {
417                        z21_val
418                    } else {
419                        z09_val
420                    }
421                }
422                (false, true) => z21_val,
423                (true, false) => z09_val,
424                (true, true) => return ConditionResult::Unknown,
425            };
426            // Compare first 6 chars (CCYYMM) — both reference dates have at least CCYYMM
427            let end_ym = &end_date[..end_date.len().min(6)];
428            let begin_ym = &dtm92_val[..dtm92_val.len().min(6)];
429            if end_ym.len() < 6 || begin_ym.len() < 6 {
430                return ConditionResult::Unknown;
431            }
432            ConditionResult::from(end_ym >= begin_ym)
433        }
434    }
435
436    /// [315] Es sind alle OBISKennzahlen gem. EDI@Energy Codeliste der OBIS-Kennzahlen und Medien für den deutschen Energiemarkt Kap. 4 anzugeben welche an der Marktlokation erforderlich sind, dabei muss der M...
437    /// EXTERNAL: Requires context from outside the message.
438    fn evaluate_315(&self, ctx: &EvaluationContext) -> ConditionResult {
439        ctx.external.evaluate("all_required_obis_codes_present")
440    }
441
442    /// [324] Es sind alle OBIS-Kennzahlen gem. EDI@Energy Codeliste der OBIS Kennzahlen Kap. 4. anzugeben welche an der Zähleinrichtung genutzt werden. Der Mindestumfang der OBIS-Kennzahlen ergibt sich aus den...
443    /// EXTERNAL: Requires context from outside the message.
444    fn evaluate_324(&self, ctx: &EvaluationContext) -> ConditionResult {
445        ctx.external.evaluate("obis_codes_complete_for_product")
446    }
447
448    /// [1] Wenn Aufteilung vorhanden
449    /// EXTERNAL: Requires context from outside the message.
450    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
451        ctx.external.evaluate("message_splitting")
452    }
453
454    /// [4] Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle LF
455    /// EXTERNAL: Requires context from outside the message.
456    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
457        ctx.external.evaluate("recipient_is_lf")
458    }
459
460    /// [5] Wenn MP-ID in SG2 NAD+MS (Nachrichtenabsender) in der Rolle LF
461    /// EXTERNAL: Requires context from outside the message.
462    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
463        ctx.external.evaluate("sender_is_lf")
464    }
465
466    /// [7] Wenn SG4 STS+7++ZG9/ZH1/ZH2 (Transaktionsgrund: Aufhebung einer zukünftigen Zuordnung wegen Auszug des Kunden / -wegen Stilllegung / -wegen aufgehobenem Vertragsverhältnis) vorhanden
467    fn evaluate_7(&self, ctx: &EvaluationContext) -> ConditionResult {
468        ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"])
469    }
470
471    /// [9] Wenn SG4 STS+7++ZE4 (Transaktionsgrund: Weggefallene Markt- bzw. Messlokation) nicht vorhanden
472    fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
473        match ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZE4"]) {
474            ConditionResult::True => ConditionResult::False,
475            ConditionResult::False | ConditionResult::Unknown => ConditionResult::True,
476        }
477    }
478
479    /// [10] Wenn SG4 STS+Z17 (Transaktionsgrund für befristete Anmeldung) vorhanden
480    fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
481        ctx.has_qualifier("STS", 0, "Z17")
482    }
483
484    /// [11] Wenn SG4 STS+7++ZG9/ZH1/ZH2 (Transaktionsgrund: Aufhebung einer zukünftigen Zuordnung wegen Auszug des Kunden / -wegen Stilllegung / -wegen aufgehobenem Vertragsverhältnis) nicht vorhanden
485    fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
486        match ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]) {
487            ConditionResult::True => ConditionResult::False,
488            ConditionResult::False | ConditionResult::Unknown => ConditionResult::True,
489        }
490    }
491
492    /// [12] Wenn SG4 DTM+471 (Ende zum nächstmöglichem Termin) nicht vorhanden
493    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
494        ctx.lacks_qualifier("DTM", 0, "471")
495    }
496
497    /// [13] Wenn SG4 STS+E01++Z01 (Status der Antwort: Zustimmung mit Terminänderung) nicht vorhanden
498    fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
499        match ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]) {
500            ConditionResult::True => ConditionResult::False,
501            ConditionResult::False | ConditionResult::Unknown => ConditionResult::True,
502        }
503    }
504
505    /// [14] Wenn Datum bekannt
506    /// EXTERNAL: Requires context from outside the message.
507    fn evaluate_14(&self, ctx: &EvaluationContext) -> ConditionResult {
508        ctx.external.evaluate("date_known")
509    }
510
511    /// [15] Wenn SG4 STS+E01++Z34 (Status der Antwort: Ablehnung Mehrfachkündigung) vorhanden
512    fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
513        ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z34"])
514    }
515
516    /// [16] Wenn SG4 STS+E01++Z12 (Status der Antwort: Ablehnung Vertragsbindung) vorhanden
517    fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
518        ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z12"])
519    }
520
521    /// [17] Wenn bereits eine bestätigte Kündigung durch Kunde oder MP vorhanden
522    /// EXTERNAL: Requires context from outside the message.
523    fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
524        ctx.external.evaluate("confirmed_cancellation_present")
525    }
526
527    /// [18] Wenn SG4 DTM+93 (Ende zum) nicht vorhanden
528    fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
529        ctx.lacks_qualifier("DTM", 0, "93")
530    }
531
532    /// [19] Wenn SG8 SEQ+Z01 (Daten der Marktlokation) SG10 CCI+++ZC0 (Prognose auf Basis von Werten) vorhanden
533    fn evaluate_19(&self, ctx: &EvaluationContext) -> ConditionResult {
534        ctx.filtered_parent_child_has_qualifier(
535            &["SG4", "SG8"],
536            "SEQ",
537            0,
538            "Z01",
539            "SG10",
540            "CCI",
541            2,
542            "ZC0",
543        )
544    }
545
546    /// [24] Wenn SG6 DTM+Z21 (Termin der Netznutzungsabrechnung) vorhanden
547    fn evaluate_24(&self, ctx: &EvaluationContext) -> ConditionResult {
548        ctx.has_qualifier("DTM", 0, "Z21")
549    }
550
551    /// [25] Wenn der Meldepunkt im SG5 LOC+172 (Meldepunkt) DE3225 das Format der Marktlokations-ID hat
552    // REVIEW: Marktlokations-ID in German gas market is a numeric (all-digit) identifier. LOC+172 has elements[0][0]='172', elements[1][0]=DE3225. Checks if value consists entirely of ASCII digits. Returns Unknown if no LOC+172 exists. (medium confidence)
553    fn evaluate_25(&self, ctx: &EvaluationContext) -> ConditionResult {
554        let segs = ctx.find_segments_with_qualifier("LOC", 0, "172");
555        if segs.is_empty() {
556            return ConditionResult::Unknown;
557        }
558        for seg in &segs {
559            if let Some(val) = seg.elements.get(1).and_then(|e| e.first()) {
560                if !val.is_empty() && val.chars().all(|c| c.is_ascii_digit()) {
561                    return ConditionResult::True;
562                }
563            }
564        }
565        ConditionResult::False
566    }
567
568    /// [26] Wenn der Meldepunkt im SG5 LOC+172 (Meldepunkt) DE3225 das Format der Zählpunktbezeichnung hat
569    // REVIEW: Zählpunktbezeichnung format contains non-digit characters (typically alphanumeric string). Negates condition 25's all-digit check. Returns True if DE3225 of LOC+172 is non-empty and contains at least one non-digit character. (medium confidence)
570    fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
571        let segs = ctx.find_segments_with_qualifier("LOC", 0, "172");
572        if segs.is_empty() {
573            return ConditionResult::Unknown;
574        }
575        for seg in &segs {
576            if let Some(val) = seg.elements.get(1).and_then(|e| e.first()) {
577                if !val.is_empty() && !val.chars().all(|c| c.is_ascii_digit()) {
578                    return ConditionResult::True;
579                }
580            }
581        }
582        ConditionResult::False
583    }
584
585    /// [28] Wenn SG4 DTM+93 (Ende zum) vorhanden
586    fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
587        ctx.has_qualifier("DTM", 0, "93")
588    }
589
590    /// [29] Wenn eine Bilanzierung stattfindet
591    /// EXTERNAL: Requires context from outside the message.
592    fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
593        ctx.external.evaluate("balancing_takes_place")
594    }
595
596    /// [32] Wenn BGM+E03 (Änderungsmeldungen) vorhanden
597    fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
598        ctx.has_qualifier("BGM", 0, "E03")
599    }
600
601    /// [33] Wenn in Abmeldung ein Bilanzierungsende vorhanden
602    // REVIEW: Wenn in Abmeldung ein Bilanzierungsende vorhanden: BGM+E02 identifies the message as Abmeldung (de-registration), DTM+159 is Bilanzierungsende. Both must coexist. (medium confidence)
603    fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
604        let has_abmeldung = !ctx.find_segments_with_qualifier("BGM", 0, "E02").is_empty();
605        let has_bilanzierungsende = !ctx.find_segments_with_qualifier("DTM", 0, "159").is_empty();
606        ConditionResult::from(has_abmeldung && has_bilanzierungsende)
607    }
608
609    /// [35] Wenn das DE2380 von SG4 DTM+Z01 (Kündigungsfrist des Vertrags) an vierter Stelle T (Termin) enthält
610    fn evaluate_35(&self, ctx: &EvaluationContext) -> ConditionResult {
611        let segs = ctx.find_segments_with_qualifier("DTM", 0, "Z01");
612        match segs.first() {
613            Some(dtm) => {
614                match dtm
615                    .elements
616                    .first()
617                    .and_then(|e| e.get(1))
618                    .map(|s| s.as_str())
619                {
620                    Some(value) => match value.chars().nth(3) {
621                        Some('T') => ConditionResult::True,
622                        Some(_) => ConditionResult::False,
623                        None => ConditionResult::False, // segment absent → condition not applicable
624                    },
625                    None => ConditionResult::False, // segment absent → condition not applicable
626                }
627            }
628            None => ConditionResult::False, // segment absent → condition not applicable
629        }
630    }
631
632    /// [36] Wenn SG4 STS+E01++ZC5 (Status der Antwort: Ablehnung andere Anmeldung in Bearbeitung) vorhanden
633    fn evaluate_36(&self, ctx: &EvaluationContext) -> ConditionResult {
634        ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["ZC5"])
635    }
636
637    /// [37] Wenn Anmeldung/ Änderung befristet
638    /// EXTERNAL: Requires context from outside the message.
639    fn evaluate_37(&self, ctx: &EvaluationContext) -> ConditionResult {
640        ctx.external.evaluate("registration_is_time_limited")
641    }
642
643    /// [39] Wenn LF beabsichtigt Zählerstand zu übermitteln
644    /// EXTERNAL: Requires context from outside the message.
645    fn evaluate_39(&self, ctx: &EvaluationContext) -> ConditionResult {
646        ctx.external
647            .evaluate("lf_intends_to_transmit_meter_reading")
648    }
649
650    /// [46] Wenn in SG8 SEQ+Z35 ein SG10 CCI+Z12 (Lastprofil) CAV (Lastprofil) DE3055 der Wert 293 enthalten ist
651    // REVIEW: In SG8 with SEQ+Z35 (Lastprofildaten), check SG10 children for CCI+Z12 (Lastprofil, elements[0][0]='Z12') AND a Klimazone CCI with DE3055=293 at elements[2][2] (responsible agency code = BDEW/GS1-DE). Uses collect_group_values to find Z35 SG8 instances by index, then navigator for SG10 child traversal. (medium confidence)
652    fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
653        let seq_values = ctx.collect_group_values("SEQ", 0, 0, &["SG4", "SG8"]);
654        let z35_instances: Vec<usize> = seq_values
655            .iter()
656            .filter(|(_, v)| v == "Z35")
657            .map(|(i, _)| *i)
658            .collect();
659        if z35_instances.is_empty() {
660            return ConditionResult::False;
661        }
662        let nav = match ctx.navigator() {
663            Some(n) => n,
664            None => return ConditionResult::Unknown,
665        };
666        for i in z35_instances {
667            let sg10_count = nav.child_group_instance_count(&["SG4", "SG8"], i, "SG10");
668            for j in 0..sg10_count {
669                let ccis = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], i, "SG10", j);
670                let has_z12 = ccis.iter().any(|s| {
671                    s.elements
672                        .first()
673                        .and_then(|e| e.first())
674                        .is_some_and(|v| v == "Z12")
675                });
676                if !has_z12 {
677                    continue;
678                }
679                // Check Klimazone CCI (Z99/ZA0) for DE3055=293 at elements[2][2]
680                let has_293 = ccis.iter().any(|s| {
681                    s.elements
682                        .get(2)
683                        .and_then(|e| e.get(2))
684                        .is_some_and(|v| v == "293")
685                });
686                if has_293 {
687                    return ConditionResult::True;
688                }
689            }
690        }
691        ConditionResult::False
692    }
693
694    /// [47] Wenn in SG8 SEQ+Z35 ein SG10 CCI+Z12 (Lastprofil) CAV (Lastprofil) DE3055 der Wert 293 nicht enthalten ist
695    // REVIEW: Logical negation of condition 46: CCI+Z12 Klimazone DE3055=293 NOT present in SG10 under SG8+Z35. Propagates Unknown to maintain three-valued semantics. (medium confidence)
696    fn evaluate_47(&self, ctx: &EvaluationContext) -> ConditionResult {
697        match self.evaluate_46(ctx) {
698            ConditionResult::True => ConditionResult::False,
699            ConditionResult::False => ConditionResult::True,
700            ConditionResult::Unknown => ConditionResult::Unknown,
701        }
702    }
703
704    /// [48] Wenn in dieser SG4 das STS+E01++E14 (Status der Antwort: Ablehnung Sonstiges) vorhanden
705    fn evaluate_48(&self, ctx: &EvaluationContext) -> ConditionResult {
706        ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["E14"])
707    }
708
709    /// [51] Bei rückwirkendem Lieferende/Lieferbeginn
710    /// EXTERNAL: Requires context from outside the message.
711    fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
712        ctx.external.evaluate("retroactive_delivery_change")
713    }
714
715    /// [58] Wenn in diesem CCI das DE3055 mit dem Code 293 vorhanden
716    // REVIEW: DE3055 (Verantwortliche Stelle für die Codepflege) appears at CCI elements[2][2] in CCI — Klimazone/Temperaturmessstelle. Checks any CCI in the message has that code. 'In diesem CCI' implies context-local check; falls back to message-wide. (medium confidence)
717    fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
718        let ccis = ctx.find_segments("CCI");
719        ConditionResult::from(ccis.iter().any(|s| {
720            s.elements
721                .get(2)
722                .and_then(|e| e.get(2))
723                .is_some_and(|v| v == "293")
724        }))
725    }
726
727    /// [64] Wenn SG4 DTM+158 (Bilanzierungsbeginn) vorhanden
728    fn evaluate_64(&self, ctx: &EvaluationContext) -> ConditionResult {
729        ctx.has_qualifier("DTM", 0, "158")
730    }
731
732    /// [65] Wenn Marktgebietsüber-lappung besteht, sind alle Bilanzkreise des LF zu melden in denen freie Kapazitäten beim NB bestehen
733    /// EXTERNAL: Requires context from outside the message.
734    fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
735        ctx.external.evaluate("market_area_overlap_exists")
736    }
737
738    /// [66] Wenn SG10 CCI+Z19 (Bilanzkreis) im Vorgang mehr als einmal vorhanden
739    fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
740        let count = ctx.find_segments_with_qualifier("CCI", 0, "Z19").len();
741        ConditionResult::from(count > 1)
742    }
743
744    /// [68] Wenn SG10 CCI+Z19 (Bilanzkreis) im Vorgang mehr als zweimal vorhanden
745    fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
746        let count = ctx.find_segments_with_qualifier("CCI", 0, "Z19").len();
747        ConditionResult::from(count > 2)
748    }
749
750    /// [69] Wenn SG10 CCI+Z19 (Bilanzkreis) im Vorgang mehr als dreimal vorhanden
751    fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
752        let count = ctx.find_segments_with_qualifier("CCI", 0, "Z19").len();
753        ConditionResult::from(count > 3)
754    }
755
756    /// [70] Wenn SG10 CCI+Z19 (Bilanzkreis) im Vorgang fünfmal vorhanden
757    fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
758        let count = ctx.find_segments_with_qualifier("CCI", 0, "Z19").len();
759        ConditionResult::from(count == 5)
760    }
761
762    /// [77] Wenn SG8 SEQ+Z03 (Zähleinrichtungsdaten) CAV+Z30 (Identifikation/Nummer des Gerätes) nicht vorhanden
763    fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
764        ctx.any_group_has_qualifier_without("SEQ", 0, "Z03", "CAV", 0, "Z30", &["SG4", "SG8"])
765    }
766
767    /// [78] Wenn SG4 STS+7++E02 (Transaktionsgrund: Einzug in Neuanlage) nicht vorhanden
768    fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
769        let result = ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E02"]);
770        match result {
771            ConditionResult::True => ConditionResult::False,
772            ConditionResult::False => ConditionResult::True,
773            ConditionResult::Unknown => ConditionResult::Unknown,
774        }
775    }
776
777    /// [81] Wenn SG4 FTX+ABO+Z05 (Beschreibung der Abweichung zur übermittelten Liste: Änderung vorhanden) vorhanden
778    fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
779        ctx.has_qualified_value("FTX", 0, "ABO", 2, 0, &["Z05"])
780    }
781
782    /// [84] Wenn SG4 STS+E01++Z35 (Status der Antwort: Ablehnung der Abmeldeanfrage) vorhanden
783    fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
784        ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z35"])
785    }
786
787    /// [92] Wenn Wert innerhalb SG bzw. Segment geändert wird
788    /// EXTERNAL: Requires context from outside the message.
789    // REVIEW: 'Wenn Wert innerhalb SG bzw. Segment geändert wird' — whether a value was changed compared to a prior state cannot be determined from the EDIFACT message alone; requires external business context comparing current vs previous transmission. (medium confidence)
790    fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
791        ctx.external.evaluate("value_changed_in_segment")
792    }
793
794    /// [98] Wenn MP-ID in SG2 NAD+MS (Nachrichtenabsender) in der Rolle NB
795    /// EXTERNAL: Requires context from outside the message.
796    // REVIEW: 'Wenn MP-ID in SG2 NAD+MS in der Rolle NB' — while we can confirm NAD+MS exists in the message, determining whether that market participant ID is registered in the role of Netzbetreiber (NB) requires an external role/master-data lookup. Cannot be derived from the EDIFACT message content alone. (medium confidence)
797    fn evaluate_98(&self, ctx: &EvaluationContext) -> ConditionResult {
798        ctx.external.evaluate("sender_is_nb")
799    }
800
801    /// [106] Wenn in dieser SG8 SEQ+Z01 SG10 CCI+++ZA6 (Prognosegrundlage der Marktlokation: Prognose auf Basis von Profilen) vorhanden
802    fn evaluate_106(&self, ctx: &EvaluationContext) -> ConditionResult {
803        ctx.filtered_parent_child_has_qualifier(
804            &["SG4", "SG8"],
805            "SEQ",
806            0,
807            "Z01",
808            "SG10",
809            "CCI",
810            2,
811            "ZA6",
812        )
813    }
814
815    /// [123] Wenn noch mindestens eine weitere SG8 SEQ+Z20 (OBIS-Daten der Zähleinrichtung / Mengenumwerter) mit dem SG8 RFF+MG / Z11 (Gerätenummer des Zählers / Mengenumwerters) auf die gleiche Identifikati...
816    // REVIEW: Collects device IDs (via RFF+MG or RFF+Z11) from all SG8 instances that have SEQ+Z20. Returns True if any device ID appears in at least two such instances, indicating at least one further SG8 references the same device. Requires navigator for proper group-scoped traversal. (medium confidence)
817    fn evaluate_123(&self, ctx: &EvaluationContext) -> ConditionResult {
818        let nav = match ctx.navigator() {
819            Some(n) => n,
820            None => {
821                let seqs = ctx.find_segments_with_qualifier("SEQ", 0, "Z20");
822                if seqs.len() < 2 {
823                    return ConditionResult::False;
824                }
825                return ConditionResult::Unknown;
826            }
827        };
828        let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
829        let mut device_ids: Vec<String> = Vec::new();
830        for i in 0..sg8_count {
831            let seqs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], i);
832            let has_z20 = seqs.iter().any(|s| {
833                s.elements
834                    .first()
835                    .and_then(|e| e.first())
836                    .is_some_and(|v| v == "Z20")
837            });
838            if !has_z20 {
839                continue;
840            }
841            let rffs = nav.find_segments_in_group("RFF", &["SG4", "SG8"], i);
842            for rff in &rffs {
843                let qual = rff
844                    .elements
845                    .first()
846                    .and_then(|e| e.first())
847                    .map(|s| s.as_str());
848                if matches!(qual, Some("MG") | Some("Z11")) {
849                    if let Some(id) = rff.elements.first().and_then(|e| e.get(1)) {
850                        if !id.is_empty() {
851                            device_ids.push(id.clone());
852                        }
853                    }
854                }
855            }
856        }
857        for i in 0..device_ids.len() {
858            for j in (i + 1)..device_ids.len() {
859                if device_ids[i] == device_ids[j] {
860                    return ConditionResult::True;
861                }
862            }
863        }
864        ConditionResult::False
865    }
866
867    /// [128] Wenn SG10 CAV+TAS/ TKS/ SAS/ KAS vorhanden
868    fn evaluate_128(&self, ctx: &EvaluationContext) -> ConditionResult {
869        ctx.any_group_has_any_qualifier(
870            "CAV",
871            0,
872            &["TAS", "TKS", "SAS", "KAS"],
873            &["SG4", "SG8", "SG10"],
874        )
875    }
876
877    /// [130] Wenn an Messlokation vorhanden
878    // REVIEW: 'Wenn an Messlokation vorhanden' is interpreted as: a Messlokation section exists in the message, indicated by SEQ+Z18 (Daten der Messlokation) in SG8. Returns True when such a section is present. (medium confidence)
879    fn evaluate_130(&self, ctx: &EvaluationContext) -> ConditionResult {
880        ctx.has_qualifier("SEQ", 0, "Z18")
881    }
882
883    /// [133] Wenn an der übermittelten Marktlokation / Messlokation vorhanden
884    /// EXTERNAL: Requires context from outside the message.
885    // REVIEW: 'Wenn an der übermittelten Marktlokation / Messlokation vorhanden' — whether a specific data element exists at the transmitted market or metering location is a business/registry lookup that cannot be determined from the EDIFACT message content alone. Marked external. (medium confidence)
886    fn evaluate_133(&self, ctx: &EvaluationContext) -> ConditionResult {
887        ctx.external.evaluate("at_marktlokation_or_messlokation")
888    }
889
890    /// [137] Nicht bei Neuanlage
891    // REVIEW: 'Nicht bei Neuanlage' — the condition is True when the transaction reason is NOT E02 (Neubau / new installation). Finds STS with elements[0][0]="7" (Transaktionsgrund), checks elements[2][0] for E02, and negates the result. (medium confidence)
892    fn evaluate_137(&self, ctx: &EvaluationContext) -> ConditionResult {
893        match ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E02"]) {
894            ConditionResult::True => ConditionResult::False,
895            ConditionResult::False => ConditionResult::True,
896            ConditionResult::Unknown => ConditionResult::Unknown,
897        }
898    }
899
900    /// [138] Wenn SG5 LOC+172 (Meldepunkt) nicht vorhanden
901    fn evaluate_138(&self, ctx: &EvaluationContext) -> ConditionResult {
902        ctx.lacks_qualifier("LOC", 0, "172")
903    }
904
905    /// [147] Wenn in Anfrage vorhanden
906    /// EXTERNAL: Requires context from outside the message.
907    // REVIEW: 'Wenn in Anfrage vorhanden' — whether something is present in the original inquiry/request message is a cross-message business context that cannot be determined from the current EDIFACT message alone. Marked external. (medium confidence)
908    fn evaluate_147(&self, ctx: &EvaluationContext) -> ConditionResult {
909        ctx.external.evaluate("present_in_request")
910    }
911
912    /// [165] Wenn bekannt
913    /// EXTERNAL: Requires context from outside the message.
914    fn evaluate_165(&self, ctx: &EvaluationContext) -> ConditionResult {
915        ctx.external.evaluate("date_known")
916    }
917
918    /// [166] Wenn vorhanden
919    /// EXTERNAL: Requires context from outside the message.
920    // REVIEW: 'Wenn vorhanden' — generic 'if present/available' without a specific segment reference. When used as a standalone condition this refers to business data availability that cannot be determined from the EDIFACT message structure alone. Marked external. (medium confidence)
921    fn evaluate_166(&self, ctx: &EvaluationContext) -> ConditionResult {
922        ctx.external.evaluate("data_available")
923    }
924
925    /// [200] Wenn BGM+Z26 (Vorläufige Meldung zur Marktraumumstellung) vorhanden
926    fn evaluate_200(&self, ctx: &EvaluationContext) -> ConditionResult {
927        ctx.has_qualifier("BGM", 0, "Z26")
928    }
929
930    /// [202] Wenn SG4 STS+E01+ZG2 (Status der Antwort: Gültiges Ergebnis nach der Datenprüfung) vorhanden
931    fn evaluate_202(&self, ctx: &EvaluationContext) -> ConditionResult {
932        ctx.has_qualified_value("STS", 0, "E01", 1, 0, &["ZG2"])
933    }
934
935    /// [203] Wenn STS+7++E06 / Z39 / ZC6 / ZC7 / ZC6 / ZT6 / ZT7
936    fn evaluate_203(&self, ctx: &EvaluationContext) -> ConditionResult {
937        ctx.has_qualified_value(
938            "STS",
939            0,
940            "7",
941            2,
942            0,
943            &["E06", "Z39", "ZC6", "ZC7", "ZT6", "ZT7"],
944        )
945    }
946
947    /// [205] Wenn SG9 QTY+Y02 (TUM Kundenwert) nicht vorhanden
948    fn evaluate_205(&self, ctx: &EvaluationContext) -> ConditionResult {
949        ctx.lacks_qualifier("QTY", 0, "Y02")
950    }
951
952    /// [209] Wenn im selben Segment im DE2379 der Code 303 vorhanden ist
953    // REVIEW: Checks if any DTM segment has DE2379 (elements[0][2]) equal to '303'. 'Im selben Segment' implies checking the format code component within the same DTM segment context; falls back to message-wide check without segment-level navigation. (medium confidence)
954    fn evaluate_209(&self, ctx: &EvaluationContext) -> ConditionResult {
955        let dtm_segs = ctx.find_segments("DTM");
956        ConditionResult::from(dtm_segs.iter().any(|s| {
957            s.elements
958                .first()
959                .and_then(|e| e.get(2))
960                .is_some_and(|v| v == "303")
961        }))
962    }
963
964    /// [212] Wenn im selben SG12 NAD DE3124 nicht vorhanden
965    // REVIEW: Checks if any NAD in SG12 has DE3124 (C058 first component, elements[2][0]) absent or empty. Without group-scoped navigation, falls back to message-wide NAD check. (medium confidence)
966    fn evaluate_212(&self, ctx: &EvaluationContext) -> ConditionResult {
967        let nads = ctx.find_segments("NAD");
968        ConditionResult::from(nads.iter().any(|s| {
969            s.elements
970                .get(2)
971                .map(|e| e.first().map(|v| v.is_empty()).unwrap_or(true))
972                .unwrap_or(true)
973        }))
974    }
975
976    /// [213] Wenn SG12 NAD+Z09 (Kunde des Lieferanten) vorhanden
977    fn evaluate_213(&self, ctx: &EvaluationContext) -> ConditionResult {
978        ctx.has_qualifier("NAD", 0, "Z09")
979    }
980
981    /// [216] Wenn CCI+++Z88 (Netznutzung) CAV+Z74:::Z08 (Netznutzungsvertrag: Direkter Vertrag zwischen Kunden und NB) vorhanden
982    // REVIEW: CCI+++Z88 means elements[2][0]=='Z88' (Netznutzung). CAV+Z74:::Z08 means elements[0][0]=='Z74' and elements[0][3]=='Z08' (Direkter Vertrag). Uses parent-child group navigation to check co-occurrence within the same SG10 instance. (medium confidence)
983    fn evaluate_216(&self, ctx: &EvaluationContext) -> ConditionResult {
984        let nav = match ctx.navigator() {
985            Some(n) => n,
986            None => {
987                let cavs = ctx.find_segments("CAV");
988                return ConditionResult::from(cavs.iter().any(|s| {
989                    let e0 = s.elements.first();
990                    e0.and_then(|e| e.first()).is_some_and(|v| v == "Z74")
991                        && e0.and_then(|e| e.get(3)).is_some_and(|v| v == "Z08")
992                }));
993            }
994        };
995        let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
996        for i in 0..sg8_count {
997            let sg10_count = nav.child_group_instance_count(&["SG4", "SG8"], i, "SG10");
998            for j in 0..sg10_count {
999                let ccis = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], i, "SG10", j);
1000                let has_cci_z88 = ccis.iter().any(|s| {
1001                    s.elements
1002                        .get(2)
1003                        .and_then(|e| e.first())
1004                        .is_some_and(|v| v == "Z88")
1005                });
1006                if has_cci_z88 {
1007                    let cavs =
1008                        nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], i, "SG10", j);
1009                    if cavs.iter().any(|s| {
1010                        let e0 = s.elements.first();
1011                        e0.and_then(|e| e.first()).is_some_and(|v| v == "Z74")
1012                            && e0.and_then(|e| e.get(3)).is_some_and(|v| v == "Z08")
1013                    }) {
1014                        return ConditionResult::True;
1015                    }
1016                }
1017            }
1018        }
1019        ConditionResult::False
1020    }
1021
1022    /// [219] Wenn an Marktlokation vorhanden
1023    /// EXTERNAL: Requires context from outside the message.
1024    // REVIEW: 'Wenn an Marktlokation vorhanden' — whether specific data exists at the market location is a registry/master-data lookup that cannot be derived from the EDIFACT message content. Marked external. (medium confidence)
1025    fn evaluate_219(&self, ctx: &EvaluationContext) -> ConditionResult {
1026        ctx.external.evaluate("at_marktlokation")
1027    }
1028
1029    /// [241] Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle MSB
1030    /// EXTERNAL: Requires context from outside the message.
1031    fn evaluate_241(&self, ctx: &EvaluationContext) -> ConditionResult {
1032        ctx.external.evaluate("recipient_is_msb")
1033    }
1034
1035    /// [249] Innerhalb eines SG4 IDE müssen alle DE1131 der SG4 STS+E01 den identischen Wert enthalten
1036    // REVIEW: Checks that all STS+E01 segments have identical DE1131 values (elements[2][1] — Codeliste, Code within C556). Returns True when all values match the first occurrence. Message-wide fallback without per-SG4 grouping. (medium confidence)
1037    fn evaluate_249(&self, ctx: &EvaluationContext) -> ConditionResult {
1038        let sts_segs = ctx.find_segments("STS");
1039        let sts_e01: Vec<_> = sts_segs
1040            .iter()
1041            .filter(|s| {
1042                s.elements
1043                    .first()
1044                    .and_then(|e| e.first())
1045                    .is_some_and(|v| v == "E01")
1046            })
1047            .collect();
1048        if sts_e01.is_empty() {
1049            return ConditionResult::Unknown;
1050        }
1051        let first_val = sts_e01
1052            .first()
1053            .and_then(|s| s.elements.get(2))
1054            .and_then(|e| e.get(1))
1055            .map(|v| v.as_str())
1056            .unwrap_or("");
1057        ConditionResult::from(sts_e01.iter().all(|s| {
1058            s.elements
1059                .get(2)
1060                .and_then(|e| e.get(1))
1061                .map(|v| v.as_str())
1062                .unwrap_or("")
1063                == first_val
1064        }))
1065    }
1066
1067    /// [252] Wenn DE0068 vorhanden
1068    // REVIEW: DE0068 is the Common Access Reference in UNH (elements[2][0]). Checks if it is present and non-empty in any UNH segment. (medium confidence)
1069    fn evaluate_252(&self, ctx: &EvaluationContext) -> ConditionResult {
1070        let unh_segs = ctx.find_segments("UNH");
1071        ConditionResult::from(unh_segs.iter().any(|s| {
1072            s.elements
1073                .get(2)
1074                .and_then(|e| e.first())
1075                .is_some_and(|v| !v.is_empty())
1076        }))
1077    }
1078
1079    /// [257] Wenn in derselben SG8 SEQ+Z02 (OBIS-Daten der Marktlokation) das PIA+5+7-0?:33.86.0 vorhanden
1080    fn evaluate_257(&self, ctx: &EvaluationContext) -> ConditionResult {
1081        ctx.any_group_has_co_occurrence(
1082            "SEQ",
1083            0,
1084            &["Z02"],
1085            "PIA",
1086            1,
1087            0,
1088            &["7-0:33.86.0"],
1089            &["SG4", "SG8"],
1090        )
1091    }
1092
1093    /// [268] Wenn der Code im DE3207 in der "EDI@Energy Codeliste der europäischen Ländercodes" in der Spalte "PLZ vorhanden" ein "X" aufgeführt ist
1094    /// EXTERNAL: Requires context from outside the message.
1095    fn evaluate_268(&self, ctx: &EvaluationContext) -> ConditionResult {
1096        ctx.external.evaluate("country_has_postal_code_requirement")
1097    }
1098
1099    /// [274] Wenn in derselben SG8 SEQ+Z20 (OBIS-Daten der Zähleinrichtung / Mengenumwerter) das PIA+5+7-b?:3.0.0 / 7-b?:6.0.0 / 7-b?:3.1.0 / 7-b?:6.1.0 / 7-b?:3.2.0 / 7-b?:6.2.0 / 7-b?:13.2.0 / 7-b?:16.2.0 / ...
1100    // REVIEW: Checks co-occurrence of SEQ+Z20 and PIA+5 in same SG8, then verifies the OBIS code (elements[1][0]) matches pattern '7-b:X.X.X' where b is variable (any subgroup) and suffix is one of the 16 listed measurement quantities. EDIFACT ?. decoded as literal colon. OBIS suffix match is message-wide as a secondary filter. (medium confidence)
1101    fn evaluate_274(&self, ctx: &EvaluationContext) -> ConditionResult {
1102        let obis_suffixes = [
1103            ":3.0.0", ":6.0.0", ":3.1.0", ":6.1.0", ":3.2.0", ":6.2.0", ":13.2.0", ":16.2.0",
1104            ":1.0.0", ":2.0.0", ":4.0.0", ":5.0.0", ":11.2.0", ":12.2.0", ":14.2.0", ":15.2.0",
1105        ];
1106        let has_seq_pia_co = ctx.any_group_has_co_occurrence(
1107            "SEQ",
1108            0,
1109            &["Z20"],
1110            "PIA",
1111            0,
1112            0,
1113            &["5"],
1114            &["SG4", "SG8"],
1115        );
1116        if !matches!(has_seq_pia_co, ConditionResult::True) {
1117            return has_seq_pia_co;
1118        }
1119        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1120        ConditionResult::from(pias.iter().any(|s| {
1121            s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
1122                v.starts_with("7-") && obis_suffixes.iter().any(|suf| v.ends_with(suf))
1123            })
1124        }))
1125    }
1126
1127    /// [276] Wenn SG4 DTM+92 (Beginn zum) vorhanden
1128    fn evaluate_276(&self, ctx: &EvaluationContext) -> ConditionResult {
1129        ctx.has_qualifier("DTM", 0, "92")
1130    }
1131
1132    /// [277] Wenn Zuordnung vorhanden
1133    // REVIEW: 'Wenn Zuordnung vorhanden' most likely refers to Bilanzkreiszuordnung zur Marktlokation, indicated by STS+Z18 per the MIG segment reference. Medium confidence as 'Zuordnung' could refer to other assignments in different PID contexts. (medium confidence)
1134    fn evaluate_277(&self, ctx: &EvaluationContext) -> ConditionResult {
1135        ctx.has_qualifier("STS", 0, "Z18")
1136    }
1137
1138    /// [283] Wenn Empfänger der Nachricht der zum Nachrichtendatum aktuell zugeordnete Lieferant ist
1139    /// EXTERNAL: Requires context from outside the message.
1140    fn evaluate_283(&self, ctx: &EvaluationContext) -> ConditionResult {
1141        ctx.external.evaluate("recipient_is_current_supplier")
1142    }
1143
1144    /// [336] Wenn in Änderungsmeldung gefüllt
1145    /// EXTERNAL: Requires context from outside the message.
1146    // REVIEW: "Wenn in Änderungsmeldung gefüllt" means the condition applies when this field is populated in the context of an amendment message (Änderungsmeldung). Whether a given transmission constitutes an amendment depends on business context (the PID / Vorgang type) that cannot be reliably determined from the raw EDIFACT segments alone. Delegated to external provider. (medium confidence)
1147    fn evaluate_336(&self, ctx: &EvaluationContext) -> ConditionResult {
1148        ctx.external.evaluate("field_filled_in_amendment_message")
1149    }
1150
1151    /// [345] Wenn 33-stelliger Meldepunkt im SG5 LOC+172 (Meldepunkt) vorhanden
1152    fn evaluate_345(&self, ctx: &EvaluationContext) -> ConditionResult {
1153        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
1154        ConditionResult::from(locs.iter().any(|s| {
1155            s.elements
1156                .get(1)
1157                .and_then(|e| e.first())
1158                .map(|id| id.len() == 33)
1159                .unwrap_or(false)
1160        }))
1161    }
1162
1163    /// [361] Wenn STS+E01++A03/ A04 nicht vorhanden
1164    fn evaluate_361(&self, ctx: &EvaluationContext) -> ConditionResult {
1165        let found = ctx
1166            .find_segments_with_qualifier("STS", 0, "E01")
1167            .iter()
1168            .any(|s| {
1169                s.elements
1170                    .get(2)
1171                    .and_then(|e| e.first())
1172                    .map(|v| v == "A03" || v == "A04")
1173                    .unwrap_or(false)
1174            });
1175        ConditionResult::from(!found)
1176    }
1177
1178    /// [362] Wenn STS+E01++A03/ A17 nicht vorhanden
1179    fn evaluate_362(&self, ctx: &EvaluationContext) -> ConditionResult {
1180        let found = ctx
1181            .find_segments_with_qualifier("STS", 0, "E01")
1182            .iter()
1183            .any(|s| {
1184                s.elements
1185                    .get(2)
1186                    .and_then(|e| e.first())
1187                    .map(|v| v == "A03" || v == "A17")
1188                    .unwrap_or(false)
1189            });
1190        ConditionResult::from(!found)
1191    }
1192
1193    /// [367] Wenn SG4 STS+E01++A04 vorhanden
1194    fn evaluate_367(&self, ctx: &EvaluationContext) -> ConditionResult {
1195        ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["A04"])
1196    }
1197
1198    /// [368] Es sind alle Codes aus der Codeliste G_0009 erlaubt
1199    fn evaluate_368(&self, _ctx: &EvaluationContext) -> ConditionResult {
1200        ConditionResult::True
1201    }
1202
1203    /// [427] Messprodukt-Code aus Kapitel 3 "Codeliste der Standard-Messprodukte Gas" der Codeliste der Konfigurationen
1204    /// EXTERNAL: Requires context from outside the message.
1205    fn evaluate_427(&self, ctx: &EvaluationContext) -> ConditionResult {
1206        ctx.external.evaluate("messprodukt_code_gas_valid")
1207    }
1208
1209    /// [442] Wenn in keinem SG8+SEQ+Z09 Mengenumwerterdaten das RFF+MG (Referenz auf die Gerätenummer) der in diesem RFF DE1154 genannte Gerätenummer des Zählers vorhanden ist
1210    // REVIEW: Condition is True when NO SG8 with SEQ+Z09 (Mengenumwerterdaten) has a co-occurring RFF+MG. Implemented by negating any_group_has_co_occurrence. Medium confidence because the 'same device number as meter' cross-reference aspect cannot be fully validated without knowing which meter device number to compare against. (medium confidence)
1211    fn evaluate_442(&self, ctx: &EvaluationContext) -> ConditionResult {
1212        let result = ctx.any_group_has_co_occurrence(
1213            "SEQ",
1214            0,
1215            &["Z09"],
1216            "RFF",
1217            0,
1218            0,
1219            &["MG"],
1220            &["SG4", "SG8"],
1221        );
1222        match result {
1223            ConditionResult::True => ConditionResult::False,
1224            ConditionResult::False => ConditionResult::True,
1225            ConditionResult::Unknown => ConditionResult::Unknown,
1226        }
1227    }
1228
1229    /// [490] Wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Prozesszeitpunkt bei MESZ mit UTC“ ist
1230    /// EXTERNAL: Requires context from outside the message.
1231    fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
1232        ctx.external.evaluate("date_in_mesz_period")
1233    }
1234
1235    /// [491] Wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Prozesszeitpunkt bei MEZ mit UTC““ ist
1236    /// EXTERNAL: Requires context from outside the message.
1237    fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1238        ctx.external.evaluate("date_in_mez_period")
1239    }
1240
1241    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
1242    fn evaluate_494(&self, _ctx: &EvaluationContext) -> ConditionResult {
1243        // Hinweis: Das genannte Datum muss dem Dokumenterstellungszeitpunkt oder einem früheren Zeitpunkt entsprechen — informational annotation
1244        ConditionResult::True
1245    }
1246
1247    /// [500] Hinweis: Code ist gemäß der Kategorie der zu stornierenden Meldung zu wählen
1248    fn evaluate_500(&self, _ctx: &EvaluationContext) -> ConditionResult {
1249        // Hinweis: Code ist gemäß der Kategorie der zu stornierenden Meldung zu wählen — informational note, always applies
1250        ConditionResult::True
1251    }
1252
1253    /// [501] Hinweis: Die Angabe wird aus dem DTM+157 (Änderung zum) der Zuordnungsliste übernommen
1254    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1255        // Hinweis: Die Angabe wird aus dem DTM+157 (Änderung zum) der Zuordnungsliste übernommen — informational note, always applies
1256        ConditionResult::True
1257    }
1258
1259    /// [502] Hinweis:  Ersatzbelieferung gibt es nur bei  - Marktlokationen in der  Niederdruckebene, die kein Haushaltskunde gem. EnWG sind und die nicht mehr der gesetzlichen Ersatzversorgung (drei Monate) un...
1260    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1261        // Hinweis: Informational note about Ersatzbelieferung scope and bilateral agreements.
1262        // No evaluatable boolean logic — purely documentary.
1263        ConditionResult::Unknown
1264    }
1265
1266    /// [504] Hinweis: Der Code Z22 wird auch in der Sparte Strom genutzt. Der Verweis auf den Einspeisevergütungsintervall ist in der Sparte Gas nicht relevant.
1267    fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1268        // Hinweis: Informational note that Z22 is also used in electricity sector;
1269        // Einspeisevergütungsintervall reference is not relevant in gas sector.
1270        // No evaluatable boolean logic — purely documentary.
1271        ConditionResult::Unknown
1272    }
1273
1274    /// [507] Hinweis: Ursprünglich vom NB bestätigtes Beginndatum
1275    fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
1276        // Hinweis: Informational note — 'ursprünglich vom NB bestätigtes Beginndatum'.
1277        // Documents the semantic meaning of the date field; no boolean logic.
1278        ConditionResult::Unknown
1279    }
1280
1281    /// [508] Hinweis: Beginndatum beim neuen NB
1282    fn evaluate_508(&self, _ctx: &EvaluationContext) -> ConditionResult {
1283        // Hinweis: Informational note — 'Beginndatum beim neuen NB'.
1284        // Documents the semantic meaning of the date field; no boolean logic.
1285        ConditionResult::Unknown
1286    }
1287
1288    /// [510] Hinweis: Zu verwenden bei der Abmeldung der ESV
1289    fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
1290        // Hinweis: Informational note — to be used for deregistration of ESV (Ersatzversorgung).
1291        // Documents usage context; no boolean logic.
1292        ConditionResult::Unknown
1293    }
1294
1295    /// [513] Hinweis: Ist SG9 QTY+Y02 (TUM Kundenwert) vorhanden, dann ist ausschließlich SG9 QTY+Y02, unabhängig von SG9 QTY+31 (Veranschlagte Jahresmenge gesamt), für die Bilanzierung und MMM-Abrechnung zu...
1296    // REVIEW: Hinweis describing precedence rule between QTY+Y02 and QTY+31. Partial implementation checks whether QTY+Y02 is present (i.e. the rule is active). Full semantic enforcement (exclusive use) cannot be expressed as a simple ConditionResult. (medium confidence)
1297    fn evaluate_513(&self, ctx: &EvaluationContext) -> ConditionResult {
1298        // Hinweis: If QTY+Y02 (TUM Kundenwert) is present, only QTY+Y02 (not QTY+31) is
1299        // to be used for billing and MMM settlement. This is a documentation note about
1300        // precedence, not a simple presence/absence condition.
1301        // Best approximation: True if QTY+Y02 is present (signalling the rule applies).
1302        ctx.has_qualifier("QTY", 0, "Y02")
1303    }
1304
1305    /// [527] Hinweis: Es ist die ID der Marktlokation und alle Identifikatoren der Messlokationen anzugeben
1306    fn evaluate_527(&self, _ctx: &EvaluationContext) -> ConditionResult {
1307        // Hinweis: Informational note — ID of Marktlokation AND all Messlokation
1308        // identifiers must be provided. No evaluatable boolean logic.
1309        ConditionResult::Unknown
1310    }
1311
1312    /// [528] Hinweis: Es ist das Datum/ Daten aus der Anfrage zu verwenden
1313    fn evaluate_528(&self, _ctx: &EvaluationContext) -> ConditionResult {
1314        // Hinweis: Informational note — the date(s) from the original request must be used.
1315        // No evaluatable boolean logic.
1316        ConditionResult::Unknown
1317    }
1318
1319    /// [530] Hinweis: Es sind alle an dem Meldepunkt vorhandenen Daten, die mit dieser Segmentgruppe übermittelt werden und zum Datum „Änderung zum“ Gültigkeit haben, anzugeben. Dies kann zur Folge haben...
1320    fn evaluate_530(&self, _ctx: &EvaluationContext) -> ConditionResult {
1321        // Hinweis: Informational note — all data valid at 'Änderung zum' date for the
1322        // reporting point must be provided, possibly requiring repeated segment groups.
1323        // No evaluatable boolean logic.
1324        ConditionResult::Unknown
1325    }
1326
1327    /// [556] Hinweis: Wenn keine Korrespondenzanschrift des Endverbrauchers/ Kunden vorliegt, ist die Anschrift der Marktlokation zu übermitteln
1328    fn evaluate_556(&self, _ctx: &EvaluationContext) -> ConditionResult {
1329        // Hinweis: Wenn keine Korrespondenzanschrift des Endverbrauchers/Kunden vorliegt, ist die Anschrift der Marktlokation zu übermitteln — informational note, always applies
1330        ConditionResult::True
1331    }
1332
1333    /// [558] Hinweis: Diese Information kann freiwillig ausgetauscht werden
1334    fn evaluate_558(&self, _ctx: &EvaluationContext) -> ConditionResult {
1335        // Hinweis: This information can be exchanged voluntarily.
1336        // No evaluatable boolean logic — purely documentary.
1337        ConditionResult::Unknown
1338    }
1339
1340    /// [559] Hinweis: Die Korrespondenzanschrift des Endverbrauchers/Kunden wird nicht zur Identifikation genutzt
1341    fn evaluate_559(&self, _ctx: &EvaluationContext) -> ConditionResult {
1342        // Hinweis: Correspondence address of end consumer/customer is not used for identification.
1343        // No evaluatable boolean logic — purely documentary.
1344        ConditionResult::Unknown
1345    }
1346
1347    /// [560] Hinweis: Die Angabe Name und Adresse für die Ablesekarte wird nicht zur Identifikation genutzt
1348    fn evaluate_560(&self, _ctx: &EvaluationContext) -> ConditionResult {
1349        // Hinweis: Name and address for Ablesekarte (meter reading card) is not used
1350        // for identification purposes. No evaluatable boolean logic — purely documentary.
1351        ConditionResult::Unknown
1352    }
1353
1354    /// [566] Hinweis: Altlieferant
1355    fn evaluate_566(&self, _ctx: &EvaluationContext) -> ConditionResult {
1356        // Hinweis: 'Altlieferant' (previous supplier). Informational label for the
1357        // market participant role context; no evaluatable boolean logic.
1358        ConditionResult::Unknown
1359    }
1360
1361    /// [567] Hinweis: Neulieferant
1362    fn evaluate_567(&self, _ctx: &EvaluationContext) -> ConditionResult {
1363        // Hinweis: 'Neulieferant' (new supplier). Informational label for the
1364        // market participant role context; no evaluatable boolean logic.
1365        ConditionResult::Unknown
1366    }
1367
1368    /// [570] Hinweis: Netzbetreiber Alt
1369    fn evaluate_570(&self, _ctx: &EvaluationContext) -> ConditionResult {
1370        // Hinweis: Netzbetreiber Alt — informational note, always applies
1371        ConditionResult::True
1372    }
1373
1374    /// [571] Hinweis: Auslösender Marktpartner (LFA bei STS+7++ZG9, LFN bei STS+7++ZH0, NB bei STS+7++ZH1)
1375    fn evaluate_571(&self, _ctx: &EvaluationContext) -> ConditionResult {
1376        // Hinweis: Auslösender Marktpartner (LFA bei STS+7++ZG9, LFN bei STS+7++ZH0, NB bei STS+7++ZH1) — informational note, always applies
1377        ConditionResult::True
1378    }
1379
1380    /// [572] Hinweis: Kundenname aus Anmeldung Lieferant neu
1381    fn evaluate_572(&self, _ctx: &EvaluationContext) -> ConditionResult {
1382        // Hinweis: Kundenname aus Anmeldung Lieferant neu — informational note, always applies
1383        ConditionResult::True
1384    }
1385
1386    /// [581] Hinweis: Es ist das nächst mögliche Kündigungsdatum anzugeben
1387    fn evaluate_581(&self, _ctx: &EvaluationContext) -> ConditionResult {
1388        // Hinweis: Es ist das nächst mögliche Kündigungsdatum anzugeben — informational note, always applies
1389        ConditionResult::True
1390    }
1391
1392    /// [583] Hinweis: Verwendung der ID der Marktlokation
1393    fn evaluate_583(&self, _ctx: &EvaluationContext) -> ConditionResult {
1394        // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1395        ConditionResult::True
1396    }
1397
1398    /// [584] Hinweis: Verwendung der ID der Messlokation
1399    fn evaluate_584(&self, _ctx: &EvaluationContext) -> ConditionResult {
1400        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1401        ConditionResult::True
1402    }
1403
1404    /// [590] Hinweis: Es ist die ID der Marktlokation, welche dem LF zugeordnet sind, sowie alle Identifikatoren der Messlokationen anzugeben
1405    fn evaluate_590(&self, _ctx: &EvaluationContext) -> ConditionResult {
1406        // Hinweis: Es ist die ID der Marktlokation, welche dem LF zugeordnet sind, sowie alle Identifikatoren der Messlokationen anzugeben — informational note, always applies
1407        ConditionResult::True
1408    }
1409
1410    /// [601] Hinweis: Es ist die ID der Marktlokation und alle Identifikatoren der Messlokationen anzugeben.
1411    fn evaluate_601(&self, _ctx: &EvaluationContext) -> ConditionResult {
1412        // Hinweis: Es ist die ID der Marktlokation und alle Identifikatoren der Messlokationen anzugeben — informational note, always applies
1413        ConditionResult::True
1414    }
1415
1416    /// [621] Hinweis: Es ist der MSB anzugeben, welcher ab dem Zeitpunkt der Lokation zugeordnet ist, der in DTM+76 (Datum zum geplanten Leistungsbeginn) genannt ist.
1417    fn evaluate_621(&self, _ctx: &EvaluationContext) -> ConditionResult {
1418        // Hinweis: Es ist der MSB anzugeben, welcher ab dem Zeitpunkt der Lokation zugeordnet ist, der in DTM+76 (Datum zum geplanten Leistungsbeginn) genannt ist.
1419        ConditionResult::True
1420    }
1421
1422    /// [622] Hinweis: Falls die OBIS-Kennzahl für mehrere Marktrollen relevant ist, so muss die Segmentgruppe pro Marktrolle wiederholt werden
1423    fn evaluate_622(&self, _ctx: &EvaluationContext) -> ConditionResult {
1424        // Hinweis: Falls die OBIS-Kennzahl für mehrere Marktrollen relevant ist, so muss die Segmentgruppe pro Marktrolle wiederholt werden
1425        ConditionResult::True
1426    }
1427
1428    /// [636] Hinweis: Dieses RFF klassifiziert mit einem Code im DE1153 die in derselben Segmentgruppe enthaltenen DTM zu einem Markt- bzw. Messlokation relevanten Inhalt
1429    fn evaluate_636(&self, _ctx: &EvaluationContext) -> ConditionResult {
1430        // Hinweis: Dieses RFF klassifiziert mit einem Code im DE1153 die in derselben Segmentgruppe enthaltenen DTM zu einem Markt- bzw. Messlokation relevanten Inhalt
1431        ConditionResult::True
1432    }
1433
1434    /// [637] Hinweis: Bei Verpflichtungsanfrage
1435    fn evaluate_637(&self, _ctx: &EvaluationContext) -> ConditionResult {
1436        // Hinweis: Bei Verpflichtungsanfrage — informational note, always applies
1437        ConditionResult::True
1438    }
1439
1440    /// [638] Hinweis: Bei Aufforderung zur Übernahme der einzelnen Messlokation durch den gMSB
1441    fn evaluate_638(&self, _ctx: &EvaluationContext) -> ConditionResult {
1442        // Hinweis: Bei Aufforderung zur Übernahme der einzelnen Messlokation durch den gMSB — informational note, always applies
1443        ConditionResult::True
1444    }
1445
1446    /// [643] Hinweis: Nachfolgender Netzbetreiber
1447    fn evaluate_643(&self, _ctx: &EvaluationContext) -> ConditionResult {
1448        // Hinweis: Nachfolgender Netzbetreiber — informational note, always applies
1449        ConditionResult::True
1450    }
1451
1452    /// [644] Hinweis: Wenn in der zugehörigen Anmeldung (44001) in diesem Segmente "Einzug in Neuanlage" (SG4 STS+7++E02) enthalten ist, wird in diesem Geschäftsvorfall  der Code E01 verwendet
1453    fn evaluate_644(&self, _ctx: &EvaluationContext) -> ConditionResult {
1454        // Hinweis: informational note about E01/E02 usage in Bestätigung — always applies
1455        ConditionResult::True
1456    }
1457
1458    /// [651] Hinweis: Bei einer Marktraumumstellung (Gas) ist zu beachten, dass die tatsächliche Meldung zur Marktraumumstellung auf Ebene der Messlokation durch Angabe der Gasqualität erfolgt. Die betroffene...
1459    fn evaluate_651(&self, _ctx: &EvaluationContext) -> ConditionResult {
1460        // Hinweis: Marktraumumstellung Gas — informational note about Messlokation reporting — always applies
1461        ConditionResult::True
1462    }
1463
1464    /// [654] Hinweis: Es sind ausschließlich die Daten zum Meldepunkt anzugeben, die für den in NAD+MR (Nachrichtenempfänger) adressierten Marktpartner relevant ist
1465    fn evaluate_654(&self, _ctx: &EvaluationContext) -> ConditionResult {
1466        // Hinweis: Only data relevant to the addressed Marktpartner (NAD+MR) should be provided — always applies
1467        ConditionResult::True
1468    }
1469
1470    /// [655] Hinweis: Wenn ein Zähler an einen Mengenumwerter angeschlossen ist werden an dem Zähler keine OBIS-Kennzahlen angegeben Hier gibt es nur OBIS Kennzahlen vom Mengenumwerter
1471    fn evaluate_655(&self, _ctx: &EvaluationContext) -> ConditionResult {
1472        // Hinweis: When a meter is connected to a volume converter, no OBIS codes are given for the meter — always applies
1473        ConditionResult::True
1474    }
1475
1476    /// [902] Format: Möglicher Wert: ≥ 0
1477    fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
1478        let segs = ctx.find_segments("QTY");
1479        match segs
1480            .first()
1481            .and_then(|s| s.elements.first())
1482            .and_then(|e| e.get(1))
1483        {
1484            Some(val) => validate_numeric(val, ">=", 0.0),
1485            None => ConditionResult::False, // segment absent → condition not applicable
1486        }
1487    }
1488
1489    /// [907] Format: max. 4 Nachkommastellen
1490    fn evaluate_907(&self, ctx: &EvaluationContext) -> ConditionResult {
1491        let segs = ctx.find_segments("QTY");
1492        match segs
1493            .first()
1494            .and_then(|s| s.elements.first())
1495            .and_then(|e| e.get(1))
1496        {
1497            Some(val) => validate_max_decimal_places(val, 4),
1498            None => ConditionResult::False, // segment absent → condition not applicable
1499        }
1500    }
1501
1502    /// [912] Format: max. 6 Nachkommastellen
1503    fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
1504        let segs = ctx.find_segments("QTY");
1505        match segs
1506            .first()
1507            .and_then(|s| s.elements.first())
1508            .and_then(|e| e.get(1))
1509        {
1510            Some(val) => validate_max_decimal_places(val, 6),
1511            None => ConditionResult::False, // segment absent → condition not applicable
1512        }
1513    }
1514
1515    /// [930] Format: max. 2 Nachkommastellen
1516    fn evaluate_930(&self, ctx: &EvaluationContext) -> ConditionResult {
1517        let segs = ctx.find_segments("QTY");
1518        match segs
1519            .first()
1520            .and_then(|s| s.elements.first())
1521            .and_then(|e| e.get(1))
1522        {
1523            Some(val) => validate_max_decimal_places(val, 2),
1524            None => ConditionResult::False, // segment absent → condition not applicable
1525        }
1526    }
1527
1528    /// [931] Format: ZZZ = +00
1529    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1530        let segs = ctx.find_segments("DTM");
1531        match segs
1532            .first()
1533            .and_then(|s| s.elements.first())
1534            .and_then(|e| e.get(1))
1535        {
1536            Some(val) => validate_timezone_utc(val),
1537            None => ConditionResult::False, // segment absent → condition not applicable
1538        }
1539    }
1540
1541    /// [934] Format: HHMM = 0400
1542    fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
1543        // Format: HHMM = 0400 — validate time component of DTM value equals 0400
1544        let dtm_segs = ctx.find_segments("DTM");
1545        match dtm_segs
1546            .first()
1547            .and_then(|s| s.elements.first())
1548            .and_then(|e| e.get(1))
1549        {
1550            Some(val) => validate_hhmm_equals(val, "0400"),
1551            None => ConditionResult::False, // segment absent → condition not applicable
1552        }
1553    }
1554
1555    /// [935] Format: HHMM = 0500
1556    fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
1557        // Format: HHMM = 0500 — validate time component of DTM value equals 0500
1558        let dtm_segs = ctx.find_segments("DTM");
1559        match dtm_segs
1560            .first()
1561            .and_then(|s| s.elements.first())
1562            .and_then(|e| e.get(1))
1563        {
1564            Some(val) => validate_hhmm_equals(val, "0500"),
1565            None => ConditionResult::False, // segment absent → condition not applicable
1566        }
1567    }
1568
1569    /// [937] Format: keine Nachkommastelle
1570    fn evaluate_937(&self, ctx: &EvaluationContext) -> ConditionResult {
1571        // Format: keine Nachkommastelle — no decimal places allowed (max 0)
1572        let segs = ctx.find_segments("QTY");
1573        match segs
1574            .first()
1575            .and_then(|s| s.elements.first())
1576            .and_then(|e| e.get(1))
1577        {
1578            Some(val) => validate_max_decimal_places(val, 0),
1579            None => ConditionResult::False, // segment absent → condition not applicable
1580        }
1581    }
1582
1583    /// [938] Format: Möglicher Wert: &lt;= 10
1584    fn evaluate_938(&self, ctx: &EvaluationContext) -> ConditionResult {
1585        // Format: Möglicher Wert: <= 10 — numeric value must be <= 10
1586        let segs = ctx.find_segments("QTY");
1587        match segs
1588            .first()
1589            .and_then(|s| s.elements.first())
1590            .and_then(|e| e.get(1))
1591        {
1592            Some(val) => validate_numeric(val, "<=", 10.0),
1593            None => ConditionResult::False, // segment absent → condition not applicable
1594        }
1595    }
1596
1597    /// [950] Format: Marktlokations-ID
1598    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
1599        // Format: Marktlokations-ID — 11-digit value with Luhn check digit
1600        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z16");
1601        match segs
1602            .first()
1603            .and_then(|s| s.elements.get(1))
1604            .and_then(|e| e.first())
1605        {
1606            Some(val) => validate_malo_id(val),
1607            None => ConditionResult::False, // segment absent → condition not applicable
1608        }
1609    }
1610
1611    /// [951] Format: Zählpunktbezeichnung
1612    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
1613        // Format: Zählpunktbezeichnung — 33 alphanumeric characters
1614        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z19");
1615        match segs
1616            .first()
1617            .and_then(|s| s.elements.get(1))
1618            .and_then(|e| e.first())
1619        {
1620            Some(val) => validate_zahlpunkt(val),
1621            None => ConditionResult::False, // segment absent → condition not applicable
1622        }
1623    }
1624
1625    /// [952] Format: Gerätenummer nach DIN 43863-5
1626    // REVIEW: DIN 43863-5 Gerätenummer is an alphanumeric device identifier with a max length of 20 characters. No dedicated validator exists, so validate_max_length(20) is used as the structural check. The exact segment/element depends on the AHB table position — IDE is the most common carrier for device IDs in UTILMD Gas, with PIA as a fallback. Medium confidence due to uncertainty about the exact segment without the full AHB table context. (medium confidence)
1627    fn evaluate_952(&self, ctx: &EvaluationContext) -> ConditionResult {
1628        // Format: Gerätenummer nach DIN 43863-5 — alphanumeric, max 20 characters
1629        // DIN 43863-5 device numbers appear in IDE segment (element 1, component 0)
1630        // or in SG8 PIA segments for device identification
1631        let ide_segs = ctx.find_segments("IDE");
1632        if let Some(val) = ide_segs
1633            .first()
1634            .and_then(|s| s.elements.get(1))
1635            .and_then(|e| e.first())
1636            .filter(|v| !v.is_empty())
1637        {
1638            return validate_max_length(val, 20);
1639        }
1640        // Fallback: check PIA segment for device number (qualifier 5 = substitute product)
1641        let pia_segs = ctx.find_segments_with_qualifier("PIA", 0, "5");
1642        match pia_segs
1643            .first()
1644            .and_then(|s| s.elements.get(1))
1645            .and_then(|e| e.first())
1646        {
1647            Some(val) => validate_max_length(val, 20),
1648            None => ConditionResult::False, // segment absent → condition not applicable
1649        }
1650    }
1651
1652    /// [953] Format: Marktlokations-ID oder Zählpunktbezeichnung
1653    fn evaluate_953(&self, ctx: &EvaluationContext) -> ConditionResult {
1654        // Format: Marktlokations-ID oder Zählpunktbezeichnung
1655        // MaLo-ID: 11 digits with Luhn check; Zählpunkt: 33 alphanumeric characters
1656        // Check LOC segments (Z16=Marktlokation, Z19=Zählpunkt) for the identifier
1657        let loc_segs = ctx.find_segments("LOC");
1658        match loc_segs
1659            .first()
1660            .and_then(|s| s.elements.get(1))
1661            .and_then(|e| e.first())
1662        {
1663            Some(val) => validate_malo_or_zahlpunkt(val),
1664            None => ConditionResult::False, // segment absent → condition not applicable
1665        }
1666    }
1667
1668    /// [2061] Segment bzw. Segmentgruppe ist genau einmal je SG4 IDE (Vorgang) anzugeben
1669    // REVIEW: Condition states the segment/group is to be given exactly once per SG4 IDE (Vorgang). As a boolean applicability condition, this is True when the IDE segment (Vorgangsidentifikator) is present, indicating the SG4 context exists. Cardinality enforcement (exactly once) is a structural rule beyond a simple boolean evaluator. (medium confidence)
1670    fn evaluate_2061(&self, ctx: &EvaluationContext) -> ConditionResult {
1671        ConditionResult::from(ctx.has_segment("IDE"))
1672    }
1673
1674    /// [2119] Je SG8 SEQ+Z13 (Smartmeter-Gateway) ist genau einmal die Segmentgruppe anzugeben
1675    fn evaluate_2119(&self, ctx: &EvaluationContext) -> ConditionResult {
1676        ctx.any_group_has_qualifier("SEQ", 0, "Z13", &["SG4", "SG8"])
1677    }
1678
1679    /// [2284] Für jede Messlokations-ID im SG5 LOC+172 (Meldepunkt) DE3225 genau einmal anzugeben
1680    fn evaluate_2284(&self, ctx: &EvaluationContext) -> ConditionResult {
1681        ctx.any_group_has_qualifier("LOC", 0, "172", &["SG4", "SG5"])
1682    }
1683
1684    /// [2286] Für jede SEQ+Z18 (Daten der Messlokation) mindestens einmal anzugeben
1685    fn evaluate_2286(&self, ctx: &EvaluationContext) -> ConditionResult {
1686        ctx.any_group_has_qualifier("SEQ", 0, "Z18", &["SG4", "SG8"])
1687    }
1688
1689    /// [2287] Für jede SEQ+Z03 (Zähleinrichtungsdaten) mindestens einmal anzugeben
1690    fn evaluate_2287(&self, ctx: &EvaluationContext) -> ConditionResult {
1691        ctx.any_group_has_qualifier("SEQ", 0, "Z03", &["SG4", "SG8"])
1692    }
1693
1694    /// [2335] Für jede SEQ+Z02 (OBIS-Daten der Marktlokation), welche im PIA+5 die OBIS-Kennzahl 7-20:99.33.17/ 7-0:33.86.0 übermittelt, genau einmal anzugeben
1695    // REVIEW: Condition states: to be given exactly once for each SEQ+Z02 (OBIS-Daten der Marktlokation) where PIA+5 transmits OBIS code 7-20:99.33.17 or 7-0:33.86.0. Per segment structure reference, PIA elements[1][0] (DE7140) holds the OBIS code. Checks message-wide PIA+5 for the target OBIS values, then verifies SEQ+Z02 is present. Since PIA is always nested in the same SG8 as its SEQ, message-wide PIA check is a valid approximation when group navigator is unavailable. (medium confidence)
1696    fn evaluate_2335(&self, ctx: &EvaluationContext) -> ConditionResult {
1697        {
1698            // Check for PIA+5 with specific OBIS codes 7-20:99.33.17 or 7-0:33.86.0 at elements[1][0]
1699            let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1700            let has_target_obis = pias.iter().any(|pia| {
1701                pia.elements
1702                    .get(1)
1703                    .and_then(|e| e.first())
1704                    .map(|v| v == "7-20:99.33.17" || v == "7-0:33.86.0")
1705                    .unwrap_or(false)
1706            });
1707            if !has_target_obis {
1708                return ConditionResult::False;
1709            }
1710            // Also verify SEQ+Z02 (OBIS-Daten der Marktlokation) is present — PIA is always co-located in the same SG8
1711            ctx.any_group_has_qualifier("SEQ", 0, "Z02", &["SG4", "SG8"])
1712        }
1713    }
1714
1715    /// [2353] Für jede SEQ+Z09 (Mengenumwerter-Daten) mindestens einmal anzugeben
1716    fn evaluate_2353(&self, ctx: &EvaluationContext) -> ConditionResult {
1717        ctx.any_group_has_qualifier("SEQ", 0, "Z09", &["SG4", "SG8"])
1718    }
1719}