automapper_validation/generated/fv2510/iftsta_conditions_fv2510.rs
1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2510/IFTSTA_AHB_2_0h_Fehlerkorrektur_20250623.xml
4// Generated: 2026-03-12T10:40:40Z
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 IFTSTA FV2510.
17pub struct IftstaConditionEvaluatorFV2510 {
18 // External condition IDs that require runtime context.
19 external_conditions: std::collections::HashSet<u32>,
20}
21
22impl Default for IftstaConditionEvaluatorFV2510 {
23 fn default() -> Self {
24 let mut external_conditions = std::collections::HashSet::new();
25 external_conditions.insert(1);
26 external_conditions.insert(7);
27 external_conditions.insert(8);
28 external_conditions.insert(10);
29 external_conditions.insert(16);
30 external_conditions.insert(17);
31 external_conditions.insert(20);
32 external_conditions.insert(26);
33 external_conditions.insert(27);
34 external_conditions.insert(28);
35 external_conditions.insert(29);
36 external_conditions.insert(72);
37 external_conditions.insert(76);
38 external_conditions.insert(86);
39 external_conditions.insert(114);
40 external_conditions.insert(115);
41 external_conditions.insert(117);
42 external_conditions.insert(492);
43 external_conditions.insert(493);
44 Self {
45 external_conditions,
46 }
47 }
48}
49
50impl ConditionEvaluator for IftstaConditionEvaluatorFV2510 {
51 fn message_type(&self) -> &str {
52 "IFTSTA"
53 }
54
55 fn format_version(&self) -> &str {
56 "FV2510"
57 }
58
59 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
60 match condition {
61 1 => self.evaluate_1(ctx),
62 3 => self.evaluate_3(ctx),
63 4 => self.evaluate_4(ctx),
64 5 => self.evaluate_5(ctx),
65 6 => self.evaluate_6(ctx),
66 7 => self.evaluate_7(ctx),
67 8 => self.evaluate_8(ctx),
68 10 => self.evaluate_10(ctx),
69 16 => self.evaluate_16(ctx),
70 17 => self.evaluate_17(ctx),
71 18 => self.evaluate_18(ctx),
72 19 => self.evaluate_19(ctx),
73 20 => self.evaluate_20(ctx),
74 23 => self.evaluate_23(ctx),
75 26 => self.evaluate_26(ctx),
76 27 => self.evaluate_27(ctx),
77 28 => self.evaluate_28(ctx),
78 29 => self.evaluate_29(ctx),
79 30 => self.evaluate_30(ctx),
80 31 => self.evaluate_31(ctx),
81 32 => self.evaluate_32(ctx),
82 33 => self.evaluate_33(ctx),
83 43 => self.evaluate_43(ctx),
84 44 => self.evaluate_44(ctx),
85 45 => self.evaluate_45(ctx),
86 46 => self.evaluate_46(ctx),
87 47 => self.evaluate_47(ctx),
88 48 => self.evaluate_48(ctx),
89 49 => self.evaluate_49(ctx),
90 50 => self.evaluate_50(ctx),
91 51 => self.evaluate_51(ctx),
92 52 => self.evaluate_52(ctx),
93 53 => self.evaluate_53(ctx),
94 54 => self.evaluate_54(ctx),
95 55 => self.evaluate_55(ctx),
96 56 => self.evaluate_56(ctx),
97 57 => self.evaluate_57(ctx),
98 58 => self.evaluate_58(ctx),
99 59 => self.evaluate_59(ctx),
100 60 => self.evaluate_60(ctx),
101 61 => self.evaluate_61(ctx),
102 62 => self.evaluate_62(ctx),
103 63 => self.evaluate_63(ctx),
104 64 => self.evaluate_64(ctx),
105 65 => self.evaluate_65(ctx),
106 66 => self.evaluate_66(ctx),
107 67 => self.evaluate_67(ctx),
108 68 => self.evaluate_68(ctx),
109 69 => self.evaluate_69(ctx),
110 70 => self.evaluate_70(ctx),
111 71 => self.evaluate_71(ctx),
112 72 => self.evaluate_72(ctx),
113 76 => self.evaluate_76(ctx),
114 77 => self.evaluate_77(ctx),
115 78 => self.evaluate_78(ctx),
116 79 => self.evaluate_79(ctx),
117 80 => self.evaluate_80(ctx),
118 83 => self.evaluate_83(ctx),
119 84 => self.evaluate_84(ctx),
120 85 => self.evaluate_85(ctx),
121 86 => self.evaluate_86(ctx),
122 91 => self.evaluate_91(ctx),
123 92 => self.evaluate_92(ctx),
124 93 => self.evaluate_93(ctx),
125 94 => self.evaluate_94(ctx),
126 95 => self.evaluate_95(ctx),
127 96 => self.evaluate_96(ctx),
128 97 => self.evaluate_97(ctx),
129 98 => self.evaluate_98(ctx),
130 99 => self.evaluate_99(ctx),
131 100 => self.evaluate_100(ctx),
132 101 => self.evaluate_101(ctx),
133 103 => self.evaluate_103(ctx),
134 107 => self.evaluate_107(ctx),
135 114 => self.evaluate_114(ctx),
136 115 => self.evaluate_115(ctx),
137 117 => self.evaluate_117(ctx),
138 118 => self.evaluate_118(ctx),
139 119 => self.evaluate_119(ctx),
140 120 => self.evaluate_120(ctx),
141 121 => self.evaluate_121(ctx),
142 129 => self.evaluate_129(ctx),
143 130 => self.evaluate_130(ctx),
144 131 => self.evaluate_131(ctx),
145 132 => self.evaluate_132(ctx),
146 133 => self.evaluate_133(ctx),
147 134 => self.evaluate_134(ctx),
148 135 => self.evaluate_135(ctx),
149 136 => self.evaluate_136(ctx),
150 137 => self.evaluate_137(ctx),
151 138 => self.evaluate_138(ctx),
152 139 => self.evaluate_139(ctx),
153 140 => self.evaluate_140(ctx),
154 141 => self.evaluate_141(ctx),
155 142 => self.evaluate_142(ctx),
156 143 => self.evaluate_143(ctx),
157 144 => self.evaluate_144(ctx),
158 145 => self.evaluate_145(ctx),
159 146 => self.evaluate_146(ctx),
160 147 => self.evaluate_147(ctx),
161 148 => self.evaluate_148(ctx),
162 149 => self.evaluate_149(ctx),
163 150 => self.evaluate_150(ctx),
164 151 => self.evaluate_151(ctx),
165 152 => self.evaluate_152(ctx),
166 153 => self.evaluate_153(ctx),
167 154 => self.evaluate_154(ctx),
168 490 => self.evaluate_490(ctx),
169 491 => self.evaluate_491(ctx),
170 492 => self.evaluate_492(ctx),
171 493 => self.evaluate_493(ctx),
172 494 => self.evaluate_494(ctx),
173 495 => self.evaluate_495(ctx),
174 496 => self.evaluate_496(ctx),
175 501 => self.evaluate_501(ctx),
176 502 => self.evaluate_502(ctx),
177 503 => self.evaluate_503(ctx),
178 504 => self.evaluate_504(ctx),
179 505 => self.evaluate_505(ctx),
180 506 => self.evaluate_506(ctx),
181 510 => self.evaluate_510(ctx),
182 512 => self.evaluate_512(ctx),
183 519 => self.evaluate_519(ctx),
184 520 => self.evaluate_520(ctx),
185 521 => self.evaluate_521(ctx),
186 522 => self.evaluate_522(ctx),
187 523 => self.evaluate_523(ctx),
188 524 => self.evaluate_524(ctx),
189 525 => self.evaluate_525(ctx),
190 530 => self.evaluate_530(ctx),
191 531 => self.evaluate_531(ctx),
192 532 => self.evaluate_532(ctx),
193 533 => self.evaluate_533(ctx),
194 534 => self.evaluate_534(ctx),
195 535 => self.evaluate_535(ctx),
196 537 => self.evaluate_537(ctx),
197 538 => self.evaluate_538(ctx),
198 902 => self.evaluate_902(ctx),
199 903 => self.evaluate_903(ctx),
200 906 => self.evaluate_906(ctx),
201 911 => self.evaluate_911(ctx),
202 931 => self.evaluate_931(ctx),
203 932 => self.evaluate_932(ctx),
204 933 => self.evaluate_933(ctx),
205 934 => self.evaluate_934(ctx),
206 935 => self.evaluate_935(ctx),
207 939 => self.evaluate_939(ctx),
208 940 => self.evaluate_940(ctx),
209 950 => self.evaluate_950(ctx),
210 951 => self.evaluate_951(ctx),
211 960 => self.evaluate_960(ctx),
212 961 => self.evaluate_961(ctx),
213 _ => ConditionResult::Unknown,
214 }
215 }
216
217 fn is_external(&self, condition: u32) -> bool {
218 self.external_conditions.contains(&condition)
219 }
220 fn is_known(&self, condition: u32) -> bool {
221 matches!(
222 condition,
223 1 | 3
224 | 4
225 | 5
226 | 6
227 | 7
228 | 8
229 | 10
230 | 16
231 | 17
232 | 18
233 | 19
234 | 20
235 | 23
236 | 26
237 | 27
238 | 28
239 | 29
240 | 30
241 | 31
242 | 32
243 | 33
244 | 43
245 | 44
246 | 45
247 | 46
248 | 47
249 | 48
250 | 49
251 | 50
252 | 51
253 | 52
254 | 53
255 | 54
256 | 55
257 | 56
258 | 57
259 | 58
260 | 59
261 | 60
262 | 61
263 | 62
264 | 63
265 | 64
266 | 65
267 | 66
268 | 67
269 | 68
270 | 69
271 | 70
272 | 71
273 | 72
274 | 76
275 | 77
276 | 78
277 | 79
278 | 80
279 | 83
280 | 84
281 | 85
282 | 86
283 | 91
284 | 92
285 | 93
286 | 94
287 | 95
288 | 96
289 | 97
290 | 98
291 | 99
292 | 100
293 | 101
294 | 103
295 | 107
296 | 114
297 | 115
298 | 117
299 | 118
300 | 119
301 | 120
302 | 121
303 | 129
304 | 130
305 | 131
306 | 132
307 | 133
308 | 134
309 | 135
310 | 136
311 | 137
312 | 138
313 | 139
314 | 140
315 | 141
316 | 142
317 | 143
318 | 144
319 | 145
320 | 146
321 | 147
322 | 148
323 | 149
324 | 150
325 | 151
326 | 152
327 | 153
328 | 154
329 | 490
330 | 491
331 | 492
332 | 493
333 | 494
334 | 495
335 | 496
336 | 501
337 | 502
338 | 503
339 | 504
340 | 505
341 | 506
342 | 510
343 | 512
344 | 519
345 | 520
346 | 521
347 | 522
348 | 523
349 | 524
350 | 525
351 | 530
352 | 531
353 | 532
354 | 533
355 | 534
356 | 535
357 | 537
358 | 538
359 | 902
360 | 903
361 | 906
362 | 911
363 | 931
364 | 932
365 | 933
366 | 934
367 | 935
368 | 939
369 | 940
370 | 950
371 | 951
372 | 960
373 | 961
374 )
375 }
376}
377
378impl IftstaConditionEvaluatorFV2510 {
379 /// [6] Wenn für das 3-Tupel (MaBiS-ZP, Betrachtungszeitraum, Version der Summenzeitreihe) dem BIKO der Prüfstatus vorliegt, so ist dieser immer zusammen mit dem Datenstatus zu übertragen.
380 // REVIEW: The condition states that when BIKO has a Prüfstatus for the 3-tuple (MaBiS-ZP, Betrachtungszeitraum, Version), it must always be transmitted together with the Datenstatus. Interpreted as: this condition is True when STS with Z03 category (Prüfstatus zur Summenzeitreihe) is present in the message, triggering the co-transmission requirement. The BIKO role context is implicit in the message structure. (medium confidence)
381 fn evaluate_6(&self, ctx: &EvaluationContext) -> ConditionResult {
382 // Condition is True when STS+Z03 (Prüfstatus zur Summenzeitreihe) is present,
383 // signalling that Datenstatus must also be transmitted alongside it.
384 ctx.has_qualifier("STS", 0, "Z03")
385 }
386
387 /// [7] Wenn der Datenstatus "Abgerechnete Daten" bzw. "Abgerechnete Daten Korrektur-BKA" nicht vorhanden.
388 /// EXTERNAL: Requires the authoritative IFTSTA Datenstatus codelist to map "Abgerechnete Daten"/"…-BKA" to STS+Z04 DE1131 codes.
389 fn evaluate_7(&self, ctx: &EvaluationContext) -> ConditionResult {
390 ctx.external
391 .evaluate("iftsta_datenstatus_abgerechnet_absent")
392 }
393
394 /// [8] Wenn der Datenstatus einer NZR vom BIKO an NB nicht vorhanden.
395 /// EXTERNAL: Requires context from outside the message.
396 fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
397 ctx.external.evaluate("datenstatus_nzr_biko_to_nb_missing")
398 }
399
400 /// [23] Wenn SG15 STS+Z25+Z31+A06 / A09 / A10 / A11 / A13 / A14 / A15 / A16 vorhanden.
401 fn evaluate_23(&self, ctx: &EvaluationContext) -> ConditionResult {
402 // STS+Z25+Z31+A06/A09/A10/A11/A13/A14/A15/A16
403 // Per segment reference for 'Status des Lieferscheins':
404 // elements[0][0] = Z25 (Statuskategorie)
405 // elements[1][0] = Z31 (Status, Code)
406 // elements[2][0] = Statusanlaß code (A06 etc.)
407 let target_causes = ["A06", "A09", "A10", "A11", "A13", "A14", "A15", "A16"];
408 let segs = ctx.find_segments("STS");
409 if segs.is_empty() {
410 return ConditionResult::Unknown;
411 }
412 let found = segs.iter().any(|seg| {
413 seg.elements
414 .first()
415 .and_then(|e| e.first())
416 .is_some_and(|v| v == "Z25")
417 && seg
418 .elements
419 .get(1)
420 .and_then(|e| e.first())
421 .is_some_and(|v| v == "Z31")
422 && seg
423 .elements
424 .get(2)
425 .and_then(|e| e.first())
426 .is_some_and(|v| target_causes.contains(&v.as_str()))
427 });
428 ConditionResult::from(found)
429 }
430
431 /// [103] Wenn in diesem STS DE1131 nicht vorhanden
432 // REVIEW: DE1131 (Codeliste, Code) appears at elements[2][1] in the majority of STS variants per the segment structure reference (C556, second component). Condition is True when that position is absent or empty. The 'in diesem STS' scope cannot be fully resolved without a current-segment concept in EvaluationContext, so this checks all STS segments. Note: STS+Z04 has DE1131 at elements[1][1] instead — this edge case is not covered. (medium confidence)
433 fn evaluate_103(&self, ctx: &EvaluationContext) -> ConditionResult {
434 // DE1131 (Codeliste, Code) is at elements[2][1] (C556 component [1]) in most STS variants.
435 // Condition is True when DE1131 is absent or empty in the relevant STS.
436 let segs = ctx.find_segments("STS");
437 if segs.is_empty() {
438 return ConditionResult::Unknown;
439 }
440 for seg in &segs {
441 let de1131 = seg.elements.get(2).and_then(|e| e.get(1));
442 if de1131.map(|v| v.is_empty()).unwrap_or(true) {
443 return ConditionResult::True;
444 }
445 }
446 ConditionResult::False
447 }
448
449 /// [149] Wenn in dieser SG14 STS DE1131 <> E_0278
450 // REVIEW: Checks DE1131 (elements[2][1]) in STS segments within SG14. Returns True if any STS has DE1131 present and not equal to E_0278, False if all present values equal E_0278, Unknown if no STS has DE1131 set. Uses navigator for group-scoped check with message-wide fallback. (medium confidence)
451 fn evaluate_149(&self, ctx: &EvaluationContext) -> ConditionResult {
452 let nav = match ctx.navigator() {
453 Some(n) => n,
454 None => {
455 let segs = ctx.find_segments("STS");
456 let mut found_any = false;
457 for seg in &segs {
458 if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
459 if !val.is_empty() {
460 found_any = true;
461 if val != "E_0278" {
462 return ConditionResult::True;
463 }
464 }
465 }
466 }
467 return if found_any {
468 ConditionResult::False
469 } else {
470 ConditionResult::Unknown
471 };
472 }
473 };
474 let sg14_count = nav.group_instance_count(&["SG14"]);
475 let mut found_any = false;
476 for i in 0..sg14_count {
477 let sts_segs = nav.find_segments_in_group("STS", &["SG14"], i);
478 for seg in &sts_segs {
479 if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
480 if !val.is_empty() {
481 found_any = true;
482 if val != "E_0278" {
483 return ConditionResult::True;
484 }
485 }
486 }
487 }
488 }
489 if found_any {
490 ConditionResult::False
491 } else {
492 ConditionResult::Unknown
493 }
494 }
495
496 /// [150] Wenn in dieser SG14 STS DE1131 <> E_0281
497 // REVIEW: Identical pattern to condition 149 but checks DE1131 != E_0281. Returns True if any STS in SG14 has DE1131 present and not equal to E_0281, False if all equal E_0281, Unknown if absent. (medium confidence)
498 fn evaluate_150(&self, ctx: &EvaluationContext) -> ConditionResult {
499 let nav = match ctx.navigator() {
500 Some(n) => n,
501 None => {
502 let segs = ctx.find_segments("STS");
503 let mut found_any = false;
504 for seg in &segs {
505 if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
506 if !val.is_empty() {
507 found_any = true;
508 if val != "E_0281" {
509 return ConditionResult::True;
510 }
511 }
512 }
513 }
514 return if found_any {
515 ConditionResult::False
516 } else {
517 ConditionResult::Unknown
518 };
519 }
520 };
521 let sg14_count = nav.group_instance_count(&["SG14"]);
522 let mut found_any = false;
523 for i in 0..sg14_count {
524 let sts_segs = nav.find_segments_in_group("STS", &["SG14"], i);
525 for seg in &sts_segs {
526 if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
527 if !val.is_empty() {
528 found_any = true;
529 if val != "E_0281" {
530 return ConditionResult::True;
531 }
532 }
533 }
534 }
535 }
536 if found_any {
537 ConditionResult::False
538 } else {
539 ConditionResult::Unknown
540 }
541 }
542
543 /// [151] Wenn STS+Z20+Z32+A99:E_0278 in dieser SG14 vorhanden
544 fn evaluate_151(&self, ctx: &EvaluationContext) -> ConditionResult {
545 // STS+Z20+Z32+A99:E_0278: elements[0][0]=Z20, elements[1][0]=Z32, elements[2][0]=A99, elements[2][1]=E_0278
546 ctx.has_segment_matching_in_group(
547 "STS",
548 &[
549 (0, 0, "Z20"),
550 (1, 0, "Z32"),
551 (2, 0, "A99"),
552 (2, 1, "E_0278"),
553 ],
554 &["SG14"],
555 )
556 }
557
558 /// [152] Wenn STS+Z20+Z32+A99:E_0281 in dieser SG14 vorhanden
559 fn evaluate_152(&self, ctx: &EvaluationContext) -> ConditionResult {
560 // STS+Z20+Z32+A99:E_0281: elements[0][0]=Z20, elements[1][0]=Z32, elements[2][0]=A99, elements[2][1]=E_0281
561 ctx.has_segment_matching_in_group(
562 "STS",
563 &[
564 (0, 0, "Z20"),
565 (1, 0, "Z32"),
566 (2, 0, "A99"),
567 (2, 1, "E_0281"),
568 ],
569 &["SG14"],
570 )
571 }
572
573 /// [153] Wenn in diesem STS DE1131 = E_0286
574 fn evaluate_153(&self, ctx: &EvaluationContext) -> ConditionResult {
575 // DE1131 is at elements[2][1] in STS (C556 Statusanlaß component 1)
576 ctx.has_segment_matching("STS", &[(2, 1, "E_0286")])
577 }
578
579 /// [154] Wenn STS+Z15+Z13+A99:E_0286 in dieser SG14 vorhanden
580 // REVIEW: Checks for STS in SG14/SG15 with Statuskategorie Z15 (Status des Umbaus der Messlokation), Status Z13, and Statusanlaß A99:E_0286. Uses has_segment_matching_in_group with authoritative element indices from the segment reference: elements[0][0]=Z15, elements[1][0]=Z13, elements[2][0]=A99 (DE9013 Prüfschritt), elements[2][1]=E_0286 (DE1131 Codeliste). Medium confidence because A99 is not listed in the abbreviated reference for this STS variant (reference shows Z74/Z75), so it may be an additional valid code from the full BDEW codelist. (medium confidence)
581 fn evaluate_154(&self, ctx: &EvaluationContext) -> ConditionResult {
582 ctx.has_segment_matching_in_group(
583 "STS",
584 &[
585 (0, 0, "Z15"),
586 (1, 0, "Z13"),
587 (2, 0, "A99"),
588 (2, 1, "E_0286"),
589 ],
590 &["SG14", "SG15"],
591 )
592 }
593
594 /// [537] Hinweis: Der Code ist nötig, da dieser Anwendungsfall auch in allen Use-Cases zur Anwendung kommt, in denen sich die in diesem Segment zu übertragende Information nicht anhand eines Entscheidungs...
595 fn evaluate_537(&self, _ctx: &EvaluationContext) -> ConditionResult {
596 // Hinweis: Der Code ist nötig, da dieser Anwendungsfall auch in allen Use-Cases zur Anwendung kommt, in denen sich die in diesem Segment zu übertragende Information nicht anhand eines Entscheidungsbaus ergibt.
597 ConditionResult::True
598 }
599
600 /// [538] Hinweis: An dieser Stelle wird der Code aus dem EBD übermittelt
601 fn evaluate_538(&self, _ctx: &EvaluationContext) -> ConditionResult {
602 // Hinweis: An dieser Stelle wird der Code aus dem EBD übermittelt
603 ConditionResult::True
604 }
605
606 /// [1] Wenn die Übermittlung nicht codierbarer Informationen nötig ist.
607 /// EXTERNAL: Requires context from outside the message.
608 // REVIEW: Whether transmission of non-codeable information is necessary is a business context decision that cannot be determined from the EDIFACT message content alone. It depends on whether the sending system has information that cannot be expressed via defined code lists. (medium confidence)
609 fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
610 ctx.external.evaluate("non_codeable_info_needed")
611 }
612
613 /// [3] Wenn SG7 STS+Z01 nicht vorhanden.
614 fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
615 ctx.lacks_qualifier("STS", 0, "Z01")
616 }
617
618 /// [4] Wenn SG7 STS+Z02 nicht vorhanden.
619 fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
620 ctx.lacks_qualifier("STS", 0, "Z02")
621 }
622
623 /// [5] Wenn SG7 STS+Z03 nicht vorhanden.
624 fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
625 ctx.lacks_qualifier("STS", 0, "Z03")
626 }
627
628 /// [10] Wenn Meldung des BKV/NB/ÜNB nach Frist eingeht.
629 /// EXTERNAL: Requires context from outside the message.
630 fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
631 ctx.external.evaluate("message_after_deadline")
632 }
633
634 /// [16] Wenn MP-ID in SG1 NAD+MR in der Rolle BKV
635 /// EXTERNAL: Requires context from outside the message.
636 fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
637 ctx.external.evaluate("recipient_is_bkv")
638 }
639
640 /// [17] Wenn Meldung des BKV auf falscher Aggregationsebene eingeht.
641 /// EXTERNAL: Requires context from outside the message.
642 fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
643 ctx.external.evaluate("bkv_wrong_aggregation_level")
644 }
645
646 /// [18] Wenn SG15 STS+Z19 nicht vorhanden.
647 fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
648 ctx.lacks_qualifier("STS", 0, "Z19")
649 }
650
651 /// [19] Wenn SG15 STS+Z24 nicht vorhanden.
652 fn evaluate_19(&self, ctx: &EvaluationContext) -> ConditionResult {
653 ctx.lacks_qualifier("STS", 0, "Z24")
654 }
655
656 /// [20] Wenn MP-ID in SG1 NAD+MR in der Rolle LF
657 /// EXTERNAL: Requires context from outside the message.
658 fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
659 ctx.external.evaluate("recipient_is_lf")
660 }
661
662 /// [26] Wenn MP-ID in SG1 NAD+MR in der Rolle NB
663 /// EXTERNAL: Requires context from outside the message.
664 fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
665 ctx.external.evaluate("recipient_is_nb")
666 }
667
668 /// [27] Nur MP-ID aus Sparte Strom
669 /// EXTERNAL: Requires context from outside the message.
670 fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
671 ctx.external.evaluate("mp_id_is_electricity_sector")
672 }
673
674 /// [28] Nur MP-ID aus Sparte Gas
675 /// EXTERNAL: Requires context from outside the message.
676 fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
677 ctx.external.evaluate("mp_id_is_gas_sector")
678 }
679
680 /// [29] Wenn MP-ID in SG1 NAD+MR in der Rolle ÜNB
681 /// EXTERNAL: Requires context from outside the message.
682 fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
683 ctx.external.evaluate("recipient_is_uenb")
684 }
685
686 /// [30] Wenn in dieser SG15 STS+Z20+Z32+A07:E_0207 vorhanden.
687 // REVIEW: Checks STS segment for Statuskategorie Z20 (elements[0][0]), Status Z32 (elements[1][0]), Prüfschritt A07 (elements[2][0]), and Codeliste E_0207 (elements[2][1]). Uses message-wide low-level access since no group-scoped helper supports simultaneous multi-field checks on a single segment. The 'A07' code is taken from the AHB shorthand notation at DE9013 position per the segment structure reference. (medium confidence)
688 fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
689 // HAND-EDITED: "in dieser/derselben SG15" — scoped to the enclosing group.
690 let found = ctx
691 .scoped_find_segments_in("SG15", "STS")
692 .into_iter()
693 .any(|s| {
694 s.elements
695 .first()
696 .and_then(|e| e.first())
697 .is_some_and(|v| v == "Z20")
698 && s.elements
699 .get(1)
700 .and_then(|e| e.first())
701 .is_some_and(|v| v == "Z32")
702 && s.elements
703 .get(2)
704 .and_then(|e| e.first())
705 .is_some_and(|v| v == "A07")
706 && s.elements
707 .get(2)
708 .and_then(|e| e.get(1))
709 .is_some_and(|v| v == "E_0207")
710 });
711 ConditionResult::from(found)
712 }
713
714 /// [31] Wenn in dieser SG16 in QTY in DE6411 KWH/K3 vorhanden.
715 fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
716 // HAND-EDITED: "in dieser/derselben SG16" — scoped to the enclosing group.
717 let found = ctx
718 .scoped_find_segments_in("SG16", "QTY")
719 .into_iter()
720 .any(|s| {
721 let unit = s
722 .elements
723 .first()
724 .and_then(|e| e.get(2))
725 .map(|v| v.as_str());
726 unit == Some("KWH") || unit == Some("K3")
727 });
728 ConditionResult::from(found)
729 }
730
731 /// [32] Wenn in dieser SG16 in QTY in DE6411 KWT/K5 vorhanden.
732 fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
733 // HAND-EDITED: "in dieser/derselben SG16" — scoped to the enclosing group.
734 let found = ctx
735 .scoped_find_segments_in("SG16", "QTY")
736 .into_iter()
737 .any(|s| {
738 let unit = s
739 .elements
740 .first()
741 .and_then(|e| e.get(2))
742 .map(|v| v.as_str());
743 unit == Some("KWT") || unit == Some("K5")
744 });
745 ConditionResult::from(found)
746 }
747
748 /// [33] Wenn in dieser SG16 DTM+163 vorhanden.
749 fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
750 // HAND-EDITED: "in dieser/derselben SG16" — scoped to the enclosing group.
751 ctx.this_group_has_qualifier("DTM", 0, "163", &["SG16"])
752 }
753
754 /// [43] Wenn STS+Z01+Z07 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
755 fn evaluate_43(&self, ctx: &EvaluationContext) -> ConditionResult {
756 ctx.has_qualified_value("STS", 0, "Z01", 1, 0, &["Z07"])
757 }
758
759 /// [44] Wenn STS+Z01+Z08 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
760 fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
761 ctx.has_qualified_value("STS", 0, "Z01", 1, 0, &["Z08"])
762 }
763
764 /// [45] Wenn STS+Z03+Z07 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
765 fn evaluate_45(&self, ctx: &EvaluationContext) -> ConditionResult {
766 ctx.has_qualified_value("STS", 0, "Z03", 1, 0, &["Z07"])
767 }
768
769 /// [46] Wenn STS+Z03+Z08 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
770 fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
771 ctx.has_qualified_value("STS", 0, "Z03", 1, 0, &["Z08"])
772 }
773
774 /// [47] Es sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
775 fn evaluate_47(&self, _ctx: &EvaluationContext) -> ConditionResult {
776 ConditionResult::True
777 }
778
779 /// [48] Es sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
780 fn evaluate_48(&self, _ctx: &EvaluationContext) -> ConditionResult {
781 ConditionResult::True
782 }
783
784 /// [49] Wenn STS+Z25+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
785 fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
786 ctx.has_qualified_value("STS", 0, "Z25", 1, 0, &["Z30"])
787 }
788
789 /// [50] Wenn STS+Z25+Z31 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
790 fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
791 ctx.has_qualified_value("STS", 0, "Z25", 1, 0, &["Z31"])
792 }
793
794 /// [51] Es sind nur Codes aus dem EBD-Cluster "Abweisung" möglich.
795 fn evaluate_51(&self, _ctx: &EvaluationContext) -> ConditionResult {
796 ConditionResult::True
797 }
798
799 /// [52] Wenn STS+Z27+Z32 vorhanden
800 fn evaluate_52(&self, ctx: &EvaluationContext) -> ConditionResult {
801 ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z32"])
802 }
803
804 /// [53] Wenn STS+Z28+Z32 vorhanden
805 fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
806 ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z32"])
807 }
808
809 /// [54] Wenn STS+Z29+Z32 vorhanden
810 fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
811 ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"])
812 }
813
814 /// [55] Wenn STS+Z30+Z32 vorhanden
815 fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
816 ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"])
817 }
818
819 /// [56] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z27 ist
820 // REVIEW: Cross-SG15 comparison: find SG15 instances with STS+Z27, collect their RFF+ACW values, then check if any SG15's RFF+ACW differs. Requires low-level navigator access. Medium confidence because the exact group path (SG14→SG15) is inferred from IFTSTA Family B structure. (medium confidence)
821 fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
822 // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z27
823 let nav = match ctx.navigator() {
824 Some(n) => n,
825 None => return ConditionResult::Unknown,
826 };
827 let sg15_path: &[&str] = &["SG14", "SG15"];
828 let sg15_count = nav.group_instance_count(sg15_path);
829 // Collect ACW reference values from SG15 instances that have STS with Z27
830 let mut ref_acw: Vec<String> = Vec::new();
831 for i in 0..sg15_count {
832 let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
833 let has_z27 = sts_segs.iter().any(|s| {
834 s.elements
835 .first()
836 .and_then(|e| e.first())
837 .is_some_and(|v| v == "Z27")
838 });
839 if has_z27 {
840 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
841 for rff in &rff_segs {
842 if rff
843 .elements
844 .first()
845 .and_then(|e| e.first())
846 .is_some_and(|v| v == "ACW")
847 {
848 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
849 if !val.is_empty() {
850 ref_acw.push(val.clone());
851 }
852 }
853 }
854 }
855 }
856 }
857 if ref_acw.is_empty() {
858 return ConditionResult::Unknown;
859 }
860 // Check if any SG15 has an ACW value not present in the Z27 SG15 ACW values
861 for i in 0..sg15_count {
862 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
863 for rff in &rff_segs {
864 if rff
865 .elements
866 .first()
867 .and_then(|e| e.first())
868 .is_some_and(|v| v == "ACW")
869 {
870 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
871 if !val.is_empty() && !ref_acw.contains(val) {
872 return ConditionResult::True;
873 }
874 }
875 }
876 }
877 }
878 ConditionResult::False
879 }
880
881 /// [57] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z28 ist
882 // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z28 (Status des Fahrplananteils) instead of Z27. (medium confidence)
883 fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
884 // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z28
885 let nav = match ctx.navigator() {
886 Some(n) => n,
887 None => return ConditionResult::Unknown,
888 };
889 let sg15_path: &[&str] = &["SG14", "SG15"];
890 let sg15_count = nav.group_instance_count(sg15_path);
891 let mut ref_acw: Vec<String> = Vec::new();
892 for i in 0..sg15_count {
893 let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
894 let has_z28 = sts_segs.iter().any(|s| {
895 s.elements
896 .first()
897 .and_then(|e| e.first())
898 .is_some_and(|v| v == "Z28")
899 });
900 if has_z28 {
901 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
902 for rff in &rff_segs {
903 if rff
904 .elements
905 .first()
906 .and_then(|e| e.first())
907 .is_some_and(|v| v == "ACW")
908 {
909 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
910 if !val.is_empty() {
911 ref_acw.push(val.clone());
912 }
913 }
914 }
915 }
916 }
917 }
918 if ref_acw.is_empty() {
919 return ConditionResult::Unknown;
920 }
921 for i in 0..sg15_count {
922 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
923 for rff in &rff_segs {
924 if rff
925 .elements
926 .first()
927 .and_then(|e| e.first())
928 .is_some_and(|v| v == "ACW")
929 {
930 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
931 if !val.is_empty() && !ref_acw.contains(val) {
932 return ConditionResult::True;
933 }
934 }
935 }
936 }
937 }
938 ConditionResult::False
939 }
940
941 /// [58] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z29 ist
942 // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z29 (Status des Gegenvorschlags der Ausfallarbeit). (medium confidence)
943 fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
944 // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z29
945 let nav = match ctx.navigator() {
946 Some(n) => n,
947 None => return ConditionResult::Unknown,
948 };
949 let sg15_path: &[&str] = &["SG14", "SG15"];
950 let sg15_count = nav.group_instance_count(sg15_path);
951 let mut ref_acw: Vec<String> = Vec::new();
952 for i in 0..sg15_count {
953 let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
954 let has_z29 = sts_segs.iter().any(|s| {
955 s.elements
956 .first()
957 .and_then(|e| e.first())
958 .is_some_and(|v| v == "Z29")
959 });
960 if has_z29 {
961 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
962 for rff in &rff_segs {
963 if rff
964 .elements
965 .first()
966 .and_then(|e| e.first())
967 .is_some_and(|v| v == "ACW")
968 {
969 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
970 if !val.is_empty() {
971 ref_acw.push(val.clone());
972 }
973 }
974 }
975 }
976 }
977 }
978 if ref_acw.is_empty() {
979 return ConditionResult::Unknown;
980 }
981 for i in 0..sg15_count {
982 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
983 for rff in &rff_segs {
984 if rff
985 .elements
986 .first()
987 .and_then(|e| e.first())
988 .is_some_and(|v| v == "ACW")
989 {
990 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
991 if !val.is_empty() && !ref_acw.contains(val) {
992 return ConditionResult::True;
993 }
994 }
995 }
996 }
997 }
998 ConditionResult::False
999 }
1000
1001 /// [59] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z30 ist
1002 // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z30 (Status des Gegenvorschlags des Fahrplananteils). (medium confidence)
1003 fn evaluate_59(&self, ctx: &EvaluationContext) -> ConditionResult {
1004 // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z30
1005 let nav = match ctx.navigator() {
1006 Some(n) => n,
1007 None => return ConditionResult::Unknown,
1008 };
1009 let sg15_path: &[&str] = &["SG14", "SG15"];
1010 let sg15_count = nav.group_instance_count(sg15_path);
1011 let mut ref_acw: Vec<String> = Vec::new();
1012 for i in 0..sg15_count {
1013 let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
1014 let has_z30 = sts_segs.iter().any(|s| {
1015 s.elements
1016 .first()
1017 .and_then(|e| e.first())
1018 .is_some_and(|v| v == "Z30")
1019 });
1020 if has_z30 {
1021 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1022 for rff in &rff_segs {
1023 if rff
1024 .elements
1025 .first()
1026 .and_then(|e| e.first())
1027 .is_some_and(|v| v == "ACW")
1028 {
1029 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1030 if !val.is_empty() {
1031 ref_acw.push(val.clone());
1032 }
1033 }
1034 }
1035 }
1036 }
1037 }
1038 if ref_acw.is_empty() {
1039 return ConditionResult::Unknown;
1040 }
1041 for i in 0..sg15_count {
1042 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1043 for rff in &rff_segs {
1044 if rff
1045 .elements
1046 .first()
1047 .and_then(|e| e.first())
1048 .is_some_and(|v| v == "ACW")
1049 {
1050 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1051 if !val.is_empty() && !ref_acw.contains(val) {
1052 return ConditionResult::True;
1053 }
1054 }
1055 }
1056 }
1057 }
1058 ConditionResult::False
1059 }
1060
1061 /// [60] Wenn zusätzlich zum Fahrplananteil auch die Ausfallarbeit zu identischem Wert aus RFF+ACW nicht o.k. ist
1062 // REVIEW: Checks whether any SG15 with STS+Z27+Z32 (Ausfallarbeit rejected) shares the same RFF+ACW reference value as an SG15 with STS+Z28+Z32 (Fahrplananteil rejected), within the same SG14 transaction group. Assumes SG15 is a child group of SG14 (consistent with IFTSTA Family B tx_group=SG14). (medium confidence)
1063 fn evaluate_60(&self, ctx: &EvaluationContext) -> ConditionResult {
1064 {
1065 let nav = match ctx.navigator() {
1066 Some(n) => n,
1067 None => return ConditionResult::Unknown,
1068 };
1069 let sg14_count = nav.group_instance_count(&["SG14"]);
1070 for i in 0..sg14_count {
1071 let sg15_count = nav.child_group_instance_count(&["SG14"], i, "SG15");
1072 let mut z27_z32_acw: Vec<String> = Vec::new();
1073 let mut z28_z32_acw: Vec<String> = Vec::new();
1074 for j in 0..sg15_count {
1075 let stses = nav.find_segments_in_child_group("STS", &["SG14"], i, "SG15", j);
1076 let rffs = nav.find_segments_in_child_group("RFF", &["SG14"], i, "SG15", j);
1077 let acw_val = rffs
1078 .iter()
1079 .find(|s| {
1080 s.elements
1081 .first()
1082 .and_then(|e| e.first())
1083 .is_some_and(|v| v == "ACW")
1084 })
1085 .and_then(|s| s.elements.first().and_then(|e| e.get(1)).cloned());
1086 let Some(acw) = acw_val else { continue };
1087 for sts in &stses {
1088 let cat = sts
1089 .elements
1090 .first()
1091 .and_then(|e| e.first())
1092 .map(|s| s.as_str());
1093 let status = sts
1094 .elements
1095 .get(1)
1096 .and_then(|e| e.first())
1097 .map(|s| s.as_str());
1098 match (cat, status) {
1099 (Some("Z27"), Some("Z32")) => z27_z32_acw.push(acw.clone()),
1100 (Some("Z28"), Some("Z32")) => z28_z32_acw.push(acw.clone()),
1101 _ => {}
1102 }
1103 }
1104 }
1105 if z27_z32_acw.iter().any(|v| z28_z32_acw.contains(v)) {
1106 return ConditionResult::True;
1107 }
1108 }
1109 ConditionResult::False
1110 }
1111 }
1112
1113 /// [61] Wenn zusätzlich zur Ausfallarbeit auch der Fahrplananteil zu identischem Wert aus RFF+ACW nicht o.k. ist
1114 // REVIEW: Mirror of condition 60: checks whether any SG15 with STS+Z28+Z32 (Fahrplananteil rejected) shares the same RFF+ACW as an SG15 with STS+Z27+Z32 (Ausfallarbeit rejected). The two conditions are semantically equivalent but expressed from different perspectives (the AHB uses them on different fields). Same SG14→SG15 navigator approach. (medium confidence)
1115 fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
1116 {
1117 let nav = match ctx.navigator() {
1118 Some(n) => n,
1119 None => return ConditionResult::Unknown,
1120 };
1121 let sg14_count = nav.group_instance_count(&["SG14"]);
1122 for i in 0..sg14_count {
1123 let sg15_count = nav.child_group_instance_count(&["SG14"], i, "SG15");
1124 let mut z27_z32_acw: Vec<String> = Vec::new();
1125 let mut z28_z32_acw: Vec<String> = Vec::new();
1126 for j in 0..sg15_count {
1127 let stses = nav.find_segments_in_child_group("STS", &["SG14"], i, "SG15", j);
1128 let rffs = nav.find_segments_in_child_group("RFF", &["SG14"], i, "SG15", j);
1129 let acw_val = rffs
1130 .iter()
1131 .find(|s| {
1132 s.elements
1133 .first()
1134 .and_then(|e| e.first())
1135 .is_some_and(|v| v == "ACW")
1136 })
1137 .and_then(|s| s.elements.first().and_then(|e| e.get(1)).cloned());
1138 let Some(acw) = acw_val else { continue };
1139 for sts in &stses {
1140 let cat = sts
1141 .elements
1142 .first()
1143 .and_then(|e| e.first())
1144 .map(|s| s.as_str());
1145 let status = sts
1146 .elements
1147 .get(1)
1148 .and_then(|e| e.first())
1149 .map(|s| s.as_str());
1150 match (cat, status) {
1151 (Some("Z27"), Some("Z32")) => z27_z32_acw.push(acw.clone()),
1152 (Some("Z28"), Some("Z32")) => z28_z32_acw.push(acw.clone()),
1153 _ => {}
1154 }
1155 }
1156 }
1157 if z28_z32_acw.iter().any(|v| z27_z32_acw.contains(v)) {
1158 return ConditionResult::True;
1159 }
1160 }
1161 ConditionResult::False
1162 }
1163 }
1164
1165 /// [62] Wenn STS+Z27+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1166 fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
1167 ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z30"])
1168 }
1169
1170 /// [63] Wenn STS+Z27+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1171 fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
1172 ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z32"])
1173 }
1174
1175 /// [64] Wenn STS+Z28+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1176 fn evaluate_64(&self, ctx: &EvaluationContext) -> ConditionResult {
1177 ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z30"])
1178 }
1179
1180 /// [65] Wenn STS+Z28+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1181 fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
1182 ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z32"])
1183 }
1184
1185 /// [66] Wenn STS+Z29+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1186 fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
1187 ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z30"])
1188 }
1189
1190 /// [67] Wenn STS+Z29+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1191 fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
1192 ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"])
1193 }
1194
1195 /// [68] Wenn STS+Z30+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1196 fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
1197 ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z30"])
1198 }
1199
1200 /// [69] Wenn STS+Z30+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1201 fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
1202 ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"])
1203 }
1204
1205 /// [70] Wenn zusätzlich zum Gegenvorschlag des Fahrplananteils auch der Gegenvorschlag der Ausfallarbeit zu identischem Wert aus RFF+ACW nicht o.k. ist
1206 // REVIEW: Condition 70 means: additionally to Fahrplananteil counter-proposal (STS Z30 Z32) being rejected, the Ausfallarbeit counter-proposal (STS Z29 Z32) is also rejected. The 'identischem Wert aus RFF+ACW' (same RFF+ACW reference) cross-group correlation is simplified — checks that both rejection STS types are present in the message. Full cross-SG14 value matching would require navigator group enumeration not fully supported by current helpers. (medium confidence)
1207 fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
1208 match ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"]) {
1209 ConditionResult::True => ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"]),
1210 ConditionResult::False => ConditionResult::False,
1211 ConditionResult::Unknown => ConditionResult::Unknown,
1212 }
1213 }
1214
1215 /// [71] Wenn zusätzlich zum Gegenvorschlag der Ausfallarbeit auch der Gegenvorschlag des Fahrplananteils zu identischem Wert aus RFF+ACW nicht o.k. ist
1216 // REVIEW: Symmetric to condition 70: additionally to Ausfallarbeit counter-proposal (STS Z29 Z32) being rejected, the Fahrplananteil counter-proposal (STS Z30 Z32) is also rejected. Same simplification of the RFF+ACW cross-group correlation applies. (medium confidence)
1217 fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
1218 match ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"]) {
1219 ConditionResult::True => ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"]),
1220 ConditionResult::False => ConditionResult::False,
1221 ConditionResult::Unknown => ConditionResult::Unknown,
1222 }
1223 }
1224
1225 /// [72] Wenn Gegenvorschlag erstellt werden kann
1226 /// EXTERNAL: Requires context from outside the message.
1227 fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
1228 ctx.external.evaluate("counter_proposal_possible")
1229 }
1230
1231 /// [76] Wenn MP-ID in SG1 NAD+MR in der Rolle ESA
1232 /// EXTERNAL: Requires context from outside the message.
1233 fn evaluate_76(&self, ctx: &EvaluationContext) -> ConditionResult {
1234 ctx.external.evaluate("recipient_is_esa")
1235 }
1236
1237 /// [77] Wenn STS+Z37+Z14 in dieser SG14 vorhanden
1238 fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
1239 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1240 ctx.this_group_has_qualified_value("STS", 0, "Z37", 1, 0, &["Z14"], &["SG14"])
1241 }
1242
1243 /// [78] Wenn STS+Z38 in dieser SG14 nicht vorhanden
1244 // REVIEW: STS+Z38 (Entsperren) is absent in the SG14 that contains STS+Z37 (Sperren). Uses any_group_has_qualifier_without to find an SG14 instance where STS+Z37 is present but STS+Z38 is not, covering the intra-SG14 scoping requirement. (medium confidence)
1245 fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
1246 ctx.any_group_has_qualifier_without("STS", 0, "Z37", "STS", 0, "Z38", &["SG14"])
1247 }
1248
1249 /// [79] Wenn STS+Z37 in dieser SG14 nicht vorhanden
1250 // REVIEW: STS+Z37 (Sperren) is absent in the SG14 that contains STS+Z38 (Entsperren). Symmetric to condition 78 — finds SG14 where STS+Z38 is present but STS+Z37 is not. (medium confidence)
1251 fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
1252 ctx.any_group_has_qualifier_without("STS", 0, "Z38", "STS", 0, "Z37", &["SG14"])
1253 }
1254
1255 /// [80] Wenn STS+Z38+Z14 in dieser SG14 vorhanden
1256 fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
1257 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1258 ctx.this_group_has_qualified_value("STS", 0, "Z38", 1, 0, &["Z14"], &["SG14"])
1259 }
1260
1261 /// [83] Wenn STS+Z37+Z13+A04/A05/A06:E_0472 in dieser SG14 vorhanden
1262 // REVIEW: STS+Z37 (Sperren) with Status Z13 and Statusanlass (elements[2][0], DE9013) in {A04, A05, A06}. The Codeliste code E_0472 in elements[2][1] is not checked as the primary discriminator is the DE9013 Prüfschritt code. Message-wide search is used as group-scoped SG15 traversal is not available via documented helpers. (medium confidence)
1263 fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
1264 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1265 let sts_segs = ctx.scoped_find_segments_in("SG14", "STS");
1266 let found = sts_segs.iter().any(|s| {
1267 s.elements
1268 .first()
1269 .and_then(|e| e.first())
1270 .is_some_and(|v| v == "Z37")
1271 && s.elements
1272 .get(1)
1273 .and_then(|e| e.first())
1274 .is_some_and(|v| v == "Z13")
1275 && s.elements
1276 .get(2)
1277 .and_then(|e| e.first())
1278 .is_some_and(|v| matches!(v.as_str(), "A04" | "A05" | "A06"))
1279 });
1280 ConditionResult::from(found)
1281 }
1282
1283 /// [84] Wenn STS+Z38+Z13+A02:E_0499 in dieser SG14 vorhanden
1284 // REVIEW: STS+Z38 (Entsperren) with Status Z13 and Statusanlass DE9013=A02 (Codeliste E_0499). Checks elements[0][0]=Z38, elements[1][0]=Z13, elements[2][0]=A02. Message-wide search used as SG14/SG15 child group traversal is not in documented helpers. (medium confidence)
1285 fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
1286 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1287 let sts_segs = ctx.scoped_find_segments_in("SG14", "STS");
1288 let found = sts_segs.iter().any(|s| {
1289 s.elements
1290 .first()
1291 .and_then(|e| e.first())
1292 .is_some_and(|v| v == "Z38")
1293 && s.elements
1294 .get(1)
1295 .and_then(|e| e.first())
1296 .is_some_and(|v| v == "Z13")
1297 && s.elements
1298 .get(2)
1299 .and_then(|e| e.first())
1300 .is_some_and(|v| v == "A02")
1301 });
1302 ConditionResult::from(found)
1303 }
1304
1305 /// [85] Wenn STS+Z37+Z32+A01:E_0501 in dieser SG14 vorhanden
1306 // REVIEW: STS+Z37 (Sperren) with Status Z32 (Ablehnung) and Statusanlass DE9013=A01 (Codeliste E_0501). Checks elements[0][0]=Z37, elements[1][0]=Z32, elements[2][0]=A01. Message-wide search used as SG14/SG15 child group traversal is not in documented helpers. (medium confidence)
1307 fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
1308 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1309 let sts_segs = ctx.scoped_find_segments_in("SG14", "STS");
1310 let found = sts_segs.iter().any(|s| {
1311 s.elements
1312 .first()
1313 .and_then(|e| e.first())
1314 .is_some_and(|v| v == "Z37")
1315 && s.elements
1316 .get(1)
1317 .and_then(|e| e.first())
1318 .is_some_and(|v| v == "Z32")
1319 && s.elements
1320 .get(2)
1321 .and_then(|e| e.first())
1322 .is_some_and(|v| v == "A01")
1323 });
1324 ConditionResult::from(found)
1325 }
1326
1327 /// [86] Wenn MP-ID in SG1 NAD+MS in der Rolle LF
1328 /// EXTERNAL: Requires context from outside the message.
1329 fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
1330 ctx.external.evaluate("sender_is_lf")
1331 }
1332
1333 /// [91] Wenn in diesem STS DE1131 = E_0472
1334 fn evaluate_91(&self, ctx: &EvaluationContext) -> ConditionResult {
1335 let segments = ctx.find_segments("STS");
1336 ConditionResult::from(segments.iter().any(|s| {
1337 s.elements
1338 .get(2)
1339 .and_then(|e| e.get(1))
1340 .map(|v| v == "E_0472")
1341 .unwrap_or(false)
1342 }))
1343 }
1344
1345 /// [92] Wenn in diesem STS DE1131 = E_0501
1346 fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
1347 let segments = ctx.find_segments("STS");
1348 ConditionResult::from(segments.iter().any(|s| {
1349 s.elements
1350 .get(2)
1351 .and_then(|e| e.get(1))
1352 .map(|v| v == "E_0501")
1353 .unwrap_or(false)
1354 }))
1355 }
1356
1357 /// [93] Wenn STS+Z37+Z13 vorhanden, dann sind nur Codes aus dem EBD-Cluster "gescheitert" möglich.
1358 fn evaluate_93(&self, ctx: &EvaluationContext) -> ConditionResult {
1359 ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z13"])
1360 }
1361
1362 /// [94] Wenn STS+Z37+Z14 vorhanden, dann sind nur Codes aus dem EBD-Cluster "erfolgreich" möglich.
1363 fn evaluate_94(&self, ctx: &EvaluationContext) -> ConditionResult {
1364 ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z14"])
1365 }
1366
1367 /// [95] Wenn in diesem STS DE1131 = E_0499
1368 fn evaluate_95(&self, ctx: &EvaluationContext) -> ConditionResult {
1369 let segments = ctx.find_segments("STS");
1370 ConditionResult::from(segments.iter().any(|s| {
1371 s.elements
1372 .get(2)
1373 .and_then(|e| e.get(1))
1374 .map(|v| v == "E_0499")
1375 .unwrap_or(false)
1376 }))
1377 }
1378
1379 /// [96] Wenn in diesem STS DE1131 = E_0487
1380 fn evaluate_96(&self, ctx: &EvaluationContext) -> ConditionResult {
1381 let segments = ctx.find_segments("STS");
1382 ConditionResult::from(segments.iter().any(|s| {
1383 s.elements
1384 .get(2)
1385 .and_then(|e| e.get(1))
1386 .map(|v| v == "E_0487")
1387 .unwrap_or(false)
1388 }))
1389 }
1390
1391 /// [97] Wenn STS+Z38+Z13 vorhanden, dann sind nur Codes aus dem EBD-Cluster "gescheitert" möglich.
1392 fn evaluate_97(&self, ctx: &EvaluationContext) -> ConditionResult {
1393 ctx.has_qualified_value("STS", 0, "Z38", 1, 0, &["Z13"])
1394 }
1395
1396 /// [98] Wenn STS+Z38+Z14 vorhanden, dann sind nur Codes aus dem EBD-Cluster "erfolgreich" möglich.
1397 fn evaluate_98(&self, ctx: &EvaluationContext) -> ConditionResult {
1398 ctx.has_qualified_value("STS", 0, "Z38", 1, 0, &["Z14"])
1399 }
1400
1401 /// [99] Wenn in diesem STS DE1131 = E_0487, dann ist nur der Code A01 möglich.
1402 fn evaluate_99(&self, ctx: &EvaluationContext) -> ConditionResult {
1403 let segments = ctx.find_segments("STS");
1404 ConditionResult::from(segments.iter().any(|s| {
1405 s.elements
1406 .get(2)
1407 .and_then(|e| e.get(1))
1408 .map(|v| v == "E_0487")
1409 .unwrap_or(false)
1410 }))
1411 }
1412
1413 /// [100] Wenn STS+Z43+Z47 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Änderung der Daten" möglich.
1414 fn evaluate_100(&self, ctx: &EvaluationContext) -> ConditionResult {
1415 ctx.has_qualified_value("STS", 0, "Z43", 1, 0, &["Z47"])
1416 }
1417
1418 /// [101] Wenn STS+Z43+Z48 vorhanden, dann sind nur Codes aus dem EBD-Cluster "keine Änderung der Daten" möglich.
1419 fn evaluate_101(&self, ctx: &EvaluationContext) -> ConditionResult {
1420 ctx.has_qualified_value("STS", 0, "Z43", 1, 0, &["Z48"])
1421 }
1422
1423 /// [107] Wenn STS+Z37+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1424 fn evaluate_107(&self, ctx: &EvaluationContext) -> ConditionResult {
1425 ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z32"])
1426 }
1427
1428 /// [114] Wenn MP-ID in SG1 NAD+MS in der Rolle MSB
1429 /// EXTERNAL: Requires context from outside the message.
1430 fn evaluate_114(&self, ctx: &EvaluationContext) -> ConditionResult {
1431 ctx.external.evaluate("sender_is_msb")
1432 }
1433
1434 /// [115] Wenn MP-ID in SG1 NAD+MS in der Rolle NB
1435 /// EXTERNAL: Requires context from outside the message.
1436 fn evaluate_115(&self, ctx: &EvaluationContext) -> ConditionResult {
1437 ctx.external.evaluate("sender_is_nb")
1438 }
1439
1440 /// [117] Wenn MP-ID in SG1 NAD+MR in der Rolle MSB
1441 /// EXTERNAL: Requires context from outside the message.
1442 fn evaluate_117(&self, ctx: &EvaluationContext) -> ConditionResult {
1443 ctx.external.evaluate("recipient_is_msb")
1444 }
1445
1446 /// [118] Wenn in diesem STS DE1131 = E_0526
1447 fn evaluate_118(&self, ctx: &EvaluationContext) -> ConditionResult {
1448 ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0526"])
1449 }
1450
1451 /// [119] Wenn in diesem STS DE1131 = E_0528
1452 fn evaluate_119(&self, ctx: &EvaluationContext) -> ConditionResult {
1453 ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0528"])
1454 }
1455
1456 /// [120] Wenn in diesem STS DE1131 = E_0529
1457 fn evaluate_120(&self, ctx: &EvaluationContext) -> ConditionResult {
1458 ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0529"])
1459 }
1460
1461 /// [121] Wenn in diesem STS DE1131 = E_0536
1462 fn evaluate_121(&self, ctx: &EvaluationContext) -> ConditionResult {
1463 ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0536"])
1464 }
1465
1466 /// [129] Wenn STS+Z20+Z32+A99:E_0524 in dieser SG14 vorhanden
1467 // REVIEW: Check STS with Statuskategorie Z20, Status Z32, Prüfschritt A99 (elements[2][0]), and DE1131 E_0524 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide search. (medium confidence)
1468 fn evaluate_129(&self, ctx: &EvaluationContext) -> ConditionResult {
1469 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1470 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1471 ConditionResult::from(segments.iter().any(|s| {
1472 s.elements
1473 .first()
1474 .and_then(|e| e.first())
1475 .map(|v| v == "Z20")
1476 .unwrap_or(false)
1477 && s.elements
1478 .get(1)
1479 .and_then(|e| e.first())
1480 .map(|v| v == "Z32")
1481 .unwrap_or(false)
1482 && s.elements
1483 .get(2)
1484 .and_then(|e| e.first())
1485 .map(|v| v == "A99")
1486 .unwrap_or(false)
1487 && s.elements
1488 .get(2)
1489 .and_then(|e| e.get(1))
1490 .map(|v| v == "E_0524")
1491 .unwrap_or(false)
1492 }))
1493 }
1494
1495 /// [130] Wenn STS+Z20+Z32+A99:E_0531 in dieser SG14 vorhanden
1496 // REVIEW: Check STS with Statuskategorie Z20, Status Z32, Prüfschritt A99 (elements[2][0]), and DE1131 E_0531 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide search. (medium confidence)
1497 fn evaluate_130(&self, ctx: &EvaluationContext) -> ConditionResult {
1498 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1499 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1500 ConditionResult::from(segments.iter().any(|s| {
1501 s.elements
1502 .first()
1503 .and_then(|e| e.first())
1504 .map(|v| v == "Z20")
1505 .unwrap_or(false)
1506 && s.elements
1507 .get(1)
1508 .and_then(|e| e.first())
1509 .map(|v| v == "Z32")
1510 .unwrap_or(false)
1511 && s.elements
1512 .get(2)
1513 .and_then(|e| e.first())
1514 .map(|v| v == "A99")
1515 .unwrap_or(false)
1516 && s.elements
1517 .get(2)
1518 .and_then(|e| e.get(1))
1519 .map(|v| v == "E_0531")
1520 .unwrap_or(false)
1521 }))
1522 }
1523
1524 /// [131] Wenn STS+Z37+Z13+A04/A05/A06:E_1003 in dieser SG14 vorhanden
1525 // REVIEW: Check STS with Statuskategorie Z37 (Auftragstatus Sperren), Status Z13, DE9013 in {A04,A05,A06} (elements[2][0]), and DE1131 E_1003 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide. (medium confidence)
1526 fn evaluate_131(&self, ctx: &EvaluationContext) -> ConditionResult {
1527 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1528 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1529 ConditionResult::from(segments.iter().any(|s| {
1530 s.elements
1531 .first()
1532 .and_then(|e| e.first())
1533 .map(|v| v == "Z37")
1534 .unwrap_or(false)
1535 && s.elements
1536 .get(1)
1537 .and_then(|e| e.first())
1538 .map(|v| v == "Z13")
1539 .unwrap_or(false)
1540 && s.elements
1541 .get(2)
1542 .and_then(|e| e.first())
1543 .map(|v| matches!(v.as_str(), "A04" | "A05" | "A06"))
1544 .unwrap_or(false)
1545 && s.elements
1546 .get(2)
1547 .and_then(|e| e.get(1))
1548 .map(|v| v == "E_1003")
1549 .unwrap_or(false)
1550 }))
1551 }
1552
1553 /// [132] Wenn STS+Z37+Z32+A01:E_1002 in dieser SG14 vorhanden
1554 // REVIEW: Check STS with Statuskategorie Z37 (Auftragstatus Sperren), Status Z32, Prüfschritt A01 (elements[2][0]), and DE1131 E_1002 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide. (medium confidence)
1555 fn evaluate_132(&self, ctx: &EvaluationContext) -> ConditionResult {
1556 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1557 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1558 ConditionResult::from(segments.iter().any(|s| {
1559 s.elements
1560 .first()
1561 .and_then(|e| e.first())
1562 .map(|v| v == "Z37")
1563 .unwrap_or(false)
1564 && s.elements
1565 .get(1)
1566 .and_then(|e| e.first())
1567 .map(|v| v == "Z32")
1568 .unwrap_or(false)
1569 && s.elements
1570 .get(2)
1571 .and_then(|e| e.first())
1572 .map(|v| v == "A01")
1573 .unwrap_or(false)
1574 && s.elements
1575 .get(2)
1576 .and_then(|e| e.get(1))
1577 .map(|v| v == "E_1002")
1578 .unwrap_or(false)
1579 }))
1580 }
1581
1582 /// [133] Wenn in diesem STS DE1131 = E_1003
1583 fn evaluate_133(&self, ctx: &EvaluationContext) -> ConditionResult {
1584 ctx.has_qualified_value("STS", 0, "Z37", 2, 1, &["E_1003"])
1585 }
1586
1587 /// [134] Wenn in diesem STS DE1131 = E_1002
1588 fn evaluate_134(&self, ctx: &EvaluationContext) -> ConditionResult {
1589 ctx.has_qualified_value("STS", 0, "Z37", 2, 1, &["E_1002"])
1590 }
1591
1592 /// [135] Wenn in dieser SG15 in STS+Z21 DE9013 = A99
1593 fn evaluate_135(&self, ctx: &EvaluationContext) -> ConditionResult {
1594 ctx.has_qualified_value("STS", 0, "Z21", 2, 0, &["A99"])
1595 }
1596
1597 /// [136] Wenn STS+Z38+Z13+A02:E_1005 in dieser SG14 vorhanden
1598 // REVIEW: Check STS with Statuskategorie Z38 (Auftragstatus Entsperren), Status Z13, Prüfschritt A02 (elements[2][0]), and DE1131 E_1005 (elements[2][1]). Group-scoped to SG14 but falls back to message-wide. (medium confidence)
1599 fn evaluate_136(&self, ctx: &EvaluationContext) -> ConditionResult {
1600 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1601 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1602 ConditionResult::from(segments.iter().any(|s| {
1603 s.elements
1604 .first()
1605 .and_then(|e| e.first())
1606 .map(|v| v == "Z38")
1607 .unwrap_or(false)
1608 && s.elements
1609 .get(1)
1610 .and_then(|e| e.first())
1611 .map(|v| v == "Z13")
1612 .unwrap_or(false)
1613 && s.elements
1614 .get(2)
1615 .and_then(|e| e.first())
1616 .map(|v| v == "A02")
1617 .unwrap_or(false)
1618 && s.elements
1619 .get(2)
1620 .and_then(|e| e.get(1))
1621 .map(|v| v == "E_1005")
1622 .unwrap_or(false)
1623 }))
1624 }
1625
1626 /// [137] Wenn in diesem STS DE1131 = E_1005
1627 fn evaluate_137(&self, ctx: &EvaluationContext) -> ConditionResult {
1628 ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1005"])
1629 }
1630
1631 /// [138] Wenn in diesem STS DE1131 = E_1020
1632 fn evaluate_138(&self, ctx: &EvaluationContext) -> ConditionResult {
1633 ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1020"])
1634 }
1635
1636 /// [139] Wenn in diesem STS DE1131 = E_1020, dann ist nur der Code A01 möglich.
1637 // REVIEW: Condition checks if DE1131=E_1020 in STS Z38 (Auftragstatus Entsperren). The clause 'dann ist nur der Code A01 möglich' documents that when this condition is true, only code A01 is valid for the dependent field — this is a downstream validation note, not an additional condition predicate. Implementation is identical to condition 138. (medium confidence)
1638 fn evaluate_139(&self, ctx: &EvaluationContext) -> ConditionResult {
1639 ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1020"])
1640 }
1641
1642 /// [140] Wenn in diesem STS DE9013 <> A01
1643 // REVIEW: Checks if any STS segment has DE9013 (elements[2][0]) with a non-empty value that is not 'A01'. Medium confidence because 'in diesem STS' implies a segment-level context that may not be fully captured by message-wide search. (medium confidence)
1644 fn evaluate_140(&self, ctx: &EvaluationContext) -> ConditionResult {
1645 let sts_segments = ctx.find_segments("STS");
1646 ConditionResult::from(sts_segments.iter().any(|s| {
1647 s.elements
1648 .get(2)
1649 .and_then(|e| e.first())
1650 .is_some_and(|v| !v.is_empty() && v != "A01")
1651 }))
1652 }
1653
1654 /// [141] Wenn in diesem STS DE9013 = A01
1655 // REVIEW: Checks if any STS segment has DE9013 (elements[2][0]) equal to 'A01'. Medium confidence due to segment-level context ambiguity. (medium confidence)
1656 fn evaluate_141(&self, ctx: &EvaluationContext) -> ConditionResult {
1657 let sts_segments = ctx.find_segments("STS");
1658 ConditionResult::from(sts_segments.iter().any(|s| {
1659 s.elements
1660 .get(2)
1661 .and_then(|e| e.first())
1662 .is_some_and(|v| v == "A01")
1663 }))
1664 }
1665
1666 /// [142] Wenn in diesem STS DE1131 = E_0535
1667 fn evaluate_142(&self, ctx: &EvaluationContext) -> ConditionResult {
1668 let sts_segments = ctx.find_segments("STS");
1669 ConditionResult::from(sts_segments.iter().any(|s| {
1670 s.elements
1671 .get(2)
1672 .and_then(|e| e.get(1))
1673 .is_some_and(|v| v == "E_0535")
1674 }))
1675 }
1676
1677 /// [143] Wenn mehr als ein Grund vorliegt und die voranstehenden DE9013 dieses STS nicht ausreichen
1678 // REVIEW: STS Z41 (Grund der Privilegierung nach EnFG) supports up to 5 C556 groups (elements[2]-[6]). 'mehr als ein Grund' is indicated when elements[3][0] (second C556 DE9013) is non-empty, meaning the first slot alone was insufficient. Medium confidence because the exact threshold depends on which positional slot this condition guards. (medium confidence)
1679 fn evaluate_143(&self, ctx: &EvaluationContext) -> ConditionResult {
1680 let sts_segments = ctx.find_segments("STS");
1681 ConditionResult::from(sts_segments.iter().any(|s| {
1682 s.elements
1683 .get(3)
1684 .and_then(|e| e.first())
1685 .is_some_and(|v| !v.is_empty())
1686 }))
1687 }
1688
1689 /// [144] Wenn RFF+Z45 in dieser SG15 nicht vorhanden
1690 // REVIEW: Checks that in some SG15 instance (inside SG14 for IFTSTA Family B), RFF+Z13 (Prüfidentifikator, always present) is present but RFF+Z45 (Artikel-ID reference) is absent. Uses RFF+Z13 as the anchor since it is mandatory in every SG15. (medium confidence)
1691 fn evaluate_144(&self, ctx: &EvaluationContext) -> ConditionResult {
1692 ctx.any_group_has_qualifier_without("RFF", 0, "Z13", "RFF", 0, "Z45", &["SG14", "SG15"])
1693 }
1694
1695 /// [145] Wenn RFF+Z17 in dieser SG15 nicht vorhanden
1696 // REVIEW: Checks that in some SG15 instance, RFF+Z13 (mandatory Prüfidentifikator) is present but RFF+Z17 (Preisschlüsselstamm reference) is absent. (medium confidence)
1697 fn evaluate_145(&self, ctx: &EvaluationContext) -> ConditionResult {
1698 ctx.any_group_has_qualifier_without("RFF", 0, "Z13", "RFF", 0, "Z17", &["SG14", "SG15"])
1699 }
1700
1701 /// [146] Wenn im DE3155 in demselben COM der Code EM vorhanden ist
1702 fn evaluate_146(&self, ctx: &EvaluationContext) -> ConditionResult {
1703 let com_segs = ctx.find_segments("COM");
1704 ConditionResult::from(com_segs.iter().any(|s| {
1705 s.elements
1706 .first()
1707 .and_then(|e| e.get(1))
1708 .is_some_and(|v| v == "EM")
1709 }))
1710 }
1711
1712 /// [147] Wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
1713 fn evaluate_147(&self, ctx: &EvaluationContext) -> ConditionResult {
1714 let com_segs = ctx.find_segments("COM");
1715 ConditionResult::from(com_segs.iter().any(|s| {
1716 s.elements
1717 .first()
1718 .and_then(|e| e.get(1))
1719 .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
1720 }))
1721 }
1722
1723 /// [148] Wenn in dieser SG15 STS+Z43+Z48+A99:E_0595 vorhanden
1724 fn evaluate_148(&self, ctx: &EvaluationContext) -> ConditionResult {
1725 // HAND-EDITED: "in dieser/derselben SG15" — scoped to the enclosing group.
1726 let sts_segs = ctx.scoped_find_segments_in("SG15", "STS");
1727 ConditionResult::from(sts_segs.iter().any(|s| {
1728 s.elements
1729 .first()
1730 .and_then(|e| e.first())
1731 .is_some_and(|v| v == "Z43")
1732 && s.elements
1733 .get(1)
1734 .and_then(|e| e.first())
1735 .is_some_and(|v| v == "Z48")
1736 && s.elements
1737 .get(2)
1738 .and_then(|e| e.first())
1739 .is_some_and(|v| v == "A99")
1740 && s.elements
1741 .get(2)
1742 .and_then(|e| e.get(1))
1743 .is_some_and(|v| v == "E_0595")
1744 }))
1745 }
1746
1747 /// [490] wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Prozesszeitpunkt bei MESZ mit UTC“ ist
1748 fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
1749 match ctx.resolved_value {
1750 Some(val) => is_mesz_utc(val),
1751 None => ConditionResult::False,
1752 }
1753 }
1754
1755 /// [491] wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Prozesszeitpunkt bei MEZ mit UTC“ ist
1756 fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1757 match ctx.resolved_value {
1758 Some(val) => is_mez_utc(val),
1759 None => ConditionResult::False,
1760 }
1761 }
1762
1763 /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
1764 /// EXTERNAL: Requires context from outside the message.
1765 fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1766 ctx.external.evaluate("recipient_is_strom")
1767 }
1768
1769 /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
1770 /// EXTERNAL: Requires context from outside the message.
1771 fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1772 ctx.external.evaluate("recipient_is_gas")
1773 }
1774
1775 /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
1776 // REVIEW: The date must be the document creation time (DTM+137) or earlier. Applied to DTM+334 (Zeitpunkt der Statusvergabe) which is the most natural target in IFTSTA. Format 303 timestamps are lexicographically ordered so string comparison is valid. (medium confidence)
1777 fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
1778 // The referenced date must be the document creation time or earlier (≤ DTM+137)
1779 let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1780 let doc_val = match doc_segs
1781 .first()
1782 .and_then(|s| s.elements.first())
1783 .and_then(|e| e.get(1))
1784 {
1785 Some(v) => v.as_str(),
1786 None => return ConditionResult::Unknown,
1787 };
1788 let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
1789 match status_segs
1790 .first()
1791 .and_then(|s| s.elements.first())
1792 .and_then(|e| e.get(1))
1793 {
1794 Some(val) => ConditionResult::from(val.as_str() <= doc_val),
1795 None => ConditionResult::False, // segment absent → condition not applicable
1796 }
1797 }
1798
1799 /// [495] Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein
1800 // REVIEW: The timestamp must be ≤ DE2380 of DTM+137 (document creation date). Compares DTM+334 against DTM+137 using lexicographic ordering, valid for format 303 (YYYYMMDDhhmm) timestamps. (medium confidence)
1801 fn evaluate_495(&self, ctx: &EvaluationContext) -> ConditionResult {
1802 // The timestamp must be ≤ the value in DE2380 of DTM+137
1803 let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1804 let doc_val = match doc_segs
1805 .first()
1806 .and_then(|s| s.elements.first())
1807 .and_then(|e| e.get(1))
1808 {
1809 Some(v) => v.as_str(),
1810 None => return ConditionResult::Unknown,
1811 };
1812 let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
1813 match status_segs
1814 .first()
1815 .and_then(|s| s.elements.first())
1816 .and_then(|e| e.get(1))
1817 {
1818 Some(val) => ConditionResult::from(val.as_str() <= doc_val),
1819 None => ConditionResult::False, // segment absent → condition not applicable
1820 }
1821 }
1822
1823 /// [496] Der Zeitpunkt muss > dem Wert im DE2380 des DTM+137 sein
1824 // REVIEW: The timestamp must be strictly greater than DE2380 of DTM+137. Inverse of condition 495. Applied to DTM+334 which must be after the document creation date in this usage context. (medium confidence)
1825 fn evaluate_496(&self, ctx: &EvaluationContext) -> ConditionResult {
1826 // The timestamp must be > the value in DE2380 of DTM+137
1827 let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
1828 let doc_val = match doc_segs
1829 .first()
1830 .and_then(|s| s.elements.first())
1831 .and_then(|e| e.get(1))
1832 {
1833 Some(v) => v.as_str(),
1834 None => return ConditionResult::Unknown,
1835 };
1836 let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
1837 match status_segs
1838 .first()
1839 .and_then(|s| s.elements.first())
1840 .and_then(|e| e.get(1))
1841 {
1842 Some(val) => ConditionResult::from(val.as_str() > doc_val),
1843 None => ConditionResult::False, // segment absent → condition not applicable
1844 }
1845 }
1846
1847 /// [501] Hinweis: Aus QUOTES BGM DE1004
1848 fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1849 // Hinweis: Aus QUOTES BGM DE1004 — informational note about field origin, always applies
1850 ConditionResult::True
1851 }
1852
1853 /// [502] Hinweis: Aus REQOTE BGM DE1004
1854 fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1855 // Hinweis: Aus REQOTE BGM DE1004 — informational note about field origin, always applies
1856 ConditionResult::True
1857 }
1858
1859 /// [503] Hinweis: Auf Selbsteinbau eines iMS oder einer mME wird verzichtet
1860 fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
1861 // Hinweis: Auf Selbsteinbau eines iMS oder einer mME wird verzichtet — informational note, always applies
1862 ConditionResult::True
1863 }
1864
1865 /// [504] Hinweis: Verwendung der ID des MaBiS-ZP
1866 fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1867 // Hinweis: Verwendung der ID des MaBiS-ZP — informational note, always applies
1868 ConditionResult::True
1869 }
1870
1871 /// [505] Hinweis: Verwendung der ID der Messlokation
1872 fn evaluate_505(&self, _ctx: &EvaluationContext) -> ConditionResult {
1873 // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1874 ConditionResult::True
1875 }
1876
1877 /// [506] Hinweis: Verwendung der ID der Marktlokation
1878 fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
1879 // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1880 ConditionResult::True
1881 }
1882
1883 /// [510] Hinweis: Es ist neben der Information über die Ablehnung auch der unverändert gebliebene Datenstatus informell mitzugeben.
1884 fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
1885 // Hinweis: Es ist neben der Information über die Ablehnung auch der unverändert gebliebene Datenstatus informell mitzugeben — informational note, always applies
1886 ConditionResult::True
1887 }
1888
1889 /// [512] Hinweis: Aus MSCONS BGM DE1004
1890 fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
1891 // Hinweis: Aus MSCONS BGM DE1004 — informational note, always applies
1892 ConditionResult::True
1893 }
1894
1895 /// [519] Hinweis: Aus ORDERS BGM DE1004
1896 fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
1897 // Hinweis: Aus ORDERS BGM DE1004 — informational note, always applies
1898 ConditionResult::True
1899 }
1900
1901 /// [520] Hinweis: Zeitpunkt, zu dem der Wechsel erfolgt, falls er zustande kommt
1902 fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
1903 // Hinweis: Zeitpunkt, zu dem der Wechsel erfolgt, falls er zustande kommt — informational note, always applies
1904 ConditionResult::True
1905 }
1906
1907 /// [521] Hinweis: Zeitpunkt, ab dem der MSBN tatsächlich den Messstellenbetrieb übernimmt
1908 fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
1909 // Hinweis: Zeitpunkt, ab dem der MSBN tatsächlich den Messstellenbetrieb übernimmt — informational note, always applies
1910 ConditionResult::True
1911 }
1912
1913 /// [522] Hinweis: Zeitpunkt, ab dem der gMSB den Messstellenbetrieb übernimmt
1914 fn evaluate_522(&self, _ctx: &EvaluationContext) -> ConditionResult {
1915 // Hinweis: Zeitpunkt, ab dem der gMSB den Messstellenbetrieb übernimmt — informational note, always applies
1916 ConditionResult::True
1917 }
1918
1919 /// [523] Hinweis: Wert aus BGM DE1004 der MSCONS, auf die sich die Statusangabe bezieht
1920 fn evaluate_523(&self, _ctx: &EvaluationContext) -> ConditionResult {
1921 // Hinweis: Wert aus BGM DE1004 der MSCONS, auf die sich die Statusangabe bezieht — informational note, always applies
1922 ConditionResult::True
1923 }
1924
1925 /// [524] Hinweis: Wert aus BGM DE1004 der MSCONS, die den Gegenvorschlag enthält
1926 fn evaluate_524(&self, _ctx: &EvaluationContext) -> ConditionResult {
1927 // Hinweis: Wert aus BGM DE1004 der MSCONS, die den Gegenvorschlag enthält — informational note, always applies
1928 ConditionResult::True
1929 }
1930
1931 /// [525] Hinweis: Je SG14 sind nur Statusinformationen zu einer MSCONS enthalten
1932 fn evaluate_525(&self, _ctx: &EvaluationContext) -> ConditionResult {
1933 // Hinweis: Je SG14 sind nur Statusinformationen zu einer MSCONS enthalten — informational note, always applies
1934 ConditionResult::True
1935 }
1936
1937 /// [530] Hinweis: Hier ist die Arbeit bzw. Leistung anzugeben, die der Sender der IFTSTA im Lieferschein für den von ihm genannten Zeitraum / Leistungsperiode erwartet hätte.
1938 fn evaluate_530(&self, _ctx: &EvaluationContext) -> ConditionResult {
1939 // Hinweis: Hier ist die Arbeit bzw. Leistung anzugeben, die der Sender der IFTSTA im Lieferschein für den von ihm genannten Zeitraum / Leistungsperiode erwartet hätte — informational note, always applies
1940 ConditionResult::True
1941 }
1942
1943 /// [531] Vom MSBN in Schritt 1 des SD verwendete Vorgangsnummer, damit der LF diese bei der Bestellung einer Konfiguration beim MSBN, unter dem Vorbehalt, dass der MSB-Wechsel an der Messlokation erfolgreic...
1944 fn evaluate_531(&self, _ctx: &EvaluationContext) -> ConditionResult {
1945 // Hinweis: Vom MSBN in Schritt 1 des SD verwendete Vorgangsnummer, damit der LF diese
1946 // bei der Bestellung einer Konfiguration beim MSBN verwenden kann — informational note
1947 // describing field semantics and usage context, always applies unconditionally
1948 ConditionResult::True
1949 }
1950
1951 /// [532] Hinweis: Verwendung der ID der Netzlokation
1952 fn evaluate_532(&self, _ctx: &EvaluationContext) -> ConditionResult {
1953 // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
1954 ConditionResult::True
1955 }
1956
1957 /// [533] Hinweis: Verwendung der ID der Steuerbaren Ressource
1958 fn evaluate_533(&self, _ctx: &EvaluationContext) -> ConditionResult {
1959 // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
1960 ConditionResult::True
1961 }
1962
1963 /// [534] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
1964 fn evaluate_534(&self, _ctx: &EvaluationContext) -> ConditionResult {
1965 // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
1966 ConditionResult::True
1967 }
1968
1969 /// [535] Hinweis: Aus UTILMD IDE DE7402
1970 fn evaluate_535(&self, _ctx: &EvaluationContext) -> ConditionResult {
1971 // Hinweis: Aus UTILMD IDE DE7402 — informational note about value origin, always applies
1972 ConditionResult::True
1973 }
1974
1975 /// [902] Format: Wert darf nur positiv oder 0 sein
1976 // REVIEW: Format condition requiring value >= 0. Applied to QTY segment value (elements[0][1]) which is the standard location for quantity values in EDIFACT. Uses validate_numeric helper with >= 0.0. (medium confidence)
1977 fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
1978 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">=", 0.0))
1979 }
1980
1981 /// [903] Format: Möglicher Wert: 1
1982 // REVIEW: Format condition requiring value == 1. Uses validate_numeric with == operator. Targets QTY segment value (elements[0][1]) as most common numeric value field in IFTSTA context. (medium confidence)
1983 fn evaluate_903(&self, ctx: &EvaluationContext) -> ConditionResult {
1984 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "==", 1.0))
1985 }
1986
1987 /// [906] Format: max. 3 Nachkommastellen
1988 // REVIEW: Format condition: max 3 decimal places. Applies to QTY segment value field (elements[0][1]). (medium confidence)
1989 fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
1990 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 3))
1991 }
1992
1993 /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht bei 1 beginnend und fortlaufend aufsteigend
1994 fn evaluate_911(&self, _ctx: &EvaluationContext) -> ConditionResult {
1995 // Hinweis: Mögliche Werte 1 bis n, je Nachricht bei 1 beginnend und fortlaufend aufsteigend
1996 // Informational note about sequence numbering — always applies
1997 ConditionResult::True
1998 }
1999
2000 /// [931] Format: ZZZ = +00
2001 // REVIEW: Format condition: timezone must be UTC (+00). Uses validate_timezone_utc on the DTM value field. Targets first DTM segment found. (medium confidence)
2002 fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
2003 ctx.format_check("DTM", 0, 1, validate_timezone_utc)
2004 }
2005
2006 /// [932] Format: HHMM = 2200
2007 // REVIEW: Format condition: HHMM must equal 2200. Uses validate_hhmm_equals. DTM+163 is a common time qualifier in IFTSTA for delivery end times. (medium confidence)
2008 fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
2009 ctx.format_check_qualified("DTM", 0, "163", 0, 1, |val| {
2010 validate_hhmm_equals(val, "2200")
2011 })
2012 }
2013
2014 /// [933] Format: HHMM = 2300
2015 fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
2016 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2300"))
2017 }
2018
2019 /// [934] Format: HHMM = 0400
2020 fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
2021 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0400"))
2022 }
2023
2024 /// [935] Format: HHMM = 0500
2025 fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
2026 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0500"))
2027 }
2028
2029 /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
2030 // REVIEW: Checks if any COM segment value contains both '@' and '.' characters, validating email address format. COM segments carry communication details; email format requires both characters. This is the standard way to detect email COM values in EDIFACT. (medium confidence)
2031 fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
2032 let com_segments = ctx.find_segments("COM");
2033 for seg in &com_segments {
2034 if let Some(value) = seg.elements.first().and_then(|e| e.first()) {
2035 if value.contains('@') && value.contains('.') {
2036 return ConditionResult::True;
2037 }
2038 }
2039 }
2040 ConditionResult::False
2041 }
2042
2043 /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
2044 // REVIEW: Checks if any COM segment value starts with '+' and is followed only by digits — the international phone number format (E.164). This is the standard pattern for phone number validation in German energy market EDIFACT messages. (medium confidence)
2045 fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
2046 let com_segments = ctx.find_segments("COM");
2047 for seg in &com_segments {
2048 if let Some(value) = seg.elements.first().and_then(|e| e.first()) {
2049 if value.starts_with('+')
2050 && value.len() > 1
2051 && value[1..].chars().all(|c| c.is_ascii_digit())
2052 {
2053 return ConditionResult::True;
2054 }
2055 }
2056 }
2057 ConditionResult::False
2058 }
2059
2060 /// [950] Format: Marktlokations-ID
2061 fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
2062 ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
2063 }
2064
2065 /// [951] Format: Zählpunktbezeichnung
2066 // REVIEW: Format condition validating Zählpunktbezeichnung (33 alphanumeric chars). In IFTSTA, Zählpunkt is typically associated with LOC+Z19 (Messlokation/Zählpunkt). Uses validate_zahlpunkt helper. Medium confidence because the exact LOC qualifier for Zählpunkt in IFTSTA may vary — could also be a different qualifier depending on context. (medium confidence)
2067 fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
2068 ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_zahlpunkt)
2069 }
2070
2071 /// [960] Format: Netzlokations-ID
2072 // HAND-EDITED: a Netzlokations-ID is "E" + ten alphanumerics (`E1688117482`),
2073 // not MaLo-shaped; `validate_malo_id` rejected every real one (#173).
2074 fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
2075 ctx.format_check_qualified("LOC", 0, "Z18", 1, 0, validate_nelo_id)
2076 }
2077
2078 /// [961] Format: SR-ID
2079 // REVIEW: SR-ID (Steuerbare Ressource ID) uses the same 11-digit Luhn check digit format as Marktlokations-ID. LOC+Z19 is the standard qualifier for SteuerbareRessource. Element 1, component 0 holds the location ID. (medium confidence)
2080 fn evaluate_961(&self, ctx: &EvaluationContext) -> ConditionResult {
2081 ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_malo_id)
2082 }
2083}