Skip to main content

automapper_validation/generated/fv2510/
mscons_conditions_fv2510.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2510/MSCONS_AHB_3_1f_Fehlerkorrektur_20250623.xml
4// Generated: 2026-03-12T10:50:55Z
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 MSCONS FV2510.
12pub struct MsconsConditionEvaluatorFV2510 {
13    // External condition IDs that require runtime context.
14    external_conditions: std::collections::HashSet<u32>,
15}
16
17impl Default for MsconsConditionEvaluatorFV2510 {
18    fn default() -> Self {
19        let mut external_conditions = std::collections::HashSet::new();
20        external_conditions.insert(1);
21        external_conditions.insert(22);
22        external_conditions.insert(32);
23        external_conditions.insert(33);
24        external_conditions.insert(35);
25        external_conditions.insert(36);
26        external_conditions.insert(42);
27        external_conditions.insert(67);
28        external_conditions.insert(77);
29        external_conditions.insert(80);
30        external_conditions.insert(81);
31        external_conditions.insert(97);
32        external_conditions.insert(117);
33        external_conditions.insert(118);
34        external_conditions.insert(126);
35        external_conditions.insert(127);
36        external_conditions.insert(138);
37        external_conditions.insert(141);
38        external_conditions.insert(492);
39        external_conditions.insert(493);
40        Self {
41            external_conditions,
42        }
43    }
44}
45
46impl ConditionEvaluator for MsconsConditionEvaluatorFV2510 {
47    fn message_type(&self) -> &str {
48        "MSCONS"
49    }
50
51    fn format_version(&self) -> &str {
52        "FV2510"
53    }
54
55    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
56        match condition {
57            1 => self.evaluate_1(ctx),
58            2 => self.evaluate_2(ctx),
59            11 => self.evaluate_11(ctx),
60            12 => self.evaluate_12(ctx),
61            17 => self.evaluate_17(ctx),
62            18 => self.evaluate_18(ctx),
63            22 => self.evaluate_22(ctx),
64            27 => self.evaluate_27(ctx),
65            28 => self.evaluate_28(ctx),
66            32 => self.evaluate_32(ctx),
67            33 => self.evaluate_33(ctx),
68            35 => self.evaluate_35(ctx),
69            36 => self.evaluate_36(ctx),
70            38 => self.evaluate_38(ctx),
71            42 => self.evaluate_42(ctx),
72            45 => self.evaluate_45(ctx),
73            46 => self.evaluate_46(ctx),
74            48 => self.evaluate_48(ctx),
75            49 => self.evaluate_49(ctx),
76            50 => self.evaluate_50(ctx),
77            51 => self.evaluate_51(ctx),
78            62 => self.evaluate_62(ctx),
79            67 => self.evaluate_67(ctx),
80            68 => self.evaluate_68(ctx),
81            69 => self.evaluate_69(ctx),
82            70 => self.evaluate_70(ctx),
83            71 => self.evaluate_71(ctx),
84            72 => self.evaluate_72(ctx),
85            73 => self.evaluate_73(ctx),
86            77 => self.evaluate_77(ctx),
87            78 => self.evaluate_78(ctx),
88            79 => self.evaluate_79(ctx),
89            80 => self.evaluate_80(ctx),
90            81 => self.evaluate_81(ctx),
91            83 => self.evaluate_83(ctx),
92            84 => self.evaluate_84(ctx),
93            85 => self.evaluate_85(ctx),
94            86 => self.evaluate_86(ctx),
95            87 => self.evaluate_87(ctx),
96            88 => self.evaluate_88(ctx),
97            90 => self.evaluate_90(ctx),
98            91 => self.evaluate_91(ctx),
99            92 => self.evaluate_92(ctx),
100            93 => self.evaluate_93(ctx),
101            94 => self.evaluate_94(ctx),
102            95 => self.evaluate_95(ctx),
103            96 => self.evaluate_96(ctx),
104            97 => self.evaluate_97(ctx),
105            98 => self.evaluate_98(ctx),
106            99 => self.evaluate_99(ctx),
107            100 => self.evaluate_100(ctx),
108            101 => self.evaluate_101(ctx),
109            108 => self.evaluate_108(ctx),
110            111 => self.evaluate_111(ctx),
111            113 => self.evaluate_113(ctx),
112            117 => self.evaluate_117(ctx),
113            118 => self.evaluate_118(ctx),
114            119 => self.evaluate_119(ctx),
115            121 => self.evaluate_121(ctx),
116            125 => self.evaluate_125(ctx),
117            126 => self.evaluate_126(ctx),
118            127 => self.evaluate_127(ctx),
119            128 => self.evaluate_128(ctx),
120            129 => self.evaluate_129(ctx),
121            130 => self.evaluate_130(ctx),
122            131 => self.evaluate_131(ctx),
123            132 => self.evaluate_132(ctx),
124            133 => self.evaluate_133(ctx),
125            134 => self.evaluate_134(ctx),
126            135 => self.evaluate_135(ctx),
127            138 => self.evaluate_138(ctx),
128            141 => self.evaluate_141(ctx),
129            142 => self.evaluate_142(ctx),
130            143 => self.evaluate_143(ctx),
131            144 => self.evaluate_144(ctx),
132            145 => self.evaluate_145(ctx),
133            146 => self.evaluate_146(ctx),
134            147 => self.evaluate_147(ctx),
135            148 => self.evaluate_148(ctx),
136            149 => self.evaluate_149(ctx),
137            150 => self.evaluate_150(ctx),
138            490 => self.evaluate_490(ctx),
139            491 => self.evaluate_491(ctx),
140            492 => self.evaluate_492(ctx),
141            493 => self.evaluate_493(ctx),
142            494 => self.evaluate_494(ctx),
143            495 => self.evaluate_495(ctx),
144            501 => self.evaluate_501(ctx),
145            502 => self.evaluate_502(ctx),
146            506 => self.evaluate_506(ctx),
147            509 => self.evaluate_509(ctx),
148            510 => self.evaluate_510(ctx),
149            511 => self.evaluate_511(ctx),
150            512 => self.evaluate_512(ctx),
151            513 => self.evaluate_513(ctx),
152            514 => self.evaluate_514(ctx),
153            515 => self.evaluate_515(ctx),
154            516 => self.evaluate_516(ctx),
155            518 => self.evaluate_518(ctx),
156            519 => self.evaluate_519(ctx),
157            520 => self.evaluate_520(ctx),
158            522 => self.evaluate_522(ctx),
159            523 => self.evaluate_523(ctx),
160            524 => self.evaluate_524(ctx),
161            525 => self.evaluate_525(ctx),
162            526 => self.evaluate_526(ctx),
163            528 => self.evaluate_528(ctx),
164            529 => self.evaluate_529(ctx),
165            530 => self.evaluate_530(ctx),
166            531 => self.evaluate_531(ctx),
167            532 => self.evaluate_532(ctx),
168            535 => self.evaluate_535(ctx),
169            538 => self.evaluate_538(ctx),
170            541 => self.evaluate_541(ctx),
171            543 => self.evaluate_543(ctx),
172            544 => self.evaluate_544(ctx),
173            545 => self.evaluate_545(ctx),
174            546 => self.evaluate_546(ctx),
175            547 => self.evaluate_547(ctx),
176            551 => self.evaluate_551(ctx),
177            553 => self.evaluate_553(ctx),
178            554 => self.evaluate_554(ctx),
179            556 => self.evaluate_556(ctx),
180            557 => self.evaluate_557(ctx),
181            558 => self.evaluate_558(ctx),
182            559 => self.evaluate_559(ctx),
183            560 => self.evaluate_560(ctx),
184            566 => self.evaluate_566(ctx),
185            567 => self.evaluate_567(ctx),
186            568 => self.evaluate_568(ctx),
187            569 => self.evaluate_569(ctx),
188            570 => self.evaluate_570(ctx),
189            571 => self.evaluate_571(ctx),
190            572 => self.evaluate_572(ctx),
191            573 => self.evaluate_573(ctx),
192            574 => self.evaluate_574(ctx),
193            575 => self.evaluate_575(ctx),
194            576 => self.evaluate_576(ctx),
195            577 => self.evaluate_577(ctx),
196            902 => self.evaluate_902(ctx),
197            904 => self.evaluate_904(ctx),
198            905 => self.evaluate_905(ctx),
199            906 => self.evaluate_906(ctx),
200            907 => self.evaluate_907(ctx),
201            908 => self.evaluate_908(ctx),
202            909 => self.evaluate_909(ctx),
203            910 => self.evaluate_910(ctx),
204            917 => self.evaluate_917(ctx),
205            918 => self.evaluate_918(ctx),
206            922 => self.evaluate_922(ctx),
207            925 => self.evaluate_925(ctx),
208            931 => self.evaluate_931(ctx),
209            932 => self.evaluate_932(ctx),
210            933 => self.evaluate_933(ctx),
211            934 => self.evaluate_934(ctx),
212            935 => self.evaluate_935(ctx),
213            937 => self.evaluate_937(ctx),
214            939 => self.evaluate_939(ctx),
215            940 => self.evaluate_940(ctx),
216            950 => self.evaluate_950(ctx),
217            951 => self.evaluate_951(ctx),
218            960 => self.evaluate_960(ctx),
219            2001 => self.evaluate_2001(ctx),
220            2002 => self.evaluate_2002(ctx),
221            2003 => self.evaluate_2003(ctx),
222            _ => ConditionResult::Unknown,
223        }
224    }
225
226    fn is_external(&self, condition: u32) -> bool {
227        self.external_conditions.contains(&condition)
228    }
229    fn is_known(&self, condition: u32) -> bool {
230        matches!(
231            condition,
232            1 | 2
233                | 11
234                | 12
235                | 17
236                | 18
237                | 22
238                | 27
239                | 28
240                | 32
241                | 33
242                | 35
243                | 36
244                | 38
245                | 42
246                | 45
247                | 46
248                | 48
249                | 49
250                | 50
251                | 51
252                | 62
253                | 67
254                | 68
255                | 69
256                | 70
257                | 71
258                | 72
259                | 73
260                | 77
261                | 78
262                | 79
263                | 80
264                | 81
265                | 83
266                | 84
267                | 85
268                | 86
269                | 87
270                | 88
271                | 90
272                | 91
273                | 92
274                | 93
275                | 94
276                | 95
277                | 96
278                | 97
279                | 98
280                | 99
281                | 100
282                | 101
283                | 108
284                | 111
285                | 113
286                | 117
287                | 118
288                | 119
289                | 121
290                | 125
291                | 126
292                | 127
293                | 128
294                | 129
295                | 130
296                | 131
297                | 132
298                | 133
299                | 134
300                | 135
301                | 138
302                | 141
303                | 142
304                | 143
305                | 144
306                | 145
307                | 146
308                | 147
309                | 148
310                | 149
311                | 150
312                | 490
313                | 491
314                | 492
315                | 493
316                | 494
317                | 495
318                | 501
319                | 502
320                | 506
321                | 509
322                | 510
323                | 511
324                | 512
325                | 513
326                | 514
327                | 515
328                | 516
329                | 518
330                | 519
331                | 520
332                | 522
333                | 523
334                | 524
335                | 525
336                | 526
337                | 528
338                | 529
339                | 530
340                | 531
341                | 532
342                | 535
343                | 538
344                | 541
345                | 543
346                | 544
347                | 545
348                | 546
349                | 547
350                | 551
351                | 553
352                | 554
353                | 556
354                | 557
355                | 558
356                | 559
357                | 560
358                | 566
359                | 567
360                | 568
361                | 569
362                | 570
363                | 571
364                | 572
365                | 573
366                | 574
367                | 575
368                | 576
369                | 577
370                | 902
371                | 904
372                | 905
373                | 906
374                | 907
375                | 908
376                | 909
377                | 910
378                | 917
379                | 918
380                | 922
381                | 925
382                | 931
383                | 932
384                | 933
385                | 934
386                | 935
387                | 937
388                | 939
389                | 940
390                | 950
391                | 951
392                | 960
393                | 2001
394                | 2002
395                | 2003
396        )
397    }
398}
399
400impl MsconsConditionEvaluatorFV2510 {
401    /// [81] Wenn MP-ID in SG2 NAD+MS in der Rolle ÜNB
402    /// EXTERNAL: Requires context from outside the message.
403    fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
404        ctx.external.evaluate("sender_is_uenb")
405    }
406
407    /// [126] wenn Plausibilisierungshinweise vorliegen
408    /// EXTERNAL: Requires context from outside the message.
409    fn evaluate_126(&self, ctx: &EvaluationContext) -> ConditionResult {
410        ctx.external.evaluate("plausibility_notes_present")
411    }
412
413    /// [127] wenn ein Korrekturgrund anzugeben ist
414    /// EXTERNAL: Requires context from outside the message.
415    fn evaluate_127(&self, ctx: &EvaluationContext) -> ConditionResult {
416        ctx.external.evaluate("correction_reason_required")
417    }
418
419    /// [130] Wenn innerhalb desselben LIN-Segments neben diesem Segment (SG10 DTM+7 Nutzungszeitpunkt) noch das SG10 DTM+60 (Ausführungs- / Änderungszeitpunkt) oder das SG10 DTM+9 (Ablesedatum) vorhanden, dar...
420    fn evaluate_130(&self, _ctx: &EvaluationContext) -> ConditionResult {
421        // TODO: Condition [130] requires manual implementation
422        // Reason: This condition requires complex datetime arithmetic: parse CCYYMMDDHHMM values from DTM+7 alongside DTM+60 or DTM+9 within the same SG10, compute the absolute difference, then apply DST-aware thresholds (< 24h normal, < 25h for summer→winter transition, < 23h for winter→summer transition). The DST transition detection requires EU DST rules applied to the interval between the two timestamps, not just individual point checks. This goes significantly beyond what the available API helpers can express cleanly without writing a full datetime/DST library inline. Returning null for human review.
423        ConditionResult::Unknown
424    }
425
426    /// [1] Sofern per ORDERS angefordert
427    /// EXTERNAL: Requires context from outside the message.
428    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
429        ctx.external.evaluate("requested_via_orders")
430    }
431
432    /// [2] Wenn das Zeitintervall zwischen ersten SG10 DTM+163 und letzten SG10 DTM+164 mindestens einen Monat umfasst
433    // REVIEW: Parses year+month from DTM+163 (first) and DTM+164 (last) in format 303 (YYYYMMDDHHmm). Computes calendar-month difference. 'Mindestens einen Monat' interpreted as end month > start month (calendar boundary). Medium confidence due to ambiguity between calendar-month vs. 30-day interpretation. (medium confidence)
434    fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
435        let dtm_163 = ctx.find_segments_with_qualifier("DTM", 0, "163");
436        let dtm_164 = ctx.find_segments_with_qualifier("DTM", 0, "164");
437        let first_start = dtm_163
438            .first()
439            .and_then(|s| s.elements.first())
440            .and_then(|e| e.get(1))
441            .map(|s| s.as_str());
442        let last_end = dtm_164
443            .last()
444            .and_then(|s| s.elements.first())
445            .and_then(|e| e.get(1))
446            .map(|s| s.as_str());
447        match (first_start, last_end) {
448            (Some(start), Some(end)) if start.len() >= 6 && end.len() >= 6 => {
449                let start_year: u32 = start[0..4].parse().unwrap_or(0);
450                let start_month: u32 = start[4..6].parse().unwrap_or(0);
451                let end_year: u32 = end[0..4].parse().unwrap_or(0);
452                let end_month: u32 = end[4..6].parse().unwrap_or(0);
453                let start_total = start_year * 12 + start_month;
454                let end_total = end_year * 12 + end_month;
455                ConditionResult::from(end_total >= start_total + 1)
456            }
457            _ => ConditionResult::Unknown,
458        }
459    }
460
461    /// [11] Wenn SG9 PIA+5+7-0?:52.0.22/7-0?:54.0.16/7-0?:54.0.20/7-0?:54.0.22
462    fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
463        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
464        let obis_values = ["7-0:52.0.22", "7-0:54.0.16", "7-0:54.0.20", "7-0:54.0.22"];
465        ConditionResult::from(pia_segments.iter().any(|s| {
466            s.elements
467                .get(1)
468                .and_then(|e| e.first())
469                .is_some_and(|v| obis_values.contains(&v.as_str()))
470        }))
471    }
472
473    /// [12] Wenn nicht SG9 PIA+5+7-0?:52.0.22/7-0?:54.0.16/7-0?:54.0.20/7-0?:54.0.22
474    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
475        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
476        let obis_values = ["7-0:52.0.22", "7-0:54.0.16", "7-0:54.0.20", "7-0:54.0.22"];
477        ConditionResult::from(!pia_segments.iter().any(|s| {
478            s.elements
479                .get(1)
480                .and_then(|e| e.first())
481                .is_some_and(|v| obis_values.contains(&v.as_str()))
482        }))
483    }
484
485    /// [17] Wenn nicht SG9 PIA+5+1-b?:9.99.0 (b= Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien)
486    // REVIEW: Checks absence of PIA+5 with OBIS matching pattern '1-{channel}:9.99.0'. The 'b' is a variable channel, so pattern-matching on prefix '1-' and suffix ':9.99.0' is used. Negated because condition is 'Wenn NICHT'. Medium confidence due to pattern-based matching of OBIS code. (medium confidence)
487    fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
488        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
489        let has_match = pia_segments.iter().any(|s| {
490            s.elements
491                .get(1)
492                .and_then(|e| e.first())
493                .is_some_and(|v| v.starts_with("1-") && v.ends_with(":9.99.0"))
494        });
495        ConditionResult::from(!has_match)
496    }
497
498    /// [18] Wenn SG9 PIA+5+1-b?:9.99.0 (b= Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien)
499    // REVIEW: Affirmative version of condition 17. True when any PIA+5 has OBIS code matching the '1-{channel}:9.99.0' pattern (combined heat/power or special metering). Medium confidence due to pattern matching. (medium confidence)
500    fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
501        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
502        ConditionResult::from(pia_segments.iter().any(|s| {
503            s.elements
504                .get(1)
505                .and_then(|e| e.first())
506                .is_some_and(|v| v.starts_with("1-") && v.ends_with(":9.99.0"))
507        }))
508    }
509
510    /// [22] Wenn Aufteilung vorhanden
511    /// EXTERNAL: Requires context from outside the message.
512    fn evaluate_22(&self, ctx: &EvaluationContext) -> ConditionResult {
513        ctx.external.evaluate("message_splitting")
514    }
515
516    /// [27] Wenn SG9 PIA+5+1-1?:1.9.0 vorhanden
517    fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
518        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
519        ConditionResult::from(pia_segments.iter().any(|s| {
520            s.elements
521                .get(1)
522                .and_then(|e| e.first())
523                .is_some_and(|v| v == "1-1:1.9.0")
524        }))
525    }
526
527    /// [28] Wenn SG9 PIA+5+1-1?:1.9.0 nicht vorhanden
528    fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
529        let pia_segments = ctx.find_segments_with_qualifier("PIA", 0, "5");
530        ConditionResult::from(!pia_segments.iter().any(|s| {
531            s.elements
532                .get(1)
533                .and_then(|e| e.first())
534                .is_some_and(|v| v == "1-1:1.9.0")
535        }))
536    }
537
538    /// [32] wenn MP-ID in SG2 NAD+MS in der Rolle NB
539    /// EXTERNAL: Requires context from outside the message.
540    fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
541        ctx.external.evaluate("sender_is_nb")
542    }
543
544    /// [33] wenn MP-ID in SG2 NAD+MR in der Rolle LF
545    /// EXTERNAL: Requires context from outside the message.
546    fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
547        ctx.external.evaluate("recipient_is_lf")
548    }
549
550    /// [35] wenn MP-ID in SG2 NAD+MS in der Rolle MSB
551    /// EXTERNAL: Requires context from outside the message.
552    fn evaluate_35(&self, ctx: &EvaluationContext) -> ConditionResult {
553        ctx.external.evaluate("sender_is_msb")
554    }
555
556    /// [36] wenn MP-ID in SG2 NAD+MR in der Rolle NB
557    /// EXTERNAL: Requires context from outside the message.
558    fn evaluate_36(&self, ctx: &EvaluationContext) -> ConditionResult {
559        ctx.external.evaluate("recipient_is_nb")
560    }
561
562    /// [38] wenn in SG6 LOC+172 DE3225 die ID der Messlokation angegeben ist
563    fn evaluate_38(&self, ctx: &EvaluationContext) -> ConditionResult {
564        let loc_segments = ctx.find_segments_with_qualifier("LOC", 0, "172");
565        match loc_segments.first() {
566            Some(loc) => {
567                let has_id = loc
568                    .elements
569                    .get(1)
570                    .and_then(|e| e.first())
571                    .is_some_and(|v| !v.is_empty());
572                ConditionResult::from(has_id)
573            }
574            None => ConditionResult::False,
575        }
576    }
577
578    /// [42] Wenn MP-ID in SG2 NAD+MR in der Rolle MSB
579    /// EXTERNAL: Requires context from outside the message.
580    fn evaluate_42(&self, ctx: &EvaluationContext) -> ConditionResult {
581        ctx.external.evaluate("recipient_is_msb")
582    }
583
584    /// [45] Wenn SG9 PIA+5+7-b:99.41.16 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
585    fn evaluate_45(&self, ctx: &EvaluationContext) -> ConditionResult {
586        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
587        ConditionResult::from(pias.iter().any(|s| {
588            s.elements
589                .get(1)
590                .and_then(|e| e.first())
591                .is_some_and(|v| v.starts_with("7-") && v.ends_with(":99.41.16"))
592        }))
593    }
594
595    /// [46] Wenn Wert in SG6 LOC+172 DE3225 genau 11 Stellen
596    fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
597        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
598        if locs.is_empty() {
599            return ConditionResult::Unknown;
600        }
601        ConditionResult::from(locs.iter().any(|s| {
602            s.elements
603                .get(1)
604                .and_then(|e| e.first())
605                .is_some_and(|v| v.len() == 11)
606        }))
607    }
608
609    /// [48] Wenn SG9 PIA+5+7-0?:52.0.22
610    fn evaluate_48(&self, ctx: &EvaluationContext) -> ConditionResult {
611        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
612        ConditionResult::from(pias.iter().any(|s| {
613            s.elements
614                .get(1)
615                .and_then(|e| e.first())
616                .is_some_and(|v| v == "7-0:52.0.22")
617        }))
618    }
619
620    /// [49] Wenn SG9 PIA+5+7-b?:70.16.16/7-b?:70.16.20/7-b?:70.16.22 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
621    fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
622        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
623        ConditionResult::from(pias.iter().any(|s| {
624            s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
625                v.starts_with("7-")
626                    && (v.ends_with(":70.16.16")
627                        || v.ends_with(":70.16.20")
628                        || v.ends_with(":70.16.22"))
629            })
630        }))
631    }
632
633    /// [50] Wenn SG9 PIA+5+7-b?:70.18.16/7-b?:70.18.20/7-b?:70.18.22 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
634    fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
635        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
636        ConditionResult::from(pias.iter().any(|s| {
637            s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
638                v.starts_with("7-")
639                    && (v.ends_with(":70.18.16")
640                        || v.ends_with(":70.18.20")
641                        || v.ends_with(":70.18.22"))
642            })
643        }))
644    }
645
646    /// [51] Wenn SG9 PIA+5+7-0?:33.86.0 vorhanden ist, darf mittels Wiederholung SG9 LIN in derselben Nachricht das SG9 PIA+5+7-0?:52.0.22/7-0?:54.0.16/7-0?:54.0.20/7-0?:54.0.22 nicht mehr angegeben werden
647    // REVIEW: Mutual-exclusion constraint: if 7-0:33.86.0 is present, none of the listed 52/54.x codes may appear. Returns True when constraint is satisfied (i.e. 33.86.0 absent, or 33.86.0 present but excluded codes absent). Returns True when 33.86.0 is not present because the constraint simply does not apply. (medium confidence)
648    fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
649        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
650        let has_3386 = pias.iter().any(|s| {
651            s.elements
652                .get(1)
653                .and_then(|e| e.first())
654                .is_some_and(|v| v == "7-0:33.86.0")
655        });
656        if !has_3386 {
657            return ConditionResult::True;
658        }
659        let excluded = ["7-0:52.0.22", "7-0:54.0.16", "7-0:54.0.20", "7-0:54.0.22"];
660        ConditionResult::from(!pias.iter().any(|s| {
661            s.elements
662                .get(1)
663                .and_then(|e| e.first())
664                .is_some_and(|v| excluded.contains(&v.as_str()))
665        }))
666    }
667
668    /// [62] Wenn Wert in SG6 LOC+172 DE3225 genau 33 Stellen
669    fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
670        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
671        if locs.is_empty() {
672            return ConditionResult::Unknown;
673        }
674        ConditionResult::from(locs.iter().any(|s| {
675            s.elements
676                .get(1)
677                .and_then(|e| e.first())
678                .is_some_and(|v| v.len() == 33)
679        }))
680    }
681
682    /// [67] Wenn es sich um die Referenz auf eine ORDERS handelt
683    /// EXTERNAL: Requires context from outside the message.
684    // REVIEW: Checks whether the current MSCONS transaction references an ORDERS message. While RFF segments can carry cross-message references, the specific qualifier that marks an ORDERS reference is not provided in the segment structure reference, and determining whether a reference points to an ORDERS vs. another message type typically requires external business context about the originating transaction. (medium confidence)
685    fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
686        ctx.external.evaluate("is_orders_reference")
687    }
688
689    /// [68] Wenn BGM+7 vorhanden
690    fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
691        ctx.has_qualifier("BGM", 0, "7")
692    }
693
694    /// [69] Wenn BGM+Z28 vorhanden
695    fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
696        ctx.has_qualifier("BGM", 0, "Z28")
697    }
698
699    /// [70] Wenn BGM+BK vorhanden
700    fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
701        ctx.has_qualifier("BGM", 0, "BK")
702    }
703
704    /// [71] Wenn BGM+Z39 vorhanden
705    fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
706        ctx.has_qualifier("BGM", 0, "Z39")
707    }
708
709    /// [72] Wenn SG9 PIA+5+1-b?:1.6.0/1-b?:3.6.0/1-b?:4.6.0/1-66?:13.6.0/1-66?:14.6.0 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
710    fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
711        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
712        ConditionResult::from(pias.iter().any(|s| {
713            s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
714                (v.starts_with("1-")
715                    && (v.ends_with(":1.6.0") || v.ends_with(":3.6.0") || v.ends_with(":4.6.0")))
716                    || v == "1-66:13.6.0"
717                    || v == "1-66:14.6.0"
718            })
719        }))
720    }
721
722    /// [73] Wenn SG9 PIA+5+1-b?:1.9.e/1-b?:3.9.0/1-b?:4.9.0/1-66?:13.9.0/1-66?:14.9.0 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien, e=Tarif: Wert gemäß Codeliste der OBIS-Kennzahlen und Me...
723    fn evaluate_73(&self, ctx: &EvaluationContext) -> ConditionResult {
724        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
725        ConditionResult::from(pias.iter().any(|s| {
726            s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
727                (v.starts_with("1-") && v.contains(":1.9."))
728                    || (v.starts_with("1-") && v.ends_with(":3.9.0"))
729                    || (v.starts_with("1-") && v.ends_with(":4.9.0"))
730                    || v == "1-66:13.9.0"
731                    || v == "1-66:14.9.0"
732            })
733        }))
734    }
735
736    /// [77] Wenn MP-ID in SG2 NAD+MR der RB HKN-R
737    /// EXTERNAL: Requires context from outside the message.
738    fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
739        ctx.external
740            .evaluate("recipient_is_hkn_registration_authority")
741    }
742
743    /// [78] Wenn SG9 PIA+5+1-66?:13.6.0/1-66?:14.6.0/1-66?:13.9.0/1-66?:14.9.0 vorhanden
744    // REVIEW: PIA+5 means elements[0][0]='5'. DE7140 (OBIS code) is elements[1][0]. EDIFACT '?:' escape sequences are resolved by the parser to literal ':', so '1-66?:13.6.0' becomes '1-66:13.6.0'. Checks any SG9 group instance for PIA+5 with one of the four OBIS codes. Group path SG6->SG9 is approximate based on MSCONS structure. (medium confidence)
745    fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
746        ctx.any_group_has_qualified_value(
747            "PIA",
748            0,
749            "5",
750            1,
751            0,
752            &["1-66:13.6.0", "1-66:14.6.0", "1-66:13.9.0", "1-66:14.9.0"],
753            &["SG6", "SG9"],
754        )
755    }
756
757    /// [79] Wenn SG9 PIA+5+1-66?:13.6.0/1-66?:14.6.0/1-66?:13.9.0/1-66?:14.9.0 nicht vorhanden
758    // REVIEW: Logical negation of condition 78. True when none of the specified OBIS codes appears in any SG9 PIA+5 segment. (medium confidence)
759    fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
760        match ctx.any_group_has_qualified_value(
761            "PIA",
762            0,
763            "5",
764            1,
765            0,
766            &["1-66:13.6.0", "1-66:14.6.0", "1-66:13.9.0", "1-66:14.9.0"],
767            &["SG6", "SG9"],
768        ) {
769            ConditionResult::True => ConditionResult::False,
770            ConditionResult::False => ConditionResult::True,
771            ConditionResult::Unknown => ConditionResult::Unknown,
772        }
773    }
774
775    /// [80] Wenn MP-ID in SG2 NAD+MR in der Rolle ÜNB
776    /// EXTERNAL: Requires context from outside the message.
777    fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
778        ctx.external.evaluate("recipient_is_uenb")
779    }
780
781    /// [83] Wenn in derselben SG9 LIN die Angabe STS+10+Z38 nicht vorhanden
782    // REVIEW: STS 'Grundlage der Energiemenge' has elements[0][0]='10' (Statuskategorie) and elements[1][0]=status code. STS lives in SG10 (child of SG9). Condition is True when NO STS with category 10 and status Z38 exists within SG9/SG10 scope. Using any_group_has_qualified_value on SG9->SG10 path then negating. Group scope is approximate — a precise check would traverse SG9 instances and their SG10 children. (medium confidence)
783    fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
784        match ctx.any_group_has_qualified_value("STS", 0, "10", 1, 0, &["Z38"], &["SG9", "SG10"]) {
785            ConditionResult::True => ConditionResult::False,
786            ConditionResult::False => ConditionResult::True,
787            ConditionResult::Unknown => ConditionResult::Unknown,
788        }
789    }
790
791    /// [84] Wenn in derselben SG9 LIN die Angabe STS+10+Z39 nicht vorhanden
792    // REVIEW: Same pattern as condition 83 but for status code Z39. True when no STS+10+Z39 is present in any SG10 within the SG9 scope. (medium confidence)
793    fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
794        match ctx.any_group_has_qualified_value("STS", 0, "10", 1, 0, &["Z39"], &["SG9", "SG10"]) {
795            ConditionResult::True => ConditionResult::False,
796            ConditionResult::False => ConditionResult::True,
797            ConditionResult::Unknown => ConditionResult::Unknown,
798        }
799    }
800
801    /// [85] Wenn in derselben SG9 LIN die Angabe STS+10+Z36 nicht vorhanden
802    // REVIEW: Same pattern as condition 83 but for status code Z36. True when no STS+10+Z36 is present in any SG10 within the SG9 scope. (medium confidence)
803    fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
804        match ctx.any_group_has_qualified_value("STS", 0, "10", 1, 0, &["Z36"], &["SG9", "SG10"]) {
805            ConditionResult::True => ConditionResult::False,
806            ConditionResult::False => ConditionResult::True,
807            ConditionResult::Unknown => ConditionResult::Unknown,
808        }
809    }
810
811    /// [86] Wenn in derselben SG9 LIN die Angabe STS+10+Z37 nicht vorhanden
812    // REVIEW: Same pattern as condition 83 but for status code Z37. True when no STS+10+Z37 is present in any SG10 within the SG9 scope. (medium confidence)
813    fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
814        match ctx.any_group_has_qualified_value("STS", 0, "10", 1, 0, &["Z37"], &["SG9", "SG10"]) {
815            ConditionResult::True => ConditionResult::False,
816            ConditionResult::False => ConditionResult::True,
817            ConditionResult::Unknown => ConditionResult::Unknown,
818        }
819    }
820
821    /// [87] Wenn der Wert in DTM+163 DE2380 derselben SG6 LOC+172 mit demselben Wert in SG9 PIA+5 DE7140 der früheste angegebene Zeitpunkt ist
822    // REVIEW: Checks that DTM+163 in SG6 (with LOC+172) equals the minimum DTM+163 across all SG10 instances beneath that SG6's SG9 children. Medium confidence because the SG9→SG10 child navigation uses a local child instance index (si) passed to the grandchild API, which may behave differently depending on navigator implementation details for two-level child traversal. (medium confidence)
823    fn evaluate_87(&self, ctx: &EvaluationContext) -> ConditionResult {
824        let nav = match ctx.navigator() {
825            Some(n) => n,
826            None => return ConditionResult::Unknown,
827        };
828        let sg6_count = nav.group_instance_count(&["SG5", "SG6"]);
829        for gi in 0..sg6_count {
830            let locs = nav.find_segments_in_group("LOC", &["SG5", "SG6"], gi);
831            let has_loc_172 = locs.iter().any(|s| {
832                s.elements
833                    .first()
834                    .and_then(|e| e.first())
835                    .is_some_and(|v| v == "172")
836            });
837            if !has_loc_172 {
838                continue;
839            }
840            let sg6_dtms = nav.find_segments_in_group("DTM", &["SG5", "SG6"], gi);
841            let sg6_val = match sg6_dtms
842                .iter()
843                .find(|s| {
844                    s.elements
845                        .first()
846                        .and_then(|e| e.first())
847                        .is_some_and(|v| v == "163")
848                })
849                .and_then(|s| s.elements.first().and_then(|e| e.get(1)))
850                .filter(|v| !v.is_empty())
851                .cloned()
852            {
853                Some(v) => v,
854                None => continue,
855            };
856            let sg9_count = nav.child_group_instance_count(&["SG5", "SG6"], gi, "SG9");
857            let mut sg10_values: Vec<String> = Vec::new();
858            for si in 0..sg9_count {
859                let sg10_count = nav.child_group_instance_count(&["SG5", "SG6", "SG9"], si, "SG10");
860                for ti in 0..sg10_count {
861                    let dtms = nav.find_segments_in_child_group(
862                        "DTM",
863                        &["SG5", "SG6", "SG9"],
864                        si,
865                        "SG10",
866                        ti,
867                    );
868                    for dtm in &dtms {
869                        if dtm
870                            .elements
871                            .first()
872                            .and_then(|e| e.first())
873                            .is_some_and(|v| v == "163")
874                        {
875                            if let Some(val) = dtm.elements.first().and_then(|e| e.get(1)) {
876                                if !val.is_empty() {
877                                    sg10_values.push(val.clone());
878                                }
879                            }
880                        }
881                    }
882                }
883            }
884            if let Some(min_val) = sg10_values.iter().min() {
885                if &sg6_val != min_val {
886                    return ConditionResult::False;
887                }
888            }
889        }
890        ConditionResult::True
891    }
892
893    /// [88] Wenn der Wert in DTM+164 DE2380 derselben SG6 LOC+172 mit demselben Wert in SG9 PIA+5 DE7140 der späteste angegebene Zeitpunkt ist
894    // REVIEW: Mirror of condition 87 but for DTM+164 (Ende Messperiode) — checks that the SG6 transmission period end equals the maximum DTM+164 across all SG10 measurement instances under that SG6. Same navigation caveat applies for two-level SG9→SG10 child traversal. (medium confidence)
895    fn evaluate_88(&self, ctx: &EvaluationContext) -> ConditionResult {
896        let nav = match ctx.navigator() {
897            Some(n) => n,
898            None => return ConditionResult::Unknown,
899        };
900        let sg6_count = nav.group_instance_count(&["SG5", "SG6"]);
901        for gi in 0..sg6_count {
902            let locs = nav.find_segments_in_group("LOC", &["SG5", "SG6"], gi);
903            let has_loc_172 = locs.iter().any(|s| {
904                s.elements
905                    .first()
906                    .and_then(|e| e.first())
907                    .is_some_and(|v| v == "172")
908            });
909            if !has_loc_172 {
910                continue;
911            }
912            let sg6_dtms = nav.find_segments_in_group("DTM", &["SG5", "SG6"], gi);
913            let sg6_val = match sg6_dtms
914                .iter()
915                .find(|s| {
916                    s.elements
917                        .first()
918                        .and_then(|e| e.first())
919                        .is_some_and(|v| v == "164")
920                })
921                .and_then(|s| s.elements.first().and_then(|e| e.get(1)))
922                .filter(|v| !v.is_empty())
923                .cloned()
924            {
925                Some(v) => v,
926                None => continue,
927            };
928            let sg9_count = nav.child_group_instance_count(&["SG5", "SG6"], gi, "SG9");
929            let mut sg10_values: Vec<String> = Vec::new();
930            for si in 0..sg9_count {
931                let sg10_count = nav.child_group_instance_count(&["SG5", "SG6", "SG9"], si, "SG10");
932                for ti in 0..sg10_count {
933                    let dtms = nav.find_segments_in_child_group(
934                        "DTM",
935                        &["SG5", "SG6", "SG9"],
936                        si,
937                        "SG10",
938                        ti,
939                    );
940                    for dtm in &dtms {
941                        if dtm
942                            .elements
943                            .first()
944                            .and_then(|e| e.first())
945                            .is_some_and(|v| v == "164")
946                        {
947                            if let Some(val) = dtm.elements.first().and_then(|e| e.get(1)) {
948                                if !val.is_empty() {
949                                    sg10_values.push(val.clone());
950                                }
951                            }
952                        }
953                    }
954                }
955            }
956            if let Some(max_val) = sg10_values.iter().max() {
957                if &sg6_val != max_val {
958                    return ConditionResult::False;
959                }
960            }
961        }
962        ConditionResult::True
963    }
964
965    /// [90] Wenn BGM+Z41 vorhanden
966    fn evaluate_90(&self, ctx: &EvaluationContext) -> ConditionResult {
967        ctx.has_qualifier("BGM", 0, "Z41")
968    }
969
970    /// [91] Wenn BGM+Z42 vorhanden
971    fn evaluate_91(&self, ctx: &EvaluationContext) -> ConditionResult {
972        ctx.has_qualifier("BGM", 0, "Z42")
973    }
974
975    /// [92] Wenn SG10 QTY DE6063 mit Wert 67 vorhanden
976    fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
977        ctx.any_group_has_qualifier("QTY", 0, "67", &["SG9", "SG10"])
978    }
979
980    /// [93] Wenn SG10 QTY DE6063 mit Wert 220 vorhanden
981    fn evaluate_93(&self, ctx: &EvaluationContext) -> ConditionResult {
982        ctx.any_group_has_qualifier("QTY", 0, "220", &["SG9", "SG10"])
983    }
984
985    /// [94] Wenn SG10 QTY DE6063 mit Wert 201 vorhanden
986    fn evaluate_94(&self, ctx: &EvaluationContext) -> ConditionResult {
987        ctx.any_group_has_qualifier("QTY", 0, "201", &["SG9", "SG10"])
988    }
989
990    /// [95] Wenn SG10 QTY DE6063 mit Wert 20 vorhanden
991    fn evaluate_95(&self, ctx: &EvaluationContext) -> ConditionResult {
992        ctx.any_group_has_qualifier("QTY", 0, "20", &["SG5", "SG10"])
993    }
994
995    /// [96] Wenn SG10 QTY DE6063 mit Wert Z18 vorhanden
996    fn evaluate_96(&self, ctx: &EvaluationContext) -> ConditionResult {
997        ctx.any_group_has_qualifier("QTY", 0, "Z18", &["SG5", "SG10"])
998    }
999
1000    /// [97] Wenn es sich um die Übermittlung eines Wertes aufgrund der Umstellung der Gasqualität handelt
1001    /// EXTERNAL: Requires context from outside the message.
1002    // REVIEW: Whether this transmission is caused by a gas quality changeover (H-Gas/L-Gas conversion) cannot be determined from EDIFACT message content alone — it is a business context condition. (medium confidence)
1003    fn evaluate_97(&self, ctx: &EvaluationContext) -> ConditionResult {
1004        ctx.external.evaluate("gas_quality_change_transmission")
1005    }
1006
1007    /// [98] Wenn SG9 PIA+5+SOL:Z08 vorhanden
1008    fn evaluate_98(&self, ctx: &EvaluationContext) -> ConditionResult {
1009        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1010        ConditionResult::from(pias.iter().any(|s| {
1011            s.elements
1012                .get(1)
1013                .and_then(|e| e.first())
1014                .is_some_and(|v| v == "SOL")
1015                && s.elements
1016                    .get(1)
1017                    .and_then(|e| e.get(1))
1018                    .is_some_and(|v| v == "Z08")
1019        }))
1020    }
1021
1022    /// [99] Wenn SG9 PIA+5+WID:Z08 vorhanden
1023    fn evaluate_99(&self, ctx: &EvaluationContext) -> ConditionResult {
1024        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1025        ConditionResult::from(pias.iter().any(|s| {
1026            s.elements
1027                .get(1)
1028                .and_then(|e| e.first())
1029                .is_some_and(|v| v == "WID")
1030                && s.elements
1031                    .get(1)
1032                    .and_then(|e| e.get(1))
1033                    .is_some_and(|v| v == "Z08")
1034        }))
1035    }
1036
1037    /// [100] Wenn in derselben SG9 LIN das PIA+5+AUA:Z08 vorhanden
1038    fn evaluate_100(&self, ctx: &EvaluationContext) -> ConditionResult {
1039        ctx.any_group_has_qualified_value("PIA", 0, "5", 1, 0, &["AUA"], &["SG5", "SG9"])
1040    }
1041
1042    /// [101] Wenn in derselben SG9 LIN das PIA+5+FPA:Z08 vorhanden
1043    fn evaluate_101(&self, ctx: &EvaluationContext) -> ConditionResult {
1044        ctx.any_group_has_qualified_value("PIA", 0, "5", 1, 0, &["FPA"], &["SG5", "SG9"])
1045    }
1046
1047    /// [108] wenn SG9 PIA+5+7-b?:99.41.16/7-b?:99.42.16 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
1048    // REVIEW: Checks PIA+5 for OBIS codes matching pattern '7-{channel}:99.41.16' or '7-{channel}:99.42.16' (Wirkenergie-Kennzahlen). The '?' in AHB notation is the EDIFACT release character escaping the colon; parsed segments will contain literal colon. Channel variable 'b' matched via starts_with("7-"). (medium confidence)
1049    fn evaluate_108(&self, ctx: &EvaluationContext) -> ConditionResult {
1050        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1051        ConditionResult::from(pias.iter().any(|s| {
1052            let obis = match s.elements.get(1).and_then(|e| e.first()) {
1053                Some(v) => v.as_str(),
1054                None => return false,
1055            };
1056            let code_list = s
1057                .elements
1058                .get(1)
1059                .and_then(|e| e.get(1))
1060                .map(|v| v.as_str())
1061                .unwrap_or("");
1062            code_list == "Z08"
1063                && obis.starts_with("7-")
1064                && (obis.contains(":99.41.16") || obis.contains(":99.42.16"))
1065        }))
1066    }
1067
1068    /// [111] Wenn SG10 DTM+9 DE2379 in demselben Segment mit Wert 303 vorhanden
1069    fn evaluate_111(&self, ctx: &EvaluationContext) -> ConditionResult {
1070        let dtms = ctx.find_segments_with_qualifier("DTM", 0, "9");
1071        ConditionResult::from(dtms.iter().any(|s| {
1072            s.elements
1073                .first()
1074                .and_then(|e| e.get(2))
1075                .is_some_and(|v| v == "303")
1076        }))
1077    }
1078
1079    /// [113] wenn SG7 RFF+AGK (Konfigurations-ID) vorhanden
1080    fn evaluate_113(&self, ctx: &EvaluationContext) -> ConditionResult {
1081        ctx.has_qualifier("RFF", 0, "AGK")
1082    }
1083
1084    /// [117] Nur MP-ID aus Sparte Strom
1085    /// EXTERNAL: Requires context from outside the message.
1086    fn evaluate_117(&self, ctx: &EvaluationContext) -> ConditionResult {
1087        ctx.external.evaluate("marktpartner_is_strom")
1088    }
1089
1090    /// [118] Nur MP-ID aus Sparte Gas
1091    /// EXTERNAL: Requires context from outside the message.
1092    fn evaluate_118(&self, ctx: &EvaluationContext) -> ConditionResult {
1093        ctx.external.evaluate("marktpartner_is_gas")
1094    }
1095
1096    /// [119] wenn in SG6 LOC+172 DE3225 die ID der Marktlokation angegeben ist
1097    fn evaluate_119(&self, ctx: &EvaluationContext) -> ConditionResult {
1098        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
1099        match locs.first() {
1100            Some(loc) => ConditionResult::from(
1101                loc.elements
1102                    .get(1)
1103                    .and_then(|e| e.first())
1104                    .is_some_and(|v| !v.is_empty()),
1105            ),
1106            None => ConditionResult::False,
1107        }
1108    }
1109
1110    /// [121] wenn BGM+Z43 (Redispatch Ausfallarbeitüberführungszeitreihe) vorhanden
1111    fn evaluate_121(&self, ctx: &EvaluationContext) -> ConditionResult {
1112        ctx.has_qualifier("BGM", 0, "Z43")
1113    }
1114
1115    /// [125] wenn SG9 PIA+5+7-0?:52.0.22/7-b?:53.0.16/7-b?:55.0.16/7-b?:55.0.20/7-b?:55.0.22 (b=Kanal: Wert gemäß Codeliste der OBIS-Kennzahlen und Medien) vorhanden
1116    // REVIEW: Checks PIA+5 for specific OBIS code patterns: '7-0:52.0.22' (fixed channel 0), '7-{b}:53.0.16', '7-{b}:55.0.16', '7-{b}:55.0.20', '7-{b}:55.0.22' (variable channel). These are Blindenergie/Leistungsmessungs-Kennzahlen. Uses contains() to match the OBIS suffix after the channel variable. (medium confidence)
1117    fn evaluate_125(&self, ctx: &EvaluationContext) -> ConditionResult {
1118        let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1119        ConditionResult::from(pias.iter().any(|s| {
1120            let obis = match s.elements.get(1).and_then(|e| e.first()) {
1121                Some(v) => v.as_str(),
1122                None => return false,
1123            };
1124            let code_list = s
1125                .elements
1126                .get(1)
1127                .and_then(|e| e.get(1))
1128                .map(|v| v.as_str())
1129                .unwrap_or("");
1130            code_list == "Z08"
1131                && obis.starts_with("7-")
1132                && (obis.contains(":52.0.22")
1133                    || obis.contains(":53.0.16")
1134                    || obis.contains(":55.0.16")
1135                    || obis.contains(":55.0.20")
1136                    || obis.contains(":55.0.22"))
1137        }))
1138    }
1139
1140    /// [128] Wenn es sich um eine Ablesung handelt, welche keine Ablesung aufgrund der Änderung an der Messtechnik oder deren Konfiguration ist (z.B. Kundenablesung).
1141    // REVIEW: A regular reading (e.g. Kundenablesung) is distinguished from a meter-change reading by the absence of DTM+60 (Ausführungs-/Änderungszeitpunkt) in the same SG10. DTM+9 presence confirms it is a reading at all; DTM+60 absence confirms it is not due to a meter/config change. Group path SG9→SG10 matches MSCONS LIN-group context; fallback to message-wide if no navigator. (medium confidence)
1142    fn evaluate_128(&self, ctx: &EvaluationContext) -> ConditionResult {
1143        // True when SG10 has a reading date (DTM+9, Ablesedatum) but no device change timestamp (DTM+60),
1144        // indicating a regular reading not caused by meter change or configuration change
1145        ctx.any_group_has_qualifier_without("DTM", 0, "9", "DTM", 0, "60", &["SG9", "SG10"])
1146    }
1147
1148    /// [129] Wenn es sich um eine Ablesung aufgrund der Änderung an der Messtechnik oder deren Konfiguration handelt (z.B. Gerätewechsel).
1149    // REVIEW: A meter-change reading is identified by the co-occurrence of DTM+9 (Ablesedatum) and DTM+60 (Ausführungs-/Änderungszeitpunkt) within the same SG10 instance. Uses direct navigator iteration to check co-occurrence within a single group instance (any_group_has_co_occurrence would have ambiguity for same-tag different qualifiers). Falls back to message-wide check without navigator. (medium confidence)
1150    fn evaluate_129(&self, ctx: &EvaluationContext) -> ConditionResult {
1151        // True when SG10 contains both DTM+9 (Ablesedatum) and DTM+60 (Ausführungs-/Änderungszeitpunkt),
1152        // indicating a reading caused by a meter change or configuration change (e.g. Gerätewechsel)
1153        let nav = match ctx.navigator() {
1154            Some(n) => n,
1155            None => {
1156                let has9 = ctx.has_qualifier("DTM", 0, "9");
1157                let has60 = ctx.has_qualifier("DTM", 0, "60");
1158                return match (has9, has60) {
1159                    (ConditionResult::True, ConditionResult::True) => ConditionResult::True,
1160                    (ConditionResult::Unknown, _) | (_, ConditionResult::Unknown) => {
1161                        ConditionResult::Unknown
1162                    }
1163                    _ => ConditionResult::False,
1164                };
1165            }
1166        };
1167        let sg10_count = nav.group_instance_count(&["SG9", "SG10"]);
1168        for i in 0..sg10_count {
1169            let dtms = nav.find_segments_in_group("DTM", &["SG9", "SG10"], i);
1170            let has9 = dtms.iter().any(|s| {
1171                s.elements
1172                    .first()
1173                    .and_then(|e| e.first())
1174                    .is_some_and(|v| v == "9")
1175            });
1176            let has60 = dtms.iter().any(|s| {
1177                s.elements
1178                    .first()
1179                    .and_then(|e| e.first())
1180                    .is_some_and(|v| v == "60")
1181            });
1182            if has9 && has60 {
1183                return ConditionResult::True;
1184            }
1185        }
1186        ConditionResult::False
1187    }
1188
1189    /// [131] wenn RFF+AGK (Konfigurations-ID) nicht vorhanden
1190    fn evaluate_131(&self, ctx: &EvaluationContext) -> ConditionResult {
1191        ctx.lacks_qualifier("RFF", 0, "AGK")
1192    }
1193
1194    /// [132] wenn LOC+172 (Identifikationsangabe) DE3225 nicht vorhanden
1195    // REVIEW: Checks if LOC+172 is absent entirely, or if DE3225 (elements[1][0], the Bezeichnung) is empty/missing within the LOC+172 segment. Returns True in either case as the condition is 'DE3225 nicht vorhanden'. (medium confidence)
1196    fn evaluate_132(&self, ctx: &EvaluationContext) -> ConditionResult {
1197        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
1198        match locs.first() {
1199            None => ConditionResult::True,
1200            Some(loc) => {
1201                let de3225_absent = loc
1202                    .elements
1203                    .get(1)
1204                    .and_then(|e| e.first())
1205                    .map(|v| v.is_empty())
1206                    .unwrap_or(true);
1207                ConditionResult::from(de3225_absent)
1208            }
1209        }
1210    }
1211
1212    /// [133] Wenn innerhalb desselben LIN-Segments neben diesem Segment (SG10 DTM+7 Nutzungszeitpunkt) noch das SG10 DTM+9 (Ablesedatum) mit dem Code 102 im DE2379 vorhanden ist, darf der Wert der Differenz zwi...
1213    // REVIEW: Checks that when DTM+7 (Nutzungszeitpunkt, format 303 CCYYMMDDHHMM) and DTM+9 (Ablesedatum, format 102 CCYYMMDD) co-exist in the same SG10 instance, the CCYYMMDD date part of DTM+7 must equal or be at most 1 calendar day different from DTM+9. Uses a Julian Day Number formula for correct cross-month/year date arithmetic. MSCONS SG10 path assumed as SG5.SG6.SG10 based on the tx_group=SG5 and SG6 being the measurement period container; medium confidence due to path uncertainty. (medium confidence)
1214    fn evaluate_133(&self, ctx: &EvaluationContext) -> ConditionResult {
1215        let nav = match ctx.navigator() {
1216            Some(n) => n,
1217            None => return ConditionResult::Unknown,
1218        };
1219        fn date_to_days(s: &str) -> Option<i64> {
1220            if s.len() < 8 {
1221                return None;
1222            }
1223            let y: i64 = s[..4].parse().ok()?;
1224            let m: i64 = s[4..6].parse().ok()?;
1225            let d: i64 = s[6..8].parse().ok()?;
1226            if m < 1 || m > 12 || d < 1 || d > 31 {
1227                return None;
1228            }
1229            let a = (14 - m) / 12;
1230            let yy = y + 4800 - a;
1231            let mm = m + 12 * a - 3;
1232            Some(d + (153 * mm + 2) / 5 + 365 * yy + yy / 4 - yy / 100 + yy / 400 - 32045)
1233        }
1234        let sg10_count = nav.group_instance_count(&["SG5", "SG6", "SG10"]);
1235        if sg10_count == 0 {
1236            return ConditionResult::Unknown;
1237        }
1238        for i in 0..sg10_count {
1239            let dtms = nav.find_segments_in_group("DTM", &["SG5", "SG6", "SG10"], i);
1240            let dtm7_val = dtms
1241                .iter()
1242                .find(|s| {
1243                    s.elements
1244                        .first()
1245                        .and_then(|e| e.first())
1246                        .is_some_and(|v| v == "7")
1247                })
1248                .and_then(|s| s.elements.first())
1249                .and_then(|e| e.get(1))
1250                .cloned();
1251            let dtm9_val = dtms
1252                .iter()
1253                .find(|s| {
1254                    s.elements
1255                        .first()
1256                        .and_then(|e| e.first())
1257                        .is_some_and(|v| v == "9")
1258                        && s.elements
1259                            .first()
1260                            .and_then(|e| e.get(2))
1261                            .is_some_and(|v| v == "102")
1262                })
1263                .and_then(|s| s.elements.first())
1264                .and_then(|e| e.get(1))
1265                .cloned();
1266            let (v7, v9) = match (dtm7_val, dtm9_val) {
1267                (Some(v7), Some(v9)) if !v7.is_empty() && !v9.is_empty() => (v7, v9),
1268                _ => continue,
1269            };
1270            let date7 = if v7.len() >= 8 { &v7[..8] } else { continue };
1271            let date9 = if v9.len() >= 8 { &v9[..8] } else { v9.as_str() };
1272            let days7 = match date_to_days(date7) {
1273                Some(d) => d,
1274                None => return ConditionResult::Unknown,
1275            };
1276            let days9 = match date_to_days(date9) {
1277                Some(d) => d,
1278                None => return ConditionResult::Unknown,
1279            };
1280            let diff = (days7 - days9).abs();
1281            if diff > 1 {
1282                return ConditionResult::False;
1283            }
1284        }
1285        ConditionResult::True
1286    }
1287
1288    /// [134] Wenn SG10 DTM+9 DE2379 in demselben Segment mit Wert 102 vorhanden
1289    fn evaluate_134(&self, ctx: &EvaluationContext) -> ConditionResult {
1290        ctx.has_qualified_value("DTM", 0, "9", 0, 2, &["102"])
1291    }
1292
1293    /// [135] Der Wert an der Stelle CCYYMMDD muss ≤ dem Wert an der Stelle CCYYMMDD im DE2380 des DTM+137 sein
1294    // REVIEW: Condition 135 compares the CCYYMMDD portion of a reading-date DTM against DTM+137 (Nachrichtendatum at message level). The most natural target is DTM+9 (Ablesedatum) in SG10. Both format 303 (CCYYMMDDhhmm+ZZ) and format 102 (CCYYMMDD) start with CCYYMMDD, so taking the first 8 characters works for both. DTM+137 uses format 303 so likewise the first 8 chars give the date. Lexicographic comparison of zero-padded CCYYMMDD strings is equivalent to numeric date comparison. Medium confidence because the condition description does not explicitly state which DTM qualifier it applies to; DTM+9 is the strongest candidate in the MSCONS SG10 context. (medium confidence)
1295    fn evaluate_135(&self, ctx: &EvaluationContext) -> ConditionResult {
1296        // The CCYYMMDD portion of the DTM value in the current context must be <= CCYYMMDD of DTM+137 (Nachrichtendatum).
1297        // Applied to DTM+9 (Ablesedatum) readings in SG10 — format 303 and 102 both start with CCYYMMDD.
1298        let dtm_137_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1299        let threshold: String = match dtm_137_segs
1300            .first()
1301            .and_then(|s| s.elements.first())
1302            .and_then(|e| e.get(1))
1303        {
1304            Some(v) => v.chars().take(8).collect(),
1305            None => return ConditionResult::Unknown,
1306        };
1307        let dtm9_segs = ctx.find_segments_with_qualifier("DTM", 0, "9");
1308        if dtm9_segs.is_empty() {
1309            return ConditionResult::Unknown;
1310        }
1311        for dtm in &dtm9_segs {
1312            let val = match dtm.elements.first().and_then(|e| e.get(1)) {
1313                Some(v) => v,
1314                None => continue,
1315            };
1316            let date_part: String = val.chars().take(8).collect();
1317            if date_part > threshold {
1318                return ConditionResult::False;
1319            }
1320        }
1321        ConditionResult::True
1322    }
1323
1324    /// [138] Wenn es sich um eine Korrekturenergiemenge auf einen Wert aus einem iMS handelt
1325    /// EXTERNAL: Requires context from outside the message.
1326    fn evaluate_138(&self, ctx: &EvaluationContext) -> ConditionResult {
1327        ctx.external.evaluate("is_correction_energy_from_ims")
1328    }
1329
1330    /// [141] Wenn MP-ID in SG2 NAD+MR in der Rolle MGV
1331    /// EXTERNAL: Requires context from outside the message.
1332    fn evaluate_141(&self, ctx: &EvaluationContext) -> ConditionResult {
1333        ctx.external.evaluate("recipient_is_mgv")
1334    }
1335
1336    /// [142] wenn im DE3155 in demselben COM der Code EM vorhanden ist
1337    fn evaluate_142(&self, ctx: &EvaluationContext) -> ConditionResult {
1338        let coms = ctx.find_segments("COM");
1339        ConditionResult::from(coms.iter().any(|s| {
1340            s.elements
1341                .first()
1342                .and_then(|e| e.get(1))
1343                .is_some_and(|v| v == "EM")
1344        }))
1345    }
1346
1347    /// [143] wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
1348    fn evaluate_143(&self, ctx: &EvaluationContext) -> ConditionResult {
1349        let coms = ctx.find_segments("COM");
1350        ConditionResult::from(coms.iter().any(|s| {
1351            s.elements
1352                .first()
1353                .and_then(|e| e.get(1))
1354                .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
1355        }))
1356    }
1357
1358    /// [144] Wenn Wert in SG7 RFF+AGK DE1154 (Konfigurations-ID) vorhanden
1359    fn evaluate_144(&self, ctx: &EvaluationContext) -> ConditionResult {
1360        let rffs = ctx.find_segments_with_qualifier("RFF", 0, "AGK");
1361        match rffs.first() {
1362            None => ConditionResult::False,
1363            Some(rff) => {
1364                let has_value = rff
1365                    .elements
1366                    .first()
1367                    .and_then(|e| e.get(1))
1368                    .map(|v| !v.is_empty())
1369                    .unwrap_or(false);
1370                ConditionResult::from(has_value)
1371            }
1372        }
1373    }
1374
1375    /// [145] Wenn in derselben S9 LIN das SG10 DTM+163 (Beginn Messperiode) nicht vorhanden ist.
1376    // REVIEW: Checks absence of DTM+163 (Beginn Messperiode) in the message. The condition references the same SG10 context within the enclosing LIN group; without the exact MSCONS group path this is a message-wide approximation. Note: DTM+163 also appears in SG6 (Übertragungszeitraum), so this may yield false-negatives for the SG10-scoped intent. (medium confidence)
1377    fn evaluate_145(&self, ctx: &EvaluationContext) -> ConditionResult {
1378        ctx.lacks_qualifier("DTM", 0, "163")
1379    }
1380
1381    /// [146] Wenn es bei dem zu übermittelnden Wert um einen Wert zu einem Zeitpunkt handelt.
1382    // REVIEW: A point-in-time value (Wert zu einem Zeitpunkt) is signalled by the presence of DTM+7 (Nutzungszeitpunkt) in SG10. If this DTM qualifier is present, the transmitted value represents a single point in time rather than an interval. (medium confidence)
1383    fn evaluate_146(&self, ctx: &EvaluationContext) -> ConditionResult {
1384        ctx.has_qualifier("DTM", 0, "7")
1385    }
1386
1387    /// [147] Wenn in derselben S9 LIN das SG10 DTM+7 (Nutzungszeitpunkt) nicht vorhanden ist.
1388    // REVIEW: Checks absence of DTM+7 (Nutzungszeitpunkt) in the message, approximating the SG10-scoped check within the enclosing LIN group. Absence means the value is not a point-in-time value. (medium confidence)
1389    fn evaluate_147(&self, ctx: &EvaluationContext) -> ConditionResult {
1390        ctx.lacks_qualifier("DTM", 0, "7")
1391    }
1392
1393    /// [148] Wenn es bei dem zu übermittelnden Wert um einen Wert in einem Zeitintervall handelt.
1394    // REVIEW: An interval value (Wert in einem Zeitintervall) is signalled by the presence of DTM+163 (Beginn Messperiode) in SG10. If Beginn Messperiode is present, the value spans a time interval rather than being a single point. (medium confidence)
1395    fn evaluate_148(&self, ctx: &EvaluationContext) -> ConditionResult {
1396        ctx.has_qualifier("DTM", 0, "163")
1397    }
1398
1399    /// [149] Wenn in derselben S9 LIN das SG10 DTM+163 (Beginn Messperiode) vorhanden ist.
1400    // REVIEW: Checks presence of DTM+163 (Beginn Messperiode) — the direct inverse of condition 145. Message-wide approximation of the SG10-scoped intent within the enclosing LIN group. (medium confidence)
1401    fn evaluate_149(&self, ctx: &EvaluationContext) -> ConditionResult {
1402        ctx.has_qualifier("DTM", 0, "163")
1403    }
1404
1405    /// [150] Wenn BGM+Z69 (Redispatch tägliche Ausfallarbeitsüberführungszeitreihe) vorhanden.
1406    fn evaluate_150(&self, ctx: &EvaluationContext) -> ConditionResult {
1407        ctx.has_qualifier("BGM", 0, "Z69")
1408    }
1409
1410    /// [490] wenn Wert in diesem DE, an der Stelle CCYYMMDDHHMM ein Zeitpunkt aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Übersicht gesetzliche deutsche Sommerzeit (MESZ)“ der Spalten: „Sommerzei...
1411    fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
1412        let dtm_segs = ctx.find_segments("DTM");
1413        match dtm_segs
1414            .first()
1415            .and_then(|s| s.elements.first())
1416            .and_then(|e| e.get(1))
1417        {
1418            Some(val) => is_mesz_utc(val),
1419            None => ConditionResult::False, // segment absent → condition not applicable
1420        }
1421    }
1422
1423    /// [491] wenn Wert in diesem DE, an der Stelle CCYYMMDDHHMM ein Zeitpunkt aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Übersicht gesetzliche deutsche Zeit (MEZ)“ der Spalten: „Winterzeit (MEZ)...
1424    // REVIEW: Checks whether the datetime value falls within German winter time (MEZ, UTC+1) using the is_mez_utc helper. Medium confidence because 'diesem DE' is context-dependent — without knowing the exact field the condition is applied to in the AHB table, we check the first DTM value available. The is_mez_utc helper applies the EU DST rule to determine MEZ vs MESZ. (medium confidence)
1425    fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1426        let dtm_segs = ctx.find_segments("DTM");
1427        match dtm_segs
1428            .first()
1429            .and_then(|s| s.elements.first())
1430            .and_then(|e| e.get(1))
1431        {
1432            Some(val) => is_mez_utc(val),
1433            None => ConditionResult::False, // segment absent → condition not applicable
1434        }
1435    }
1436
1437    /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
1438    /// EXTERNAL: Requires context from outside the message.
1439    fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1440        ctx.external.evaluate("recipient_is_electricity_sector")
1441    }
1442
1443    /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
1444    /// EXTERNAL: Requires context from outside the message.
1445    fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1446        ctx.external.evaluate("recipient_is_gas_sector")
1447    }
1448
1449    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt.
1450    // REVIEW: 'Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt' — the date in this DE must be the document creation time or earlier. Implemented by comparing the target DTM value to DTM+137 (Nachrichtendatum / document creation date). Medium confidence because 'this DE' is context-dependent without the full AHB table cross-reference. String comparison on CCYYMMDDHHMM format is lexicographically valid for datetime ordering. (medium confidence)
1451    fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
1452        // The date in this DE must be the document creation time or a point before it
1453        // Verify by checking that the relevant DTM value <= DTM+137 (document creation date)
1454        let dtm_137_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1455        let creation_val = match dtm_137_segs
1456            .first()
1457            .and_then(|s| s.elements.first())
1458            .and_then(|e| e.get(1))
1459        {
1460            Some(v) if !v.is_empty() => v.clone(),
1461            _ => return ConditionResult::Unknown,
1462        };
1463        // Find the first non-137 DTM in the message to compare against creation date
1464        let all_dtms = ctx.find_segments("DTM");
1465        for dtm in &all_dtms {
1466            let qual = dtm
1467                .elements
1468                .first()
1469                .and_then(|e| e.first())
1470                .map(|s| s.as_str());
1471            if qual != Some("137") {
1472                if let Some(val) = dtm.elements.first().and_then(|e| e.get(1)) {
1473                    if !val.is_empty() {
1474                        return ConditionResult::from(val.as_str() <= creation_val.as_str());
1475                    }
1476                }
1477            }
1478        }
1479        ConditionResult::Unknown
1480    }
1481
1482    /// [495] Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein
1483    // REVIEW: 'Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein' — the timestamp must be ≤ DTM+137's DE2380 value. The reference segment is explicitly DTM+137 (elements[0][0]=137, value at elements[0][1]). Implemented by extracting DTM+137's value and comparing the target timestamp (DTM+163 as the most likely MSCONS candidate for this constraint) using lexicographic string ordering, which is valid for CCYYMMDDHHMM format 303 strings. Medium confidence because the specific source DE this condition is attached to in the AHB is ambiguous without the full AHB table. (medium confidence)
1484    fn evaluate_495(&self, ctx: &EvaluationContext) -> ConditionResult {
1485        let dtm_137_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1486        let threshold = match dtm_137_segs
1487            .first()
1488            .and_then(|s| s.elements.first())
1489            .and_then(|e| e.get(1))
1490        {
1491            Some(v) if !v.is_empty() => v.clone(),
1492            _ => return ConditionResult::Unknown,
1493        };
1494        // The timestamp in this DE must be <= DE2380 of DTM+137
1495        // For MSCONS, the most likely candidate is DTM+163 (Beginn Messperiode)
1496        let dtm_163_segs = ctx.find_segments_with_qualifier("DTM", 0, "163");
1497        match dtm_163_segs
1498            .first()
1499            .and_then(|s| s.elements.first())
1500            .and_then(|e| e.get(1))
1501        {
1502            Some(val) if !val.is_empty() => {
1503                ConditionResult::from(val.as_str() <= threshold.as_str())
1504            }
1505            _ => ConditionResult::Unknown,
1506        }
1507    }
1508
1509    /// [501] Hinweis: Es sind nur die Werte erlaubt, die in der EDI@Energy Codeliste der OBIS-Kennzahlen und Medien mit dem entsprechenden Prüfidentifikator versehen sind.
1510    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1511        // Hinweis: Informational note about allowed OBIS codes — always applies unconditionally
1512        ConditionResult::True
1513    }
1514
1515    /// [502] Hinweis: Einmal für die Energiemenge von Beginn des Kalenderjahres bis zum Lieferbeginn und bis zu zweimal für die zwei höchsten Monatsleistungswerte (wegen KAV) von Beginn des Kalenderjahres bi...
1516    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1517        // Hinweis: Informational note about cardinality (once for energy from start of calendar year,
1518        // up to twice for two highest monthly peak values due to KAV) — always applies unconditionally
1519        ConditionResult::True
1520    }
1521
1522    /// [506] Hinweis: Nur bei Einspeisemengen und bei Gas zur stündlichen Energiedatenübermittlung
1523    fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
1524        // Hinweis: Informational note — only for feed-in quantities and gas hourly energy data transmission
1525        // This is an informational annotation, not a boolean predicate — always applies
1526        ConditionResult::True
1527    }
1528
1529    /// [509] Hinweis: Falls es sich um eine Korrekturenergiemenge handelt, ist hier die Referenz auf die MSCONS anzugeben, in der der Zählerstand vorab übermittelt wurde.
1530    fn evaluate_509(&self, _ctx: &EvaluationContext) -> ConditionResult {
1531        // Hinweis: Informational note — for correction energy amounts, reference the prior MSCONS
1532        // in which the meter reading was transmitted. Informational annotation, always applies.
1533        ConditionResult::True
1534    }
1535
1536    /// [510] Hinweis: Verwendung der ID der Messlokation
1537    fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
1538        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1539        ConditionResult::True
1540    }
1541
1542    /// [511] Hinweis: Verwendung der ID des MaBiS-ZP
1543    fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
1544        // Hinweis: Verwendung der ID des MaBiS-ZP — informational note, always applies
1545        ConditionResult::True
1546    }
1547
1548    /// [512] Hinweis: Verwendung der Bilanzkreisbezeichnung
1549    fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
1550        // Hinweis: Verwendung der Bilanzkreisbezeichnung — informational note, always applies
1551        ConditionResult::True
1552    }
1553
1554    /// [513] Hinweis: Verwendung der Bezeichnung des Bilanzierungsgebietes
1555    fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
1556        // Hinweis: Verwendung der Bezeichnung des Bilanzierungsgebietes — informational note, always applies
1557        ConditionResult::True
1558    }
1559
1560    /// [514] Hinweis: Verwendung der ID der Marktlokation
1561    fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
1562        // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1563        ConditionResult::True
1564    }
1565
1566    /// [515] Hinweis: Verwendung der Profilbezeichnung
1567    fn evaluate_515(&self, _ctx: &EvaluationContext) -> ConditionResult {
1568        // Hinweis: Verwendung der Profilbezeichnung — informational note, always applies
1569        ConditionResult::True
1570    }
1571
1572    /// [516] Hinweis: Verwendung der Bezeichnung der Profilschar
1573    fn evaluate_516(&self, _ctx: &EvaluationContext) -> ConditionResult {
1574        // Hinweis: Verwendung der Bezeichnung der Profilschar — informational note, always applies
1575        ConditionResult::True
1576    }
1577
1578    /// [518] Hinweis: Verwendung der ID der Tranche
1579    fn evaluate_518(&self, _ctx: &EvaluationContext) -> ConditionResult {
1580        // Hinweis: Verwendung der ID der Tranche — informational note, always applies
1581        ConditionResult::True
1582    }
1583
1584    /// [519] Hinweis: Nur wenn der gemessene Lastgang der Messlokation nicht dem Lastgang der Marktlokation 1:1 entspricht.
1585    fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
1586        // Hinweis: Nur wenn der gemessene Lastgang der Messlokation nicht dem Lastgang der Marktlokation 1:1 entspricht — informational note, always applies
1587        ConditionResult::True
1588    }
1589
1590    /// [520] Hinweis: Wenn es sich um eine 1:1 Beziehung zwischen Messlokation und Marktlokation handelt und der gemessene Lastgang der Messlokation dem Lastgang der Marktlokation 1:1 entspricht, oder wenn der ...
1591    fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
1592        // Hinweis: Wenn es sich um eine 1:1 Beziehung zwischen Messlokation und Marktlokation handelt... — informational note, always applies
1593        ConditionResult::True
1594    }
1595
1596    /// [522] Hinweis: Nur für die Übermittlung der Korrekturenergiemengen im Zeitintervall zwischen zwei Messwerten.
1597    fn evaluate_522(&self, _ctx: &EvaluationContext) -> ConditionResult {
1598        // Hinweis: Nur für die Übermittlung der Korrekturenergiemengen im Zeitintervall zwischen zwei Messwerten.
1599        ConditionResult::True
1600    }
1601
1602    /// [523] Hinweis: Nur für die Übermittlung der Energiemenge im Zeitintervall zwischen zwei Messwerten vor der Netznutzungsabrechnung.
1603    fn evaluate_523(&self, _ctx: &EvaluationContext) -> ConditionResult {
1604        // Hinweis: Nur für die Übermittlung der Energiemenge im Zeitintervall zwischen zwei Messwerten vor der Netznutzungsabrechnung.
1605        ConditionResult::True
1606    }
1607
1608    /// [524] Hinweis: Nur, wenn es sich um die Übermittlung von Abrechnungsbrennwert und Z-Zahl für den vom Lieferanten über eine Geschäftsdatenanfrage angeforderten Zeitraum handelt.
1609    fn evaluate_524(&self, _ctx: &EvaluationContext) -> ConditionResult {
1610        // Hinweis: Nur, wenn es sich um die Übermittlung von Abrechnungsbrennwert und Z-Zahl für den vom Lieferanten über eine Geschäftsdatenanfrage angeforderten Zeitraum handelt.
1611        ConditionResult::True
1612    }
1613
1614    /// [525] Hinweis: Nur für die Übermittlung der Energiemenge im Zeitintervall für eine Marktlokation ohne Messlokation (Pauschalanlage) wenn eines der Ereignisse aus Kapitel 4.2 eingetreten ist.
1615    fn evaluate_525(&self, _ctx: &EvaluationContext) -> ConditionResult {
1616        // Hinweis: Nur für die Übermittlung der Energiemenge im Zeitintervall für eine Marktlokation ohne Messlokation (Pauschalanlage) wenn eines der Ereignisse aus Kapitel 4.2 eingetreten ist.
1617        ConditionResult::True
1618    }
1619
1620    /// [526] Hinweis: Wert aus BGM+Z24 DE1004 der ORDERS mit der die Allokationsliste bestellt wurde.
1621    fn evaluate_526(&self, _ctx: &EvaluationContext) -> ConditionResult {
1622        // Hinweis: Wert aus BGM+Z24 DE1004 der ORDERS mit der die Allokationsliste bestellt wurde.
1623        ConditionResult::True
1624    }
1625
1626    /// [528] Hinweis: Wert aus BGM+Z28 DE1004 der ORDERS mit der die Anforderung von Messwerten erfolgt ist.
1627    fn evaluate_528(&self, _ctx: &EvaluationContext) -> ConditionResult {
1628        // Hinweis: Wert aus BGM+Z28 DE1004 der ORDERS mit der die Anforderung von Messwerten erfolgt ist.
1629        // Informational note, always applies
1630        ConditionResult::True
1631    }
1632
1633    /// [529] Hinweis: Wert aus BGM+7 DE1004 der ORDERS mit der die Anforderung von Messwerten erfolgt ist.
1634    fn evaluate_529(&self, _ctx: &EvaluationContext) -> ConditionResult {
1635        // Hinweis: Wert aus BGM+7 DE1004 der ORDERS mit der die Anforderung von Messwerten erfolgt ist.
1636        // Informational note, always applies
1637        ConditionResult::True
1638    }
1639
1640    /// [530] Hinweis: Wert aus SG4 IDE+24 DE7402 der UTILMD mit dem der Sender der MSCONS die vorherigen Stammdaten mittels UTILMD übermittelt hat.
1641    fn evaluate_530(&self, _ctx: &EvaluationContext) -> ConditionResult {
1642        // Hinweis: Wert aus SG4 IDE+24 DE7402 der UTILMD mit dem der Sender der MSCONS die vorherigen Stammdaten mittels UTILMD übermittelt hat.
1643        // Informational note, always applies
1644        ConditionResult::True
1645    }
1646
1647    /// [531] Hinweis: Wert aus BGM+7 DE1004 der MSCONS mit der der Zählerstand übermittelt wurde.
1648    fn evaluate_531(&self, _ctx: &EvaluationContext) -> ConditionResult {
1649        // Hinweis: Wert aus BGM+7 DE1004 der MSCONS mit der der Zählerstand übermittelt wurde.
1650        // Informational note, always applies
1651        ConditionResult::True
1652    }
1653
1654    /// [532] Hinweis: Wert aus BGM+7/Z27/Z28/270/Z41/Z42/Z85 DE1004 der MSCONS Nachricht, die storniert wird
1655    fn evaluate_532(&self, _ctx: &EvaluationContext) -> ConditionResult {
1656        // Hinweis: Wert aus BGM+7/Z27/Z28/270/Z41/Z42/Z85 DE1004 der MSCONS Nachricht, die storniert wird
1657        // Informational note, always applies
1658        ConditionResult::True
1659    }
1660
1661    /// [535] Hinweis: Verwendung der ID des Netzkoppelpunktes Strom/Gas
1662    fn evaluate_535(&self, _ctx: &EvaluationContext) -> ConditionResult {
1663        // Hinweis: Verwendung der ID des Netzkoppelpunktes Strom/Gas — informational note, always applies
1664        ConditionResult::True
1665    }
1666
1667    /// [538] Hinweis: Die Referenz auf die ORDERS ist nur dann anzugeben, wenn diese Werte vom Empfänger auch ursprünglich mittels ORDERS angefragt wurden.
1668    fn evaluate_538(&self, _ctx: &EvaluationContext) -> ConditionResult {
1669        // Hinweis: Die Referenz auf die ORDERS ist nur dann anzugeben, wenn diese Werte vom Empfänger auch ursprünglich mittels ORDERS angefragt wurden — informational note, always applies
1670        ConditionResult::True
1671    }
1672
1673    /// [541] Hinweis: Ein Korrekturgrund ist anzugeben, wenn: 1. ein bereits an den MP übermittelter vorläufiger Wert nach Stornierung durch einen Ersatzwert ersetzt wird, oder  2. ein bereits an den MP über...
1674    fn evaluate_541(&self, _ctx: &EvaluationContext) -> ConditionResult {
1675        // Hinweis: Ein Korrekturgrund ist anzugeben in bestimmten Ersatzwert/Stornierung-Szenarien — informational note, always applies
1676        ConditionResult::True
1677    }
1678
1679    /// [543] Hinweis: Wert aus BGM+Z23 DE1004 der ORDERS mit der die bilanzierte Menge bestellt wurde.
1680    fn evaluate_543(&self, _ctx: &EvaluationContext) -> ConditionResult {
1681        // Hinweis: Wert aus BGM+Z23 DE1004 der ORDERS mit der die bilanzierte Menge bestellt wurde — informational note, always applies
1682        ConditionResult::True
1683    }
1684
1685    /// [544] Hinweis: Bei einer Mengenaufteilung (z. B. Aufgrund einer Abgrenzung) für SG6 LOC+172 muss für den frühesten angegebenen Zeitpunkt zum Beginn des Zeitintervalls  (über alle Wiederholungen der L...
1686    fn evaluate_544(&self, _ctx: &EvaluationContext) -> ConditionResult {
1687        // Hinweis: Bei einer Mengenaufteilung für SG6 LOC+172 muss für den frühesten Zeitpunkt zu jeder OBIS-Kennziffer ein Zählerstand vorhanden sein — informational note, always applies
1688        ConditionResult::True
1689    }
1690
1691    /// [545] Hinweis: Bei einer Mengenaufteilung (z. B. Aufgrund einer Abgrenzung) für SG6 LOC+172 muss für den spätesten angegebenen Zeitpunkt zum Ende des Zeitintervalls (über alle Wiederholungen der LIN-...
1692    fn evaluate_545(&self, _ctx: &EvaluationContext) -> ConditionResult {
1693        // Hinweis: informational note about Mengenaufteilung and Zählerstand requirement — always applies
1694        ConditionResult::True
1695    }
1696
1697    /// [546] Hinweis: Eine Referenz auf die Stammdatenänderung des Gerätewechsels ist immer anzugeben, wenn diese dem Sender vorliegt.
1698    fn evaluate_546(&self, _ctx: &EvaluationContext) -> ConditionResult {
1699        // Hinweis: informational note about referencing master data changes for device replacements — always applies
1700        ConditionResult::True
1701    }
1702
1703    /// [547] Hinweis: Der Code 270 ist nur zu nutzen, wenn ein Lieferschein, der vor dem 1.4.2021 erstellt wurde, storniert wird.
1704    fn evaluate_547(&self, _ctx: &EvaluationContext) -> ConditionResult {
1705        // Hinweis: informational note — code 270 only for cancelling delivery notes created before 1.4.2021 — always applies
1706        ConditionResult::True
1707    }
1708
1709    /// [551] Hinweis: Ein Korrekturgrund ist anzugeben, wenn: 1. ein bereits an den MP übermittelter vorläufiger Wert durch einen Ersatzwert ersetzt wird, oder  2. ein bereits an den MP übermittelter Ersatzw...
1710    fn evaluate_551(&self, _ctx: &EvaluationContext) -> ConditionResult {
1711        // Hinweis: informational note describing when a Korrekturgrund must be provided — always applies
1712        ConditionResult::True
1713    }
1714
1715    /// [553] Hinweis: Wert aus BGM+Z34 DE1004 der ORDERS mit der die Reklamation von Werten erfolgt ist
1716    fn evaluate_553(&self, _ctx: &EvaluationContext) -> ConditionResult {
1717        // Hinweis: informational note — value from BGM+Z34 DE1004 of the ORDERS used for value complaint — always applies
1718        ConditionResult::True
1719    }
1720
1721    /// [554] Hinweis: Verwendung der ID der Technischen Ressource
1722    fn evaluate_554(&self, _ctx: &EvaluationContext) -> ConditionResult {
1723        // Hinweis: Verwendung der ID der Technischen Ressource — informational note, always applies
1724        ConditionResult::True
1725    }
1726
1727    /// [556] Hinweis: Wert aus BGM+Z45 DE1004 der ORDERS mit der die Anforderung der Ausfallarbeit durch den anfNB erfolgt ist.
1728    fn evaluate_556(&self, _ctx: &EvaluationContext) -> ConditionResult {
1729        // Hinweis: Wert aus BGM+Z45 DE1004 der ORDERS — informational note, always applies
1730        ConditionResult::True
1731    }
1732
1733    /// [557] Hinweis: Die Referenz auf die ursprüngliche MSCONS ist anzugeben, wenn es sich um die Übermittlung eines Gegenvorschlags durch den BTR handelt.
1734    fn evaluate_557(&self, _ctx: &EvaluationContext) -> ConditionResult {
1735        // Hinweis: Referenz auf ursprüngliche MSCONS bei Gegenvorschlag — informational note, always applies
1736        ConditionResult::True
1737    }
1738
1739    /// [558] Hinweis: Wert aus BGM+Z45 DE1004 der MSCONS auf die sich die Übermittlung des Gegenvorschlags durch den BTR bezieht.
1740    fn evaluate_558(&self, _ctx: &EvaluationContext) -> ConditionResult {
1741        // Hinweis: Wert aus BGM+Z45 DE1004 der MSCONS bei Gegenvorschlag — informational note, always applies
1742        ConditionResult::True
1743    }
1744
1745    /// [559] Hinweis: Ein Korrekturgrund ist anzugeben, wenn: 1. ein bereits an den MP übermittelter vorläufiger Wert nach Stornierung durch einen Ersatzwert ersetzt wird, oder  2. ein bereits an den MP über...
1746    fn evaluate_559(&self, _ctx: &EvaluationContext) -> ConditionResult {
1747        // Hinweis: Korrekturgrund bei Ersatz/Stornierung von Werten — informational note, always applies
1748        ConditionResult::True
1749    }
1750
1751    /// [560] Hinweis: Ein Korrekturgrund ist anzugeben, wenn: 1. ein bereits an den MP übermittelter vorläufiger Wert durch einen Ersatzwert ersetzt wird, oder  2. ein bereits an den MP übermittelter Ersatzw...
1752    fn evaluate_560(&self, _ctx: &EvaluationContext) -> ConditionResult {
1753        // Hinweis: Ein Korrekturgrund ist anzugeben, wenn ein vorläufiger/Ersatz/wahrer Wert ersetzt wird — informational note, always applies
1754        ConditionResult::True
1755    }
1756
1757    /// [566] Hinweis: Es sind nur die Werte erlaubt, die im vorherigen Stammdatenaustausch zu diesem Meldepunkt vom MSB zum Zeitpunkt übermittelt wurden.
1758    fn evaluate_566(&self, _ctx: &EvaluationContext) -> ConditionResult {
1759        // Hinweis: Es sind nur die Werte erlaubt, die im vorherigen Stammdatenaustausch übermittelt wurden — informational note, always applies
1760        ConditionResult::True
1761    }
1762
1763    /// [567] Hinweis: Es ist die Konfigurations-ID anzugeben, die im vorherigen Stammdatenaustausch kommuniziert wurde.
1764    fn evaluate_567(&self, _ctx: &EvaluationContext) -> ConditionResult {
1765        // Hinweis: Es ist die Konfigurations-ID anzugeben, die im vorherigen Stammdatenaustausch kommuniziert wurde — informational note, always applies
1766        ConditionResult::True
1767    }
1768
1769    /// [568] Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung zwischen Markt- und Messlokation handelt und auf Ebene der Messlokation unterschiedliche Ersatzwertbildungsverfahren verwendet u...
1770    fn evaluate_568(&self, _ctx: &EvaluationContext) -> ConditionResult {
1771        // Hinweis: Verwendung nur zulässig bei 1:n Beziehung zwischen Markt- und Messlokation mit unterschiedlichen Ersatzwertbildungsverfahren — informational note, always applies
1772        ConditionResult::True
1773    }
1774
1775    /// [569] Hinweis: Bei mehreren Zählerständen einer Messlokation (z. B. HT/NT) ist diese Zeitangabe zu nutzen und eine Wiederholung das SG9 LIN durchzuführen.
1776    fn evaluate_569(&self, _ctx: &EvaluationContext) -> ConditionResult {
1777        // Hinweis: Bei mehreren Zählerständen einer Messlokation (z. B. HT/NT) ist diese Zeitangabe zu nutzen und SG9 LIN zu wiederholen — informational note, always applies
1778        ConditionResult::True
1779    }
1780
1781    /// [570] Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung zwischen Markt- und Messlokation handelt und auf Ebene der Messlokation unterschiedliche Gründe für die Ersatzwertbildung vorl...
1782    fn evaluate_570(&self, _ctx: &EvaluationContext) -> ConditionResult {
1783        // Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung zwischen Markt- und Messlokation handelt und auf Ebene der Messlokation unterschiedliche Gründe für die Ersatzwertbildung vorliegen und kommuniziert wurden.
1784        ConditionResult::True
1785    }
1786
1787    /// [571] Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung handelt und auf Ebene der Netzkopplungspunkte unterschiedliche Gründe für die Ersatzwertbildung vorliegen und kommuniziert wur...
1788    fn evaluate_571(&self, _ctx: &EvaluationContext) -> ConditionResult {
1789        // Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung handelt und auf Ebene der Netzkopplungspunkte unterschiedliche Gründe für die Ersatzwertbildung vorliegen und kommuniziert wurden.
1790        ConditionResult::True
1791    }
1792
1793    /// [572] Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung handelt und auf Ebene der Netzkopplungspunkte unterschiedliche Ersatzwertbildungsverfahren vorliegen und kommuniziert wurden.
1794    fn evaluate_572(&self, _ctx: &EvaluationContext) -> ConditionResult {
1795        // Hinweis: Verwendung ist nur zulässig, wenn es sich um 1:n Beziehung handelt und auf Ebene der Netzkopplungspunkte unterschiedliche Ersatzwertbildungsverfahren vorliegen und kommuniziert wurden.
1796        ConditionResult::True
1797    }
1798
1799    /// [573] Hinweis: Eine Energiemenge in der Sparte Gas ist gemäß DVGW G685 Arbeitsblatt 4 Kapitel 5.3 auf ganze Kilowattstunden zu runden.
1800    fn evaluate_573(&self, _ctx: &EvaluationContext) -> ConditionResult {
1801        // Hinweis: Eine Energiemenge in der Sparte Gas ist gemäß DVGW G685 Arbeitsblatt 4 Kapitel 5.3 auf ganze Kilowattstunden zu runden.
1802        ConditionResult::True
1803    }
1804
1805    /// [574] Hinweis: Wert aus BGM DE1004 der ORDERS mit der die Bestellung der Werte nach Typ 2 erfolg ist
1806    fn evaluate_574(&self, _ctx: &EvaluationContext) -> ConditionResult {
1807        // Hinweis: Wert aus BGM DE1004 der ORDERS mit der die Bestellung der Werte nach Typ 2 erfolg ist
1808        ConditionResult::True
1809    }
1810
1811    /// [575] Hinweis: Verwendung der ID der Netzlokation
1812    fn evaluate_575(&self, _ctx: &EvaluationContext) -> ConditionResult {
1813        // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
1814        ConditionResult::True
1815    }
1816
1817    /// [576] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
1818    fn evaluate_576(&self, _ctx: &EvaluationContext) -> ConditionResult {
1819        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
1820        ConditionResult::True
1821    }
1822
1823    /// [577] Hinweis: Dieser Code ist auch zu verwenden, wenn aufgrund der Beendigung einer Messlokation (Stilllegung) die Beendigung der Marktlokation (Stilllegung) zu unterschiedlichen Zeitpunkten erfolgt, da...
1824    fn evaluate_577(&self, _ctx: &EvaluationContext) -> ConditionResult {
1825        // Hinweis: Informational note about Messlokation/Marktlokation termination timing — always applies
1826        ConditionResult::True
1827    }
1828
1829    /// [902] Format: Möglicher Wert: ≥ 0
1830    fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
1831        // Format: Möglicher Wert: ≥ 0
1832        let segs = ctx.find_segments("QTY");
1833        match segs
1834            .first()
1835            .and_then(|s| s.elements.first())
1836            .and_then(|e| e.get(1))
1837        {
1838            Some(val) => validate_numeric(val, ">=", 0.0),
1839            None => ConditionResult::False, // segment absent → condition not applicable
1840        }
1841    }
1842
1843    /// [904] Format: genau 16 Stellen
1844    // REVIEW: Format condition requiring exactly 16 characters. Applied to QTY value (most common numeric format condition target in MSCONS). Confidence is medium because the exact target segment/element is not specified in the condition text — QTY is the most likely candidate in MSCONS context. (medium confidence)
1845    fn evaluate_904(&self, ctx: &EvaluationContext) -> ConditionResult {
1846        // Format: genau 16 Stellen
1847        let segs = ctx.find_segments("QTY");
1848        match segs
1849            .first()
1850            .and_then(|s| s.elements.first())
1851            .and_then(|e| e.get(1))
1852        {
1853            Some(val) => validate_exact_length(val, 16),
1854            None => ConditionResult::False, // segment absent → condition not applicable
1855        }
1856    }
1857
1858    /// [905] Format: max. 3 Stellen
1859    fn evaluate_905(&self, ctx: &EvaluationContext) -> ConditionResult {
1860        let segs = ctx.find_segments("QTY");
1861        match segs
1862            .first()
1863            .and_then(|s| s.elements.first())
1864            .and_then(|e| e.get(1))
1865        {
1866            Some(val) => validate_max_integer_digits(val, 3),
1867            None => ConditionResult::False, // segment absent → condition not applicable
1868        }
1869    }
1870
1871    /// [906] Format: max. 3 Nachkommastellen
1872    fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
1873        let segs = ctx.find_segments("QTY");
1874        match segs
1875            .first()
1876            .and_then(|s| s.elements.first())
1877            .and_then(|e| e.get(1))
1878        {
1879            Some(val) => validate_max_decimal_places(val, 3),
1880            None => ConditionResult::False, // segment absent → condition not applicable
1881        }
1882    }
1883
1884    /// [907] Format: max. 4 Nachkommastellen
1885    fn evaluate_907(&self, ctx: &EvaluationContext) -> ConditionResult {
1886        let segs = ctx.find_segments("QTY");
1887        match segs
1888            .first()
1889            .and_then(|s| s.elements.first())
1890            .and_then(|e| e.get(1))
1891        {
1892            Some(val) => validate_max_decimal_places(val, 4),
1893            None => ConditionResult::False, // segment absent → condition not applicable
1894        }
1895    }
1896
1897    /// [908] Format: Mögliche Werte: 1 bis n
1898    fn evaluate_908(&self, ctx: &EvaluationContext) -> ConditionResult {
1899        let segs = ctx.find_segments("QTY");
1900        match segs
1901            .first()
1902            .and_then(|s| s.elements.first())
1903            .and_then(|e| e.get(1))
1904        {
1905            Some(val) => validate_numeric(val, ">=", 1.0),
1906            None => ConditionResult::False, // segment absent → condition not applicable
1907        }
1908    }
1909
1910    /// [909] Format: Mögliche Werte: 0 bis n
1911    fn evaluate_909(&self, ctx: &EvaluationContext) -> ConditionResult {
1912        let segs = ctx.find_segments("QTY");
1913        match segs
1914            .first()
1915            .and_then(|s| s.elements.first())
1916            .and_then(|e| e.get(1))
1917        {
1918            Some(val) => validate_numeric(val, ">=", 0.0),
1919            None => ConditionResult::False, // segment absent → condition not applicable
1920        }
1921    }
1922
1923    /// [910] Format: Möglicher Wert: &lt; 0 oder ≥ 0
1924    fn evaluate_910(&self, ctx: &EvaluationContext) -> ConditionResult {
1925        let qty_segments = ctx.find_segments("QTY");
1926        let val = qty_segments
1927            .first()
1928            .and_then(|s| s.elements.first())
1929            .and_then(|e| e.get(1))
1930            .map(|s| s.as_str())
1931            .unwrap_or("");
1932        if val.is_empty() {
1933            return ConditionResult::Unknown;
1934        }
1935        match val.parse::<f64>() {
1936            Ok(_) => ConditionResult::True,
1937            Err(_) => ConditionResult::False,
1938        }
1939    }
1940
1941    /// [917] Format: max. 4 Vorkommastellen
1942    fn evaluate_917(&self, ctx: &EvaluationContext) -> ConditionResult {
1943        let qty_segments = ctx.find_segments("QTY");
1944        match qty_segments
1945            .first()
1946            .and_then(|s| s.elements.first())
1947            .and_then(|e| e.get(1))
1948        {
1949            Some(val) => validate_max_integer_digits(val, 4),
1950            None => ConditionResult::False, // segment absent → condition not applicable
1951        }
1952    }
1953
1954    /// [918] Format: Zeichen aus dem über UNOC definierten Zeichensatz, wobei von den Buchstaben nur Großbuchstaben erlaubt sind.
1955    // REVIEW: UNOC charset requires uppercase-only alphabetics. Checks all alphabetic characters in FTX text fields (C108, elements[3], components 0..4) are uppercase. Non-alphabetic characters (digits, punctuation) are permitted. Medium confidence — UNOC validation approximated as uppercase-only alphabetic check without full ISO 8859-1 codepoint enumeration. (medium confidence)
1956    fn evaluate_918(&self, ctx: &EvaluationContext) -> ConditionResult {
1957        let ftx_segments = ctx.find_segments("FTX");
1958        for seg in &ftx_segments {
1959            if let Some(c108) = seg.elements.get(3) {
1960                for component in c108 {
1961                    let s = component.as_str();
1962                    if s.is_empty() {
1963                        continue;
1964                    }
1965                    if !s.chars().all(|c| !c.is_alphabetic() || c.is_uppercase()) {
1966                        return ConditionResult::False;
1967                    }
1968                }
1969            }
1970        }
1971        ConditionResult::True
1972    }
1973
1974    /// [922] Format: TR-ID
1975    // REVIEW: Format: TR-ID — validates that the value is a valid Transaktionsreferenz-ID (1–35 alphanumeric characters) using validate_tr_id. In MSCONS, the document/transaction ID is typically found in BGM elements[1][0] (the document number composite). Medium confidence because the exact DE this format condition is applied to depends on the AHB field context, which could alternatively be RFF or another reference segment. (medium confidence)
1976    fn evaluate_922(&self, ctx: &EvaluationContext) -> ConditionResult {
1977        let bgm_segs = ctx.find_segments("BGM");
1978        match bgm_segs
1979            .first()
1980            .and_then(|s| s.elements.get(1))
1981            .and_then(|e| e.first())
1982        {
1983            Some(val) => validate_tr_id(val),
1984            None => ConditionResult::False, // segment absent → condition not applicable
1985        }
1986    }
1987
1988    /// [925] Format: max. 5 Nachkommastellen
1989    fn evaluate_925(&self, ctx: &EvaluationContext) -> ConditionResult {
1990        let qty_segments = ctx.find_segments("QTY");
1991        match qty_segments
1992            .first()
1993            .and_then(|s| s.elements.first())
1994            .and_then(|e| e.get(1))
1995        {
1996            Some(val) => validate_max_decimal_places(val, 5),
1997            None => ConditionResult::False, // segment absent → condition not applicable
1998        }
1999    }
2000
2001    /// [931] Format: ZZZ = +00
2002    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
2003        let dtm_segs = ctx.find_segments("DTM");
2004        match dtm_segs
2005            .first()
2006            .and_then(|s| s.elements.first())
2007            .and_then(|e| e.get(1))
2008        {
2009            Some(val) => validate_timezone_utc(val),
2010            None => ConditionResult::False, // segment absent → condition not applicable
2011        }
2012    }
2013
2014    /// [932] Format: HHMM = 2200
2015    fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
2016        let dtm_segs = ctx.find_segments("DTM");
2017        match dtm_segs
2018            .first()
2019            .and_then(|s| s.elements.first())
2020            .and_then(|e| e.get(1))
2021        {
2022            Some(val) => validate_hhmm_equals(val, "2200"),
2023            None => ConditionResult::False, // segment absent → condition not applicable
2024        }
2025    }
2026
2027    /// [933] Format: HHMM = 2300
2028    fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
2029        let dtm_segs = ctx.find_segments("DTM");
2030        match dtm_segs
2031            .first()
2032            .and_then(|s| s.elements.first())
2033            .and_then(|e| e.get(1))
2034        {
2035            Some(val) => validate_hhmm_equals(val, "2300"),
2036            None => ConditionResult::False, // segment absent → condition not applicable
2037        }
2038    }
2039
2040    /// [934] Format: HHMM = 0400
2041    fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
2042        let dtm_segs = ctx.find_segments("DTM");
2043        match dtm_segs
2044            .first()
2045            .and_then(|s| s.elements.first())
2046            .and_then(|e| e.get(1))
2047        {
2048            Some(val) => validate_hhmm_equals(val, "0400"),
2049            None => ConditionResult::False, // segment absent → condition not applicable
2050        }
2051    }
2052
2053    /// [935] Format: HHMM = 0500
2054    fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
2055        let dtm_segs = ctx.find_segments("DTM");
2056        match dtm_segs
2057            .first()
2058            .and_then(|s| s.elements.first())
2059            .and_then(|e| e.get(1))
2060        {
2061            Some(val) => validate_hhmm_equals(val, "0500"),
2062            None => ConditionResult::False, // segment absent → condition not applicable
2063        }
2064    }
2065
2066    /// [937] Format: keine Nachkommastelle
2067    fn evaluate_937(&self, ctx: &EvaluationContext) -> ConditionResult {
2068        let segs = ctx.find_segments("QTY");
2069        match segs
2070            .first()
2071            .and_then(|s| s.elements.first())
2072            .and_then(|e| e.get(1))
2073        {
2074            Some(val) => validate_max_decimal_places(val, 0),
2075            None => ConditionResult::False, // segment absent → condition not applicable
2076        }
2077    }
2078
2079    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
2080    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
2081        let segs = ctx.find_segments("COM");
2082        match segs
2083            .first()
2084            .and_then(|s| s.elements.first())
2085            .and_then(|e| e.first())
2086        {
2087            Some(val) => validate_email(val),
2088            None => ConditionResult::False, // segment absent → condition not applicable
2089        }
2090    }
2091
2092    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
2093    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
2094        let segs = ctx.find_segments("COM");
2095        match segs
2096            .first()
2097            .and_then(|s| s.elements.first())
2098            .and_then(|e| e.first())
2099        {
2100            Some(val) => validate_phone(val),
2101            None => ConditionResult::False, // segment absent → condition not applicable
2102        }
2103    }
2104
2105    /// [950] Format: Marktlokations-ID
2106    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
2107        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z16");
2108        match segs
2109            .first()
2110            .and_then(|s| s.elements.get(1))
2111            .and_then(|e| e.first())
2112        {
2113            Some(val) => validate_malo_id(val),
2114            None => ConditionResult::False, // segment absent → condition not applicable
2115        }
2116    }
2117
2118    /// [951] Format: Zählpunktbezeichnung
2119    // REVIEW: Format: Zählpunktbezeichnung — 33 alphanumeric characters. In MSCONS, Zählpunktbezeichnung typically appears in LOC+Z19. Using validate_zahlpunkt helper. (medium confidence)
2120    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
2121        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z19");
2122        match segs
2123            .first()
2124            .and_then(|s| s.elements.get(1))
2125            .and_then(|e| e.first())
2126        {
2127            Some(val) => validate_zahlpunkt(val),
2128            None => ConditionResult::False, // segment absent → condition not applicable
2129        }
2130    }
2131
2132    /// [960] Format: Netzlokations-ID
2133    // REVIEW: Netzlokations-ID uses the same 11-digit Luhn-check format as Marktlokations-ID (validate_malo_id). In MSCONS, Netzlokation is typically referenced via LOC+Z17. Segment structure reference does not include LOC for MSCONS, so Z17 qualifier is assumed from German energy market convention. (medium confidence)
2134    fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
2135        // Format: Netzlokations-ID — 11-digit ID with Luhn check digit (same format as MaLo-ID)
2136        // In MSCONS, Netzlokation is identified by LOC+Z17
2137        let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z17");
2138        match segs
2139            .first()
2140            .and_then(|s| s.elements.get(1))
2141            .and_then(|e| e.first())
2142        {
2143            Some(val) => validate_malo_id(val),
2144            None => ConditionResult::False, // segment absent → condition not applicable
2145        }
2146    }
2147
2148    /// [2001] Segmentgruppe ist nur einmal je UNH anzugeben
2149    fn evaluate_2001(&self, _ctx: &EvaluationContext) -> ConditionResult {
2150        // Hinweis: Segmentgruppe ist nur einmal je UNH anzugeben — cardinality annotation, always applies
2151        ConditionResult::True
2152    }
2153
2154    /// [2002] Segmentgruppe ist bis zu drei Mal je SG5 NAD+DP anzugeben
2155    fn evaluate_2002(&self, _ctx: &EvaluationContext) -> ConditionResult {
2156        // Hinweis: Segmentgruppe ist bis zu drei Mal je SG5 NAD+DP anzugeben — cardinality annotation, always applies
2157        ConditionResult::True
2158    }
2159
2160    /// [2003] Segmentgruppe ist genau zwei Mal je SG9 LIN anzugeben
2161    fn evaluate_2003(&self, _ctx: &EvaluationContext) -> ConditionResult {
2162        // Hinweis: Segmentgruppe ist genau zwei Mal je SG9 LIN anzugeben — cardinality annotation, always applies
2163        ConditionResult::True
2164    }
2165}