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