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