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