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