automapper_validation/generated/fv2504/invoic_conditions_fv2504.rs
1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2504/INVOIC_AHB_2_5d_Fehlerkorrektur_20250623.xml
4// Generated: 2026-03-12T10:18:43Z
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 INVOIC FV2504.
12pub struct InvoicConditionEvaluatorFV2504 {
13 // External condition IDs that require runtime context.
14 external_conditions: std::collections::HashSet<u32>,
15}
16
17impl Default for InvoicConditionEvaluatorFV2504 {
18 fn default() -> Self {
19 let mut external_conditions = std::collections::HashSet::new();
20 external_conditions.insert(1);
21 external_conditions.insert(2);
22 external_conditions.insert(3);
23 external_conditions.insert(7);
24 external_conditions.insert(8);
25 external_conditions.insert(11);
26 external_conditions.insert(13);
27 external_conditions.insert(15);
28 external_conditions.insert(16);
29 external_conditions.insert(28);
30 external_conditions.insert(30);
31 external_conditions.insert(31);
32 external_conditions.insert(34);
33 external_conditions.insert(37);
34 external_conditions.insert(40);
35 external_conditions.insert(45);
36 external_conditions.insert(67);
37 external_conditions.insert(68);
38 external_conditions.insert(69);
39 external_conditions.insert(72);
40 external_conditions.insert(76);
41 external_conditions.insert(492);
42 external_conditions.insert(493);
43 Self {
44 external_conditions,
45 }
46 }
47}
48
49impl ConditionEvaluator for InvoicConditionEvaluatorFV2504 {
50 fn message_type(&self) -> &str {
51 "INVOIC"
52 }
53
54 fn format_version(&self) -> &str {
55 "FV2504"
56 }
57
58 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
59 match condition {
60 1 => self.evaluate_1(ctx),
61 2 => self.evaluate_2(ctx),
62 3 => self.evaluate_3(ctx),
63 4 => self.evaluate_4(ctx),
64 5 => self.evaluate_5(ctx),
65 6 => self.evaluate_6(ctx),
66 7 => self.evaluate_7(ctx),
67 8 => self.evaluate_8(ctx),
68 9 => self.evaluate_9(ctx),
69 10 => self.evaluate_10(ctx),
70 11 => self.evaluate_11(ctx),
71 12 => self.evaluate_12(ctx),
72 13 => self.evaluate_13(ctx),
73 14 => self.evaluate_14(ctx),
74 15 => self.evaluate_15(ctx),
75 16 => self.evaluate_16(ctx),
76 17 => self.evaluate_17(ctx),
77 18 => self.evaluate_18(ctx),
78 19 => self.evaluate_19(ctx),
79 20 => self.evaluate_20(ctx),
80 21 => self.evaluate_21(ctx),
81 22 => self.evaluate_22(ctx),
82 23 => self.evaluate_23(ctx),
83 24 => self.evaluate_24(ctx),
84 25 => self.evaluate_25(ctx),
85 26 => self.evaluate_26(ctx),
86 27 => self.evaluate_27(ctx),
87 28 => self.evaluate_28(ctx),
88 29 => self.evaluate_29(ctx),
89 30 => self.evaluate_30(ctx),
90 31 => self.evaluate_31(ctx),
91 32 => self.evaluate_32(ctx),
92 34 => self.evaluate_34(ctx),
93 36 => self.evaluate_36(ctx),
94 37 => self.evaluate_37(ctx),
95 40 => self.evaluate_40(ctx),
96 41 => self.evaluate_41(ctx),
97 42 => self.evaluate_42(ctx),
98 44 => self.evaluate_44(ctx),
99 45 => self.evaluate_45(ctx),
100 47 => self.evaluate_47(ctx),
101 48 => self.evaluate_48(ctx),
102 49 => self.evaluate_49(ctx),
103 50 => self.evaluate_50(ctx),
104 51 => self.evaluate_51(ctx),
105 53 => self.evaluate_53(ctx),
106 54 => self.evaluate_54(ctx),
107 55 => self.evaluate_55(ctx),
108 56 => self.evaluate_56(ctx),
109 57 => self.evaluate_57(ctx),
110 58 => self.evaluate_58(ctx),
111 59 => self.evaluate_59(ctx),
112 60 => self.evaluate_60(ctx),
113 61 => self.evaluate_61(ctx),
114 64 => self.evaluate_64(ctx),
115 65 => self.evaluate_65(ctx),
116 66 => self.evaluate_66(ctx),
117 67 => self.evaluate_67(ctx),
118 68 => self.evaluate_68(ctx),
119 69 => self.evaluate_69(ctx),
120 70 => self.evaluate_70(ctx),
121 71 => self.evaluate_71(ctx),
122 72 => self.evaluate_72(ctx),
123 73 => self.evaluate_73(ctx),
124 74 => self.evaluate_74(ctx),
125 75 => self.evaluate_75(ctx),
126 76 => self.evaluate_76(ctx),
127 77 => self.evaluate_77(ctx),
128 78 => self.evaluate_78(ctx),
129 79 => self.evaluate_79(ctx),
130 80 => self.evaluate_80(ctx),
131 81 => self.evaluate_81(ctx),
132 82 => self.evaluate_82(ctx),
133 84 => self.evaluate_84(ctx),
134 85 => self.evaluate_85(ctx),
135 86 => self.evaluate_86(ctx),
136 88 => self.evaluate_88(ctx),
137 490 => self.evaluate_490(ctx),
138 491 => self.evaluate_491(ctx),
139 492 => self.evaluate_492(ctx),
140 493 => self.evaluate_493(ctx),
141 501 => self.evaluate_501(ctx),
142 502 => self.evaluate_502(ctx),
143 503 => self.evaluate_503(ctx),
144 504 => self.evaluate_504(ctx),
145 507 => self.evaluate_507(ctx),
146 508 => self.evaluate_508(ctx),
147 509 => self.evaluate_509(ctx),
148 510 => self.evaluate_510(ctx),
149 512 => self.evaluate_512(ctx),
150 513 => self.evaluate_513(ctx),
151 514 => self.evaluate_514(ctx),
152 515 => self.evaluate_515(ctx),
153 516 => self.evaluate_516(ctx),
154 517 => self.evaluate_517(ctx),
155 518 => self.evaluate_518(ctx),
156 519 => self.evaluate_519(ctx),
157 520 => self.evaluate_520(ctx),
158 521 => self.evaluate_521(ctx),
159 522 => self.evaluate_522(ctx),
160 523 => self.evaluate_523(ctx),
161 524 => self.evaluate_524(ctx),
162 525 => self.evaluate_525(ctx),
163 526 => self.evaluate_526(ctx),
164 902 => self.evaluate_902(ctx),
165 906 => self.evaluate_906(ctx),
166 908 => self.evaluate_908(ctx),
167 910 => self.evaluate_910(ctx),
168 911 => self.evaluate_911(ctx),
169 912 => self.evaluate_912(ctx),
170 914 => self.evaluate_914(ctx),
171 927 => self.evaluate_927(ctx),
172 930 => self.evaluate_930(ctx),
173 931 => self.evaluate_931(ctx),
174 932 => self.evaluate_932(ctx),
175 933 => self.evaluate_933(ctx),
176 934 => self.evaluate_934(ctx),
177 935 => self.evaluate_935(ctx),
178 937 => self.evaluate_937(ctx),
179 939 => self.evaluate_939(ctx),
180 940 => self.evaluate_940(ctx),
181 946 => self.evaluate_946(ctx),
182 950 => self.evaluate_950(ctx),
183 951 => self.evaluate_951(ctx),
184 960 => self.evaluate_960(ctx),
185 961 => self.evaluate_961(ctx),
186 _ => ConditionResult::Unknown,
187 }
188 }
189
190 fn is_external(&self, condition: u32) -> bool {
191 self.external_conditions.contains(&condition)
192 }
193 fn is_known(&self, condition: u32) -> bool {
194 matches!(
195 condition,
196 1 | 2
197 | 3
198 | 4
199 | 5
200 | 6
201 | 7
202 | 8
203 | 9
204 | 10
205 | 11
206 | 12
207 | 13
208 | 14
209 | 15
210 | 16
211 | 17
212 | 18
213 | 19
214 | 20
215 | 21
216 | 22
217 | 23
218 | 24
219 | 25
220 | 26
221 | 27
222 | 28
223 | 29
224 | 30
225 | 31
226 | 32
227 | 34
228 | 36
229 | 37
230 | 40
231 | 41
232 | 42
233 | 44
234 | 45
235 | 47
236 | 48
237 | 49
238 | 50
239 | 51
240 | 53
241 | 54
242 | 55
243 | 56
244 | 57
245 | 58
246 | 59
247 | 60
248 | 61
249 | 64
250 | 65
251 | 66
252 | 67
253 | 68
254 | 69
255 | 70
256 | 71
257 | 72
258 | 73
259 | 74
260 | 75
261 | 76
262 | 77
263 | 78
264 | 79
265 | 80
266 | 81
267 | 82
268 | 84
269 | 85
270 | 86
271 | 88
272 | 490
273 | 491
274 | 492
275 | 493
276 | 501
277 | 502
278 | 503
279 | 504
280 | 507
281 | 508
282 | 509
283 | 510
284 | 512
285 | 513
286 | 514
287 | 515
288 | 516
289 | 517
290 | 518
291 | 519
292 | 520
293 | 521
294 | 522
295 | 523
296 | 524
297 | 525
298 | 526
299 | 902
300 | 906
301 | 908
302 | 910
303 | 911
304 | 912
305 | 914
306 | 927
307 | 930
308 | 931
309 | 932
310 | 933
311 | 934
312 | 935
313 | 937
314 | 939
315 | 940
316 | 946
317 | 950
318 | 951
319 | 960
320 | 961
321 )
322 }
323}
324
325impl InvoicConditionEvaluatorFV2504 {
326 /// [1] Wenn in zu stornierender Rechnung gefüllt
327 /// EXTERNAL: Requires context from outside the message.
328 fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
329 ctx.external
330 .evaluate("invoice_being_cancelled_field_filled")
331 }
332
333 /// [2] Wenn es sich um eine Nutzungsüberlassung (Pacht) eines Gerätes handelt
334 /// EXTERNAL: Requires context from outside the message.
335 fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
336 ctx.external.evaluate("is_device_lease")
337 }
338
339 /// [3] Wenn es sich um einen Kauf eines Gerätes handelt
340 /// EXTERNAL: Requires context from outside the message.
341 fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
342 ctx.external.evaluate("is_device_purchase")
343 }
344
345 /// [7] Sofern keine Großkundenpostleitzahl verwendet wird
346 /// EXTERNAL: Requires context from outside the message.
347 fn evaluate_7(&self, ctx: &EvaluationContext) -> ConditionResult {
348 ctx.external.evaluate("no_large_customer_postal_code")
349 }
350
351 /// [8] Bei zeitabhängigen Preisen
352 /// EXTERNAL: Requires context from outside the message.
353 fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
354 ctx.external.evaluate("is_time_dependent_pricing")
355 }
356
357 /// [11] Wenn Abschlag anfällt
358 /// EXTERNAL: Requires context from outside the message.
359 // REVIEW: Whether a deposit/advance payment (Abschlag) applies is business context that cannot be determined from the EDIFACT message structure alone — it depends on the contract type and billing arrangement. (medium confidence)
360 fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
361 ctx.external.evaluate("advance_payment_applies")
362 }
363
364 /// [13] Wenn vorausbezahlter Betrag vorliegt
365 /// EXTERNAL: Requires context from outside the message.
366 // REVIEW: Whether a prepaid amount (vorausbezahlter Betrag) is present is billing business context that cannot be derived solely from the EDIFACT segments in the message. (medium confidence)
367 fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
368 ctx.external.evaluate("prepaid_amount_present")
369 }
370
371 /// [15] Wenn eine Bilanzierung erfolgt ist
372 /// EXTERNAL: Requires context from outside the message.
373 // REVIEW: Whether a balancing (Bilanzierung) has occurred is operational/business context external to the EDIFACT message itself. (medium confidence)
374 fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
375 ctx.external.evaluate("balancing_performed")
376 }
377
378 /// [16] Wenn eine Netznutzung erfolgt ist
379 /// EXTERNAL: Requires context from outside the message.
380 // REVIEW: Whether network usage (Netznutzung) has occurred is business context that cannot be determined solely from the EDIFACT message content. (medium confidence)
381 fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
382 ctx.external.evaluate("network_usage_performed")
383 }
384
385 /// [24] Wert muss mindestens 10 WT nach Wert aus DTM+137 DE2380 liegen
386 fn evaluate_24(&self, _ctx: &EvaluationContext) -> ConditionResult {
387 // TODO: Condition [24] requires manual implementation
388 // Reason: Condition requires calculating whether a date value is at least 10 Werktage (working days) after DTM+137 DE2380. Working-day arithmetic requires a German public holiday calendar, which is not available in the EvaluationContext API. Cannot implement accurately without external calendar data.
389 ConditionResult::Unknown
390 }
391
392 /// [25] Wert darf maximal 10 WT nach Wert aus DTM+137 DE2380 liegen
393 fn evaluate_25(&self, _ctx: &EvaluationContext) -> ConditionResult {
394 // TODO: Condition [25] requires manual implementation
395 // Reason: Requires computing a date that is at most 10 Werktage (working days) after DTM+137 DE2380. Working-day calculation requires a German public-holiday calendar (which varies by Bundesland) and cannot be performed with the available API helpers. Marking as low confidence / null.
396 ConditionResult::Unknown
397 }
398
399 /// [34] Wenn in Ursprungsrechnung vorhanden
400 /// EXTERNAL: Requires context from outside the message.
401 // REVIEW: "Wenn in Ursprungsrechnung vorhanden" means the condition is met when the corresponding value was present in the referenced original invoice. That original document is not available inside the current EDIFACT message, so this is an external business-context check. (medium confidence)
402 fn evaluate_34(&self, ctx: &EvaluationContext) -> ConditionResult {
403 ctx.external.evaluate("in_original_invoice")
404 }
405
406 /// [37] Wenn Lieferschein zuvor ausgetauscht wurde
407 /// EXTERNAL: Requires context from outside the message.
408 // REVIEW: "Wenn Lieferschein zuvor ausgetauscht wurde" depends on whether a delivery note (Lieferschein) was exchanged in a prior business transaction. This cannot be determined from the current EDIFACT message alone; it requires external business context. (medium confidence)
409 fn evaluate_37(&self, ctx: &EvaluationContext) -> ConditionResult {
410 ctx.external.evaluate("delivery_note_previously_exchanged")
411 }
412
413 /// [40] Es sind nur die Artikelnummern erlaubt, die in der Codeliste der Artikelnummern und Artikel-ID mit dem entsprechenden Prüfidentifikator versehen sind oder Artikel-ID aus der Codeliste der Artikeln...
414 /// EXTERNAL: Requires context from outside the message.
415 // REVIEW: Condition requires validating that the article number/ID appears in the external "Codeliste der Artikelnummern und Artikel-ID" for the given Prüfidentifikator. Code-list membership cannot be verified from the EDIFACT message structure alone; it is an external reference-data check. (medium confidence)
416 fn evaluate_40(&self, ctx: &EvaluationContext) -> ConditionResult {
417 ctx.external.evaluate("artikel_id_in_codeliste")
418 }
419
420 /// [45] Nur MP-ID aus Sparte Strom
421 /// EXTERNAL: Requires context from outside the message.
422 // REVIEW: "Nur MP-ID aus Sparte Strom" means only market-participant IDs that belong to the electricity sector are allowed. Determining which IDs are registered for Sparte Strom requires an external code list or market master data lookup and cannot be inferred from the EDIFACT message content. (medium confidence)
423 fn evaluate_45(&self, ctx: &EvaluationContext) -> ConditionResult {
424 ctx.external.evaluate("mp_id_sparte_strom")
425 }
426
427 /// [67] Es sind nur die Artikelnummern erlaubt, die im Kapitel 2 „Codeliste der Artikelnummer“ in der Codeliste der Artikelnummern und Artikel-ID ein „X“ für den entsprechenden Prüfidentifikator ...
428 /// EXTERNAL: Requires context from outside the message.
429 fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
430 ctx.external.evaluate("artikelnummer_allowed_for_pid")
431 }
432
433 /// [68] Es sind nur die Artikel-ID erlaubt, die im Kapitel 3 „Codeliste der Gruppenartikel-ID und Artikel-ID“ in der Codeliste der Artikelnummern und Artikel-ID ein „X“ in der Spalte „INVOIC Cod...
434 /// EXTERNAL: Requires context from outside the message.
435 fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
436 ctx.external.evaluate("artikel_id_invoic_allowed")
437 }
438
439 /// [69] Es sind nur die Artikel-ID erlaubt, die im Kapitel 3 „Codeliste der Gruppenartikel-ID und Artikel-ID“ in der Codeliste der Artikelnummern und Artikel-ID ein „SOR“ in der Spalte „INVOIC C...
440 /// EXTERNAL: Requires context from outside the message.
441 fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
442 ctx.external.evaluate("artikel_id_invoic_sor_allowed")
443 }
444
445 /// [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
446 fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
447 let dtm_segs = ctx.find_segments("DTM");
448 match dtm_segs
449 .first()
450 .and_then(|s| s.elements.first())
451 .and_then(|e| e.get(1))
452 {
453 Some(val) => is_mesz_utc(val),
454 None => ConditionResult::False, // segment absent → condition not applicable
455 }
456 }
457
458 /// [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
459 fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
460 let dtm_segs = ctx.find_segments("DTM");
461 match dtm_segs
462 .first()
463 .and_then(|s| s.elements.first())
464 .and_then(|e| e.get(1))
465 {
466 Some(val) => is_mez_utc(val),
467 None => ConditionResult::False, // segment absent → condition not applicable
468 }
469 }
470
471 /// [4] Wenn Steuerschuldnerschaft des Leistungsempfängers vorliegt
472 // REVIEW: "Wenn Steuerschuldnerschaft des Leistungsempfängers vorliegt" is the reverse charge mechanism. In UN/EDIFACT INVOIC, TAX category code "AE" (VAT Reverse Charge) signals this. The TAX segment composite C241 component 3 holds the tax category code. Medium confidence because element indices are derived from standard UN/EDIFACT TAX structure without a specific MIG segment structure reference to confirm the exact index for this INVOIC variant. (medium confidence)
473 fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
474 // Steuerschuldnerschaft des Leistungsempfängers = reverse charge (VAT category AE)
475 // In EDIFACT INVOIC the TAX segment carries duty/tax type category code.
476 // UN/EDIFACT code "AE" = VAT Reverse Charge (Steuerschuldnerschaft des Leistungsempfängers).
477 // TAX+7+VAT+++:::AE — elements[4][3] holds the tax category code.
478 let tax_segs = ctx.find_segments("TAX");
479 for seg in &tax_segs {
480 // elements[4] is the duty/tax account detail composite (C241 in UN/EDIFACT TAX)
481 // component 3 (index 3) is the duty/tax rate category code
482 if let Some(code) = seg.elements.get(4).and_then(|e| e.get(3)) {
483 if code == "AE" {
484 return ConditionResult::True;
485 }
486 }
487 }
488 ConditionResult::False
489 }
490
491 /// [5] Wenn NAD+MR DE3207 <> „DE“
492 fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
493 let nads = ctx.find_segments_with_qualifier("NAD", 0, "MR");
494 match nads.first() {
495 Some(nad) => {
496 match nad
497 .elements
498 .get(8)
499 .and_then(|e| e.first())
500 .map(|s| s.as_str())
501 {
502 Some(country) => ConditionResult::from(country != "DE"),
503 None => ConditionResult::False, // segment absent → condition not applicable
504 }
505 }
506 None => ConditionResult::False, // segment absent → condition not applicable
507 }
508 }
509
510 /// [6] Wenn NAD+MR DE3207 = „DE“
511 fn evaluate_6(&self, ctx: &EvaluationContext) -> ConditionResult {
512 let nads = ctx.find_segments_with_qualifier("NAD", 0, "MR");
513 match nads.first() {
514 Some(nad) => {
515 match nad
516 .elements
517 .get(8)
518 .and_then(|e| e.first())
519 .map(|s| s.as_str())
520 {
521 Some(country) => ConditionResult::from(country == "DE"),
522 None => ConditionResult::False, // segment absent → condition not applicable
523 }
524 }
525 None => ConditionResult::False, // segment absent → condition not applicable
526 }
527 }
528
529 /// [9] Wenn SG26 DTM+203 nicht vorhanden
530 // REVIEW: Checks whether any SG26 instance lacks DTM+203 (Ausführungsdatum). Uses group navigator when available, falling back to message-wide absence check. DTM qualifier at elements[0][0]. (medium confidence)
531 fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
532 // DTM+203 (Ausführungsdatum) absence in the current SG26 context.
533 // Falls back to message-wide check when no group navigator available.
534 let nav = match ctx.navigator() {
535 Some(n) => n,
536 None => return ctx.lacks_qualifier("DTM", 0, "203"),
537 };
538 let sg26_count = nav.group_instance_count(&["SG26"]);
539 for i in 0..sg26_count {
540 let dtms = nav.find_segments_in_group("DTM", &["SG26"], i);
541 let has_203 = dtms.iter().any(|s| {
542 s.elements
543 .first()
544 .and_then(|e| e.first())
545 .is_some_and(|v| v == "203")
546 });
547 if !has_203 {
548 return ConditionResult::True;
549 }
550 }
551 if sg26_count == 0 {
552 return ctx.lacks_qualifier("DTM", 0, "203");
553 }
554 ConditionResult::False
555 }
556
557 /// [10] Wenn SG26 DTM+155/156 nicht vorhanden
558 // REVIEW: Both DTM+155 (Abrechnungszeitraum Beginn) and DTM+156 (Ende) must be absent in the SG26. Uses group navigator; both must be absent simultaneously for the condition to be True. (medium confidence)
559 fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
560 // DTM+155 (Abrechnungszeitraum Beginn) and DTM+156 (Ende) both absent in SG26.
561 // Falls back to message-wide check when no group navigator available.
562 let nav = match ctx.navigator() {
563 Some(n) => n,
564 None => {
565 let no_155 = ctx.lacks_qualifier("DTM", 0, "155");
566 let no_156 = ctx.lacks_qualifier("DTM", 0, "156");
567 return match (no_155, no_156) {
568 (ConditionResult::True, ConditionResult::True) => ConditionResult::True,
569 (ConditionResult::False, _) | (_, ConditionResult::False) => {
570 ConditionResult::False
571 }
572 _ => ConditionResult::Unknown,
573 };
574 }
575 };
576 let sg26_count = nav.group_instance_count(&["SG26"]);
577 for i in 0..sg26_count {
578 let dtms = nav.find_segments_in_group("DTM", &["SG26"], i);
579 let has_155 = dtms.iter().any(|s| {
580 s.elements
581 .first()
582 .and_then(|e| e.first())
583 .is_some_and(|v| v == "155")
584 });
585 let has_156 = dtms.iter().any(|s| {
586 s.elements
587 .first()
588 .and_then(|e| e.first())
589 .is_some_and(|v| v == "156")
590 });
591 if !has_155 && !has_156 {
592 return ConditionResult::True;
593 }
594 }
595 if sg26_count == 0 {
596 let no_155 = ctx.lacks_qualifier("DTM", 0, "155");
597 let no_156 = ctx.lacks_qualifier("DTM", 0, "156");
598 return match (no_155, no_156) {
599 (ConditionResult::True, ConditionResult::True) => ConditionResult::True,
600 (ConditionResult::False, _) | (_, ConditionResult::False) => ConditionResult::False,
601 _ => ConditionResult::Unknown,
602 };
603 }
604 ConditionResult::False
605 }
606
607 /// [12] Wenn SG26 QTY+136 vorhanden
608 fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
609 ctx.has_qualifier("QTY", 0, "136")
610 }
611
612 /// [14] Wenn in selben SG26 LIN DE7140 = "9990001000748" (Mehrmenge)
613 fn evaluate_14(&self, ctx: &EvaluationContext) -> ConditionResult {
614 // Check if any SG26 LIN has DE7140 (Produkt-/Leistungsnummer) = "9990001000748" (Mehrmenge).
615 // LIN is the entry segment of SG26; elements[2][0] = DE7140.
616 let lins = ctx.find_segments("LIN");
617 ConditionResult::from(lins.iter().any(|s| {
618 s.elements
619 .get(2)
620 .and_then(|e| e.first())
621 .is_some_and(|v| v == "9990001000748")
622 }))
623 }
624
625 /// [17] Wenn DTM+Z11 vorhanden
626 fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
627 ctx.has_qualifier("DTM", 0, "Z11")
628 }
629
630 /// [18] Wenn IMD++WIM nicht vorhanden
631 fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
632 ctx.lacks_qualifier("IMD", 1, "WIM")
633 }
634
635 /// [19] Wenn IMD++WIM vorhanden
636 fn evaluate_19(&self, ctx: &EvaluationContext) -> ConditionResult {
637 ctx.has_qualifier("IMD", 1, "WIM")
638 }
639
640 /// [20] Wenn fälliger Betrag (SG50 MOA+9) ≥ 0
641 // REVIEW: MOA+9 is fälliger Betrag (due amount): elements[0][0]='9', elements[0][1]=amount. Parses the numeric value and checks >= 0. (medium confidence)
642 fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
643 let moas = ctx.find_segments_with_qualifier("MOA", 0, "9");
644 match moas.first() {
645 Some(moa) => match moa.elements.first().and_then(|e| e.get(1)) {
646 Some(val) => match val.parse::<f64>() {
647 Ok(n) => ConditionResult::from(n >= 0.0),
648 Err(_) => ConditionResult::Unknown,
649 },
650 None => ConditionResult::False, // segment absent → condition not applicable
651 },
652 None => ConditionResult::False, // segment absent → condition not applicable
653 }
654 }
655
656 /// [21] Wenn fälliger Betrag (SG50 MOA+9) < 0
657 // REVIEW: MOA+9 is fälliger Betrag. Parses numeric value and checks < 0. Inverse of condition 20. (medium confidence)
658 fn evaluate_21(&self, ctx: &EvaluationContext) -> ConditionResult {
659 let moas = ctx.find_segments_with_qualifier("MOA", 0, "9");
660 match moas.first() {
661 Some(moa) => match moa.elements.first().and_then(|e| e.get(1)) {
662 Some(val) => match val.parse::<f64>() {
663 Ok(n) => ConditionResult::from(n < 0.0),
664 Err(_) => ConditionResult::Unknown,
665 },
666 None => ConditionResult::False, // segment absent → condition not applicable
667 },
668 None => ConditionResult::False, // segment absent → condition not applicable
669 }
670 }
671
672 /// [22] Wenn vorhanden
673 fn evaluate_22(&self, _ctx: &EvaluationContext) -> ConditionResult {
674 // Wenn vorhanden — self-referential presence condition, always True when evaluated
675 ConditionResult::True
676 }
677
678 /// [23] Wenn im selben NAD DE3124 nicht vorhanden
679 // REVIEW: DE3124 resides in C058 at elements[2][0] for all NAD variants. 'Im selben NAD' means within the same NAD instance; evaluated message-wide as: any NAD where this component is absent or empty. (medium confidence)
680 fn evaluate_23(&self, ctx: &EvaluationContext) -> ConditionResult {
681 let nads = ctx.find_segments("NAD");
682 if nads.is_empty() {
683 return ConditionResult::Unknown;
684 }
685 // True if any NAD has DE3124 (elements[2][0] from C058) absent or empty
686 ConditionResult::from(nads.iter().any(|nad| {
687 nad.elements
688 .get(2)
689 .and_then(|e| e.first())
690 .map_or(true, |v| v.is_empty())
691 }))
692 }
693
694 /// [26] Wenn SG39 ALC+A+:Z04 vorhanden
695 fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
696 let alcs = ctx.find_segments_with_qualifier("ALC", 0, "A");
697 ConditionResult::from(alcs.iter().any(|alc| {
698 alc.elements
699 .get(1)
700 .and_then(|e| e.get(1))
701 .map_or(false, |v| v == "Z04")
702 }))
703 }
704
705 /// [27] Wenn SG39 ALC+C vorhanden
706 fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
707 ctx.has_qualifier("ALC", 0, "C")
708 }
709
710 /// [28] Wenn Zuschlag anfällt
711 /// EXTERNAL: Requires context from outside the message.
712 // REVIEW: 'Wenn Zuschlag anfällt' (if a surcharge applies) is a business-context determination that cannot be derived from EDIFACT segment content alone. It depends on contract terms or tariff configuration held outside the message. (medium confidence)
713 fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
714 ctx.external.evaluate("surcharge_applies")
715 }
716
717 /// [29] [Wenn DTM+155 (Abrechnungszeitraum Beginn) nicht größer 31.12.2015
718 // REVIEW: DTM+155 is Abrechnungszeitraum Beginn with format 303 (starts YYYYMMDD). 'nicht größer 31.12.2015' means <= 31.12.2015. Lexicographic comparison of 8-char ISO prefix is correct for this date format. (medium confidence)
719 fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
720 let dtms = ctx.find_segments_with_qualifier("DTM", 0, "155");
721 match dtms.first() {
722 Some(dtm) => {
723 match dtm.elements.first().and_then(|e| e.get(1)) {
724 Some(val) if val.len() >= 8 => {
725 // Format 303: CCYYMMDDHHMMSSZZZ — compare YYYYMMDD prefix
726 let date_prefix = &val[..8];
727 ConditionResult::from(date_prefix <= "20151231")
728 }
729 _ => ConditionResult::Unknown,
730 }
731 }
732 None => ConditionResult::False, // segment absent → condition not applicable
733 }
734 }
735
736 /// [30] Wenn MP-ID in NAD+MR nicht in der Rolle MGV oder KN
737 /// EXTERNAL: Requires context from outside the message.
738 // REVIEW: 'Wenn MP-ID in NAD+MR nicht in der Rolle MGV oder KN' requires knowledge of which market-participant IDs correspond to the MGV (Messstellenbetreiber Gasvertrieb) or KN (Kunde/Netz) roles. Role assignment is maintained in an external registry and cannot be determined from the NAD segment alone. (medium confidence)
739 fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
740 ctx.external.evaluate("recipient_not_mgv_or_kn")
741 }
742
743 /// [31] Wenn MP-ID in NAD+MR in der Rolle MGV
744 /// EXTERNAL: Requires context from outside the message.
745 fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
746 ctx.external.evaluate("recipient_is_mgv")
747 }
748
749 /// [32] Wenn SG39 ALC+A+:Z01 (Gemeinderabatt) vorhanden
750 fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
751 ctx.has_qualified_value("ALC", 0, "A", 1, 1, &["Z01"])
752 }
753
754 /// [36] Wenn DTM+156 (Abrechnungszeitraum Ende) ≥ 01.12.2019
755 // REVIEW: DTM+156 (Abrechnungszeitraum Ende) uses format 303 (CCYYMMDDHHMM), so lexicographic string comparison is correct. German date 01.12.2019 = December 1, 2019 = "201912010000". (medium confidence)
756 fn evaluate_36(&self, ctx: &EvaluationContext) -> ConditionResult {
757 let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "156");
758 match dtm_segs.first() {
759 Some(dtm) => {
760 match dtm
761 .elements
762 .first()
763 .and_then(|e| e.get(1))
764 .map(|s| s.as_str())
765 {
766 Some(value) if !value.is_empty() => {
767 // Format 303 = CCYYMMDDHHMM; lexicographic comparison works for zero-padded timestamps.
768 // 01.12.2019 (DD.MM.YYYY) = December 1, 2019 = "201912010000"
769 ConditionResult::from(value >= "201912010000")
770 }
771 _ => ConditionResult::Unknown,
772 }
773 }
774 None => ConditionResult::False, // segment absent → condition not applicable
775 }
776 }
777
778 /// [41] Wenn IMD++SOR vorhanden
779 fn evaluate_41(&self, ctx: &EvaluationContext) -> ConditionResult {
780 let imds = ctx.find_segments("IMD");
781 ConditionResult::from(imds.iter().any(|s| {
782 s.elements
783 .get(1)
784 .and_then(|e| e.first())
785 .is_some_and(|v| v == "SOR")
786 }))
787 }
788
789 /// [42] Wenn DTM+155 (Abrechnungszeitraum Beginn) ≥ 1.1.2023 0:00 gesetzlicher deutscher Zeit
790 // REVIEW: DTM+155 (Abrechnungszeitraum Beginn) uses format 303 (CCYYMMDDHHMM). January 1, 2023 at 00:00 = "202301010000". Lexicographic comparison is correct for zero-padded CCYYMMDDHHMM. (medium confidence)
791 fn evaluate_42(&self, ctx: &EvaluationContext) -> ConditionResult {
792 let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "155");
793 match dtm_segs.first() {
794 Some(dtm) => {
795 match dtm
796 .elements
797 .first()
798 .and_then(|e| e.get(1))
799 .map(|s| s.as_str())
800 {
801 Some(value) if !value.is_empty() => {
802 // Format 303 = CCYYMMDDHHMM; 1.1.2023 0:00 gesetzlicher Zeit = "202301010000"
803 ConditionResult::from(value >= "202301010000")
804 }
805 _ => ConditionResult::Unknown,
806 }
807 }
808 None => ConditionResult::False, // segment absent → condition not applicable
809 }
810 }
811
812 /// [44] Wenn IMD++ABS nicht vorhanden
813 fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
814 let imds = ctx.find_segments("IMD");
815 ConditionResult::from(!imds.iter().any(|s| {
816 s.elements
817 .get(1)
818 .and_then(|e| e.first())
819 .is_some_and(|v| v == "ABS")
820 }))
821 }
822
823 /// [47] Wenn IMD++Z43 vorhanden
824 fn evaluate_47(&self, ctx: &EvaluationContext) -> ConditionResult {
825 let imds = ctx.find_segments("IMD");
826 ConditionResult::from(imds.iter().any(|s| {
827 s.elements
828 .get(1)
829 .and_then(|e| e.first())
830 .is_some_and(|v| v == "Z43")
831 }))
832 }
833
834 /// [48] Wenn IMD++Z44 vorhanden
835 fn evaluate_48(&self, ctx: &EvaluationContext) -> ConditionResult {
836 let imds = ctx.find_segments("IMD");
837 ConditionResult::from(imds.iter().any(|s| {
838 s.elements
839 .get(1)
840 .and_then(|e| e.first())
841 .is_some_and(|v| v == "Z44")
842 }))
843 }
844
845 /// [49] Wenn IMD++Z43 und IMD+Z44 nicht vorhanden
846 fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
847 let imds = ctx.find_segments("IMD");
848 let has_z43 = imds.iter().any(|s| {
849 s.elements
850 .get(1)
851 .and_then(|e| e.first())
852 .is_some_and(|v| v == "Z43")
853 });
854 let has_z44 = imds.iter().any(|s| {
855 s.elements
856 .get(1)
857 .and_then(|e| e.first())
858 .is_some_and(|v| v == "Z44")
859 });
860 ConditionResult::from(!has_z43 && !has_z44)
861 }
862
863 /// [50] Wenn IMD++SOR nicht vorhanden
864 fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
865 let imds = ctx.find_segments("IMD");
866 ConditionResult::from(!imds.iter().any(|s| {
867 s.elements
868 .get(1)
869 .and_then(|e| e.first())
870 .is_some_and(|v| v == "SOR")
871 }))
872 }
873
874 /// [51] Wenn SG26 DTM+156 (Positionsbezogener Abrechnungszeitraum Ende) ≤ 1.1.2023 0:00 gesetzlicher deutscher Zeit
875 // REVIEW: DTM+156 in SG26 (Positionsbezogener Abrechnungszeitraum Ende) uses format 303 (CCYYMMDDHHMM). January 1, 2023 at 00:00 = "202301010000". Without SG26-scoped group navigation, message-wide search is used; qualifier 156 in the SG26 context is distinct from the message-level 156, but without group navigation this is the best available approximation. (medium confidence)
876 fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
877 let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "156");
878 match dtm_segs.first() {
879 Some(dtm) => {
880 match dtm
881 .elements
882 .first()
883 .and_then(|e| e.get(1))
884 .map(|s| s.as_str())
885 {
886 Some(value) if !value.is_empty() => {
887 // Format 303 = CCYYMMDDHHMM; 1.1.2023 0:00 = "202301010000"
888 ConditionResult::from(value <= "202301010000")
889 }
890 _ => ConditionResult::Unknown,
891 }
892 }
893 None => ConditionResult::False, // segment absent → condition not applicable
894 }
895 }
896
897 /// [53] Wenn IMD++Z45 vorhanden
898 fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
899 ctx.has_qualifier("IMD", 1, "Z45")
900 }
901
902 /// [54] Wenn IMD++ABR/JVR/ZVR vorhanden
903 fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
904 {
905 let segs = ctx.find_segments("IMD");
906 if segs.is_empty() {
907 return ConditionResult::False;
908 }
909 ConditionResult::from(segs.iter().any(|s| {
910 s.elements
911 .get(1)
912 .and_then(|e| e.first())
913 .is_some_and(|v| matches!(v.as_str(), "ABR" | "JVR" | "ZVR"))
914 }))
915 }
916 }
917
918 /// [55] Wenn IMD++ABS vorhanden
919 fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
920 ctx.has_qualifier("IMD", 1, "ABS")
921 }
922
923 /// [56] Wenn DTM+137 (Nachrichtendatum ≥ 1.1.2023 0:00 gesetzlicher deutscher Zeit
924 // REVIEW: DTM+137 Nachrichtendatum >= 2023-01-01. Parses the date value (format 303 = YYYYMMDDHHmm) and compares the first 8 characters against '20230101'. String comparison is valid for ISO date format. (medium confidence)
925 fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
926 {
927 let segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
928 match segs.first() {
929 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
930 Some(val) if val.len() >= 8 => {
931 ConditionResult::from(val[..8].cmp("20230101") != std::cmp::Ordering::Less)
932 }
933 _ => ConditionResult::Unknown,
934 },
935 None => ConditionResult::False, // segment absent → condition not applicable
936 }
937 }
938 }
939
940 /// [57] Wenn DTM+156 (Abrechnungszeitraum Ende) ≤ 1.1.2023 0:00 gesetzlicher deutscher Zeit
941 // REVIEW: DTM+156 (Abrechnungszeitraum Ende) <= 2023-01-01 0:00. Compares first 8 chars of date value against '20230101'. Returns True when the end date is on or before Jan 1 2023. (medium confidence)
942 fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
943 {
944 let segs = ctx.find_segments_with_qualifier("DTM", 0, "156");
945 match segs.first() {
946 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
947 Some(val) if val.len() >= 8 => ConditionResult::from(
948 val[..8].cmp("20230101") != std::cmp::Ordering::Greater,
949 ),
950 _ => ConditionResult::Unknown,
951 },
952 None => ConditionResult::False, // segment absent → condition not applicable
953 }
954 }
955 }
956
957 /// [58] Wenn in dieser SG52 MOA+113 vorhanden
958 fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
959 ctx.any_group_has_qualifier("MOA", 0, "113", &["SG52"])
960 }
961
962 /// [59] Wenn SG26 DTM+155 (Positionsbezogener Abrechnungszeitraum Beginn) ≥ 1.1.2023 0:00 gesetzlicher deutscher Zeit
963 // REVIEW: SG26 DTM+155 (Positionsbezogener Abrechnungszeitraum Beginn) >= 2023-01-01 0:00. Finds DTM with qualifier 155 and compares first 8 chars of the date value against '20230101'. (medium confidence)
964 fn evaluate_59(&self, ctx: &EvaluationContext) -> ConditionResult {
965 {
966 let segs = ctx.find_segments_with_qualifier("DTM", 0, "155");
967 match segs.first() {
968 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
969 Some(val) if val.len() >= 8 => {
970 ConditionResult::from(val[..8].cmp("20230101") != std::cmp::Ordering::Less)
971 }
972 _ => ConditionResult::Unknown,
973 },
974 None => ConditionResult::False, // segment absent → condition not applicable
975 }
976 }
977 }
978
979 /// [60] Wenn DTM+203 DE2379 in demselben Segment mit Wert 303 vorhanden
980 fn evaluate_60(&self, ctx: &EvaluationContext) -> ConditionResult {
981 ctx.has_qualified_value("DTM", 0, "203", 0, 2, &["303"])
982 }
983
984 /// [61] Wenn DTM+203 DE2379 in demselben Segment mit Wert 102 vorhanden
985 fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
986 ctx.has_qualified_value("DTM", 0, "203", 0, 2, &["102"])
987 }
988
989 /// [64] Wenn DTM+156 (Abrechnungszeitraum Ende) ≤ 1.1.2024 0:00 gesetzlicher deutscher Zeit
990 // REVIEW: DTM+156 (Abrechnungszeitraum Ende) <= 2024-01-01 0:00. Same pattern as condition 57 but with threshold '20240101'. (medium confidence)
991 fn evaluate_64(&self, ctx: &EvaluationContext) -> ConditionResult {
992 {
993 let segs = ctx.find_segments_with_qualifier("DTM", 0, "156");
994 match segs.first() {
995 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
996 Some(val) if val.len() >= 8 => ConditionResult::from(
997 val[..8].cmp("20240101") != std::cmp::Ordering::Greater,
998 ),
999 _ => ConditionResult::Unknown,
1000 },
1001 None => ConditionResult::False, // segment absent → condition not applicable
1002 }
1003 }
1004 }
1005
1006 /// [65] Wenn SG26 DTM+155 (Positionsbezogener Abrechnungszeitraum Beginn) ≥ 1.1.2024 0:00 gesetzlicher deutscher Zeit
1007 // REVIEW: SG26 DTM+155 (Positionsbezogener Abrechnungszeitraum Beginn) >= 2024-01-01 0:00. Same pattern as condition 59 but with threshold '20240101'. (medium confidence)
1008 fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
1009 {
1010 let segs = ctx.find_segments_with_qualifier("DTM", 0, "155");
1011 match segs.first() {
1012 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1013 Some(val) if val.len() >= 8 => {
1014 ConditionResult::from(val[..8].cmp("20240101") != std::cmp::Ordering::Less)
1015 }
1016 _ => ConditionResult::Unknown,
1017 },
1018 None => ConditionResult::False, // segment absent → condition not applicable
1019 }
1020 }
1021 }
1022
1023 /// [66] Wenn IMD++KON vorhanden
1024 fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
1025 ctx.has_qualifier("IMD", 1, "KON")
1026 }
1027
1028 /// [70] Wenn IMD++Z45 nicht vorhanden
1029 fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
1030 let imd_segments = ctx.find_segments("IMD");
1031 let found = imd_segments.iter().any(|s| {
1032 s.elements
1033 .get(1)
1034 .and_then(|e| e.first())
1035 .is_some_and(|v| v == "Z45")
1036 });
1037 ConditionResult::from(!found)
1038 }
1039
1040 /// [71] Wenn DTM+155 (Abrechnungszeitraum Beginn) ≥ 1.1.2024 0:00 gesetzlicher deutscher Zeit
1041 // REVIEW: DTM+155 means elements[0][0]=="155". Value is at elements[0][1] in 303 format (YYYYMMDDhhmm). String comparison works for chronological ordering. Timezone nuance (German legal time) ignored — medium confidence. (medium confidence)
1042 fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
1043 let dtm_segments = ctx.find_segments_with_qualifier("DTM", 0, "155");
1044 match dtm_segments.first() {
1045 Some(dtm) => {
1046 match dtm
1047 .elements
1048 .first()
1049 .and_then(|e| e.get(1))
1050 .map(|s| s.as_str())
1051 {
1052 Some(value) if !value.is_empty() => {
1053 // 303 format: YYYYMMDDhhmm — lexicographic comparison is chronological for zero-padded strings
1054 ConditionResult::from(value >= "202401010000")
1055 }
1056 _ => ConditionResult::Unknown,
1057 }
1058 }
1059 None => ConditionResult::False, // segment absent → condition not applicable
1060 }
1061 }
1062
1063 /// [72] Wenn MP-ID in NAD+MR in der Rolle LF
1064 /// EXTERNAL: Requires context from outside the message.
1065 // REVIEW: Whether the MP-ID in NAD+MR has the market role LF (Lieferant) cannot be determined from the EDIFACT message alone — it requires a business registry lookup. The NAD+MR segment only provides the party identifier, not the assigned market role. (medium confidence)
1066 fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
1067 ctx.external.evaluate("recipient_is_lf")
1068 }
1069
1070 /// [73] Wenn IMD++MSB vorhanden
1071 fn evaluate_73(&self, ctx: &EvaluationContext) -> ConditionResult {
1072 let imd_segments = ctx.find_segments("IMD");
1073 let found = imd_segments.iter().any(|s| {
1074 s.elements
1075 .get(1)
1076 .and_then(|e| e.first())
1077 .is_some_and(|v| v == "MSB")
1078 });
1079 ConditionResult::from(found)
1080 }
1081
1082 /// [74] wenn im DE3155 in demselben COM der Code EM vorhanden ist
1083 fn evaluate_74(&self, ctx: &EvaluationContext) -> ConditionResult {
1084 let com_segments = ctx.find_segments("COM");
1085 let found = com_segments.iter().any(|s| {
1086 s.elements
1087 .first()
1088 .and_then(|e| e.get(1))
1089 .is_some_and(|v| v == "EM")
1090 });
1091 ConditionResult::from(found)
1092 }
1093
1094 /// [75] wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
1095 fn evaluate_75(&self, ctx: &EvaluationContext) -> ConditionResult {
1096 let com_segments = ctx.find_segments("COM");
1097 let found = com_segments.iter().any(|s| {
1098 s.elements
1099 .first()
1100 .and_then(|e| e.get(1))
1101 .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
1102 });
1103 ConditionResult::from(found)
1104 }
1105
1106 /// [76] Wenn MP-ID in NAD+MR in der Rolle NB
1107 /// EXTERNAL: Requires context from outside the message.
1108 // REVIEW: Whether the MP-ID in NAD+MR has the market role NB (Netzbetreiber) cannot be determined from the EDIFACT message alone — it requires a business registry lookup. The NAD+MR segment only provides the party identifier, not the assigned market role. (medium confidence)
1109 fn evaluate_76(&self, ctx: &EvaluationContext) -> ConditionResult {
1110 ctx.external.evaluate("recipient_is_nb")
1111 }
1112
1113 /// [77] Wenn SG26 DTM+156 (Positionsbezogener Abrechnungszeitraum Ende) ≤ 1.1.2024 0:00 gesetzlicher deutscher Zeit
1114 // REVIEW: SG26 DTM+156 means elements[0][0]=="156". Value at elements[0][1] in 303 format. Compare <= "202401010000" (Jan 1 2024 00:00). Medium confidence due to timezone nuance. (medium confidence)
1115 fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
1116 let dtm_segments = ctx.find_segments_with_qualifier("DTM", 0, "156");
1117 match dtm_segments.first() {
1118 Some(dtm) => {
1119 match dtm
1120 .elements
1121 .first()
1122 .and_then(|e| e.get(1))
1123 .map(|s| s.as_str())
1124 {
1125 Some(value) if !value.is_empty() => {
1126 // 303 format: YYYYMMDDhhmm — lexicographic comparison is chronological
1127 ConditionResult::from(value <= "202401010000")
1128 }
1129 _ => ConditionResult::Unknown,
1130 }
1131 }
1132 None => ConditionResult::False, // segment absent → condition not applicable
1133 }
1134 }
1135
1136 /// [78] Wenn GEI+Z08 nicht vorhanden
1137 // REVIEW: GEI schema shows elements[0] qualifier is always Z01, while the specification code is at elements[1][0] (DE7365) with codes Z01-Z06 +3 more. 'GEI+Z08' in AHB shorthand refers to elements[1][0]=="Z08". Absence check returns True when not found. (medium confidence)
1138 fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
1139 let found = ctx.find_segments("GEI").iter().any(|s| {
1140 s.elements
1141 .get(1)
1142 .and_then(|e| e.first())
1143 .is_some_and(|v| v == "Z08")
1144 });
1145 ConditionResult::from(!found)
1146 }
1147
1148 /// [79] Wenn GEI+Z09 nicht vorhanden
1149 // REVIEW: Same pattern as condition 78. 'GEI+Z09' in AHB shorthand refers to elements[1][0]=="Z09". (medium confidence)
1150 fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
1151 let found = ctx.find_segments("GEI").iter().any(|s| {
1152 s.elements
1153 .get(1)
1154 .and_then(|e| e.first())
1155 .is_some_and(|v| v == "Z09")
1156 });
1157 ConditionResult::from(!found)
1158 }
1159
1160 /// [80] Wenn GEI+Z01 nicht vorhanden
1161 // REVIEW: 'GEI+Z01' refers to elements[1][0]=="Z01" (the specification code, not the fixed qualifier at elements[0]). Absence check. (medium confidence)
1162 fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
1163 let found = ctx.find_segments("GEI").iter().any(|s| {
1164 s.elements
1165 .get(1)
1166 .and_then(|e| e.first())
1167 .is_some_and(|v| v == "Z01")
1168 });
1169 ConditionResult::from(!found)
1170 }
1171
1172 /// [81] Wenn GEI+Z04 nicht vorhanden
1173 // REVIEW: Same pattern as condition 78. 'GEI+Z04' refers to elements[1][0]=="Z04". (medium confidence)
1174 fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
1175 let found = ctx.find_segments("GEI").iter().any(|s| {
1176 s.elements
1177 .get(1)
1178 .and_then(|e| e.first())
1179 .is_some_and(|v| v == "Z04")
1180 });
1181 ConditionResult::from(!found)
1182 }
1183
1184 /// [82] Wenn GEI+Z05 nicht vorhanden
1185 // REVIEW: Same pattern as condition 78. 'GEI+Z05' refers to elements[1][0]=="Z05". (medium confidence)
1186 fn evaluate_82(&self, ctx: &EvaluationContext) -> ConditionResult {
1187 let found = ctx.find_segments("GEI").iter().any(|s| {
1188 s.elements
1189 .get(1)
1190 .and_then(|e| e.first())
1191 .is_some_and(|v| v == "Z05")
1192 });
1193 ConditionResult::from(!found)
1194 }
1195
1196 /// [84] Wenn in dieser SG39 ALC+C+:Z02 / Z03 / Z04 vorhanden
1197 fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
1198 ctx.any_group_has_qualified_value("ALC", 0, "C", 1, 1, &["Z02", "Z03", "Z04"], &["SG39"])
1199 }
1200
1201 /// [85] Wenn in diesem Segment DE6411 = DAY
1202 // REVIEW: DE6411 is the measurement unit code, appearing in QTY segments as C186 composite: elements[0][0]=DE6063 (qualifier), elements[0][1]=DE6060 (quantity), elements[0][2]=DE6411 (unit code). 'DAY' is the unit code for daily quantities. Medium confidence as the specific segment context ('in diesem Segment') may require group scoping. (medium confidence)
1203 fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
1204 let qty_segments = ctx.find_segments("QTY");
1205 let found = qty_segments.iter().any(|s| {
1206 s.elements
1207 .first()
1208 .and_then(|e| e.get(2))
1209 .is_some_and(|v| v == "DAY")
1210 });
1211 ConditionResult::from(found)
1212 }
1213
1214 /// [86] Wenn in diesem Segment DE6411 = MON/ANN
1215 // REVIEW: DE6411 is the unit of measure code in QTY segments at elements[0][2] (C186 component 2). 'In diesem Segment' means within the current QTY context. Check message-wide for QTY with MON or ANN unit code. (medium confidence)
1216 fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
1217 let qty_segments = ctx.find_segments("QTY");
1218 ConditionResult::from(qty_segments.iter().any(|s| {
1219 s.elements
1220 .first()
1221 .and_then(|e| e.get(2))
1222 .map(|v| v == "MON" || v == "ANN")
1223 .unwrap_or(false)
1224 }))
1225 }
1226
1227 /// [88] Wenn DTM+155 (Abrechnungszeitraum Beginn) ≥ 1.1.2026 0:00 gesetzlicher deutscher Zeit
1228 fn evaluate_88(&self, ctx: &EvaluationContext) -> ConditionResult {
1229 let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "155");
1230 match dtm_segs.first() {
1231 Some(dtm) => {
1232 match dtm
1233 .elements
1234 .first()
1235 .and_then(|e| e.get(1))
1236 .map(|s| s.as_str())
1237 {
1238 Some(value) if value.len() >= 8 => {
1239 let date_part = &value[..8];
1240 ConditionResult::from(date_part >= "20260101")
1241 }
1242 _ => ConditionResult::Unknown,
1243 }
1244 }
1245 None => ConditionResult::False, // segment absent → condition not applicable
1246 }
1247 }
1248
1249 /// [492] Wenn MP-ID in NAD+MR (Nachrichtenempfänger) aus Sparte Strom
1250 /// EXTERNAL: Requires context from outside the message.
1251 // REVIEW: Checks whether the NAD+MR (message recipient) MP-ID belongs to the electricity (Strom) sector. The NAD+MR element structure is known (elements[0]=MR, elements[1][0]=MP-ID), but sector membership cannot be determined from the EDIFACT message content alone — it requires an external market participant registry. Delegated to external provider. (medium confidence)
1252 fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1253 ctx.external.evaluate("recipient_is_strom")
1254 }
1255
1256 /// [493] Wenn MP-ID in NAD+MR (Nachrichtenempfänger) aus Sparte Gas
1257 /// EXTERNAL: Requires context from outside the message.
1258 // REVIEW: Checks whether the NAD+MR (message recipient) MP-ID belongs to the gas (Gas) sector. Same reasoning as 492 — sector membership cannot be determined from the EDIFACT message alone; requires external market participant registry. Delegated to external provider. (medium confidence)
1259 fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1260 ctx.external.evaluate("recipient_is_gas")
1261 }
1262
1263 /// [501] Hinweis: Dokumentennummer der ORDERS
1264 fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1265 // Hinweis: Dokumentennummer der ORDERS — informational note, always applies
1266 ConditionResult::True
1267 }
1268
1269 /// [502] Hinweis: Dokumentennummer der Bilanzierungs-MSCONS
1270 fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1271 // Hinweis: Dokumentennummer der Bilanzierungs-MSCONS — informational note, always applies
1272 ConditionResult::True
1273 }
1274
1275 /// [503] Hinweis: Ein positiver Betrag ist eine Forderung des Rechnungsstellers.
1276 fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
1277 // Hinweis: Ein positiver Betrag ist eine Forderung des Rechnungsstellers — informational note, always applies
1278 ConditionResult::True
1279 }
1280
1281 /// [504] Hinweis: Ein positiver Betrag ist eine Forderung des Rechnungsempfängers.
1282 fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1283 // Hinweis: Ein positiver Betrag ist eine Forderung des Rechnungsempfängers — informational note, always applies
1284 ConditionResult::True
1285 }
1286
1287 /// [507] Hinweis: Dokumentennummer der SSQNOT
1288 fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
1289 // Hinweis: Dokumentennummer der SSQNOT — informational note, always applies
1290 ConditionResult::True
1291 }
1292
1293 /// [508] Hinweis: Dokumentennummer der QUOTES
1294 fn evaluate_508(&self, _ctx: &EvaluationContext) -> ConditionResult {
1295 // Hinweis: Dokumentennummer der QUOTES — informational note, always applies
1296 ConditionResult::True
1297 }
1298
1299 /// [509] Hinweis: Verwendung der ID der Marktlokation
1300 fn evaluate_509(&self, _ctx: &EvaluationContext) -> ConditionResult {
1301 // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1302 ConditionResult::True
1303 }
1304
1305 /// [510] Hinweis: Verwendung der ID der Messlokation
1306 fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
1307 // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1308 ConditionResult::True
1309 }
1310
1311 /// [512] Hinweis: Hier ist entweder der Betrag aus MOA+203 oder der um den gültigen Steuerbetrag erhöhte Betrag aus MOA+203 anzugeben.
1312 fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
1313 // Hinweis: Hier ist entweder der Betrag aus MOA+203 oder der um den gültigen Steuerbetrag erhöhte Betrag aus MOA+203 anzugeben — informational note, always applies
1314 ConditionResult::True
1315 }
1316
1317 /// [513] Hinweis: Hier ist das Ergebnis der Multiplikation von MOA+25 mit PCD+3 anzugeben.
1318 fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
1319 // Hinweis: Hier ist das Ergebnis der Multiplikation von MOA+25 mit PCD+3 anzugeben — informational note, always applies
1320 ConditionResult::True
1321 }
1322
1323 /// [514] Hinweis: Dokumentennummer der Lieferschein-MSCONS
1324 fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
1325 // Hinweis: Dokumentennummer der Lieferschein-MSCONS — informational note, always applies
1326 ConditionResult::True
1327 }
1328
1329 /// [515] Hinweis: BGM DE1004 aus INVOIC-Nachricht, die storniert werden soll
1330 fn evaluate_515(&self, _ctx: &EvaluationContext) -> ConditionResult {
1331 // Hinweis: BGM DE1004 aus INVOIC-Nachricht, die storniert werden soll — informational note, always applies
1332 ConditionResult::True
1333 }
1334
1335 /// [516] Hinweis: Ein Lieferschein zu einer Rechnung ist für alle Abrechnungszeiträume, die erstmals nach dem 1.12.2019 abgerechnet werden und für alle Abrechnungszeiträume, für die sich nach dem 1.12....
1336 fn evaluate_516(&self, _ctx: &EvaluationContext) -> ConditionResult {
1337 // Hinweis: Ein Lieferschein zu einer Rechnung ist für alle Abrechnungszeiträume, die erstmals nach dem 1.12.2019 abgerechnet werden und für alle Abrechnungszeiträume, für die sich nach dem 1.12.2019 geänderte Mengen oder Leistungswerte ergeben, nötig.
1338 ConditionResult::True
1339 }
1340
1341 /// [517] Hinweis: Dokumentennummer der PDF-Kapazitätsrechnung
1342 fn evaluate_517(&self, _ctx: &EvaluationContext) -> ConditionResult {
1343 // Hinweis: Dokumentennummer der PDF-Kapazitätsrechnung — informational annotation, always applies
1344 ConditionResult::True
1345 }
1346
1347 /// [518] Hinweis: Im Fall der Stornierung des Auftrags der Unterbrechung: Der Tag an dem der NB die Stornierung empfangen hat Bei erfolgreicher Sperrung: Tag der durchgeführten Sperrung Bei erfolgloser Sp...
1348 fn evaluate_518(&self, _ctx: &EvaluationContext) -> ConditionResult {
1349 // Hinweis: Date semantics for cancellation/blocking scenarios — informational annotation, always applies
1350 ConditionResult::True
1351 }
1352
1353 /// [519] Hinweis: Stornierte Abschlagsrechnungen sind nicht aufzuführen
1354 fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
1355 // Hinweis: Stornierte Abschlagsrechnungen sind nicht aufzuführen — informational annotation, always applies
1356 ConditionResult::True
1357 }
1358
1359 /// [520] Hinweis: Es sind nur die Artikel-IDs aus dem Preisblatt erlaubt
1360 fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
1361 // Hinweis: Es sind nur die Artikel-IDs aus dem Preisblatt erlaubt — informational annotation, always applies
1362 ConditionResult::True
1363 }
1364
1365 /// [521] Hinweis: BGM DE1004 aus der INVOIC-Nachricht, für die Verzugskosten erhoben werden
1366 fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
1367 // Hinweis: BGM DE1004 aus der INVOIC-Nachricht, für die Verzugskosten erhoben werden — informational note, always applies
1368 ConditionResult::True
1369 }
1370
1371 /// [522] Hinweis: Verwendung der ID der Netzlokation
1372 fn evaluate_522(&self, _ctx: &EvaluationContext) -> ConditionResult {
1373 // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
1374 ConditionResult::True
1375 }
1376
1377 /// [523] Hinweis: Verwendung der ID der Steuerbaren Ressource
1378 fn evaluate_523(&self, _ctx: &EvaluationContext) -> ConditionResult {
1379 // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
1380 ConditionResult::True
1381 }
1382
1383 /// [524] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
1384 fn evaluate_524(&self, _ctx: &EvaluationContext) -> ConditionResult {
1385 // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
1386 ConditionResult::True
1387 }
1388
1389 /// [525] Hinweis: Verwendung, sofern Netzentgelte geringer als die Pauschale Netzentgeltreduzierung
1390 fn evaluate_525(&self, _ctx: &EvaluationContext) -> ConditionResult {
1391 // Hinweis: Verwendung, sofern Netzentgelte geringer als die Pauschale Netzentgeltreduzierung — informational note, always applies
1392 ConditionResult::True
1393 }
1394
1395 /// [526] Hinweis: Der hier angegebene Geldbetrag muss mit dem identisch sein, der in SG50 MOA+77 im DE5004 der Abschlagsrechnung steht, die die Rechnungsnummer hat, die in dieser SG50 in SG51-RFF+AFL in DE1...
1396 fn evaluate_526(&self, _ctx: &EvaluationContext) -> ConditionResult {
1397 // Hinweis: Der hier angegebene Geldbetrag muss mit dem identisch sein, der in SG50 MOA+77
1398 // im DE5004 der Abschlagsrechnung steht, die die Rechnungsnummer hat, die in dieser SG50
1399 // in SG51-RFF+AFL in DE1154 genannt ist.
1400 // Informational note — always applies
1401 ConditionResult::True
1402 }
1403
1404 /// [902] Format: Möglicher Wert: ≥ 0
1405 // REVIEW: Format condition requiring value >= 0, applied to MOA amount (DE5004 at elements[0][1]). Medium confidence because exact segment/group context is inferred from INVOIC structure — the full AHB table row would clarify which specific MOA qualifier this applies to. (medium confidence)
1406 fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
1407 // Format: Möglicher Wert: ≥ 0
1408 // Applied to MOA monetary amounts in INVOIC
1409 let segs = ctx.find_segments("MOA");
1410 match segs
1411 .first()
1412 .and_then(|s| s.elements.first())
1413 .and_then(|e| e.get(1))
1414 {
1415 Some(val) => validate_numeric(val, ">=", 0.0),
1416 None => ConditionResult::False, // segment absent → condition not applicable
1417 }
1418 }
1419
1420 /// [906] Format: max. 3 Nachkommastellen
1421 // REVIEW: Format condition requiring at most 3 decimal places. Most likely applies to MOA DE5004 (Geldbetrag) in INVOIC. Medium confidence — the exact MOA qualifier or group (SG27/SG42/SG50/SG52) depends on the AHB table row context not included in the condition description alone. (medium confidence)
1422 fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
1423 // Format: max. 3 Nachkommastellen
1424 // Applied to MOA monetary amounts in INVOIC
1425 let segs = ctx.find_segments("MOA");
1426 match segs
1427 .first()
1428 .and_then(|s| s.elements.first())
1429 .and_then(|e| e.get(1))
1430 {
1431 Some(val) => validate_max_decimal_places(val, 3),
1432 None => ConditionResult::False, // segment absent → condition not applicable
1433 }
1434 }
1435
1436 /// [908] Format: Mögliche Werte: 1 bis n
1437 // REVIEW: Format condition requiring value in range 1..n (strictly positive). Applied to QTY quantity values in INVOIC (DE6060 at elements[0][1]). Medium confidence — the exact segment context depends on the full AHB table row. (medium confidence)
1438 fn evaluate_908(&self, ctx: &EvaluationContext) -> ConditionResult {
1439 // Format: Mögliche Werte: 1 bis n — value must be >= 1 (positive integer range)
1440 // Applied to QTY quantities in INVOIC
1441 let segs = ctx.find_segments("QTY");
1442 match segs
1443 .first()
1444 .and_then(|s| s.elements.first())
1445 .and_then(|e| e.get(1))
1446 {
1447 Some(val) => validate_numeric(val, ">=", 1.0),
1448 None => ConditionResult::False, // segment absent → condition not applicable
1449 }
1450 }
1451
1452 /// [910] Format: Möglicher Wert: < 0 oder ≥ 0
1453 // REVIEW: Format condition accepting any numeric value (< 0 OR >= 0 is universally true). This documents that the field accepts negative values (unlike condition 902 which restricts to >= 0). Validates the value is a parseable number. Medium confidence on the exact segment — likely a MOA that can be a credit/deduction amount. (medium confidence)
1454 fn evaluate_910(&self, ctx: &EvaluationContext) -> ConditionResult {
1455 // Format: Möglicher Wert: < 0 oder ≥ 0 — any numeric value is valid (no sign constraint)
1456 // Applied to MOA amounts that may be negative (e.g., credits, deductions)
1457 let segs = ctx.find_segments("MOA");
1458 match segs
1459 .first()
1460 .and_then(|s| s.elements.first())
1461 .and_then(|e| e.get(1))
1462 {
1463 Some(val) if !val.is_empty() => {
1464 // < 0 or >= 0 covers all real numbers — validate it is a parseable number
1465 match val.trim_start_matches('-').parse::<f64>() {
1466 Ok(_) => ConditionResult::True,
1467 Err(_) => ConditionResult::False,
1468 }
1469 }
1470 _ => ConditionResult::Unknown,
1471 }
1472 }
1473
1474 /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend und fortlaufend aufsteigend
1475 // REVIEW: Sequential numbering validation (1 to n, ascending) — only the >= 1 integer constraint can be checked on a single value. Full sequential ordering cannot be verified without stateful iteration across all instances. Applies to LIN line item numbers in INVOIC SG26. Medium confidence due to segment assumption and inability to verify sequential ordering in isolation. (medium confidence)
1476 fn evaluate_911(&self, ctx: &EvaluationContext) -> ConditionResult {
1477 // Format: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend und fortlaufend aufsteigend
1478 // Validates that the value is a positive integer >= 1 (sequential enforcement requires external state)
1479 let segs = ctx.find_segments("LIN");
1480 match segs
1481 .first()
1482 .and_then(|s| s.elements.first())
1483 .and_then(|e| e.first())
1484 {
1485 Some(val) => match val.parse::<i64>() {
1486 Ok(n) if n >= 1 => ConditionResult::True,
1487 Ok(_) => ConditionResult::False,
1488 Err(_) => ConditionResult::False,
1489 },
1490 None => ConditionResult::False, // segment absent → condition not applicable
1491 }
1492 }
1493
1494 /// [912] Format: max. 6 Nachkommastellen
1495 fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
1496 // Format: max. 6 Nachkommastellen
1497 let segs = ctx.find_segments("QTY");
1498 match segs
1499 .first()
1500 .and_then(|s| s.elements.first())
1501 .and_then(|e| e.get(1))
1502 {
1503 Some(val) => validate_max_decimal_places(val, 6),
1504 None => ConditionResult::False, // segment absent → condition not applicable
1505 }
1506 }
1507
1508 /// [914] Format: Möglicher Wert: > 0
1509 fn evaluate_914(&self, ctx: &EvaluationContext) -> ConditionResult {
1510 // Format: Möglicher Wert: > 0
1511 let segs = ctx.find_segments("QTY");
1512 match segs
1513 .first()
1514 .and_then(|s| s.elements.first())
1515 .and_then(|e| e.get(1))
1516 {
1517 Some(val) => validate_numeric(val, ">", 0.0),
1518 None => ConditionResult::False, // segment absent → condition not applicable
1519 }
1520 }
1521
1522 /// [927] Format: Möglicher Wert: -1
1523 // REVIEW: Value must be exactly -1. Used for special indicator values (e.g., a reversal or placeholder quantity). validate_numeric with == operator handles exact match. Medium confidence due to segment assumption — the -1 constraint is unusual and the exact segment context (QTY vs MOA) depends on which AHB data element references this condition. (medium confidence)
1524 fn evaluate_927(&self, ctx: &EvaluationContext) -> ConditionResult {
1525 // Format: Möglicher Wert: -1
1526 let segs = ctx.find_segments("QTY");
1527 match segs
1528 .first()
1529 .and_then(|s| s.elements.first())
1530 .and_then(|e| e.get(1))
1531 {
1532 Some(val) => validate_numeric(val, "==", -1.0),
1533 None => ConditionResult::False, // segment absent → condition not applicable
1534 }
1535 }
1536
1537 /// [930] Format: max. 2 Nachkommastellen
1538 fn evaluate_930(&self, ctx: &EvaluationContext) -> ConditionResult {
1539 // Format: max. 2 Nachkommastellen
1540 let segs = ctx.find_segments("QTY");
1541 match segs
1542 .first()
1543 .and_then(|s| s.elements.first())
1544 .and_then(|e| e.get(1))
1545 {
1546 Some(val) => validate_max_decimal_places(val, 2),
1547 None => ConditionResult::False, // segment absent → condition not applicable
1548 }
1549 }
1550
1551 /// [931] Format: ZZZ = +00
1552 fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1553 let dtm_segs = ctx.find_segments("DTM");
1554 match dtm_segs
1555 .first()
1556 .and_then(|s| s.elements.first())
1557 .and_then(|e| e.get(1))
1558 {
1559 Some(val) => validate_timezone_utc(val),
1560 None => ConditionResult::False, // segment absent → condition not applicable
1561 }
1562 }
1563
1564 /// [932] Format: HHMM = 2200
1565 fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
1566 let dtm_segs = ctx.find_segments("DTM");
1567 match dtm_segs
1568 .first()
1569 .and_then(|s| s.elements.first())
1570 .and_then(|e| e.get(1))
1571 {
1572 Some(val) => validate_hhmm_equals(val, "2200"),
1573 None => ConditionResult::False, // segment absent → condition not applicable
1574 }
1575 }
1576
1577 /// [933] Format: HHMM = 2300
1578 fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
1579 let dtm_segs = ctx.find_segments("DTM");
1580 match dtm_segs
1581 .first()
1582 .and_then(|s| s.elements.first())
1583 .and_then(|e| e.get(1))
1584 {
1585 Some(val) => validate_hhmm_equals(val, "2300"),
1586 None => ConditionResult::False, // segment absent → condition not applicable
1587 }
1588 }
1589
1590 /// [934] Format: HHMM = 0400
1591 fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
1592 let dtm_segs = ctx.find_segments("DTM");
1593 match dtm_segs
1594 .first()
1595 .and_then(|s| s.elements.first())
1596 .and_then(|e| e.get(1))
1597 {
1598 Some(val) => validate_hhmm_equals(val, "0400"),
1599 None => ConditionResult::False, // segment absent → condition not applicable
1600 }
1601 }
1602
1603 /// [935] Format: HHMM = 0500
1604 fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
1605 let dtm_segs = ctx.find_segments("DTM");
1606 match dtm_segs
1607 .first()
1608 .and_then(|s| s.elements.first())
1609 .and_then(|e| e.get(1))
1610 {
1611 Some(val) => validate_hhmm_equals(val, "0500"),
1612 None => ConditionResult::False, // segment absent → condition not applicable
1613 }
1614 }
1615
1616 /// [937] Format: keine Nachkommastelle
1617 fn evaluate_937(&self, ctx: &EvaluationContext) -> ConditionResult {
1618 let segs = ctx.find_segments("QTY");
1619 match segs
1620 .first()
1621 .and_then(|s| s.elements.first())
1622 .and_then(|e| e.get(1))
1623 {
1624 Some(val) => validate_max_decimal_places(val, 0),
1625 None => ConditionResult::False, // segment absent → condition not applicable
1626 }
1627 }
1628
1629 /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
1630 fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
1631 let segs = ctx.find_segments("COM");
1632 match segs
1633 .first()
1634 .and_then(|s| s.elements.first())
1635 .and_then(|e| e.first())
1636 {
1637 Some(val) => validate_email(val),
1638 None => ConditionResult::False, // segment absent → condition not applicable
1639 }
1640 }
1641
1642 /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
1643 fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
1644 let segs = ctx.find_segments("COM");
1645 match segs
1646 .first()
1647 .and_then(|s| s.elements.first())
1648 .and_then(|e| e.first())
1649 {
1650 Some(val) => validate_phone(val),
1651 None => ConditionResult::False, // segment absent → condition not applicable
1652 }
1653 }
1654
1655 /// [946] Format: max. 11 Nachkommastellen
1656 fn evaluate_946(&self, ctx: &EvaluationContext) -> ConditionResult {
1657 let segs = ctx.find_segments("QTY");
1658 match segs
1659 .first()
1660 .and_then(|s| s.elements.first())
1661 .and_then(|e| e.get(1))
1662 {
1663 Some(val) => validate_max_decimal_places(val, 11),
1664 None => ConditionResult::False, // segment absent → condition not applicable
1665 }
1666 }
1667
1668 /// [950] Format: Marktlokations-ID
1669 fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
1670 let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z16");
1671 match segs
1672 .first()
1673 .and_then(|s| s.elements.get(1))
1674 .and_then(|e| e.first())
1675 {
1676 Some(val) => validate_malo_id(val),
1677 None => ConditionResult::False, // segment absent → condition not applicable
1678 }
1679 }
1680
1681 /// [951] Format: Zählpunktbezeichnung
1682 fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
1683 let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z17");
1684 match segs
1685 .first()
1686 .and_then(|s| s.elements.get(1))
1687 .and_then(|e| e.first())
1688 {
1689 Some(val) => validate_zahlpunkt(val),
1690 None => ConditionResult::False, // segment absent → condition not applicable
1691 }
1692 }
1693
1694 /// [960] Format: Netzlokations-ID
1695 fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
1696 let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z18");
1697 match segs
1698 .first()
1699 .and_then(|s| s.elements.get(1))
1700 .and_then(|e| e.first())
1701 {
1702 Some(val) => validate_malo_id(val),
1703 None => ConditionResult::False, // segment absent → condition not applicable
1704 }
1705 }
1706
1707 /// [961] Format: SR-ID
1708 fn evaluate_961(&self, ctx: &EvaluationContext) -> ConditionResult {
1709 let segs = ctx.find_segments_with_qualifier("LOC", 0, "Z19");
1710 match segs
1711 .first()
1712 .and_then(|s| s.elements.get(1))
1713 .and_then(|e| e.first())
1714 {
1715 Some(val) => validate_malo_id(val),
1716 None => ConditionResult::False, // segment absent → condition not applicable
1717 }
1718 }
1719}