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