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 // HAND-EDITED: "in dieser SG14 STS DE1131 <> E_0278; Unknown without any DE1131, as before" — the enclosing SG14 only, when the scope
453 // reaches one; the message as before otherwise.
454 if let Some(scope) = ctx.enclosing_scope("SG14") {
455 let codes: Vec<String> = ctx
456 .find_segments_at(scope, "STS")
457 .iter()
458 .map(|s| s.get_component(2, 1).to_string())
459 .filter(|v| !v.is_empty())
460 .collect();
461 if codes.is_empty() {
462 return ConditionResult::Unknown;
463 }
464 return ConditionResult::from(codes.iter().any(|v| v != "E_0278"));
465 }
466 let nav = match ctx.navigator() {
467 Some(n) => n,
468 None => {
469 let segs = ctx.find_segments("STS");
470 let mut found_any = false;
471 for seg in &segs {
472 if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
473 if !val.is_empty() {
474 found_any = true;
475 if val != "E_0278" {
476 return ConditionResult::True;
477 }
478 }
479 }
480 }
481 return if found_any {
482 ConditionResult::False
483 } else {
484 ConditionResult::Unknown
485 };
486 }
487 };
488 let sg14_count = nav.group_instance_count(&["SG14"]);
489 let mut found_any = false;
490 for i in 0..sg14_count {
491 let sts_segs = nav.find_segments_in_group("STS", &["SG14"], i);
492 for seg in &sts_segs {
493 if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
494 if !val.is_empty() {
495 found_any = true;
496 if val != "E_0278" {
497 return ConditionResult::True;
498 }
499 }
500 }
501 }
502 }
503 if found_any {
504 ConditionResult::False
505 } else {
506 ConditionResult::Unknown
507 }
508 }
509
510 /// [150] Wenn in dieser SG14 STS DE1131 <> E_0281
511 // 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)
512 fn evaluate_150(&self, ctx: &EvaluationContext) -> ConditionResult {
513 // HAND-EDITED: "in dieser SG14 STS DE1131 <> E_0281; Unknown without any DE1131, as before" — the enclosing SG14 only, when the scope
514 // reaches one; the message as before otherwise.
515 if let Some(scope) = ctx.enclosing_scope("SG14") {
516 let codes: Vec<String> = ctx
517 .find_segments_at(scope, "STS")
518 .iter()
519 .map(|s| s.get_component(2, 1).to_string())
520 .filter(|v| !v.is_empty())
521 .collect();
522 if codes.is_empty() {
523 return ConditionResult::Unknown;
524 }
525 return ConditionResult::from(codes.iter().any(|v| v != "E_0281"));
526 }
527 let nav = match ctx.navigator() {
528 Some(n) => n,
529 None => {
530 let segs = ctx.find_segments("STS");
531 let mut found_any = false;
532 for seg in &segs {
533 if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
534 if !val.is_empty() {
535 found_any = true;
536 if val != "E_0281" {
537 return ConditionResult::True;
538 }
539 }
540 }
541 }
542 return if found_any {
543 ConditionResult::False
544 } else {
545 ConditionResult::Unknown
546 };
547 }
548 };
549 let sg14_count = nav.group_instance_count(&["SG14"]);
550 let mut found_any = false;
551 for i in 0..sg14_count {
552 let sts_segs = nav.find_segments_in_group("STS", &["SG14"], i);
553 for seg in &sts_segs {
554 if let Some(val) = seg.elements.get(2).and_then(|e| e.get(1)) {
555 if !val.is_empty() {
556 found_any = true;
557 if val != "E_0281" {
558 return ConditionResult::True;
559 }
560 }
561 }
562 }
563 }
564 if found_any {
565 ConditionResult::False
566 } else {
567 ConditionResult::Unknown
568 }
569 }
570
571 /// [151] Wenn STS+Z20+Z32+A99:E_0278 in dieser SG14 vorhanden
572 fn evaluate_151(&self, ctx: &EvaluationContext) -> ConditionResult {
573 // HAND-EDITED: "STS+Z20+Z32+A99:E_0278 in dieser SG14" — the enclosing SG14 only, when the scope
574 // reaches one; the message as before otherwise.
575 if let Some(scope) = ctx.enclosing_scope("SG14") {
576 return ConditionResult::from(ctx.find_segments_at(scope, "STS").iter().any(|s| {
577 s.get_element(0) == "Z20"
578 && s.get_component(1, 0) == "Z32"
579 && s.get_component(2, 0) == "A99"
580 && s.get_component(2, 1) == "E_0278"
581 }));
582 }
583 // STS+Z20+Z32+A99:E_0278: elements[0][0]=Z20, elements[1][0]=Z32, elements[2][0]=A99, elements[2][1]=E_0278
584 ctx.has_segment_matching_in_group(
585 "STS",
586 &[
587 (0, 0, "Z20"),
588 (1, 0, "Z32"),
589 (2, 0, "A99"),
590 (2, 1, "E_0278"),
591 ],
592 &["SG14"],
593 )
594 }
595
596 /// [152] Wenn STS+Z20+Z32+A99:E_0281 in dieser SG14 vorhanden
597 fn evaluate_152(&self, ctx: &EvaluationContext) -> ConditionResult {
598 // HAND-EDITED: "STS+Z20+Z32+A99:E_0281 in dieser SG14" — the enclosing SG14 only, when the scope
599 // reaches one; the message as before otherwise.
600 if let Some(scope) = ctx.enclosing_scope("SG14") {
601 return ConditionResult::from(ctx.find_segments_at(scope, "STS").iter().any(|s| {
602 s.get_element(0) == "Z20"
603 && s.get_component(1, 0) == "Z32"
604 && s.get_component(2, 0) == "A99"
605 && s.get_component(2, 1) == "E_0281"
606 }));
607 }
608 // STS+Z20+Z32+A99:E_0281: elements[0][0]=Z20, elements[1][0]=Z32, elements[2][0]=A99, elements[2][1]=E_0281
609 ctx.has_segment_matching_in_group(
610 "STS",
611 &[
612 (0, 0, "Z20"),
613 (1, 0, "Z32"),
614 (2, 0, "A99"),
615 (2, 1, "E_0281"),
616 ],
617 &["SG14"],
618 )
619 }
620
621 /// [153] Wenn in diesem STS DE1131 = E_0286
622 fn evaluate_153(&self, ctx: &EvaluationContext) -> ConditionResult {
623 // DE1131 is at elements[2][1] in STS (C556 Statusanlaß component 1)
624 ctx.has_segment_matching("STS", &[(2, 1, "E_0286")])
625 }
626
627 /// [154] Wenn STS+Z15+Z13+A99:E_0286 in dieser SG14 vorhanden
628 // 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)
629 fn evaluate_154(&self, ctx: &EvaluationContext) -> ConditionResult {
630 // HAND-EDITED: "STS+Z15+Z13+A99:E_0286 in dieser SG14" — the enclosing SG14 only, when the scope
631 // reaches one; the message as before otherwise.
632 if let Some(scope) = ctx.enclosing_scope("SG14") {
633 return ConditionResult::from(ctx.find_segments_at(scope, "STS").iter().any(|s| {
634 s.get_element(0) == "Z15"
635 && s.get_component(1, 0) == "Z13"
636 && s.get_component(2, 0) == "A99"
637 && s.get_component(2, 1) == "E_0286"
638 }));
639 }
640 ctx.has_segment_matching_in_group(
641 "STS",
642 &[
643 (0, 0, "Z15"),
644 (1, 0, "Z13"),
645 (2, 0, "A99"),
646 (2, 1, "E_0286"),
647 ],
648 &["SG14", "SG15"],
649 )
650 }
651
652 /// [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...
653 fn evaluate_537(&self, _ctx: &EvaluationContext) -> ConditionResult {
654 // 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.
655 ConditionResult::True
656 }
657
658 /// [538] Hinweis: An dieser Stelle wird der Code aus dem EBD übermittelt
659 fn evaluate_538(&self, _ctx: &EvaluationContext) -> ConditionResult {
660 // Hinweis: An dieser Stelle wird der Code aus dem EBD übermittelt
661 ConditionResult::True
662 }
663
664 /// [1] Wenn die Übermittlung nicht codierbarer Informationen nötig ist.
665 /// EXTERNAL: Requires context from outside the message.
666 // 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)
667 fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
668 ctx.external.evaluate("non_codeable_info_needed")
669 }
670
671 /// [3] Wenn SG7 STS+Z01 nicht vorhanden.
672 fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
673 ctx.lacks_qualifier("STS", 0, "Z01")
674 }
675
676 /// [4] Wenn SG7 STS+Z02 nicht vorhanden.
677 fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
678 ctx.lacks_qualifier("STS", 0, "Z02")
679 }
680
681 /// [5] Wenn SG7 STS+Z03 nicht vorhanden.
682 fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
683 ctx.lacks_qualifier("STS", 0, "Z03")
684 }
685
686 /// [10] Wenn Meldung des BKV/NB/ÜNB nach Frist eingeht.
687 /// EXTERNAL: Requires context from outside the message.
688 fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
689 ctx.external.evaluate("message_after_deadline")
690 }
691
692 /// [16] Wenn MP-ID in SG1 NAD+MR in der Rolle BKV
693 /// EXTERNAL: Requires context from outside the message.
694 fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
695 ctx.external.evaluate("recipient_is_bkv")
696 }
697
698 /// [17] Wenn Meldung des BKV auf falscher Aggregationsebene eingeht.
699 /// EXTERNAL: Requires context from outside the message.
700 fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
701 ctx.external.evaluate("bkv_wrong_aggregation_level")
702 }
703
704 /// [18] Wenn SG15 STS+Z19 nicht vorhanden.
705 fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
706 ctx.lacks_qualifier("STS", 0, "Z19")
707 }
708
709 /// [19] Wenn SG15 STS+Z24 nicht vorhanden.
710 fn evaluate_19(&self, ctx: &EvaluationContext) -> ConditionResult {
711 ctx.lacks_qualifier("STS", 0, "Z24")
712 }
713
714 /// [20] Wenn MP-ID in SG1 NAD+MR in der Rolle LF
715 /// EXTERNAL: Requires context from outside the message.
716 fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
717 ctx.external.evaluate("recipient_is_lf")
718 }
719
720 /// [26] Wenn MP-ID in SG1 NAD+MR in der Rolle NB
721 /// EXTERNAL: Requires context from outside the message.
722 fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
723 ctx.external.evaluate("recipient_is_nb")
724 }
725
726 /// [27] Nur MP-ID aus Sparte Strom
727 /// EXTERNAL: Requires context from outside the message.
728 fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
729 ctx.external.evaluate("mp_id_is_electricity_sector")
730 }
731
732 /// [28] Nur MP-ID aus Sparte Gas
733 /// EXTERNAL: Requires context from outside the message.
734 fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
735 ctx.external.evaluate("mp_id_is_gas_sector")
736 }
737
738 /// [29] Wenn MP-ID in SG1 NAD+MR in der Rolle ÜNB
739 /// EXTERNAL: Requires context from outside the message.
740 fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
741 ctx.external.evaluate("recipient_is_uenb")
742 }
743
744 /// [30] Wenn in dieser SG15 STS+Z20+Z32+A07:E_0207 vorhanden.
745 // 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)
746 fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
747 // HAND-EDITED: "in dieser/derselben SG15" — scoped to the enclosing group.
748 let found = ctx
749 .scoped_find_segments_in("SG15", "STS")
750 .into_iter()
751 .any(|s| {
752 s.elements
753 .first()
754 .and_then(|e| e.first())
755 .is_some_and(|v| v == "Z20")
756 && s.elements
757 .get(1)
758 .and_then(|e| e.first())
759 .is_some_and(|v| v == "Z32")
760 && s.elements
761 .get(2)
762 .and_then(|e| e.first())
763 .is_some_and(|v| v == "A07")
764 && s.elements
765 .get(2)
766 .and_then(|e| e.get(1))
767 .is_some_and(|v| v == "E_0207")
768 });
769 ConditionResult::from(found)
770 }
771
772 /// [31] Wenn in dieser SG16 in QTY in DE6411 KWH/K3 vorhanden.
773 fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
774 // HAND-EDITED: "in dieser/derselben SG16" — scoped to the enclosing group.
775 let found = ctx
776 .scoped_find_segments_in("SG16", "QTY")
777 .into_iter()
778 .any(|s| {
779 let unit = s
780 .elements
781 .first()
782 .and_then(|e| e.get(2))
783 .map(|v| v.as_str());
784 unit == Some("KWH") || unit == Some("K3")
785 });
786 ConditionResult::from(found)
787 }
788
789 /// [32] Wenn in dieser SG16 in QTY in DE6411 KWT/K5 vorhanden.
790 fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
791 // HAND-EDITED: "in dieser/derselben SG16" — scoped to the enclosing group.
792 let found = ctx
793 .scoped_find_segments_in("SG16", "QTY")
794 .into_iter()
795 .any(|s| {
796 let unit = s
797 .elements
798 .first()
799 .and_then(|e| e.get(2))
800 .map(|v| v.as_str());
801 unit == Some("KWT") || unit == Some("K5")
802 });
803 ConditionResult::from(found)
804 }
805
806 /// [33] Wenn in dieser SG16 DTM+163 vorhanden.
807 fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
808 // HAND-EDITED: "in dieser/derselben SG16" — scoped to the enclosing group.
809 ctx.this_group_has_qualifier("DTM", 0, "163", &["SG16"])
810 }
811
812 /// [43] Wenn STS+Z01+Z07 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
813 fn evaluate_43(&self, ctx: &EvaluationContext) -> ConditionResult {
814 ctx.has_qualified_value("STS", 0, "Z01", 1, 0, &["Z07"])
815 }
816
817 /// [44] Wenn STS+Z01+Z08 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
818 fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
819 ctx.has_qualified_value("STS", 0, "Z01", 1, 0, &["Z08"])
820 }
821
822 /// [45] Wenn STS+Z03+Z07 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
823 fn evaluate_45(&self, ctx: &EvaluationContext) -> ConditionResult {
824 ctx.has_qualified_value("STS", 0, "Z03", 1, 0, &["Z07"])
825 }
826
827 /// [46] Wenn STS+Z03+Z08 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
828 fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
829 ctx.has_qualified_value("STS", 0, "Z03", 1, 0, &["Z08"])
830 }
831
832 /// [47] Es sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
833 fn evaluate_47(&self, _ctx: &EvaluationContext) -> ConditionResult {
834 ConditionResult::True
835 }
836
837 /// [48] Es sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
838 fn evaluate_48(&self, _ctx: &EvaluationContext) -> ConditionResult {
839 ConditionResult::True
840 }
841
842 /// [49] Wenn STS+Z25+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
843 fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
844 ctx.has_qualified_value("STS", 0, "Z25", 1, 0, &["Z30"])
845 }
846
847 /// [50] Wenn STS+Z25+Z31 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
848 fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
849 ctx.has_qualified_value("STS", 0, "Z25", 1, 0, &["Z31"])
850 }
851
852 /// [51] Es sind nur Codes aus dem EBD-Cluster "Abweisung" möglich.
853 fn evaluate_51(&self, _ctx: &EvaluationContext) -> ConditionResult {
854 ConditionResult::True
855 }
856
857 /// [52] Wenn STS+Z27+Z32 vorhanden
858 fn evaluate_52(&self, ctx: &EvaluationContext) -> ConditionResult {
859 ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z32"])
860 }
861
862 /// [53] Wenn STS+Z28+Z32 vorhanden
863 fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
864 ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z32"])
865 }
866
867 /// [54] Wenn STS+Z29+Z32 vorhanden
868 fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
869 ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"])
870 }
871
872 /// [55] Wenn STS+Z30+Z32 vorhanden
873 fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
874 ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"])
875 }
876
877 /// [56] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z27 ist
878 // 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)
879 fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
880 // HAND-EDITED: "nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z27" — compared with the other instances when the
881 // scope reaches an SG15; the message as before otherwise.
882 if let Some(scope) = ctx.enclosing_scope("SG15") {
883 let acw = |path: &[(&str, usize)]| -> Vec<String> {
884 ctx.find_segments_at(path, "RFF")
885 .iter()
886 .filter(|s| s.get_element(0) == "ACW")
887 .map(|s| s.get_component(0, 1).to_string())
888 .filter(|v| !v.is_empty())
889 .collect()
890 };
891 let own = acw(scope);
892 // The STS+Z27 SG15 of the same SG14 (Vorgang).
893 let reference: Vec<String> = ctx
894 .scopes_under("SG14", "SG15")
895 .unwrap_or_default()
896 .iter()
897 .filter(|p| {
898 ctx.find_segments_at(p, "STS")
899 .iter()
900 .any(|s| s.get_element(0) == "Z27")
901 })
902 .flat_map(|p| acw(p))
903 .collect();
904 if own.is_empty() || reference.is_empty() {
905 return ConditionResult::Unknown;
906 }
907 return ConditionResult::from(own.iter().any(|v| !reference.contains(v)));
908 }
909 // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z27
910 let nav = match ctx.navigator() {
911 Some(n) => n,
912 None => return ConditionResult::Unknown,
913 };
914 let sg15_path: &[&str] = &["SG14", "SG15"];
915 let sg15_count = nav.group_instance_count(sg15_path);
916 // Collect ACW reference values from SG15 instances that have STS with Z27
917 let mut ref_acw: Vec<String> = Vec::new();
918 for i in 0..sg15_count {
919 let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
920 let has_z27 = sts_segs.iter().any(|s| {
921 s.elements
922 .first()
923 .and_then(|e| e.first())
924 .is_some_and(|v| v == "Z27")
925 });
926 if has_z27 {
927 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
928 for rff in &rff_segs {
929 if rff
930 .elements
931 .first()
932 .and_then(|e| e.first())
933 .is_some_and(|v| v == "ACW")
934 {
935 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
936 if !val.is_empty() {
937 ref_acw.push(val.clone());
938 }
939 }
940 }
941 }
942 }
943 }
944 if ref_acw.is_empty() {
945 return ConditionResult::Unknown;
946 }
947 // Check if any SG15 has an ACW value not present in the Z27 SG15 ACW values
948 for i in 0..sg15_count {
949 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
950 for rff in &rff_segs {
951 if rff
952 .elements
953 .first()
954 .and_then(|e| e.first())
955 .is_some_and(|v| v == "ACW")
956 {
957 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
958 if !val.is_empty() && !ref_acw.contains(val) {
959 return ConditionResult::True;
960 }
961 }
962 }
963 }
964 }
965 ConditionResult::False
966 }
967
968 /// [57] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z28 ist
969 // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z28 (Status des Fahrplananteils) instead of Z27. (medium confidence)
970 fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
971 // HAND-EDITED: "nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z28" — compared with the other instances when the
972 // scope reaches an SG15; the message as before otherwise.
973 if let Some(scope) = ctx.enclosing_scope("SG15") {
974 let acw = |path: &[(&str, usize)]| -> Vec<String> {
975 ctx.find_segments_at(path, "RFF")
976 .iter()
977 .filter(|s| s.get_element(0) == "ACW")
978 .map(|s| s.get_component(0, 1).to_string())
979 .filter(|v| !v.is_empty())
980 .collect()
981 };
982 let own = acw(scope);
983 // The STS+Z28 SG15 of the same SG14 (Vorgang).
984 let reference: Vec<String> = ctx
985 .scopes_under("SG14", "SG15")
986 .unwrap_or_default()
987 .iter()
988 .filter(|p| {
989 ctx.find_segments_at(p, "STS")
990 .iter()
991 .any(|s| s.get_element(0) == "Z28")
992 })
993 .flat_map(|p| acw(p))
994 .collect();
995 if own.is_empty() || reference.is_empty() {
996 return ConditionResult::Unknown;
997 }
998 return ConditionResult::from(own.iter().any(|v| !reference.contains(v)));
999 }
1000 // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z28
1001 let nav = match ctx.navigator() {
1002 Some(n) => n,
1003 None => return ConditionResult::Unknown,
1004 };
1005 let sg15_path: &[&str] = &["SG14", "SG15"];
1006 let sg15_count = nav.group_instance_count(sg15_path);
1007 let mut ref_acw: Vec<String> = Vec::new();
1008 for i in 0..sg15_count {
1009 let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
1010 let has_z28 = sts_segs.iter().any(|s| {
1011 s.elements
1012 .first()
1013 .and_then(|e| e.first())
1014 .is_some_and(|v| v == "Z28")
1015 });
1016 if has_z28 {
1017 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1018 for rff in &rff_segs {
1019 if rff
1020 .elements
1021 .first()
1022 .and_then(|e| e.first())
1023 .is_some_and(|v| v == "ACW")
1024 {
1025 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1026 if !val.is_empty() {
1027 ref_acw.push(val.clone());
1028 }
1029 }
1030 }
1031 }
1032 }
1033 }
1034 if ref_acw.is_empty() {
1035 return ConditionResult::Unknown;
1036 }
1037 for i in 0..sg15_count {
1038 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1039 for rff in &rff_segs {
1040 if rff
1041 .elements
1042 .first()
1043 .and_then(|e| e.first())
1044 .is_some_and(|v| v == "ACW")
1045 {
1046 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1047 if !val.is_empty() && !ref_acw.contains(val) {
1048 return ConditionResult::True;
1049 }
1050 }
1051 }
1052 }
1053 }
1054 ConditionResult::False
1055 }
1056
1057 /// [58] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z29 ist
1058 // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z29 (Status des Gegenvorschlags der Ausfallarbeit). (medium confidence)
1059 fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
1060 // HAND-EDITED: "nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z29" — compared with the other instances when the
1061 // scope reaches an SG15; the message as before otherwise.
1062 if let Some(scope) = ctx.enclosing_scope("SG15") {
1063 let acw = |path: &[(&str, usize)]| -> Vec<String> {
1064 ctx.find_segments_at(path, "RFF")
1065 .iter()
1066 .filter(|s| s.get_element(0) == "ACW")
1067 .map(|s| s.get_component(0, 1).to_string())
1068 .filter(|v| !v.is_empty())
1069 .collect()
1070 };
1071 let own = acw(scope);
1072 // The STS+Z29 SG15 of the same SG14 (Vorgang).
1073 let reference: Vec<String> = ctx
1074 .scopes_under("SG14", "SG15")
1075 .unwrap_or_default()
1076 .iter()
1077 .filter(|p| {
1078 ctx.find_segments_at(p, "STS")
1079 .iter()
1080 .any(|s| s.get_element(0) == "Z29")
1081 })
1082 .flat_map(|p| acw(p))
1083 .collect();
1084 if own.is_empty() || reference.is_empty() {
1085 return ConditionResult::Unknown;
1086 }
1087 return ConditionResult::from(own.iter().any(|v| !reference.contains(v)));
1088 }
1089 // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z29
1090 let nav = match ctx.navigator() {
1091 Some(n) => n,
1092 None => return ConditionResult::Unknown,
1093 };
1094 let sg15_path: &[&str] = &["SG14", "SG15"];
1095 let sg15_count = nav.group_instance_count(sg15_path);
1096 let mut ref_acw: Vec<String> = Vec::new();
1097 for i in 0..sg15_count {
1098 let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
1099 let has_z29 = sts_segs.iter().any(|s| {
1100 s.elements
1101 .first()
1102 .and_then(|e| e.first())
1103 .is_some_and(|v| v == "Z29")
1104 });
1105 if has_z29 {
1106 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1107 for rff in &rff_segs {
1108 if rff
1109 .elements
1110 .first()
1111 .and_then(|e| e.first())
1112 .is_some_and(|v| v == "ACW")
1113 {
1114 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1115 if !val.is_empty() {
1116 ref_acw.push(val.clone());
1117 }
1118 }
1119 }
1120 }
1121 }
1122 }
1123 if ref_acw.is_empty() {
1124 return ConditionResult::Unknown;
1125 }
1126 for i in 0..sg15_count {
1127 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1128 for rff in &rff_segs {
1129 if rff
1130 .elements
1131 .first()
1132 .and_then(|e| e.first())
1133 .is_some_and(|v| v == "ACW")
1134 {
1135 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1136 if !val.is_empty() && !ref_acw.contains(val) {
1137 return ConditionResult::True;
1138 }
1139 }
1140 }
1141 }
1142 }
1143 ConditionResult::False
1144 }
1145
1146 /// [59] Wenn in dieser SG15 STS das SG15 RFF+ACW nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z30 ist
1147 // REVIEW: Same cross-SG15 comparison pattern as condition 56, but referencing STS+Z30 (Status des Gegenvorschlags des Fahrplananteils). (medium confidence)
1148 fn evaluate_59(&self, ctx: &EvaluationContext) -> ConditionResult {
1149 // HAND-EDITED: "nicht identisch mit dem SG15 RFF+ACW der SG15 STS+Z30" — compared with the other instances when the
1150 // scope reaches an SG15; the message as before otherwise.
1151 if let Some(scope) = ctx.enclosing_scope("SG15") {
1152 let acw = |path: &[(&str, usize)]| -> Vec<String> {
1153 ctx.find_segments_at(path, "RFF")
1154 .iter()
1155 .filter(|s| s.get_element(0) == "ACW")
1156 .map(|s| s.get_component(0, 1).to_string())
1157 .filter(|v| !v.is_empty())
1158 .collect()
1159 };
1160 let own = acw(scope);
1161 // The STS+Z30 SG15 of the same SG14 (Vorgang).
1162 let reference: Vec<String> = ctx
1163 .scopes_under("SG14", "SG15")
1164 .unwrap_or_default()
1165 .iter()
1166 .filter(|p| {
1167 ctx.find_segments_at(p, "STS")
1168 .iter()
1169 .any(|s| s.get_element(0) == "Z30")
1170 })
1171 .flat_map(|p| acw(p))
1172 .collect();
1173 if own.is_empty() || reference.is_empty() {
1174 return ConditionResult::Unknown;
1175 }
1176 return ConditionResult::from(own.iter().any(|v| !reference.contains(v)));
1177 }
1178 // True when this SG15's RFF+ACW value differs from the RFF+ACW of the SG15 containing STS+Z30
1179 let nav = match ctx.navigator() {
1180 Some(n) => n,
1181 None => return ConditionResult::Unknown,
1182 };
1183 let sg15_path: &[&str] = &["SG14", "SG15"];
1184 let sg15_count = nav.group_instance_count(sg15_path);
1185 let mut ref_acw: Vec<String> = Vec::new();
1186 for i in 0..sg15_count {
1187 let sts_segs = nav.find_segments_in_group("STS", sg15_path, i);
1188 let has_z30 = sts_segs.iter().any(|s| {
1189 s.elements
1190 .first()
1191 .and_then(|e| e.first())
1192 .is_some_and(|v| v == "Z30")
1193 });
1194 if has_z30 {
1195 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1196 for rff in &rff_segs {
1197 if rff
1198 .elements
1199 .first()
1200 .and_then(|e| e.first())
1201 .is_some_and(|v| v == "ACW")
1202 {
1203 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1204 if !val.is_empty() {
1205 ref_acw.push(val.clone());
1206 }
1207 }
1208 }
1209 }
1210 }
1211 }
1212 if ref_acw.is_empty() {
1213 return ConditionResult::Unknown;
1214 }
1215 for i in 0..sg15_count {
1216 let rff_segs = nav.find_segments_in_group("RFF", sg15_path, i);
1217 for rff in &rff_segs {
1218 if rff
1219 .elements
1220 .first()
1221 .and_then(|e| e.first())
1222 .is_some_and(|v| v == "ACW")
1223 {
1224 if let Some(val) = rff.elements.first().and_then(|e| e.get(1)) {
1225 if !val.is_empty() && !ref_acw.contains(val) {
1226 return ConditionResult::True;
1227 }
1228 }
1229 }
1230 }
1231 }
1232 ConditionResult::False
1233 }
1234
1235 /// [60] Wenn zusätzlich zum Fahrplananteil auch die Ausfallarbeit zu identischem Wert aus RFF+ACW nicht o.k. ist
1236 // 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)
1237 fn evaluate_60(&self, ctx: &EvaluationContext) -> ConditionResult {
1238 {
1239 let nav = match ctx.navigator() {
1240 Some(n) => n,
1241 None => return ConditionResult::Unknown,
1242 };
1243 let sg14_count = nav.group_instance_count(&["SG14"]);
1244 for i in 0..sg14_count {
1245 let sg15_count = nav.child_group_instance_count(&["SG14"], i, "SG15");
1246 let mut z27_z32_acw: Vec<String> = Vec::new();
1247 let mut z28_z32_acw: Vec<String> = Vec::new();
1248 for j in 0..sg15_count {
1249 let stses = nav.find_segments_in_child_group("STS", &["SG14"], i, "SG15", j);
1250 let rffs = nav.find_segments_in_child_group("RFF", &["SG14"], i, "SG15", j);
1251 let acw_val = rffs
1252 .iter()
1253 .find(|s| {
1254 s.elements
1255 .first()
1256 .and_then(|e| e.first())
1257 .is_some_and(|v| v == "ACW")
1258 })
1259 .and_then(|s| s.elements.first().and_then(|e| e.get(1)).cloned());
1260 let Some(acw) = acw_val else { continue };
1261 for sts in &stses {
1262 let cat = sts
1263 .elements
1264 .first()
1265 .and_then(|e| e.first())
1266 .map(|s| s.as_str());
1267 let status = sts
1268 .elements
1269 .get(1)
1270 .and_then(|e| e.first())
1271 .map(|s| s.as_str());
1272 match (cat, status) {
1273 (Some("Z27"), Some("Z32")) => z27_z32_acw.push(acw.clone()),
1274 (Some("Z28"), Some("Z32")) => z28_z32_acw.push(acw.clone()),
1275 _ => {}
1276 }
1277 }
1278 }
1279 if z27_z32_acw.iter().any(|v| z28_z32_acw.contains(v)) {
1280 return ConditionResult::True;
1281 }
1282 }
1283 ConditionResult::False
1284 }
1285 }
1286
1287 /// [61] Wenn zusätzlich zur Ausfallarbeit auch der Fahrplananteil zu identischem Wert aus RFF+ACW nicht o.k. ist
1288 // 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)
1289 fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
1290 {
1291 let nav = match ctx.navigator() {
1292 Some(n) => n,
1293 None => return ConditionResult::Unknown,
1294 };
1295 let sg14_count = nav.group_instance_count(&["SG14"]);
1296 for i in 0..sg14_count {
1297 let sg15_count = nav.child_group_instance_count(&["SG14"], i, "SG15");
1298 let mut z27_z32_acw: Vec<String> = Vec::new();
1299 let mut z28_z32_acw: Vec<String> = Vec::new();
1300 for j in 0..sg15_count {
1301 let stses = nav.find_segments_in_child_group("STS", &["SG14"], i, "SG15", j);
1302 let rffs = nav.find_segments_in_child_group("RFF", &["SG14"], i, "SG15", j);
1303 let acw_val = rffs
1304 .iter()
1305 .find(|s| {
1306 s.elements
1307 .first()
1308 .and_then(|e| e.first())
1309 .is_some_and(|v| v == "ACW")
1310 })
1311 .and_then(|s| s.elements.first().and_then(|e| e.get(1)).cloned());
1312 let Some(acw) = acw_val else { continue };
1313 for sts in &stses {
1314 let cat = sts
1315 .elements
1316 .first()
1317 .and_then(|e| e.first())
1318 .map(|s| s.as_str());
1319 let status = sts
1320 .elements
1321 .get(1)
1322 .and_then(|e| e.first())
1323 .map(|s| s.as_str());
1324 match (cat, status) {
1325 (Some("Z27"), Some("Z32")) => z27_z32_acw.push(acw.clone()),
1326 (Some("Z28"), Some("Z32")) => z28_z32_acw.push(acw.clone()),
1327 _ => {}
1328 }
1329 }
1330 }
1331 if z28_z32_acw.iter().any(|v| z27_z32_acw.contains(v)) {
1332 return ConditionResult::True;
1333 }
1334 }
1335 ConditionResult::False
1336 }
1337 }
1338
1339 /// [62] Wenn STS+Z27+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1340 fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
1341 ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z30"])
1342 }
1343
1344 /// [63] Wenn STS+Z27+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1345 fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
1346 ctx.has_qualified_value("STS", 0, "Z27", 1, 0, &["Z32"])
1347 }
1348
1349 /// [64] Wenn STS+Z28+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1350 fn evaluate_64(&self, ctx: &EvaluationContext) -> ConditionResult {
1351 ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z30"])
1352 }
1353
1354 /// [65] Wenn STS+Z28+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1355 fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
1356 ctx.has_qualified_value("STS", 0, "Z28", 1, 0, &["Z32"])
1357 }
1358
1359 /// [66] Wenn STS+Z29+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1360 fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
1361 ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z30"])
1362 }
1363
1364 /// [67] Wenn STS+Z29+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1365 fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
1366 ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"])
1367 }
1368
1369 /// [68] Wenn STS+Z30+Z30 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Zustimmung" möglich.
1370 fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
1371 ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z30"])
1372 }
1373
1374 /// [69] Wenn STS+Z30+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1375 fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
1376 ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"])
1377 }
1378
1379 /// [70] Wenn zusätzlich zum Gegenvorschlag des Fahrplananteils auch der Gegenvorschlag der Ausfallarbeit zu identischem Wert aus RFF+ACW nicht o.k. ist
1380 // 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)
1381 fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
1382 match ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"]) {
1383 ConditionResult::True => ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"]),
1384 ConditionResult::False => ConditionResult::False,
1385 ConditionResult::Unknown => ConditionResult::Unknown,
1386 }
1387 }
1388
1389 /// [71] Wenn zusätzlich zum Gegenvorschlag der Ausfallarbeit auch der Gegenvorschlag des Fahrplananteils zu identischem Wert aus RFF+ACW nicht o.k. ist
1390 // 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)
1391 fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
1392 match ctx.has_qualified_value("STS", 0, "Z29", 1, 0, &["Z32"]) {
1393 ConditionResult::True => ctx.has_qualified_value("STS", 0, "Z30", 1, 0, &["Z32"]),
1394 ConditionResult::False => ConditionResult::False,
1395 ConditionResult::Unknown => ConditionResult::Unknown,
1396 }
1397 }
1398
1399 /// [72] Wenn Gegenvorschlag erstellt werden kann
1400 /// EXTERNAL: Requires context from outside the message.
1401 fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
1402 ctx.external.evaluate("counter_proposal_possible")
1403 }
1404
1405 /// [76] Wenn MP-ID in SG1 NAD+MR in der Rolle ESA
1406 /// EXTERNAL: Requires context from outside the message.
1407 fn evaluate_76(&self, ctx: &EvaluationContext) -> ConditionResult {
1408 ctx.external.evaluate("recipient_is_esa")
1409 }
1410
1411 /// [77] Wenn STS+Z37+Z14 in dieser SG14 vorhanden
1412 fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
1413 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1414 ctx.this_group_has_qualified_value("STS", 0, "Z37", 1, 0, &["Z14"], &["SG14"])
1415 }
1416
1417 /// [78] Wenn STS+Z38 in dieser SG14 nicht vorhanden
1418 // 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)
1419 fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
1420 // HAND-EDITED: "STS+Z38 in dieser SG14 nicht vorhanden" — the enclosing SG14 only, when the scope
1421 // reaches one; the message as before otherwise.
1422 if let Some(scope) = ctx.enclosing_scope("SG14") {
1423 return ConditionResult::from(
1424 !ctx.find_segments_at(scope, "STS")
1425 .iter()
1426 .any(|s| s.get_element(0) == "Z38"),
1427 );
1428 }
1429 ctx.any_group_has_qualifier_without("STS", 0, "Z37", "STS", 0, "Z38", &["SG14"])
1430 }
1431
1432 /// [79] Wenn STS+Z37 in dieser SG14 nicht vorhanden
1433 // 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)
1434 fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
1435 // HAND-EDITED: "STS+Z37 in dieser SG14 nicht vorhanden" — the enclosing SG14 only, when the scope
1436 // reaches one; the message as before otherwise.
1437 if let Some(scope) = ctx.enclosing_scope("SG14") {
1438 return ConditionResult::from(
1439 !ctx.find_segments_at(scope, "STS")
1440 .iter()
1441 .any(|s| s.get_element(0) == "Z37"),
1442 );
1443 }
1444 ctx.any_group_has_qualifier_without("STS", 0, "Z38", "STS", 0, "Z37", &["SG14"])
1445 }
1446
1447 /// [80] Wenn STS+Z38+Z14 in dieser SG14 vorhanden
1448 fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
1449 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1450 ctx.this_group_has_qualified_value("STS", 0, "Z38", 1, 0, &["Z14"], &["SG14"])
1451 }
1452
1453 /// [83] Wenn STS+Z37+Z13+A04/A05/A06:E_0472 in dieser SG14 vorhanden
1454 // 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)
1455 fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
1456 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1457 let sts_segs = ctx.scoped_find_segments_in("SG14", "STS");
1458 let found = sts_segs.iter().any(|s| {
1459 s.elements
1460 .first()
1461 .and_then(|e| e.first())
1462 .is_some_and(|v| v == "Z37")
1463 && s.elements
1464 .get(1)
1465 .and_then(|e| e.first())
1466 .is_some_and(|v| v == "Z13")
1467 && s.elements
1468 .get(2)
1469 .and_then(|e| e.first())
1470 .is_some_and(|v| matches!(v.as_str(), "A04" | "A05" | "A06"))
1471 });
1472 ConditionResult::from(found)
1473 }
1474
1475 /// [84] Wenn STS+Z38+Z13+A02:E_0499 in dieser SG14 vorhanden
1476 // 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)
1477 fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
1478 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1479 let sts_segs = ctx.scoped_find_segments_in("SG14", "STS");
1480 let found = sts_segs.iter().any(|s| {
1481 s.elements
1482 .first()
1483 .and_then(|e| e.first())
1484 .is_some_and(|v| v == "Z38")
1485 && s.elements
1486 .get(1)
1487 .and_then(|e| e.first())
1488 .is_some_and(|v| v == "Z13")
1489 && s.elements
1490 .get(2)
1491 .and_then(|e| e.first())
1492 .is_some_and(|v| v == "A02")
1493 });
1494 ConditionResult::from(found)
1495 }
1496
1497 /// [85] Wenn STS+Z37+Z32+A01:E_0501 in dieser SG14 vorhanden
1498 // 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)
1499 fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
1500 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1501 let sts_segs = ctx.scoped_find_segments_in("SG14", "STS");
1502 let found = sts_segs.iter().any(|s| {
1503 s.elements
1504 .first()
1505 .and_then(|e| e.first())
1506 .is_some_and(|v| v == "Z37")
1507 && s.elements
1508 .get(1)
1509 .and_then(|e| e.first())
1510 .is_some_and(|v| v == "Z32")
1511 && s.elements
1512 .get(2)
1513 .and_then(|e| e.first())
1514 .is_some_and(|v| v == "A01")
1515 });
1516 ConditionResult::from(found)
1517 }
1518
1519 /// [86] Wenn MP-ID in SG1 NAD+MS in der Rolle LF
1520 /// EXTERNAL: Requires context from outside the message.
1521 fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
1522 ctx.external.evaluate("sender_is_lf")
1523 }
1524
1525 /// [91] Wenn in diesem STS DE1131 = E_0472
1526 fn evaluate_91(&self, ctx: &EvaluationContext) -> ConditionResult {
1527 let segments = ctx.find_segments("STS");
1528 ConditionResult::from(segments.iter().any(|s| {
1529 s.elements
1530 .get(2)
1531 .and_then(|e| e.get(1))
1532 .map(|v| v == "E_0472")
1533 .unwrap_or(false)
1534 }))
1535 }
1536
1537 /// [92] Wenn in diesem STS DE1131 = E_0501
1538 fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
1539 let segments = ctx.find_segments("STS");
1540 ConditionResult::from(segments.iter().any(|s| {
1541 s.elements
1542 .get(2)
1543 .and_then(|e| e.get(1))
1544 .map(|v| v == "E_0501")
1545 .unwrap_or(false)
1546 }))
1547 }
1548
1549 /// [93] Wenn STS+Z37+Z13 vorhanden, dann sind nur Codes aus dem EBD-Cluster "gescheitert" möglich.
1550 fn evaluate_93(&self, ctx: &EvaluationContext) -> ConditionResult {
1551 ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z13"])
1552 }
1553
1554 /// [94] Wenn STS+Z37+Z14 vorhanden, dann sind nur Codes aus dem EBD-Cluster "erfolgreich" möglich.
1555 fn evaluate_94(&self, ctx: &EvaluationContext) -> ConditionResult {
1556 ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z14"])
1557 }
1558
1559 /// [95] Wenn in diesem STS DE1131 = E_0499
1560 fn evaluate_95(&self, ctx: &EvaluationContext) -> ConditionResult {
1561 let segments = ctx.find_segments("STS");
1562 ConditionResult::from(segments.iter().any(|s| {
1563 s.elements
1564 .get(2)
1565 .and_then(|e| e.get(1))
1566 .map(|v| v == "E_0499")
1567 .unwrap_or(false)
1568 }))
1569 }
1570
1571 /// [96] Wenn in diesem STS DE1131 = E_0487
1572 fn evaluate_96(&self, ctx: &EvaluationContext) -> ConditionResult {
1573 let segments = ctx.find_segments("STS");
1574 ConditionResult::from(segments.iter().any(|s| {
1575 s.elements
1576 .get(2)
1577 .and_then(|e| e.get(1))
1578 .map(|v| v == "E_0487")
1579 .unwrap_or(false)
1580 }))
1581 }
1582
1583 /// [97] Wenn STS+Z38+Z13 vorhanden, dann sind nur Codes aus dem EBD-Cluster "gescheitert" möglich.
1584 fn evaluate_97(&self, ctx: &EvaluationContext) -> ConditionResult {
1585 ctx.has_qualified_value("STS", 0, "Z38", 1, 0, &["Z13"])
1586 }
1587
1588 /// [98] Wenn STS+Z38+Z14 vorhanden, dann sind nur Codes aus dem EBD-Cluster "erfolgreich" möglich.
1589 fn evaluate_98(&self, ctx: &EvaluationContext) -> ConditionResult {
1590 ctx.has_qualified_value("STS", 0, "Z38", 1, 0, &["Z14"])
1591 }
1592
1593 /// [99] Wenn in diesem STS DE1131 = E_0487, dann ist nur der Code A01 möglich.
1594 fn evaluate_99(&self, ctx: &EvaluationContext) -> ConditionResult {
1595 let segments = ctx.find_segments("STS");
1596 ConditionResult::from(segments.iter().any(|s| {
1597 s.elements
1598 .get(2)
1599 .and_then(|e| e.get(1))
1600 .map(|v| v == "E_0487")
1601 .unwrap_or(false)
1602 }))
1603 }
1604
1605 /// [100] Wenn STS+Z43+Z47 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Änderung der Daten" möglich.
1606 fn evaluate_100(&self, ctx: &EvaluationContext) -> ConditionResult {
1607 ctx.has_qualified_value("STS", 0, "Z43", 1, 0, &["Z47"])
1608 }
1609
1610 /// [101] Wenn STS+Z43+Z48 vorhanden, dann sind nur Codes aus dem EBD-Cluster "keine Änderung der Daten" möglich.
1611 fn evaluate_101(&self, ctx: &EvaluationContext) -> ConditionResult {
1612 ctx.has_qualified_value("STS", 0, "Z43", 1, 0, &["Z48"])
1613 }
1614
1615 /// [107] Wenn STS+Z37+Z32 vorhanden, dann sind nur Codes aus dem EBD-Cluster "Ablehnung" möglich.
1616 fn evaluate_107(&self, ctx: &EvaluationContext) -> ConditionResult {
1617 ctx.has_qualified_value("STS", 0, "Z37", 1, 0, &["Z32"])
1618 }
1619
1620 /// [114] Wenn MP-ID in SG1 NAD+MS in der Rolle MSB
1621 /// EXTERNAL: Requires context from outside the message.
1622 fn evaluate_114(&self, ctx: &EvaluationContext) -> ConditionResult {
1623 ctx.external.evaluate("sender_is_msb")
1624 }
1625
1626 /// [115] Wenn MP-ID in SG1 NAD+MS in der Rolle NB
1627 /// EXTERNAL: Requires context from outside the message.
1628 fn evaluate_115(&self, ctx: &EvaluationContext) -> ConditionResult {
1629 ctx.external.evaluate("sender_is_nb")
1630 }
1631
1632 /// [117] Wenn MP-ID in SG1 NAD+MR in der Rolle MSB
1633 /// EXTERNAL: Requires context from outside the message.
1634 fn evaluate_117(&self, ctx: &EvaluationContext) -> ConditionResult {
1635 ctx.external.evaluate("recipient_is_msb")
1636 }
1637
1638 /// [118] Wenn in diesem STS DE1131 = E_0526
1639 fn evaluate_118(&self, ctx: &EvaluationContext) -> ConditionResult {
1640 ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0526"])
1641 }
1642
1643 /// [119] Wenn in diesem STS DE1131 = E_0528
1644 fn evaluate_119(&self, ctx: &EvaluationContext) -> ConditionResult {
1645 ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0528"])
1646 }
1647
1648 /// [120] Wenn in diesem STS DE1131 = E_0529
1649 fn evaluate_120(&self, ctx: &EvaluationContext) -> ConditionResult {
1650 ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0529"])
1651 }
1652
1653 /// [121] Wenn in diesem STS DE1131 = E_0536
1654 fn evaluate_121(&self, ctx: &EvaluationContext) -> ConditionResult {
1655 ctx.has_qualified_value("STS", 0, "Z21", 2, 1, &["E_0536"])
1656 }
1657
1658 /// [129] Wenn STS+Z20+Z32+A99:E_0524 in dieser SG14 vorhanden
1659 // 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)
1660 fn evaluate_129(&self, ctx: &EvaluationContext) -> ConditionResult {
1661 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1662 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1663 ConditionResult::from(segments.iter().any(|s| {
1664 s.elements
1665 .first()
1666 .and_then(|e| e.first())
1667 .map(|v| v == "Z20")
1668 .unwrap_or(false)
1669 && s.elements
1670 .get(1)
1671 .and_then(|e| e.first())
1672 .map(|v| v == "Z32")
1673 .unwrap_or(false)
1674 && s.elements
1675 .get(2)
1676 .and_then(|e| e.first())
1677 .map(|v| v == "A99")
1678 .unwrap_or(false)
1679 && s.elements
1680 .get(2)
1681 .and_then(|e| e.get(1))
1682 .map(|v| v == "E_0524")
1683 .unwrap_or(false)
1684 }))
1685 }
1686
1687 /// [130] Wenn STS+Z20+Z32+A99:E_0531 in dieser SG14 vorhanden
1688 // 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)
1689 fn evaluate_130(&self, ctx: &EvaluationContext) -> ConditionResult {
1690 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1691 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1692 ConditionResult::from(segments.iter().any(|s| {
1693 s.elements
1694 .first()
1695 .and_then(|e| e.first())
1696 .map(|v| v == "Z20")
1697 .unwrap_or(false)
1698 && s.elements
1699 .get(1)
1700 .and_then(|e| e.first())
1701 .map(|v| v == "Z32")
1702 .unwrap_or(false)
1703 && s.elements
1704 .get(2)
1705 .and_then(|e| e.first())
1706 .map(|v| v == "A99")
1707 .unwrap_or(false)
1708 && s.elements
1709 .get(2)
1710 .and_then(|e| e.get(1))
1711 .map(|v| v == "E_0531")
1712 .unwrap_or(false)
1713 }))
1714 }
1715
1716 /// [131] Wenn STS+Z37+Z13+A04/A05/A06:E_1003 in dieser SG14 vorhanden
1717 // 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)
1718 fn evaluate_131(&self, ctx: &EvaluationContext) -> ConditionResult {
1719 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1720 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1721 ConditionResult::from(segments.iter().any(|s| {
1722 s.elements
1723 .first()
1724 .and_then(|e| e.first())
1725 .map(|v| v == "Z37")
1726 .unwrap_or(false)
1727 && s.elements
1728 .get(1)
1729 .and_then(|e| e.first())
1730 .map(|v| v == "Z13")
1731 .unwrap_or(false)
1732 && s.elements
1733 .get(2)
1734 .and_then(|e| e.first())
1735 .map(|v| matches!(v.as_str(), "A04" | "A05" | "A06"))
1736 .unwrap_or(false)
1737 && s.elements
1738 .get(2)
1739 .and_then(|e| e.get(1))
1740 .map(|v| v == "E_1003")
1741 .unwrap_or(false)
1742 }))
1743 }
1744
1745 /// [132] Wenn STS+Z37+Z32+A01:E_1002 in dieser SG14 vorhanden
1746 // 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)
1747 fn evaluate_132(&self, ctx: &EvaluationContext) -> ConditionResult {
1748 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1749 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1750 ConditionResult::from(segments.iter().any(|s| {
1751 s.elements
1752 .first()
1753 .and_then(|e| e.first())
1754 .map(|v| v == "Z37")
1755 .unwrap_or(false)
1756 && s.elements
1757 .get(1)
1758 .and_then(|e| e.first())
1759 .map(|v| v == "Z32")
1760 .unwrap_or(false)
1761 && s.elements
1762 .get(2)
1763 .and_then(|e| e.first())
1764 .map(|v| v == "A01")
1765 .unwrap_or(false)
1766 && s.elements
1767 .get(2)
1768 .and_then(|e| e.get(1))
1769 .map(|v| v == "E_1002")
1770 .unwrap_or(false)
1771 }))
1772 }
1773
1774 /// [133] Wenn in diesem STS DE1131 = E_1003
1775 fn evaluate_133(&self, ctx: &EvaluationContext) -> ConditionResult {
1776 ctx.has_qualified_value("STS", 0, "Z37", 2, 1, &["E_1003"])
1777 }
1778
1779 /// [134] Wenn in diesem STS DE1131 = E_1002
1780 fn evaluate_134(&self, ctx: &EvaluationContext) -> ConditionResult {
1781 ctx.has_qualified_value("STS", 0, "Z37", 2, 1, &["E_1002"])
1782 }
1783
1784 /// [135] Wenn in dieser SG15 in STS+Z21 DE9013 = A99
1785 fn evaluate_135(&self, ctx: &EvaluationContext) -> ConditionResult {
1786 // HAND-EDITED: "in dieser SG15 in STS+Z21 DE9013 = A99" — the enclosing SG15 only, when the scope
1787 // reaches one; the message as before otherwise.
1788 if let Some(scope) = ctx.enclosing_scope("SG15") {
1789 // As `has_qualified_value`: Unknown without any STS+Z21.
1790 let sts: Vec<_> = ctx
1791 .find_segments_at(scope, "STS")
1792 .into_iter()
1793 .filter(|s| s.get_element(0) == "Z21")
1794 .collect();
1795 if sts.is_empty() {
1796 return ConditionResult::Unknown;
1797 }
1798 return ConditionResult::from(sts.iter().any(|s| s.get_component(2, 0) == "A99"));
1799 }
1800 ctx.has_qualified_value("STS", 0, "Z21", 2, 0, &["A99"])
1801 }
1802
1803 /// [136] Wenn STS+Z38+Z13+A02:E_1005 in dieser SG14 vorhanden
1804 // 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)
1805 fn evaluate_136(&self, ctx: &EvaluationContext) -> ConditionResult {
1806 // HAND-EDITED: "in dieser/derselben SG14" — scoped to the enclosing group.
1807 let segments = ctx.scoped_find_segments_in("SG14", "STS");
1808 ConditionResult::from(segments.iter().any(|s| {
1809 s.elements
1810 .first()
1811 .and_then(|e| e.first())
1812 .map(|v| v == "Z38")
1813 .unwrap_or(false)
1814 && s.elements
1815 .get(1)
1816 .and_then(|e| e.first())
1817 .map(|v| v == "Z13")
1818 .unwrap_or(false)
1819 && s.elements
1820 .get(2)
1821 .and_then(|e| e.first())
1822 .map(|v| v == "A02")
1823 .unwrap_or(false)
1824 && s.elements
1825 .get(2)
1826 .and_then(|e| e.get(1))
1827 .map(|v| v == "E_1005")
1828 .unwrap_or(false)
1829 }))
1830 }
1831
1832 /// [137] Wenn in diesem STS DE1131 = E_1005
1833 fn evaluate_137(&self, ctx: &EvaluationContext) -> ConditionResult {
1834 ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1005"])
1835 }
1836
1837 /// [138] Wenn in diesem STS DE1131 = E_1020
1838 fn evaluate_138(&self, ctx: &EvaluationContext) -> ConditionResult {
1839 ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1020"])
1840 }
1841
1842 /// [139] Wenn in diesem STS DE1131 = E_1020, dann ist nur der Code A01 möglich.
1843 // 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)
1844 fn evaluate_139(&self, ctx: &EvaluationContext) -> ConditionResult {
1845 ctx.has_qualified_value("STS", 0, "Z38", 2, 1, &["E_1020"])
1846 }
1847
1848 /// [140] Wenn in diesem STS DE9013 <> A01
1849 // 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)
1850 fn evaluate_140(&self, ctx: &EvaluationContext) -> ConditionResult {
1851 let sts_segments = ctx.find_segments("STS");
1852 ConditionResult::from(sts_segments.iter().any(|s| {
1853 s.elements
1854 .get(2)
1855 .and_then(|e| e.first())
1856 .is_some_and(|v| !v.is_empty() && v != "A01")
1857 }))
1858 }
1859
1860 /// [141] Wenn in diesem STS DE9013 = A01
1861 // REVIEW: Checks if any STS segment has DE9013 (elements[2][0]) equal to 'A01'. Medium confidence due to segment-level context ambiguity. (medium confidence)
1862 fn evaluate_141(&self, ctx: &EvaluationContext) -> ConditionResult {
1863 let sts_segments = ctx.find_segments("STS");
1864 ConditionResult::from(sts_segments.iter().any(|s| {
1865 s.elements
1866 .get(2)
1867 .and_then(|e| e.first())
1868 .is_some_and(|v| v == "A01")
1869 }))
1870 }
1871
1872 /// [142] Wenn in diesem STS DE1131 = E_0535
1873 fn evaluate_142(&self, ctx: &EvaluationContext) -> ConditionResult {
1874 let sts_segments = ctx.find_segments("STS");
1875 ConditionResult::from(sts_segments.iter().any(|s| {
1876 s.elements
1877 .get(2)
1878 .and_then(|e| e.get(1))
1879 .is_some_and(|v| v == "E_0535")
1880 }))
1881 }
1882
1883 /// [143] Wenn mehr als ein Grund vorliegt und die voranstehenden DE9013 dieses STS nicht ausreichen
1884 // 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)
1885 fn evaluate_143(&self, ctx: &EvaluationContext) -> ConditionResult {
1886 let sts_segments = ctx.find_segments("STS");
1887 ConditionResult::from(sts_segments.iter().any(|s| {
1888 s.elements
1889 .get(3)
1890 .and_then(|e| e.first())
1891 .is_some_and(|v| !v.is_empty())
1892 }))
1893 }
1894
1895 /// [144] Wenn RFF+Z45 in dieser SG15 nicht vorhanden
1896 // 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)
1897 fn evaluate_144(&self, ctx: &EvaluationContext) -> ConditionResult {
1898 // HAND-EDITED: "RFF+Z45 in dieser SG15 nicht vorhanden" — the enclosing SG15 only, when the scope
1899 // reaches one; the message as before otherwise.
1900 if let Some(scope) = ctx.enclosing_scope("SG15") {
1901 return ConditionResult::from(
1902 !ctx.find_segments_at(scope, "RFF")
1903 .iter()
1904 .any(|s| s.get_element(0) == "Z45"),
1905 );
1906 }
1907 ctx.any_group_has_qualifier_without("RFF", 0, "Z13", "RFF", 0, "Z45", &["SG14", "SG15"])
1908 }
1909
1910 /// [145] Wenn RFF+Z17 in dieser SG15 nicht vorhanden
1911 // REVIEW: Checks that in some SG15 instance, RFF+Z13 (mandatory Prüfidentifikator) is present but RFF+Z17 (Preisschlüsselstamm reference) is absent. (medium confidence)
1912 fn evaluate_145(&self, ctx: &EvaluationContext) -> ConditionResult {
1913 // HAND-EDITED: "RFF+Z17 in dieser SG15 nicht vorhanden" — the enclosing SG15 only, when the scope
1914 // reaches one; the message as before otherwise.
1915 if let Some(scope) = ctx.enclosing_scope("SG15") {
1916 return ConditionResult::from(
1917 !ctx.find_segments_at(scope, "RFF")
1918 .iter()
1919 .any(|s| s.get_element(0) == "Z17"),
1920 );
1921 }
1922 ctx.any_group_has_qualifier_without("RFF", 0, "Z13", "RFF", 0, "Z17", &["SG14", "SG15"])
1923 }
1924
1925 /// [146] Wenn im DE3155 in demselben COM der Code EM vorhanden ist
1926 fn evaluate_146(&self, ctx: &EvaluationContext) -> ConditionResult {
1927 let com_segs = ctx.find_segments("COM");
1928 ConditionResult::from(com_segs.iter().any(|s| {
1929 s.elements
1930 .first()
1931 .and_then(|e| e.get(1))
1932 .is_some_and(|v| v == "EM")
1933 }))
1934 }
1935
1936 /// [147] Wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
1937 fn evaluate_147(&self, ctx: &EvaluationContext) -> ConditionResult {
1938 let com_segs = ctx.find_segments("COM");
1939 ConditionResult::from(com_segs.iter().any(|s| {
1940 s.elements
1941 .first()
1942 .and_then(|e| e.get(1))
1943 .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
1944 }))
1945 }
1946
1947 /// [148] Wenn in dieser SG15 STS+Z43+Z48+A99:E_0595 vorhanden
1948 fn evaluate_148(&self, ctx: &EvaluationContext) -> ConditionResult {
1949 // HAND-EDITED: "in dieser/derselben SG15" — scoped to the enclosing group.
1950 let sts_segs = ctx.scoped_find_segments_in("SG15", "STS");
1951 ConditionResult::from(sts_segs.iter().any(|s| {
1952 s.elements
1953 .first()
1954 .and_then(|e| e.first())
1955 .is_some_and(|v| v == "Z43")
1956 && s.elements
1957 .get(1)
1958 .and_then(|e| e.first())
1959 .is_some_and(|v| v == "Z48")
1960 && s.elements
1961 .get(2)
1962 .and_then(|e| e.first())
1963 .is_some_and(|v| v == "A99")
1964 && s.elements
1965 .get(2)
1966 .and_then(|e| e.get(1))
1967 .is_some_and(|v| v == "E_0595")
1968 }))
1969 }
1970
1971 /// [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
1972 fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
1973 match ctx.resolved_value {
1974 Some(val) => is_mesz_utc(val),
1975 None => ConditionResult::False,
1976 }
1977 }
1978
1979 /// [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
1980 fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1981 match ctx.resolved_value {
1982 Some(val) => is_mez_utc(val),
1983 None => ConditionResult::False,
1984 }
1985 }
1986
1987 /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
1988 /// EXTERNAL: Requires context from outside the message.
1989 fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1990 ctx.external.evaluate("recipient_is_strom")
1991 }
1992
1993 /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
1994 /// EXTERNAL: Requires context from outside the message.
1995 fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1996 ctx.external.evaluate("recipient_is_gas")
1997 }
1998
1999 /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
2000 // 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)
2001 fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
2002 // The referenced date must be the document creation time or earlier (≤ DTM+137)
2003 let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
2004 let doc_val = match doc_segs
2005 .first()
2006 .and_then(|s| s.elements.first())
2007 .and_then(|e| e.get(1))
2008 {
2009 Some(v) => v.as_str(),
2010 None => return ConditionResult::Unknown,
2011 };
2012 let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
2013 match status_segs
2014 .first()
2015 .and_then(|s| s.elements.first())
2016 .and_then(|e| e.get(1))
2017 {
2018 Some(val) => ConditionResult::from(val.as_str() <= doc_val),
2019 None => ConditionResult::False, // segment absent → condition not applicable
2020 }
2021 }
2022
2023 /// [495] Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein
2024 // 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)
2025 fn evaluate_495(&self, ctx: &EvaluationContext) -> ConditionResult {
2026 // The timestamp must be ≤ the value in DE2380 of DTM+137
2027 let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
2028 let doc_val = match doc_segs
2029 .first()
2030 .and_then(|s| s.elements.first())
2031 .and_then(|e| e.get(1))
2032 {
2033 Some(v) => v.as_str(),
2034 None => return ConditionResult::Unknown,
2035 };
2036 let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
2037 match status_segs
2038 .first()
2039 .and_then(|s| s.elements.first())
2040 .and_then(|e| e.get(1))
2041 {
2042 Some(val) => ConditionResult::from(val.as_str() <= doc_val),
2043 None => ConditionResult::False, // segment absent → condition not applicable
2044 }
2045 }
2046
2047 /// [496] Der Zeitpunkt muss > dem Wert im DE2380 des DTM+137 sein
2048 // 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)
2049 fn evaluate_496(&self, ctx: &EvaluationContext) -> ConditionResult {
2050 // The timestamp must be > the value in DE2380 of DTM+137
2051 let doc_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
2052 let doc_val = match doc_segs
2053 .first()
2054 .and_then(|s| s.elements.first())
2055 .and_then(|e| e.get(1))
2056 {
2057 Some(v) => v.as_str(),
2058 None => return ConditionResult::Unknown,
2059 };
2060 let status_segs = ctx.find_segments_with_qualifier("DTM", 0, "334");
2061 match status_segs
2062 .first()
2063 .and_then(|s| s.elements.first())
2064 .and_then(|e| e.get(1))
2065 {
2066 Some(val) => ConditionResult::from(val.as_str() > doc_val),
2067 None => ConditionResult::False, // segment absent → condition not applicable
2068 }
2069 }
2070
2071 /// [501] Hinweis: Aus QUOTES BGM DE1004
2072 fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
2073 // Hinweis: Aus QUOTES BGM DE1004 — informational note about field origin, always applies
2074 ConditionResult::True
2075 }
2076
2077 /// [502] Hinweis: Aus REQOTE BGM DE1004
2078 fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
2079 // Hinweis: Aus REQOTE BGM DE1004 — informational note about field origin, always applies
2080 ConditionResult::True
2081 }
2082
2083 /// [503] Hinweis: Auf Selbsteinbau eines iMS oder einer mME wird verzichtet
2084 fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
2085 // Hinweis: Auf Selbsteinbau eines iMS oder einer mME wird verzichtet — informational note, always applies
2086 ConditionResult::True
2087 }
2088
2089 /// [504] Hinweis: Verwendung der ID des MaBiS-ZP
2090 fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
2091 // Hinweis: Verwendung der ID des MaBiS-ZP — informational note, always applies
2092 ConditionResult::True
2093 }
2094
2095 /// [505] Hinweis: Verwendung der ID der Messlokation
2096 fn evaluate_505(&self, _ctx: &EvaluationContext) -> ConditionResult {
2097 // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
2098 ConditionResult::True
2099 }
2100
2101 /// [506] Hinweis: Verwendung der ID der Marktlokation
2102 fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
2103 // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
2104 ConditionResult::True
2105 }
2106
2107 /// [510] Hinweis: Es ist neben der Information über die Ablehnung auch der unverändert gebliebene Datenstatus informell mitzugeben.
2108 fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
2109 // Hinweis: Es ist neben der Information über die Ablehnung auch der unverändert gebliebene Datenstatus informell mitzugeben — informational note, always applies
2110 ConditionResult::True
2111 }
2112
2113 /// [512] Hinweis: Aus MSCONS BGM DE1004
2114 fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
2115 // Hinweis: Aus MSCONS BGM DE1004 — informational note, always applies
2116 ConditionResult::True
2117 }
2118
2119 /// [519] Hinweis: Aus ORDERS BGM DE1004
2120 fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
2121 // Hinweis: Aus ORDERS BGM DE1004 — informational note, always applies
2122 ConditionResult::True
2123 }
2124
2125 /// [520] Hinweis: Zeitpunkt, zu dem der Wechsel erfolgt, falls er zustande kommt
2126 fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
2127 // Hinweis: Zeitpunkt, zu dem der Wechsel erfolgt, falls er zustande kommt — informational note, always applies
2128 ConditionResult::True
2129 }
2130
2131 /// [521] Hinweis: Zeitpunkt, ab dem der MSBN tatsächlich den Messstellenbetrieb übernimmt
2132 fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
2133 // Hinweis: Zeitpunkt, ab dem der MSBN tatsächlich den Messstellenbetrieb übernimmt — informational note, always applies
2134 ConditionResult::True
2135 }
2136
2137 /// [522] Hinweis: Zeitpunkt, ab dem der gMSB den Messstellenbetrieb übernimmt
2138 fn evaluate_522(&self, _ctx: &EvaluationContext) -> ConditionResult {
2139 // Hinweis: Zeitpunkt, ab dem der gMSB den Messstellenbetrieb übernimmt — informational note, always applies
2140 ConditionResult::True
2141 }
2142
2143 /// [523] Hinweis: Wert aus BGM DE1004 der MSCONS, auf die sich die Statusangabe bezieht
2144 fn evaluate_523(&self, _ctx: &EvaluationContext) -> ConditionResult {
2145 // Hinweis: Wert aus BGM DE1004 der MSCONS, auf die sich die Statusangabe bezieht — informational note, always applies
2146 ConditionResult::True
2147 }
2148
2149 /// [524] Hinweis: Wert aus BGM DE1004 der MSCONS, die den Gegenvorschlag enthält
2150 fn evaluate_524(&self, _ctx: &EvaluationContext) -> ConditionResult {
2151 // Hinweis: Wert aus BGM DE1004 der MSCONS, die den Gegenvorschlag enthält — informational note, always applies
2152 ConditionResult::True
2153 }
2154
2155 /// [525] Hinweis: Je SG14 sind nur Statusinformationen zu einer MSCONS enthalten
2156 fn evaluate_525(&self, _ctx: &EvaluationContext) -> ConditionResult {
2157 // Hinweis: Je SG14 sind nur Statusinformationen zu einer MSCONS enthalten — informational note, always applies
2158 ConditionResult::True
2159 }
2160
2161 /// [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.
2162 fn evaluate_530(&self, _ctx: &EvaluationContext) -> ConditionResult {
2163 // 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
2164 ConditionResult::True
2165 }
2166
2167 /// [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...
2168 fn evaluate_531(&self, _ctx: &EvaluationContext) -> ConditionResult {
2169 // Hinweis: Vom MSBN in Schritt 1 des SD verwendete Vorgangsnummer, damit der LF diese
2170 // bei der Bestellung einer Konfiguration beim MSBN verwenden kann — informational note
2171 // describing field semantics and usage context, always applies unconditionally
2172 ConditionResult::True
2173 }
2174
2175 /// [532] Hinweis: Verwendung der ID der Netzlokation
2176 fn evaluate_532(&self, _ctx: &EvaluationContext) -> ConditionResult {
2177 // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
2178 ConditionResult::True
2179 }
2180
2181 /// [533] Hinweis: Verwendung der ID der Steuerbaren Ressource
2182 fn evaluate_533(&self, _ctx: &EvaluationContext) -> ConditionResult {
2183 // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
2184 ConditionResult::True
2185 }
2186
2187 /// [534] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
2188 fn evaluate_534(&self, _ctx: &EvaluationContext) -> ConditionResult {
2189 // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
2190 ConditionResult::True
2191 }
2192
2193 /// [535] Hinweis: Aus UTILMD IDE DE7402
2194 fn evaluate_535(&self, _ctx: &EvaluationContext) -> ConditionResult {
2195 // Hinweis: Aus UTILMD IDE DE7402 — informational note about value origin, always applies
2196 ConditionResult::True
2197 }
2198
2199 /// [902] Format: Wert darf nur positiv oder 0 sein
2200 // 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)
2201 fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
2202 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">=", 0.0))
2203 }
2204
2205 /// [903] Format: Möglicher Wert: 1
2206 // 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)
2207 fn evaluate_903(&self, ctx: &EvaluationContext) -> ConditionResult {
2208 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "==", 1.0))
2209 }
2210
2211 /// [906] Format: max. 3 Nachkommastellen
2212 // REVIEW: Format condition: max 3 decimal places. Applies to QTY segment value field (elements[0][1]). (medium confidence)
2213 fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
2214 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 3))
2215 }
2216
2217 /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht bei 1 beginnend und fortlaufend aufsteigend
2218 fn evaluate_911(&self, _ctx: &EvaluationContext) -> ConditionResult {
2219 // Hinweis: Mögliche Werte 1 bis n, je Nachricht bei 1 beginnend und fortlaufend aufsteigend
2220 // Informational note about sequence numbering — always applies
2221 ConditionResult::True
2222 }
2223
2224 /// [931] Format: ZZZ = +00
2225 // REVIEW: Format condition: timezone must be UTC (+00). Uses validate_timezone_utc on the DTM value field. Targets first DTM segment found. (medium confidence)
2226 fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
2227 ctx.format_check("DTM", 0, 1, validate_timezone_utc)
2228 }
2229
2230 /// [932] Format: HHMM = 2200
2231 // 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)
2232 fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
2233 ctx.format_check_qualified("DTM", 0, "163", 0, 1, |val| {
2234 validate_hhmm_equals(val, "2200")
2235 })
2236 }
2237
2238 /// [933] Format: HHMM = 2300
2239 fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
2240 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2300"))
2241 }
2242
2243 /// [934] Format: HHMM = 0400
2244 fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
2245 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0400"))
2246 }
2247
2248 /// [935] Format: HHMM = 0500
2249 fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
2250 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0500"))
2251 }
2252
2253 /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
2254 // 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)
2255 fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
2256 let com_segments = ctx.find_segments("COM");
2257 for seg in &com_segments {
2258 if let Some(value) = seg.elements.first().and_then(|e| e.first()) {
2259 if value.contains('@') && value.contains('.') {
2260 return ConditionResult::True;
2261 }
2262 }
2263 }
2264 ConditionResult::False
2265 }
2266
2267 /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
2268 // 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)
2269 fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
2270 let com_segments = ctx.find_segments("COM");
2271 for seg in &com_segments {
2272 if let Some(value) = seg.elements.first().and_then(|e| e.first()) {
2273 if value.starts_with('+')
2274 && value.len() > 1
2275 && value[1..].chars().all(|c| c.is_ascii_digit())
2276 {
2277 return ConditionResult::True;
2278 }
2279 }
2280 }
2281 ConditionResult::False
2282 }
2283
2284 /// [950] Format: Marktlokations-ID
2285 fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
2286 ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
2287 }
2288
2289 /// [951] Format: Zählpunktbezeichnung
2290 // 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)
2291 fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
2292 ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_zahlpunkt)
2293 }
2294
2295 /// [960] Format: Netzlokations-ID
2296 // HAND-EDITED: a Netzlokations-ID is "E" + ten alphanumerics (`E1688117482`),
2297 // not MaLo-shaped; `validate_malo_id` rejected every real one (#173).
2298 fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
2299 ctx.format_check_qualified("LOC", 0, "Z18", 1, 0, validate_nelo_id)
2300 }
2301
2302 /// [961] Format: SR-ID
2303 // 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)
2304 fn evaluate_961(&self, ctx: &EvaluationContext) -> ConditionResult {
2305 ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_malo_id)
2306 }
2307}