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