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