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