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