automapper_validation/generated/fv2504/utilmd_gas_conditions_fv2504.rs
1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2504/UTILMD_AHB_Gas_1_0a_außerordentliche_20240726.xml
4// Generated: 2026-03-12T10:28:10Z
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 UTILMD_Gas FV2504.
17pub struct UtilmdGasConditionEvaluatorFV2504 {
18 // External condition IDs that require runtime context.
19 external_conditions: std::collections::HashSet<u32>,
20}
21
22impl Default for UtilmdGasConditionEvaluatorFV2504 {
23 fn default() -> Self {
24 let mut external_conditions = std::collections::HashSet::new();
25 external_conditions.insert(1);
26 external_conditions.insert(4);
27 external_conditions.insert(5);
28 external_conditions.insert(14);
29 external_conditions.insert(17);
30 external_conditions.insert(29);
31 external_conditions.insert(39);
32 external_conditions.insert(51);
33 external_conditions.insert(65);
34 external_conditions.insert(92);
35 external_conditions.insert(98);
36 external_conditions.insert(108);
37 external_conditions.insert(127);
38 external_conditions.insert(129);
39 external_conditions.insert(133);
40 external_conditions.insert(147);
41 external_conditions.insert(165);
42 external_conditions.insert(166);
43 external_conditions.insert(219);
44 external_conditions.insert(241);
45 external_conditions.insert(268);
46 external_conditions.insert(283);
47 external_conditions.insert(315);
48 external_conditions.insert(324);
49 external_conditions.insert(336);
50 external_conditions.insert(427);
51 Self {
52 external_conditions,
53 }
54 }
55}
56
57impl ConditionEvaluator for UtilmdGasConditionEvaluatorFV2504 {
58 fn message_type(&self) -> &str {
59 "UTILMD_Gas"
60 }
61
62 fn format_version(&self) -> &str {
63 "FV2504"
64 }
65
66 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
67 match condition {
68 1 => self.evaluate_1(ctx),
69 4 => self.evaluate_4(ctx),
70 5 => self.evaluate_5(ctx),
71 7 => self.evaluate_7(ctx),
72 9 => self.evaluate_9(ctx),
73 10 => self.evaluate_10(ctx),
74 11 => self.evaluate_11(ctx),
75 12 => self.evaluate_12(ctx),
76 13 => self.evaluate_13(ctx),
77 14 => self.evaluate_14(ctx),
78 15 => self.evaluate_15(ctx),
79 16 => self.evaluate_16(ctx),
80 17 => self.evaluate_17(ctx),
81 18 => self.evaluate_18(ctx),
82 19 => self.evaluate_19(ctx),
83 24 => self.evaluate_24(ctx),
84 25 => self.evaluate_25(ctx),
85 26 => self.evaluate_26(ctx),
86 28 => self.evaluate_28(ctx),
87 29 => self.evaluate_29(ctx),
88 32 => self.evaluate_32(ctx),
89 33 => self.evaluate_33(ctx),
90 35 => self.evaluate_35(ctx),
91 36 => self.evaluate_36(ctx),
92 37 => self.evaluate_37(ctx),
93 39 => self.evaluate_39(ctx),
94 46 => self.evaluate_46(ctx),
95 47 => self.evaluate_47(ctx),
96 48 => self.evaluate_48(ctx),
97 51 => self.evaluate_51(ctx),
98 58 => self.evaluate_58(ctx),
99 64 => self.evaluate_64(ctx),
100 65 => self.evaluate_65(ctx),
101 66 => self.evaluate_66(ctx),
102 68 => self.evaluate_68(ctx),
103 69 => self.evaluate_69(ctx),
104 70 => self.evaluate_70(ctx),
105 77 => self.evaluate_77(ctx),
106 78 => self.evaluate_78(ctx),
107 81 => self.evaluate_81(ctx),
108 84 => self.evaluate_84(ctx),
109 92 => self.evaluate_92(ctx),
110 98 => self.evaluate_98(ctx),
111 106 => self.evaluate_106(ctx),
112 108 => self.evaluate_108(ctx),
113 123 => self.evaluate_123(ctx),
114 127 => self.evaluate_127(ctx),
115 128 => self.evaluate_128(ctx),
116 129 => self.evaluate_129(ctx),
117 130 => self.evaluate_130(ctx),
118 133 => self.evaluate_133(ctx),
119 137 => self.evaluate_137(ctx),
120 138 => self.evaluate_138(ctx),
121 147 => self.evaluate_147(ctx),
122 165 => self.evaluate_165(ctx),
123 166 => self.evaluate_166(ctx),
124 200 => self.evaluate_200(ctx),
125 202 => self.evaluate_202(ctx),
126 203 => self.evaluate_203(ctx),
127 205 => self.evaluate_205(ctx),
128 209 => self.evaluate_209(ctx),
129 212 => self.evaluate_212(ctx),
130 213 => self.evaluate_213(ctx),
131 216 => self.evaluate_216(ctx),
132 219 => self.evaluate_219(ctx),
133 230 => self.evaluate_230(ctx),
134 241 => self.evaluate_241(ctx),
135 249 => self.evaluate_249(ctx),
136 252 => self.evaluate_252(ctx),
137 257 => self.evaluate_257(ctx),
138 268 => self.evaluate_268(ctx),
139 274 => self.evaluate_274(ctx),
140 276 => self.evaluate_276(ctx),
141 277 => self.evaluate_277(ctx),
142 283 => self.evaluate_283(ctx),
143 315 => self.evaluate_315(ctx),
144 324 => self.evaluate_324(ctx),
145 336 => self.evaluate_336(ctx),
146 345 => self.evaluate_345(ctx),
147 361 => self.evaluate_361(ctx),
148 362 => self.evaluate_362(ctx),
149 367 => self.evaluate_367(ctx),
150 368 => self.evaluate_368(ctx),
151 427 => self.evaluate_427(ctx),
152 442 => self.evaluate_442(ctx),
153 490 => self.evaluate_490(ctx),
154 491 => self.evaluate_491(ctx),
155 494 => self.evaluate_494(ctx),
156 500 => self.evaluate_500(ctx),
157 501 => self.evaluate_501(ctx),
158 502 => self.evaluate_502(ctx),
159 504 => self.evaluate_504(ctx),
160 507 => self.evaluate_507(ctx),
161 508 => self.evaluate_508(ctx),
162 510 => self.evaluate_510(ctx),
163 513 => self.evaluate_513(ctx),
164 527 => self.evaluate_527(ctx),
165 528 => self.evaluate_528(ctx),
166 530 => self.evaluate_530(ctx),
167 556 => self.evaluate_556(ctx),
168 558 => self.evaluate_558(ctx),
169 559 => self.evaluate_559(ctx),
170 560 => self.evaluate_560(ctx),
171 566 => self.evaluate_566(ctx),
172 567 => self.evaluate_567(ctx),
173 570 => self.evaluate_570(ctx),
174 571 => self.evaluate_571(ctx),
175 572 => self.evaluate_572(ctx),
176 581 => self.evaluate_581(ctx),
177 583 => self.evaluate_583(ctx),
178 584 => self.evaluate_584(ctx),
179 590 => self.evaluate_590(ctx),
180 601 => self.evaluate_601(ctx),
181 621 => self.evaluate_621(ctx),
182 622 => self.evaluate_622(ctx),
183 636 => self.evaluate_636(ctx),
184 637 => self.evaluate_637(ctx),
185 638 => self.evaluate_638(ctx),
186 643 => self.evaluate_643(ctx),
187 644 => self.evaluate_644(ctx),
188 651 => self.evaluate_651(ctx),
189 654 => self.evaluate_654(ctx),
190 655 => self.evaluate_655(ctx),
191 902 => self.evaluate_902(ctx),
192 907 => self.evaluate_907(ctx),
193 912 => self.evaluate_912(ctx),
194 930 => self.evaluate_930(ctx),
195 931 => self.evaluate_931(ctx),
196 934 => self.evaluate_934(ctx),
197 935 => self.evaluate_935(ctx),
198 937 => self.evaluate_937(ctx),
199 938 => self.evaluate_938(ctx),
200 950 => self.evaluate_950(ctx),
201 951 => self.evaluate_951(ctx),
202 952 => self.evaluate_952(ctx),
203 953 => self.evaluate_953(ctx),
204 2061 => self.evaluate_2061(ctx),
205 2119 => self.evaluate_2119(ctx),
206 2284 => self.evaluate_2284(ctx),
207 2286 => self.evaluate_2286(ctx),
208 2287 => self.evaluate_2287(ctx),
209 2335 => self.evaluate_2335(ctx),
210 2353 => self.evaluate_2353(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 | 4
222 | 5
223 | 7
224 | 9
225 | 10
226 | 11
227 | 12
228 | 13
229 | 14
230 | 15
231 | 16
232 | 17
233 | 18
234 | 19
235 | 24
236 | 25
237 | 26
238 | 28
239 | 29
240 | 32
241 | 33
242 | 35
243 | 36
244 | 37
245 | 39
246 | 46
247 | 47
248 | 48
249 | 51
250 | 58
251 | 64
252 | 65
253 | 66
254 | 68
255 | 69
256 | 70
257 | 77
258 | 78
259 | 81
260 | 84
261 | 92
262 | 98
263 | 106
264 | 108
265 | 123
266 | 127
267 | 128
268 | 129
269 | 130
270 | 133
271 | 137
272 | 138
273 | 147
274 | 165
275 | 166
276 | 200
277 | 202
278 | 203
279 | 205
280 | 209
281 | 212
282 | 213
283 | 216
284 | 219
285 | 230
286 | 241
287 | 249
288 | 252
289 | 257
290 | 268
291 | 274
292 | 276
293 | 277
294 | 283
295 | 315
296 | 324
297 | 336
298 | 345
299 | 361
300 | 362
301 | 367
302 | 368
303 | 427
304 | 442
305 | 490
306 | 491
307 | 494
308 | 500
309 | 501
310 | 502
311 | 504
312 | 507
313 | 508
314 | 510
315 | 513
316 | 527
317 | 528
318 | 530
319 | 556
320 | 558
321 | 559
322 | 560
323 | 566
324 | 567
325 | 570
326 | 571
327 | 572
328 | 581
329 | 583
330 | 584
331 | 590
332 | 601
333 | 621
334 | 622
335 | 636
336 | 637
337 | 638
338 | 643
339 | 644
340 | 651
341 | 654
342 | 655
343 | 902
344 | 907
345 | 912
346 | 930
347 | 931
348 | 934
349 | 935
350 | 937
351 | 938
352 | 950
353 | 951
354 | 952
355 | 953
356 | 2061
357 | 2119
358 | 2284
359 | 2286
360 | 2287
361 | 2335
362 | 2353
363 )
364 }
365}
366
367impl UtilmdGasConditionEvaluatorFV2504 {
368 /// [108] Wenn Kundenwertverfahren (z. B. TU München)
369 /// EXTERNAL: Requires context from outside the message.
370 // REVIEW: Kundenwertverfahren (e.g., TU München) is a specific billing methodology determined by contract terms, not derivable from the EDIFACT message content itself. (medium confidence)
371 fn evaluate_108(&self, ctx: &EvaluationContext) -> ConditionResult {
372 ctx.external.evaluate("kundenwertverfahren")
373 }
374
375 /// [127] Hat der Lieferant auf Grund seines Vertrags Kenntnis, dass der Kunde keine hohe KA hat so muss er dies dem NB mitteilen
376 /// EXTERNAL: Requires context from outside the message.
377 // REVIEW: Whether the supplier knows from their contract that the customer has no high KA (Kapazitätsanteil) is contract-based knowledge external to the EDIFACT message. Cannot be determined from segment data alone. (medium confidence)
378 fn evaluate_127(&self, ctx: &EvaluationContext) -> ConditionResult {
379 ctx.external.evaluate("supplier_knows_no_high_ka")
380 }
381
382 /// [129] Hat der Lieferant auf Grund seines Vertrags Kenntnis über die Höhe der Sonder-KA, so muss er diesen dem NB mitteilen
383 /// EXTERNAL: Requires context from outside the message.
384 // REVIEW: Whether the supplier has contract knowledge about the amount of a special KA (Sonder-Kapazitätsanteil) is contract-based knowledge. Cannot be derived from the EDIFACT message. (medium confidence)
385 fn evaluate_129(&self, ctx: &EvaluationContext) -> ConditionResult {
386 ctx.external.evaluate("supplier_knows_special_ka_amount")
387 }
388
389 /// [230] Sich ergebendes Datum/ bzw. Endedatum des Turnuszeitraums aus DTM+Z21 (Termin der Netznutzungsabrechnung) und DTM+Z09 (Nächste Netznutzungsabrechnung) muss >= DTM+92 (Beginn zum) sein
390 // REVIEW: Compares the later of DTM+Z21 and DTM+Z09 (end of Turnuszeitraum) against DTM+92 (start date). Date formats differ across the three DTM qualifiers (Z21=CCYYMM/format 106, Z09=format 602, DTM+92=CCYYMMDDHHMM/format 303), so comparison is normalized to the CCYYMM prefix (first 6 chars). The exact definition of 'sich ergebendes Datum' from Z21+Z09 introduces some ambiguity — using max(Z21,Z09) as the best approximation. (medium confidence)
391 fn evaluate_230(&self, ctx: &EvaluationContext) -> ConditionResult {
392 {
393 // DTM+Z21 (Termin der Netznutzungsabrechnung): elements[0][1], format 106/104
394 let z21_val = ctx
395 .find_segments_with_qualifier("DTM", 0, "Z21")
396 .into_iter()
397 .find_map(|s| s.elements.first().and_then(|e| e.get(1)).cloned())
398 .unwrap_or_default();
399 // DTM+Z09 (Nächste Netznutzungsabrechnung): elements[0][1], format 602
400 let z09_val = ctx
401 .find_segments_with_qualifier("DTM", 0, "Z09")
402 .into_iter()
403 .find_map(|s| s.elements.first().and_then(|e| e.get(1)).cloned())
404 .unwrap_or_default();
405 // DTM+92 (Beginn zum): elements[0][1], format 303 (CCYYMMDDHHMM)
406 let dtm92_val = match ctx
407 .find_segments_with_qualifier("DTM", 0, "92")
408 .into_iter()
409 .find_map(|s| s.elements.first().and_then(|e| e.get(1)).cloned())
410 {
411 Some(v) if !v.is_empty() => v,
412 _ => return ConditionResult::Unknown,
413 };
414 // Use the later of Z21 and Z09 as the end date of the Turnuszeitraum
415 // Note: formats differ (Z21=106/104, Z09=602, DTM+92=303) — use first 6 chars (CCYYMM) for comparison
416 let end_date = match (z21_val.is_empty(), z09_val.is_empty()) {
417 (false, false) => {
418 if z21_val >= z09_val {
419 z21_val
420 } else {
421 z09_val
422 }
423 }
424 (false, true) => z21_val,
425 (true, false) => z09_val,
426 (true, true) => return ConditionResult::Unknown,
427 };
428 // Compare first 6 chars (CCYYMM) — both reference dates have at least CCYYMM
429 let end_ym = &end_date[..end_date.len().min(6)];
430 let begin_ym = &dtm92_val[..dtm92_val.len().min(6)];
431 if end_ym.len() < 6 || begin_ym.len() < 6 {
432 return ConditionResult::Unknown;
433 }
434 ConditionResult::from(end_ym >= begin_ym)
435 }
436 }
437
438 /// [315] Es sind alle OBISKennzahlen gem. EDI@Energy Codeliste der OBIS-Kennzahlen und Medien für den deutschen Energiemarkt Kap. 4 anzugeben welche an der Marktlokation erforderlich sind, dabei muss der M...
439 /// EXTERNAL: Requires context from outside the message.
440 fn evaluate_315(&self, ctx: &EvaluationContext) -> ConditionResult {
441 ctx.external.evaluate("all_required_obis_codes_present")
442 }
443
444 /// [324] Es sind alle OBIS-Kennzahlen gem. EDI@Energy Codeliste der OBIS Kennzahlen Kap. 4. anzugeben welche an der Zähleinrichtung genutzt werden. Der Mindestumfang der OBIS-Kennzahlen ergibt sich aus den...
445 /// EXTERNAL: Requires context from outside the message.
446 fn evaluate_324(&self, ctx: &EvaluationContext) -> ConditionResult {
447 ctx.external.evaluate("obis_codes_complete_for_product")
448 }
449
450 /// [1] Wenn Aufteilung vorhanden
451 /// EXTERNAL: Requires context from outside the message.
452 fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
453 ctx.external.evaluate("message_splitting")
454 }
455
456 /// [4] Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle LF
457 /// EXTERNAL: Requires context from outside the message.
458 fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
459 ctx.external.evaluate("recipient_is_lf")
460 }
461
462 /// [5] Wenn MP-ID in SG2 NAD+MS (Nachrichtenabsender) in der Rolle LF
463 /// EXTERNAL: Requires context from outside the message.
464 fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
465 ctx.external.evaluate("sender_is_lf")
466 }
467
468 /// [7] Wenn SG4 STS+7++ZG9/ZH1/ZH2 (Transaktionsgrund: Aufhebung einer zukünftigen Zuordnung wegen Auszug des Kunden / -wegen Stilllegung / -wegen aufgehobenem Vertragsverhältnis) vorhanden
469 fn evaluate_7(&self, ctx: &EvaluationContext) -> ConditionResult {
470 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"])
471 }
472
473 /// [9] Wenn SG4 STS+7++ZE4 (Transaktionsgrund: Weggefallene Markt- bzw. Messlokation) nicht vorhanden
474 fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
475 match ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZE4"]) {
476 ConditionResult::True => ConditionResult::False,
477 ConditionResult::False | ConditionResult::Unknown => ConditionResult::True,
478 }
479 }
480
481 /// [10] Wenn SG4 STS+Z17 (Transaktionsgrund für befristete Anmeldung) vorhanden
482 fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
483 ctx.has_qualifier("STS", 0, "Z17")
484 }
485
486 /// [11] Wenn SG4 STS+7++ZG9/ZH1/ZH2 (Transaktionsgrund: Aufhebung einer zukünftigen Zuordnung wegen Auszug des Kunden / -wegen Stilllegung / -wegen aufgehobenem Vertragsverhältnis) nicht vorhanden
487 fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
488 match ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]) {
489 ConditionResult::True => ConditionResult::False,
490 ConditionResult::False | ConditionResult::Unknown => ConditionResult::True,
491 }
492 }
493
494 /// [12] Wenn SG4 DTM+471 (Ende zum nächstmöglichem Termin) nicht vorhanden
495 fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
496 ctx.lacks_qualifier("DTM", 0, "471")
497 }
498
499 /// [13] Wenn SG4 STS+E01++Z01 (Status der Antwort: Zustimmung mit Terminänderung) nicht vorhanden
500 fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
501 match ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]) {
502 ConditionResult::True => ConditionResult::False,
503 ConditionResult::False | ConditionResult::Unknown => ConditionResult::True,
504 }
505 }
506
507 /// [14] Wenn Datum bekannt
508 /// EXTERNAL: Requires context from outside the message.
509 fn evaluate_14(&self, ctx: &EvaluationContext) -> ConditionResult {
510 ctx.external.evaluate("date_known")
511 }
512
513 /// [15] Wenn SG4 STS+E01++Z34 (Status der Antwort: Ablehnung Mehrfachkündigung) vorhanden
514 fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
515 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z34"])
516 }
517
518 /// [16] Wenn SG4 STS+E01++Z12 (Status der Antwort: Ablehnung Vertragsbindung) vorhanden
519 fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
520 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z12"])
521 }
522
523 /// [17] Wenn bereits eine bestätigte Kündigung durch Kunde oder MP vorhanden
524 /// EXTERNAL: Requires context from outside the message.
525 fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
526 ctx.external.evaluate("confirmed_cancellation_present")
527 }
528
529 /// [18] Wenn SG4 DTM+93 (Ende zum) nicht vorhanden
530 fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
531 ctx.lacks_qualifier("DTM", 0, "93")
532 }
533
534 /// [19] Wenn SG8 SEQ+Z01 (Daten der Marktlokation) SG10 CCI+++ZC0 (Prognose auf Basis von Werten) vorhanden
535 fn evaluate_19(&self, ctx: &EvaluationContext) -> ConditionResult {
536 ctx.filtered_parent_child_has_qualifier(
537 &["SG4", "SG8"],
538 "SEQ",
539 0,
540 "Z01",
541 "SG10",
542 "CCI",
543 2,
544 "ZC0",
545 )
546 }
547
548 /// [24] Wenn SG6 DTM+Z21 (Termin der Netznutzungsabrechnung) vorhanden
549 fn evaluate_24(&self, ctx: &EvaluationContext) -> ConditionResult {
550 ctx.has_qualifier("DTM", 0, "Z21")
551 }
552
553 /// [25] Wenn der Meldepunkt im SG5 LOC+172 (Meldepunkt) DE3225 das Format der Marktlokations-ID hat
554 // REVIEW: Marktlokations-ID in German gas market is a numeric (all-digit) identifier. LOC+172 has elements[0][0]='172', elements[1][0]=DE3225. Checks if value consists entirely of ASCII digits. Returns Unknown if no LOC+172 exists. (medium confidence)
555 fn evaluate_25(&self, ctx: &EvaluationContext) -> ConditionResult {
556 let segs = ctx.find_segments_with_qualifier("LOC", 0, "172");
557 if segs.is_empty() {
558 return ConditionResult::Unknown;
559 }
560 for seg in &segs {
561 if let Some(val) = seg.elements.get(1).and_then(|e| e.first()) {
562 if !val.is_empty() && val.chars().all(|c| c.is_ascii_digit()) {
563 return ConditionResult::True;
564 }
565 }
566 }
567 ConditionResult::False
568 }
569
570 /// [26] Wenn der Meldepunkt im SG5 LOC+172 (Meldepunkt) DE3225 das Format der Zählpunktbezeichnung hat
571 // REVIEW: Zählpunktbezeichnung format contains non-digit characters (typically alphanumeric string). Negates condition 25's all-digit check. Returns True if DE3225 of LOC+172 is non-empty and contains at least one non-digit character. (medium confidence)
572 fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
573 let segs = ctx.find_segments_with_qualifier("LOC", 0, "172");
574 if segs.is_empty() {
575 return ConditionResult::Unknown;
576 }
577 for seg in &segs {
578 if let Some(val) = seg.elements.get(1).and_then(|e| e.first()) {
579 if !val.is_empty() && !val.chars().all(|c| c.is_ascii_digit()) {
580 return ConditionResult::True;
581 }
582 }
583 }
584 ConditionResult::False
585 }
586
587 /// [28] Wenn SG4 DTM+93 (Ende zum) vorhanden
588 fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
589 ctx.has_qualifier("DTM", 0, "93")
590 }
591
592 /// [29] Wenn eine Bilanzierung stattfindet
593 /// EXTERNAL: Requires context from outside the message.
594 fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
595 ctx.external.evaluate("balancing_takes_place")
596 }
597
598 /// [32] Wenn BGM+E03 (Änderungsmeldungen) vorhanden
599 fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
600 ctx.has_qualifier("BGM", 0, "E03")
601 }
602
603 /// [33] Wenn in Abmeldung ein Bilanzierungsende vorhanden
604 // REVIEW: Wenn in Abmeldung ein Bilanzierungsende vorhanden: BGM+E02 identifies the message as Abmeldung (de-registration), DTM+159 is Bilanzierungsende. Both must coexist. (medium confidence)
605 fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
606 let has_abmeldung = !ctx.find_segments_with_qualifier("BGM", 0, "E02").is_empty();
607 let has_bilanzierungsende = !ctx.find_segments_with_qualifier("DTM", 0, "159").is_empty();
608 ConditionResult::from(has_abmeldung && has_bilanzierungsende)
609 }
610
611 /// [35] Wenn das DE2380 von SG4 DTM+Z01 (Kündigungsfrist des Vertrags) an vierter Stelle T (Termin) enthält
612 fn evaluate_35(&self, ctx: &EvaluationContext) -> ConditionResult {
613 let segs = ctx.find_segments_with_qualifier("DTM", 0, "Z01");
614 match segs.first() {
615 Some(dtm) => {
616 match dtm
617 .elements
618 .first()
619 .and_then(|e| e.get(1))
620 .map(|s| s.as_str())
621 {
622 Some(value) => match value.chars().nth(3) {
623 Some('T') => ConditionResult::True,
624 Some(_) => ConditionResult::False,
625 None => ConditionResult::False, // segment absent → condition not applicable
626 },
627 None => ConditionResult::False, // segment absent → condition not applicable
628 }
629 }
630 None => ConditionResult::False, // segment absent → condition not applicable
631 }
632 }
633
634 /// [36] Wenn SG4 STS+E01++ZC5 (Status der Antwort: Ablehnung andere Anmeldung in Bearbeitung) vorhanden
635 fn evaluate_36(&self, ctx: &EvaluationContext) -> ConditionResult {
636 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["ZC5"])
637 }
638
639 /// [37] Wenn Anmeldung/ Änderung befristet
640 fn evaluate_37(&self, ctx: &EvaluationContext) -> ConditionResult {
641 // Same check as [10]: STS+Z17 indicates befristete Anmeldung in Gas
642 ctx.has_qualifier("STS", 0, "Z17")
643 }
644
645 /// [39] Wenn LF beabsichtigt Zählerstand zu übermitteln
646 /// EXTERNAL: Requires context from outside the message.
647 fn evaluate_39(&self, ctx: &EvaluationContext) -> ConditionResult {
648 ctx.external
649 .evaluate("lf_intends_to_transmit_meter_reading")
650 }
651
652 /// [46] Wenn in SG8 SEQ+Z35 ein SG10 CCI+Z12 (Lastprofil) CAV (Lastprofil) DE3055 der Wert 293 enthalten ist
653 fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
654 let seq_values = ctx.collect_group_values("SEQ", 0, 0, &["SG4", "SG8"]);
655 let z35_instances: Vec<usize> = seq_values
656 .iter()
657 .filter(|(_, v)| v == "Z35")
658 .map(|(i, _)| *i)
659 .collect();
660 if z35_instances.is_empty() {
661 return ConditionResult::False;
662 }
663 let nav = match ctx.navigator() {
664 Some(n) => n,
665 None => return ConditionResult::Unknown,
666 };
667 for i in z35_instances {
668 let sg10_count = nav.child_group_instance_count(&["SG4", "SG8"], i, "SG10");
669 for j in 0..sg10_count {
670 let ccis = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], i, "SG10", j);
671 let has_z12 = ccis.iter().any(|s| {
672 s.elements
673 .first()
674 .and_then(|e| e.first())
675 .is_some_and(|v| v == "Z12")
676 });
677 if !has_z12 {
678 continue;
679 }
680 // HAND-EDITED: DE3055 of the CAV (Lastprofil) of this SG10, not of a CCI (#255).
681 let cavs = nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], i, "SG10", j);
682 let has_293 = cavs.iter().any(|s| s.get_component(0, 2) == "293");
683 if has_293 {
684 return ConditionResult::True;
685 }
686 }
687 }
688 ConditionResult::False
689 }
690
691 /// [47] Wenn in SG8 SEQ+Z35 ein SG10 CCI+Z12 (Lastprofil) CAV (Lastprofil) DE3055 der Wert 293 nicht enthalten ist
692 // REVIEW: Logical negation of condition 46: CCI+Z12 Klimazone DE3055=293 NOT present in SG10 under SG8+Z35. Propagates Unknown to maintain three-valued semantics. (medium confidence)
693 fn evaluate_47(&self, ctx: &EvaluationContext) -> ConditionResult {
694 match self.evaluate_46(ctx) {
695 ConditionResult::True => ConditionResult::False,
696 ConditionResult::False => ConditionResult::True,
697 ConditionResult::Unknown => ConditionResult::Unknown,
698 }
699 }
700
701 /// [48] Wenn in dieser SG4 das STS+E01++E14 (Status der Antwort: Ablehnung Sonstiges) vorhanden
702 fn evaluate_48(&self, ctx: &EvaluationContext) -> ConditionResult {
703 // HAND-EDITED: "in dieser SG4" — the enclosing SG4 only, when the scope
704 // reaches one; the message as before otherwise. As
705 // `has_qualified_value`: Unknown without any STS+E01.
706 if let Some(scope) = ctx.enclosing_scope("SG4") {
707 let e01: Vec<_> = ctx
708 .find_segments_at(scope, "STS")
709 .into_iter()
710 .filter(|s| s.get_element(0) == "E01")
711 .collect();
712 if e01.is_empty() {
713 return ConditionResult::Unknown;
714 }
715 return ConditionResult::from(e01.iter().any(|s| s.get_component(2, 0) == "E14"));
716 }
717 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["E14"])
718 }
719
720 /// [51] Bei rückwirkendem Lieferende/Lieferbeginn
721 /// EXTERNAL: Requires context from outside the message.
722 fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
723 ctx.external.evaluate("retroactive_delivery_change")
724 }
725
726 /// [58] Wenn in diesem CCI das DE3055 mit dem Code 293 vorhanden
727 // REVIEW: DE3055 (Verantwortliche Stelle für die Codepflege) appears at CCI elements[2][2] in CCI — Klimazone/Temperaturmessstelle. Checks any CCI in the message has that code. 'In diesem CCI' implies context-local check; falls back to message-wide. (medium confidence)
728 fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
729 // HAND-EDITED (#379): "in diesem CCI" — the CCI whose field is judged
730 // (C240 DE3055 is component 2 of element 2); any CCI of the message only
731 // where none is resolved.
732 let codepflege =
733 |e: &[Vec<String>]| e.get(2).and_then(|c| c.get(2)).is_some_and(|v| v == "293");
734 match ctx.resolved_segment {
735 Some(elements) => ConditionResult::from(codepflege(elements)),
736 None => ConditionResult::from(
737 ctx.find_segments("CCI")
738 .iter()
739 .any(|s| codepflege(&s.elements)),
740 ),
741 }
742 }
743
744 /// [64] Wenn SG4 DTM+158 (Bilanzierungsbeginn) vorhanden
745 fn evaluate_64(&self, ctx: &EvaluationContext) -> ConditionResult {
746 ctx.has_qualifier("DTM", 0, "158")
747 }
748
749 /// [65] Wenn Marktgebietsüber-lappung besteht, sind alle Bilanzkreise des LF zu melden in denen freie Kapazitäten beim NB bestehen
750 /// EXTERNAL: Requires context from outside the message.
751 fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
752 ctx.external.evaluate("market_area_overlap_exists")
753 }
754
755 /// [66] Wenn SG10 CCI+Z19 (Bilanzkreis) im Vorgang mehr als einmal vorhanden
756 fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
757 let count = ctx.find_segments_with_qualifier("CCI", 0, "Z19").len();
758 ConditionResult::from(count > 1)
759 }
760
761 /// [68] Wenn SG10 CCI+Z19 (Bilanzkreis) im Vorgang mehr als zweimal vorhanden
762 fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
763 let count = ctx.find_segments_with_qualifier("CCI", 0, "Z19").len();
764 ConditionResult::from(count > 2)
765 }
766
767 /// [69] Wenn SG10 CCI+Z19 (Bilanzkreis) im Vorgang mehr als dreimal vorhanden
768 fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
769 let count = ctx.find_segments_with_qualifier("CCI", 0, "Z19").len();
770 ConditionResult::from(count > 3)
771 }
772
773 /// [70] Wenn SG10 CCI+Z19 (Bilanzkreis) im Vorgang fünfmal vorhanden
774 fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
775 let count = ctx.find_segments_with_qualifier("CCI", 0, "Z19").len();
776 ConditionResult::from(count == 5)
777 }
778
779 /// [77] Wenn SG8 SEQ+Z03 (Zähleinrichtungsdaten) CAV+Z30 (Identifikation/Nummer des Gerätes) nicht vorhanden
780 fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
781 ctx.any_group_has_qualifier_without("SEQ", 0, "Z03", "CAV", 0, "Z30", &["SG4", "SG8"])
782 }
783
784 /// [78] Wenn SG4 STS+7++E02 (Transaktionsgrund: Einzug in Neuanlage) nicht vorhanden
785 fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
786 let result = ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E02"]);
787 match result {
788 ConditionResult::True => ConditionResult::False,
789 ConditionResult::False => ConditionResult::True,
790 ConditionResult::Unknown => ConditionResult::Unknown,
791 }
792 }
793
794 /// [81] Wenn SG4 FTX+ABO+Z05 (Beschreibung der Abweichung zur übermittelten Liste: Änderung vorhanden) vorhanden
795 fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
796 ctx.has_qualified_value("FTX", 0, "ABO", 2, 0, &["Z05"])
797 }
798
799 /// [84] Wenn SG4 STS+E01++Z35 (Status der Antwort: Ablehnung der Abmeldeanfrage) vorhanden
800 fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
801 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z35"])
802 }
803
804 /// [92] Wenn Wert innerhalb SG bzw. Segment geändert wird
805 /// EXTERNAL: Requires context from outside the message.
806 // REVIEW: 'Wenn Wert innerhalb SG bzw. Segment geändert wird' — whether a value was changed compared to a prior state cannot be determined from the EDIFACT message alone; requires external business context comparing current vs previous transmission. (medium confidence)
807 fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
808 ctx.external.evaluate("value_changed_in_segment")
809 }
810
811 /// [98] Wenn MP-ID in SG2 NAD+MS (Nachrichtenabsender) in der Rolle NB
812 /// EXTERNAL: Requires context from outside the message.
813 // REVIEW: 'Wenn MP-ID in SG2 NAD+MS in der Rolle NB' — while we can confirm NAD+MS exists in the message, determining whether that market participant ID is registered in the role of Netzbetreiber (NB) requires an external role/master-data lookup. Cannot be derived from the EDIFACT message content alone. (medium confidence)
814 fn evaluate_98(&self, ctx: &EvaluationContext) -> ConditionResult {
815 ctx.external.evaluate("sender_is_nb")
816 }
817
818 /// [106] Wenn in dieser SG8 SEQ+Z01 SG10 CCI+++ZA6 (Prognosegrundlage der Marktlokation: Prognose auf Basis von Profilen) vorhanden
819 fn evaluate_106(&self, ctx: &EvaluationContext) -> ConditionResult {
820 // HAND-EDITED: "in dieser/derselben SG8" — the enclosing SG8 only, when the
821 // scope reaches one; the message as before otherwise.
822 if let Some(scope) = ctx.enclosing_scope("SG8") {
823 return ConditionResult::from(
824 ctx.find_segments_at(scope, "SEQ")
825 .iter()
826 .any(|s| matches!(s.get_element(0), "Z01"))
827 && ctx
828 .find_segments_at(scope, "CCI")
829 .iter()
830 .any(|s| s.get_component(2, 0) == "ZA6"),
831 );
832 }
833 ctx.filtered_parent_child_has_qualifier(
834 &["SG4", "SG8"],
835 "SEQ",
836 0,
837 "Z01",
838 "SG10",
839 "CCI",
840 2,
841 "ZA6",
842 )
843 }
844
845 /// [123] Wenn noch mindestens eine weitere SG8 SEQ+Z20 (OBIS-Daten der Zähleinrichtung / Mengenumwerter) mit dem SG8 RFF+MG / Z11 (Gerätenummer des Zählers / Mengenumwerters) auf die gleiche Identifikati...
846 // REVIEW: Collects device IDs (via RFF+MG or RFF+Z11) from all SG8 instances that have SEQ+Z20. Returns True if any device ID appears in at least two such instances, indicating at least one further SG8 references the same device. Requires navigator for proper group-scoped traversal. (medium confidence)
847 fn evaluate_123(&self, ctx: &EvaluationContext) -> ConditionResult {
848 let nav = match ctx.navigator() {
849 Some(n) => n,
850 None => {
851 let seqs = ctx.find_segments_with_qualifier("SEQ", 0, "Z20");
852 if seqs.len() < 2 {
853 return ConditionResult::False;
854 }
855 return ConditionResult::Unknown;
856 }
857 };
858 let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
859 let mut device_ids: Vec<String> = Vec::new();
860 for i in 0..sg8_count {
861 let seqs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], i);
862 let has_z20 = seqs.iter().any(|s| {
863 s.elements
864 .first()
865 .and_then(|e| e.first())
866 .is_some_and(|v| v == "Z20")
867 });
868 if !has_z20 {
869 continue;
870 }
871 let rffs = nav.find_segments_in_group("RFF", &["SG4", "SG8"], i);
872 for rff in &rffs {
873 let qual = rff
874 .elements
875 .first()
876 .and_then(|e| e.first())
877 .map(|s| s.as_str());
878 if matches!(qual, Some("MG") | Some("Z11")) {
879 if let Some(id) = rff.elements.first().and_then(|e| e.get(1)) {
880 if !id.is_empty() {
881 device_ids.push(id.clone());
882 }
883 }
884 }
885 }
886 }
887 for i in 0..device_ids.len() {
888 for j in (i + 1)..device_ids.len() {
889 if device_ids[i] == device_ids[j] {
890 return ConditionResult::True;
891 }
892 }
893 }
894 ConditionResult::False
895 }
896
897 /// [128] Wenn SG10 CAV+TAS/ TKS/ SAS/ KAS vorhanden
898 fn evaluate_128(&self, ctx: &EvaluationContext) -> ConditionResult {
899 ctx.any_group_has_any_qualifier(
900 "CAV",
901 0,
902 &["TAS", "TKS", "SAS", "KAS"],
903 &["SG4", "SG8", "SG10"],
904 )
905 }
906
907 /// [130] Wenn an Messlokation vorhanden
908 // REVIEW: 'Wenn an Messlokation vorhanden' is interpreted as: a Messlokation section exists in the message, indicated by SEQ+Z18 (Daten der Messlokation) in SG8. Returns True when such a section is present. (medium confidence)
909 fn evaluate_130(&self, ctx: &EvaluationContext) -> ConditionResult {
910 ctx.has_qualifier("SEQ", 0, "Z18")
911 }
912
913 /// [133] Wenn an der übermittelten Marktlokation / Messlokation vorhanden
914 /// EXTERNAL: Requires context from outside the message.
915 // REVIEW: 'Wenn an der übermittelten Marktlokation / Messlokation vorhanden' — whether a specific data element exists at the transmitted market or metering location is a business/registry lookup that cannot be determined from the EDIFACT message content alone. Marked external. (medium confidence)
916 fn evaluate_133(&self, ctx: &EvaluationContext) -> ConditionResult {
917 ctx.external.evaluate("at_marktlokation_or_messlokation")
918 }
919
920 /// [137] Nicht bei Neuanlage
921 // REVIEW: 'Nicht bei Neuanlage' — the condition is True when the transaction reason is NOT E02 (Neubau / new installation). Finds STS with elements[0][0]="7" (Transaktionsgrund), checks elements[2][0] for E02, and negates the result. (medium confidence)
922 fn evaluate_137(&self, ctx: &EvaluationContext) -> ConditionResult {
923 match ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E02"]) {
924 ConditionResult::True => ConditionResult::False,
925 ConditionResult::False => ConditionResult::True,
926 ConditionResult::Unknown => ConditionResult::Unknown,
927 }
928 }
929
930 /// [138] Wenn SG5 LOC+172 (Meldepunkt) nicht vorhanden
931 fn evaluate_138(&self, ctx: &EvaluationContext) -> ConditionResult {
932 ctx.lacks_qualifier("LOC", 0, "172")
933 }
934
935 /// [147] Wenn in Anfrage vorhanden
936 /// EXTERNAL: Requires context from outside the message.
937 // REVIEW: 'Wenn in Anfrage vorhanden' — whether something is present in the original inquiry/request message is a cross-message business context that cannot be determined from the current EDIFACT message alone. Marked external. (medium confidence)
938 fn evaluate_147(&self, ctx: &EvaluationContext) -> ConditionResult {
939 ctx.external.evaluate("present_in_request")
940 }
941
942 /// [165] Wenn bekannt
943 /// EXTERNAL: Requires context from outside the message.
944 fn evaluate_165(&self, ctx: &EvaluationContext) -> ConditionResult {
945 ctx.external.evaluate("date_known")
946 }
947
948 /// [166] Wenn vorhanden
949 /// EXTERNAL: Requires context from outside the message.
950 // REVIEW: 'Wenn vorhanden' — generic 'if present/available' without a specific segment reference. When used as a standalone condition this refers to business data availability that cannot be determined from the EDIFACT message structure alone. Marked external. (medium confidence)
951 fn evaluate_166(&self, ctx: &EvaluationContext) -> ConditionResult {
952 ctx.external.evaluate("data_available")
953 }
954
955 /// [200] Wenn BGM+Z26 (Vorläufige Meldung zur Marktraumumstellung) vorhanden
956 fn evaluate_200(&self, ctx: &EvaluationContext) -> ConditionResult {
957 ctx.has_qualifier("BGM", 0, "Z26")
958 }
959
960 /// [202] Wenn SG4 STS+E01+ZG2 (Status der Antwort: Gültiges Ergebnis nach der Datenprüfung) vorhanden
961 fn evaluate_202(&self, ctx: &EvaluationContext) -> ConditionResult {
962 ctx.has_qualified_value("STS", 0, "E01", 1, 0, &["ZG2"])
963 }
964
965 /// [203] Wenn STS+7++E06 / Z39 / ZC6 / ZC7 / ZC6 / ZT6 / ZT7
966 fn evaluate_203(&self, ctx: &EvaluationContext) -> ConditionResult {
967 ctx.has_qualified_value(
968 "STS",
969 0,
970 "7",
971 2,
972 0,
973 &["E06", "Z39", "ZC6", "ZC7", "ZT6", "ZT7"],
974 )
975 }
976
977 /// [205] Wenn SG9 QTY+Y02 (TUM Kundenwert) nicht vorhanden
978 fn evaluate_205(&self, ctx: &EvaluationContext) -> ConditionResult {
979 ctx.lacks_qualifier("QTY", 0, "Y02")
980 }
981
982 /// [209] Wenn im selben Segment im DE2379 der Code 303 vorhanden ist
983 fn evaluate_209(&self, ctx: &EvaluationContext) -> ConditionResult {
984 ConditionResult::from(ctx.self_segment_value_equals("DTM", 0, 2, "303"))
985 }
986
987 /// [212] Wenn im selben SG12 NAD DE3124 nicht vorhanden
988 // REVIEW: Checks if any NAD in SG12 has DE3124 (C058 first component, elements[2][0]) absent or empty. Without group-scoped navigation, falls back to message-wide NAD check. (medium confidence)
989 fn evaluate_212(&self, ctx: &EvaluationContext) -> ConditionResult {
990 let nads = ctx.find_segments("NAD");
991 ConditionResult::from(nads.iter().any(|s| {
992 s.elements
993 .get(2)
994 .map(|e| e.first().map(|v| v.is_empty()).unwrap_or(true))
995 .unwrap_or(true)
996 }))
997 }
998
999 /// [213] Wenn SG12 NAD+Z09 (Kunde des Lieferanten) vorhanden
1000 fn evaluate_213(&self, ctx: &EvaluationContext) -> ConditionResult {
1001 ctx.has_qualifier("NAD", 0, "Z09")
1002 }
1003
1004 /// [216] Wenn CCI+++Z88 (Netznutzung) CAV+Z74:::Z08 (Netznutzungsvertrag: Direkter Vertrag zwischen Kunden und NB) vorhanden
1005 // REVIEW: CCI+++Z88 means elements[2][0]=='Z88' (Netznutzung). CAV+Z74:::Z08 means elements[0][0]=='Z74' and elements[0][3]=='Z08' (Direkter Vertrag). Uses parent-child group navigation to check co-occurrence within the same SG10 instance. (medium confidence)
1006 fn evaluate_216(&self, ctx: &EvaluationContext) -> ConditionResult {
1007 let nav = match ctx.navigator() {
1008 Some(n) => n,
1009 None => {
1010 let cavs = ctx.find_segments("CAV");
1011 return ConditionResult::from(cavs.iter().any(|s| {
1012 let e0 = s.elements.first();
1013 e0.and_then(|e| e.first()).is_some_and(|v| v == "Z74")
1014 && e0.and_then(|e| e.get(3)).is_some_and(|v| v == "Z08")
1015 }));
1016 }
1017 };
1018 let sg8_count = nav.group_instance_count(&["SG4", "SG8"]);
1019 for i in 0..sg8_count {
1020 let sg10_count = nav.child_group_instance_count(&["SG4", "SG8"], i, "SG10");
1021 for j in 0..sg10_count {
1022 let ccis = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], i, "SG10", j);
1023 let has_cci_z88 = ccis.iter().any(|s| {
1024 s.elements
1025 .get(2)
1026 .and_then(|e| e.first())
1027 .is_some_and(|v| v == "Z88")
1028 });
1029 if has_cci_z88 {
1030 let cavs =
1031 nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], i, "SG10", j);
1032 if cavs.iter().any(|s| {
1033 let e0 = s.elements.first();
1034 e0.and_then(|e| e.first()).is_some_and(|v| v == "Z74")
1035 && e0.and_then(|e| e.get(3)).is_some_and(|v| v == "Z08")
1036 }) {
1037 return ConditionResult::True;
1038 }
1039 }
1040 }
1041 }
1042 ConditionResult::False
1043 }
1044
1045 /// [219] Wenn an Marktlokation vorhanden
1046 /// EXTERNAL: Requires context from outside the message.
1047 // REVIEW: 'Wenn an Marktlokation vorhanden' — whether specific data exists at the market location is a registry/master-data lookup that cannot be derived from the EDIFACT message content. Marked external. (medium confidence)
1048 fn evaluate_219(&self, ctx: &EvaluationContext) -> ConditionResult {
1049 ctx.external.evaluate("at_marktlokation")
1050 }
1051
1052 /// [241] Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle MSB
1053 /// EXTERNAL: Requires context from outside the message.
1054 fn evaluate_241(&self, ctx: &EvaluationContext) -> ConditionResult {
1055 ctx.external.evaluate("recipient_is_msb")
1056 }
1057
1058 /// [249] Innerhalb eines SG4 IDE müssen alle DE1131 der SG4 STS+E01 den identischen Wert enthalten
1059 // REVIEW: Checks that all STS+E01 segments have identical DE1131 values (elements[2][1] — Codeliste, Code within C556). Returns True when all values match the first occurrence. Message-wide fallback without per-SG4 grouping. (medium confidence)
1060 fn evaluate_249(&self, ctx: &EvaluationContext) -> ConditionResult {
1061 let sts_segs = ctx.find_segments("STS");
1062 let sts_e01: Vec<_> = sts_segs
1063 .iter()
1064 .filter(|s| {
1065 s.elements
1066 .first()
1067 .and_then(|e| e.first())
1068 .is_some_and(|v| v == "E01")
1069 })
1070 .collect();
1071 if sts_e01.is_empty() {
1072 return ConditionResult::Unknown;
1073 }
1074 let first_val = sts_e01
1075 .first()
1076 .and_then(|s| s.elements.get(2))
1077 .and_then(|e| e.get(1))
1078 .map(|v| v.as_str())
1079 .unwrap_or("");
1080 ConditionResult::from(sts_e01.iter().all(|s| {
1081 s.elements
1082 .get(2)
1083 .and_then(|e| e.get(1))
1084 .map(|v| v.as_str())
1085 .unwrap_or("")
1086 == first_val
1087 }))
1088 }
1089
1090 /// [252] Wenn DE0068 vorhanden
1091 // REVIEW: DE0068 is the Common Access Reference in UNH (elements[2][0]). Checks if it is present and non-empty in any UNH segment. (medium confidence)
1092 fn evaluate_252(&self, ctx: &EvaluationContext) -> ConditionResult {
1093 let unh_segs = ctx.find_segments("UNH");
1094 ConditionResult::from(unh_segs.iter().any(|s| {
1095 s.elements
1096 .get(2)
1097 .and_then(|e| e.first())
1098 .is_some_and(|v| !v.is_empty())
1099 }))
1100 }
1101
1102 /// [257] Wenn in derselben SG8 SEQ+Z02 (OBIS-Daten der Marktlokation) das PIA+5+7-0?:33.86.0 vorhanden
1103 fn evaluate_257(&self, ctx: &EvaluationContext) -> ConditionResult {
1104 // HAND-EDITED: "in dieser/derselben SG8" — the enclosing SG8 only, when the
1105 // scope reaches one; the message as before otherwise.
1106 if let Some(scope) = ctx.enclosing_scope("SG8") {
1107 return ConditionResult::from(
1108 ctx.find_segments_at(scope, "SEQ")
1109 .iter()
1110 .any(|s| matches!(s.get_element(0), "Z02"))
1111 && ctx
1112 .find_segments_at(scope, "PIA")
1113 .iter()
1114 .any(|s| s.get_component(1, 0) == "7-0:33.86.0"),
1115 );
1116 }
1117 ctx.any_group_has_co_occurrence(
1118 "SEQ",
1119 0,
1120 &["Z02"],
1121 "PIA",
1122 1,
1123 0,
1124 &["7-0:33.86.0"],
1125 &["SG4", "SG8"],
1126 )
1127 }
1128
1129 /// [268] Wenn der Code im DE3207 in der "EDI@Energy Codeliste der europäischen Ländercodes" in der Spalte "PLZ vorhanden" ein "X" aufgeführt ist
1130 /// EXTERNAL: Requires context from outside the message.
1131 fn evaluate_268(&self, ctx: &EvaluationContext) -> ConditionResult {
1132 ctx.external.evaluate("country_has_postal_code_requirement")
1133 }
1134
1135 /// [274] Wenn in derselben SG8 SEQ+Z20 (OBIS-Daten der Zähleinrichtung / Mengenumwerter) das PIA+5+7-b?:3.0.0 / 7-b?:6.0.0 / 7-b?:3.1.0 / 7-b?:6.1.0 / 7-b?:3.2.0 / 7-b?:6.2.0 / 7-b?:13.2.0 / 7-b?:16.2.0 / ...
1136 // REVIEW: Checks co-occurrence of SEQ+Z20 and PIA+5 in same SG8, then verifies the OBIS code (elements[1][0]) matches pattern '7-b:X.X.X' where b is variable (any subgroup) and suffix is one of the 16 listed measurement quantities. EDIFACT ?. decoded as literal colon. OBIS suffix match is message-wide as a secondary filter. (medium confidence)
1137 fn evaluate_274(&self, ctx: &EvaluationContext) -> ConditionResult {
1138 let obis_suffixes = [
1139 ":3.0.0", ":6.0.0", ":3.1.0", ":6.1.0", ":3.2.0", ":6.2.0", ":13.2.0", ":16.2.0",
1140 ":1.0.0", ":2.0.0", ":4.0.0", ":5.0.0", ":11.2.0", ":12.2.0", ":14.2.0", ":15.2.0",
1141 ];
1142 // HAND-EDITED: "in dieser/derselben SG8" — the enclosing SG8 only, when the
1143 // scope reaches one; the message as before otherwise.
1144 if let Some(scope) = ctx.enclosing_scope("SG8") {
1145 return ConditionResult::from(
1146 ctx.find_segments_at(scope, "SEQ")
1147 .iter()
1148 .any(|s| matches!(s.get_element(0), "Z20"))
1149 && ctx.find_segments_at(scope, "PIA").iter().any(|s| {
1150 let v = s.get_component(1, 0);
1151 s.get_element(0) == "5"
1152 && v.starts_with("7-")
1153 && obis_suffixes.iter().any(|suf| v.ends_with(suf))
1154 }),
1155 );
1156 }
1157 let has_seq_pia_co = ctx.any_group_has_co_occurrence(
1158 "SEQ",
1159 0,
1160 &["Z20"],
1161 "PIA",
1162 0,
1163 0,
1164 &["5"],
1165 &["SG4", "SG8"],
1166 );
1167 if !matches!(has_seq_pia_co, ConditionResult::True) {
1168 return has_seq_pia_co;
1169 }
1170 let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1171 ConditionResult::from(pias.iter().any(|s| {
1172 s.elements.get(1).and_then(|e| e.first()).is_some_and(|v| {
1173 v.starts_with("7-") && obis_suffixes.iter().any(|suf| v.ends_with(suf))
1174 })
1175 }))
1176 }
1177
1178 /// [276] Wenn SG4 DTM+92 (Beginn zum) vorhanden
1179 fn evaluate_276(&self, ctx: &EvaluationContext) -> ConditionResult {
1180 ctx.has_qualifier("DTM", 0, "92")
1181 }
1182
1183 /// [277] Wenn Zuordnung vorhanden
1184 // REVIEW: 'Wenn Zuordnung vorhanden' most likely refers to Bilanzkreiszuordnung zur Marktlokation, indicated by STS+Z18 per the MIG segment reference. Medium confidence as 'Zuordnung' could refer to other assignments in different PID contexts. (medium confidence)
1185 fn evaluate_277(&self, ctx: &EvaluationContext) -> ConditionResult {
1186 ctx.has_qualifier("STS", 0, "Z18")
1187 }
1188
1189 /// [283] Wenn Empfänger der Nachricht der zum Nachrichtendatum aktuell zugeordnete Lieferant ist
1190 /// EXTERNAL: Requires context from outside the message.
1191 fn evaluate_283(&self, ctx: &EvaluationContext) -> ConditionResult {
1192 ctx.external.evaluate("recipient_is_current_supplier")
1193 }
1194
1195 /// [336] Wenn in Änderungsmeldung gefüllt
1196 /// EXTERNAL: Requires context from outside the message.
1197 // REVIEW: "Wenn in Änderungsmeldung gefüllt" means the condition applies when this field is populated in the context of an amendment message (Änderungsmeldung). Whether a given transmission constitutes an amendment depends on business context (the PID / Vorgang type) that cannot be reliably determined from the raw EDIFACT segments alone. Delegated to external provider. (medium confidence)
1198 fn evaluate_336(&self, ctx: &EvaluationContext) -> ConditionResult {
1199 ctx.external.evaluate("field_filled_in_amendment_message")
1200 }
1201
1202 /// [345] Wenn 33-stelliger Meldepunkt im SG5 LOC+172 (Meldepunkt) vorhanden
1203 fn evaluate_345(&self, ctx: &EvaluationContext) -> ConditionResult {
1204 let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
1205 ConditionResult::from(locs.iter().any(|s| {
1206 s.elements
1207 .get(1)
1208 .and_then(|e| e.first())
1209 .map(|id| id.len() == 33)
1210 .unwrap_or(false)
1211 }))
1212 }
1213
1214 /// [361] Wenn STS+E01++A03/ A04 nicht vorhanden
1215 fn evaluate_361(&self, ctx: &EvaluationContext) -> ConditionResult {
1216 let found = ctx
1217 .find_segments_with_qualifier("STS", 0, "E01")
1218 .iter()
1219 .any(|s| {
1220 s.elements
1221 .get(2)
1222 .and_then(|e| e.first())
1223 .map(|v| v == "A03" || v == "A04")
1224 .unwrap_or(false)
1225 });
1226 ConditionResult::from(!found)
1227 }
1228
1229 /// [362] Wenn STS+E01++A03/ A17 nicht vorhanden
1230 fn evaluate_362(&self, ctx: &EvaluationContext) -> ConditionResult {
1231 let found = ctx
1232 .find_segments_with_qualifier("STS", 0, "E01")
1233 .iter()
1234 .any(|s| {
1235 s.elements
1236 .get(2)
1237 .and_then(|e| e.first())
1238 .map(|v| v == "A03" || v == "A17")
1239 .unwrap_or(false)
1240 });
1241 ConditionResult::from(!found)
1242 }
1243
1244 /// [367] Wenn SG4 STS+E01++A04 vorhanden
1245 fn evaluate_367(&self, ctx: &EvaluationContext) -> ConditionResult {
1246 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["A04"])
1247 }
1248
1249 /// [368] Es sind alle Codes aus der Codeliste G_0009 erlaubt
1250 fn evaluate_368(&self, _ctx: &EvaluationContext) -> ConditionResult {
1251 ConditionResult::True
1252 }
1253
1254 /// [427] Messprodukt-Code aus Kapitel 3 "Codeliste der Standard-Messprodukte Gas" der Codeliste der Konfigurationen
1255 /// EXTERNAL: Requires context from outside the message.
1256 fn evaluate_427(&self, ctx: &EvaluationContext) -> ConditionResult {
1257 ctx.external.evaluate("messprodukt_code_gas_valid")
1258 }
1259
1260 /// [442] Wenn in keinem SG8+SEQ+Z09 Mengenumwerterdaten das RFF+MG (Referenz auf die Gerätenummer) der in diesem RFF DE1154 genannte Gerätenummer des Zählers vorhanden ist
1261 fn evaluate_442(&self, ctx: &EvaluationContext) -> ConditionResult {
1262 // HAND-EDITED (#379): the device number this RFF names (DE1154) in an
1263 // RFF+MG of a SEQ+Z09 SG8 — not any RFF+MG there. A Zähler whose own
1264 // number no Mengenumwerter references may carry its RFF+MG.
1265 let Some(number) = ctx
1266 .resolved_segment
1267 .and_then(|e| e.first())
1268 .and_then(|c| c.get(1))
1269 .filter(|v| !v.is_empty())
1270 else {
1271 return ConditionResult::Unknown;
1272 };
1273 let sg8 = ["SG4", "SG8"];
1274 let referenced = (0..ctx.group_instance_count(&sg8)).any(|i| {
1275 ctx.find_segments_in_group("SEQ", &sg8, i)
1276 .iter()
1277 .any(|s| s.get_element(0) == "Z09")
1278 && ctx
1279 .find_segments_in_group("RFF", &sg8, i)
1280 .iter()
1281 .any(|s| s.get_component(0, 0) == "MG" && s.get_component(0, 1) == number)
1282 });
1283 ConditionResult::from(!referenced)
1284 }
1285
1286 /// [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
1287 fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
1288 match ctx.resolved_value {
1289 Some(val) => is_mesz_utc(val),
1290 None => ConditionResult::False,
1291 }
1292 }
1293
1294 /// [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
1295 fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1296 match ctx.resolved_value {
1297 Some(val) => is_mez_utc(val),
1298 None => ConditionResult::False,
1299 }
1300 }
1301
1302 /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
1303 fn evaluate_494(&self, _ctx: &EvaluationContext) -> ConditionResult {
1304 // Hinweis: Das genannte Datum muss dem Dokumenterstellungszeitpunkt oder einem früheren Zeitpunkt entsprechen — informational annotation
1305 ConditionResult::True
1306 }
1307
1308 /// [500] Hinweis: Code ist gemäß der Kategorie der zu stornierenden Meldung zu wählen
1309 fn evaluate_500(&self, _ctx: &EvaluationContext) -> ConditionResult {
1310 // Hinweis: Code ist gemäß der Kategorie der zu stornierenden Meldung zu wählen — informational note, always applies
1311 ConditionResult::True
1312 }
1313
1314 /// [501] Hinweis: Die Angabe wird aus dem DTM+157 (Änderung zum) der Zuordnungsliste übernommen
1315 fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1316 // Hinweis: Die Angabe wird aus dem DTM+157 (Änderung zum) der Zuordnungsliste übernommen — informational note, always applies
1317 ConditionResult::True
1318 }
1319
1320 /// [502] Hinweis: Ersatzbelieferung gibt es nur bei - Marktlokationen in der Niederdruckebene, die kein Haushaltskunde gem. EnWG sind und die nicht mehr der gesetzlichen Ersatzversorgung (drei Monate) un...
1321 fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1322 // Hinweis: Informational note about Ersatzbelieferung scope and bilateral agreements.
1323 // No evaluatable boolean logic — purely documentary.
1324 ConditionResult::Unknown
1325 }
1326
1327 /// [504] Hinweis: Der Code Z22 wird auch in der Sparte Strom genutzt. Der Verweis auf den Einspeisevergütungsintervall ist in der Sparte Gas nicht relevant.
1328 fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1329 // Hinweis: Informational note that Z22 is also used in electricity sector;
1330 // Einspeisevergütungsintervall reference is not relevant in gas sector.
1331 // No evaluatable boolean logic — purely documentary.
1332 ConditionResult::Unknown
1333 }
1334
1335 /// [507] Hinweis: Ursprünglich vom NB bestätigtes Beginndatum
1336 fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
1337 // Hinweis: Informational note — 'ursprünglich vom NB bestätigtes Beginndatum'.
1338 // Documents the semantic meaning of the date field; no boolean logic.
1339 ConditionResult::Unknown
1340 }
1341
1342 /// [508] Hinweis: Beginndatum beim neuen NB
1343 fn evaluate_508(&self, _ctx: &EvaluationContext) -> ConditionResult {
1344 // Hinweis: Informational note — 'Beginndatum beim neuen NB'.
1345 // Documents the semantic meaning of the date field; no boolean logic.
1346 ConditionResult::Unknown
1347 }
1348
1349 /// [510] Hinweis: Zu verwenden bei der Abmeldung der ESV
1350 fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
1351 // Hinweis: Informational note — to be used for deregistration of ESV (Ersatzversorgung).
1352 // Documents usage context; no boolean logic.
1353 ConditionResult::Unknown
1354 }
1355
1356 /// [513] Hinweis: Ist SG9 QTY+Y02 (TUM Kundenwert) vorhanden, dann ist ausschließlich SG9 QTY+Y02, unabhängig von SG9 QTY+31 (Veranschlagte Jahresmenge gesamt), für die Bilanzierung und MMM-Abrechnung zu...
1357 // REVIEW: Hinweis describing precedence rule between QTY+Y02 and QTY+31. Partial implementation checks whether QTY+Y02 is present (i.e. the rule is active). Full semantic enforcement (exclusive use) cannot be expressed as a simple ConditionResult. (medium confidence)
1358 fn evaluate_513(&self, ctx: &EvaluationContext) -> ConditionResult {
1359 // Hinweis: If QTY+Y02 (TUM Kundenwert) is present, only QTY+Y02 (not QTY+31) is
1360 // to be used for billing and MMM settlement. This is a documentation note about
1361 // precedence, not a simple presence/absence condition.
1362 // Best approximation: True if QTY+Y02 is present (signalling the rule applies).
1363 ctx.has_qualifier("QTY", 0, "Y02")
1364 }
1365
1366 /// [527] Hinweis: Es ist die ID der Marktlokation und alle Identifikatoren der Messlokationen anzugeben
1367 fn evaluate_527(&self, _ctx: &EvaluationContext) -> ConditionResult {
1368 // Hinweis: Informational note — ID of Marktlokation AND all Messlokation
1369 // identifiers must be provided. No evaluatable boolean logic.
1370 ConditionResult::Unknown
1371 }
1372
1373 /// [528] Hinweis: Es ist das Datum/ Daten aus der Anfrage zu verwenden
1374 fn evaluate_528(&self, _ctx: &EvaluationContext) -> ConditionResult {
1375 // Hinweis: Informational note — the date(s) from the original request must be used.
1376 // No evaluatable boolean logic.
1377 ConditionResult::Unknown
1378 }
1379
1380 /// [530] Hinweis: Es sind alle an dem Meldepunkt vorhandenen Daten, die mit dieser Segmentgruppe übermittelt werden und zum Datum „Änderung zum“ Gültigkeit haben, anzugeben. Dies kann zur Folge haben...
1381 fn evaluate_530(&self, _ctx: &EvaluationContext) -> ConditionResult {
1382 // Hinweis: Informational note — all data valid at 'Änderung zum' date for the
1383 // reporting point must be provided, possibly requiring repeated segment groups.
1384 // No evaluatable boolean logic.
1385 ConditionResult::Unknown
1386 }
1387
1388 /// [556] Hinweis: Wenn keine Korrespondenzanschrift des Endverbrauchers/ Kunden vorliegt, ist die Anschrift der Marktlokation zu übermitteln
1389 fn evaluate_556(&self, _ctx: &EvaluationContext) -> ConditionResult {
1390 // Hinweis: Wenn keine Korrespondenzanschrift des Endverbrauchers/Kunden vorliegt, ist die Anschrift der Marktlokation zu übermitteln — informational note, always applies
1391 ConditionResult::True
1392 }
1393
1394 /// [558] Hinweis: Diese Information kann freiwillig ausgetauscht werden
1395 fn evaluate_558(&self, _ctx: &EvaluationContext) -> ConditionResult {
1396 // Hinweis: This information can be exchanged voluntarily.
1397 // No evaluatable boolean logic — purely documentary.
1398 ConditionResult::Unknown
1399 }
1400
1401 /// [559] Hinweis: Die Korrespondenzanschrift des Endverbrauchers/Kunden wird nicht zur Identifikation genutzt
1402 fn evaluate_559(&self, _ctx: &EvaluationContext) -> ConditionResult {
1403 // Hinweis: Correspondence address of end consumer/customer is not used for identification.
1404 // No evaluatable boolean logic — purely documentary.
1405 ConditionResult::Unknown
1406 }
1407
1408 /// [560] Hinweis: Die Angabe Name und Adresse für die Ablesekarte wird nicht zur Identifikation genutzt
1409 fn evaluate_560(&self, _ctx: &EvaluationContext) -> ConditionResult {
1410 // Hinweis: Name and address for Ablesekarte (meter reading card) is not used
1411 // for identification purposes. No evaluatable boolean logic — purely documentary.
1412 ConditionResult::Unknown
1413 }
1414
1415 /// [566] Hinweis: Altlieferant
1416 fn evaluate_566(&self, _ctx: &EvaluationContext) -> ConditionResult {
1417 // Hinweis: 'Altlieferant' (previous supplier). Informational label for the
1418 // market participant role context; no evaluatable boolean logic.
1419 ConditionResult::Unknown
1420 }
1421
1422 /// [567] Hinweis: Neulieferant
1423 fn evaluate_567(&self, _ctx: &EvaluationContext) -> ConditionResult {
1424 // Hinweis: 'Neulieferant' (new supplier). Informational label for the
1425 // market participant role context; no evaluatable boolean logic.
1426 ConditionResult::Unknown
1427 }
1428
1429 /// [570] Hinweis: Netzbetreiber Alt
1430 fn evaluate_570(&self, _ctx: &EvaluationContext) -> ConditionResult {
1431 // Hinweis: Netzbetreiber Alt — informational note, always applies
1432 ConditionResult::True
1433 }
1434
1435 /// [571] Hinweis: Auslösender Marktpartner (LFA bei STS+7++ZG9, LFN bei STS+7++ZH0, NB bei STS+7++ZH1)
1436 fn evaluate_571(&self, _ctx: &EvaluationContext) -> ConditionResult {
1437 // Hinweis: Auslösender Marktpartner (LFA bei STS+7++ZG9, LFN bei STS+7++ZH0, NB bei STS+7++ZH1) — informational note, always applies
1438 ConditionResult::True
1439 }
1440
1441 /// [572] Hinweis: Kundenname aus Anmeldung Lieferant neu
1442 fn evaluate_572(&self, _ctx: &EvaluationContext) -> ConditionResult {
1443 // Hinweis: Kundenname aus Anmeldung Lieferant neu — informational note, always applies
1444 ConditionResult::True
1445 }
1446
1447 /// [581] Hinweis: Es ist das nächst mögliche Kündigungsdatum anzugeben
1448 fn evaluate_581(&self, _ctx: &EvaluationContext) -> ConditionResult {
1449 // Hinweis: Es ist das nächst mögliche Kündigungsdatum anzugeben — informational note, always applies
1450 ConditionResult::True
1451 }
1452
1453 /// [583] Hinweis: Verwendung der ID der Marktlokation
1454 fn evaluate_583(&self, _ctx: &EvaluationContext) -> ConditionResult {
1455 // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1456 ConditionResult::True
1457 }
1458
1459 /// [584] Hinweis: Verwendung der ID der Messlokation
1460 fn evaluate_584(&self, _ctx: &EvaluationContext) -> ConditionResult {
1461 // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1462 ConditionResult::True
1463 }
1464
1465 /// [590] Hinweis: Es ist die ID der Marktlokation, welche dem LF zugeordnet sind, sowie alle Identifikatoren der Messlokationen anzugeben
1466 fn evaluate_590(&self, _ctx: &EvaluationContext) -> ConditionResult {
1467 // Hinweis: Es ist die ID der Marktlokation, welche dem LF zugeordnet sind, sowie alle Identifikatoren der Messlokationen anzugeben — informational note, always applies
1468 ConditionResult::True
1469 }
1470
1471 /// [601] Hinweis: Es ist die ID der Marktlokation und alle Identifikatoren der Messlokationen anzugeben.
1472 fn evaluate_601(&self, _ctx: &EvaluationContext) -> ConditionResult {
1473 // Hinweis: Es ist die ID der Marktlokation und alle Identifikatoren der Messlokationen anzugeben — informational note, always applies
1474 ConditionResult::True
1475 }
1476
1477 /// [621] Hinweis: Es ist der MSB anzugeben, welcher ab dem Zeitpunkt der Lokation zugeordnet ist, der in DTM+76 (Datum zum geplanten Leistungsbeginn) genannt ist.
1478 fn evaluate_621(&self, _ctx: &EvaluationContext) -> ConditionResult {
1479 // Hinweis: Es ist der MSB anzugeben, welcher ab dem Zeitpunkt der Lokation zugeordnet ist, der in DTM+76 (Datum zum geplanten Leistungsbeginn) genannt ist.
1480 ConditionResult::True
1481 }
1482
1483 /// [622] Hinweis: Falls die OBIS-Kennzahl für mehrere Marktrollen relevant ist, so muss die Segmentgruppe pro Marktrolle wiederholt werden
1484 fn evaluate_622(&self, _ctx: &EvaluationContext) -> ConditionResult {
1485 // Hinweis: Falls die OBIS-Kennzahl für mehrere Marktrollen relevant ist, so muss die Segmentgruppe pro Marktrolle wiederholt werden
1486 ConditionResult::True
1487 }
1488
1489 /// [636] Hinweis: Dieses RFF klassifiziert mit einem Code im DE1153 die in derselben Segmentgruppe enthaltenen DTM zu einem Markt- bzw. Messlokation relevanten Inhalt
1490 fn evaluate_636(&self, _ctx: &EvaluationContext) -> ConditionResult {
1491 // Hinweis: Dieses RFF klassifiziert mit einem Code im DE1153 die in derselben Segmentgruppe enthaltenen DTM zu einem Markt- bzw. Messlokation relevanten Inhalt
1492 ConditionResult::True
1493 }
1494
1495 /// [637] Hinweis: Bei Verpflichtungsanfrage
1496 fn evaluate_637(&self, _ctx: &EvaluationContext) -> ConditionResult {
1497 // Hinweis: Bei Verpflichtungsanfrage — informational note, always applies
1498 ConditionResult::True
1499 }
1500
1501 /// [638] Hinweis: Bei Aufforderung zur Übernahme der einzelnen Messlokation durch den gMSB
1502 fn evaluate_638(&self, _ctx: &EvaluationContext) -> ConditionResult {
1503 // Hinweis: Bei Aufforderung zur Übernahme der einzelnen Messlokation durch den gMSB — informational note, always applies
1504 ConditionResult::True
1505 }
1506
1507 /// [643] Hinweis: Nachfolgender Netzbetreiber
1508 fn evaluate_643(&self, _ctx: &EvaluationContext) -> ConditionResult {
1509 // Hinweis: Nachfolgender Netzbetreiber — informational note, always applies
1510 ConditionResult::True
1511 }
1512
1513 /// [644] Hinweis: Wenn in der zugehörigen Anmeldung (44001) in diesem Segmente "Einzug in Neuanlage" (SG4 STS+7++E02) enthalten ist, wird in diesem Geschäftsvorfall der Code E01 verwendet
1514 fn evaluate_644(&self, _ctx: &EvaluationContext) -> ConditionResult {
1515 // Hinweis: informational note about E01/E02 usage in Bestätigung — always applies
1516 ConditionResult::True
1517 }
1518
1519 /// [651] Hinweis: Bei einer Marktraumumstellung (Gas) ist zu beachten, dass die tatsächliche Meldung zur Marktraumumstellung auf Ebene der Messlokation durch Angabe der Gasqualität erfolgt. Die betroffene...
1520 fn evaluate_651(&self, _ctx: &EvaluationContext) -> ConditionResult {
1521 // Hinweis: Marktraumumstellung Gas — informational note about Messlokation reporting — always applies
1522 ConditionResult::True
1523 }
1524
1525 /// [654] Hinweis: Es sind ausschließlich die Daten zum Meldepunkt anzugeben, die für den in NAD+MR (Nachrichtenempfänger) adressierten Marktpartner relevant ist
1526 fn evaluate_654(&self, _ctx: &EvaluationContext) -> ConditionResult {
1527 // Hinweis: Only data relevant to the addressed Marktpartner (NAD+MR) should be provided — always applies
1528 ConditionResult::True
1529 }
1530
1531 /// [655] Hinweis: Wenn ein Zähler an einen Mengenumwerter angeschlossen ist werden an dem Zähler keine OBIS-Kennzahlen angegeben Hier gibt es nur OBIS Kennzahlen vom Mengenumwerter
1532 fn evaluate_655(&self, _ctx: &EvaluationContext) -> ConditionResult {
1533 // Hinweis: When a meter is connected to a volume converter, no OBIS codes are given for the meter — always applies
1534 ConditionResult::True
1535 }
1536
1537 /// [902] Format: Möglicher Wert: ≥ 0
1538 fn evaluate_902(&self, ctx: &EvaluationContext) -> ConditionResult {
1539 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">=", 0.0))
1540 }
1541
1542 /// [907] Format: max. 4 Nachkommastellen
1543 fn evaluate_907(&self, ctx: &EvaluationContext) -> ConditionResult {
1544 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 4))
1545 }
1546
1547 /// [912] Format: max. 6 Nachkommastellen
1548 fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
1549 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 6))
1550 }
1551
1552 /// [930] Format: max. 2 Nachkommastellen
1553 fn evaluate_930(&self, ctx: &EvaluationContext) -> ConditionResult {
1554 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 2))
1555 }
1556
1557 /// [931] Format: ZZZ = +00
1558 fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1559 ctx.format_check("DTM", 0, 1, validate_timezone_utc)
1560 }
1561
1562 /// [934] Format: HHMM = 0400
1563 fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
1564 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0400"))
1565 }
1566
1567 /// [935] Format: HHMM = 0500
1568 fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
1569 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0500"))
1570 }
1571
1572 /// [937] Format: keine Nachkommastelle
1573 fn evaluate_937(&self, ctx: &EvaluationContext) -> ConditionResult {
1574 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 0))
1575 }
1576
1577 /// [938] Format: Möglicher Wert: <= 10
1578 fn evaluate_938(&self, ctx: &EvaluationContext) -> ConditionResult {
1579 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "<=", 10.0))
1580 }
1581
1582 /// [950] Format: Marktlokations-ID
1583 // HAND-EDITED (#373): the condition sits on several LOC and RFF C506 1154
1584 // positions, not only on LOC+Z16; judge the value it is attached to.
1585 fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
1586 ctx.format_check_resolved(validate_malo_id)
1587 }
1588
1589 /// [951] Format: Zählpunktbezeichnung
1590 // HAND-EDITED (#373): the condition sits on several LOC and RFF C506 1154
1591 // positions, not only on LOC+Z19; judge the value it is attached to.
1592 fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
1593 ctx.format_check_resolved(validate_zahlpunkt)
1594 }
1595
1596 /// [952] Format: Gerätenummer nach DIN 43863-5
1597 // REVIEW: DIN 43863-5 Gerätenummer is an alphanumeric device identifier with a max length of 20 characters. No dedicated validator exists, so validate_max_length(20) is used as the structural check. The exact segment/element depends on the AHB table position — IDE is the most common carrier for device IDs in UTILMD Gas, with PIA as a fallback. Medium confidence due to uncertainty about the exact segment without the full AHB table context. (medium confidence)
1598 fn evaluate_952(&self, ctx: &EvaluationContext) -> ConditionResult {
1599 // Format: Gerätenummer nach DIN 43863-5 — alphanumeric, max 20 characters
1600 // DIN 43863-5 device numbers appear in IDE segment (element 1, component 0)
1601 // or in SG8 PIA segments for device identification
1602 let ide_segs = ctx.find_segments("IDE");
1603 if let Some(val) = ide_segs
1604 .first()
1605 .and_then(|s| s.elements.get(1))
1606 .and_then(|e| e.first())
1607 .filter(|v| !v.is_empty())
1608 {
1609 return validate_max_length(val, 20);
1610 }
1611 // Fallback: check PIA segment for device number (qualifier 5 = substitute product)
1612 let pia_segs = ctx.find_segments_with_qualifier("PIA", 0, "5");
1613 match pia_segs
1614 .first()
1615 .and_then(|s| s.elements.get(1))
1616 .and_then(|e| e.first())
1617 {
1618 Some(val) => validate_max_length(val, 20),
1619 None => ConditionResult::False, // segment absent → condition not applicable
1620 }
1621 }
1622
1623 /// [953] Format: Marktlokations-ID oder Zählpunktbezeichnung
1624 fn evaluate_953(&self, ctx: &EvaluationContext) -> ConditionResult {
1625 ctx.format_check("LOC", 1, 0, validate_malo_or_zahlpunkt)
1626 }
1627
1628 /// [2061] Segment bzw. Segmentgruppe ist genau einmal je SG4 IDE (Vorgang) anzugeben
1629 // REVIEW: Condition states the segment/group is to be given exactly once per SG4 IDE (Vorgang). As a boolean applicability condition, this is True when the IDE segment (Vorgangsidentifikator) is present, indicating the SG4 context exists. Cardinality enforcement (exactly once) is a structural rule beyond a simple boolean evaluator. (medium confidence)
1630 fn evaluate_2061(&self, ctx: &EvaluationContext) -> ConditionResult {
1631 ConditionResult::from(ctx.has_segment("IDE"))
1632 }
1633
1634 /// [2119] Je SG8 SEQ+Z13 (Smartmeter-Gateway) ist genau einmal die Segmentgruppe anzugeben
1635 fn evaluate_2119(&self, ctx: &EvaluationContext) -> ConditionResult {
1636 ctx.any_group_has_qualifier("SEQ", 0, "Z13", &["SG4", "SG8"])
1637 }
1638
1639 /// [2284] Für jede Messlokations-ID im SG5 LOC+172 (Meldepunkt) DE3225 genau einmal anzugeben
1640 fn evaluate_2284(&self, ctx: &EvaluationContext) -> ConditionResult {
1641 ctx.any_group_has_qualifier("LOC", 0, "172", &["SG4", "SG5"])
1642 }
1643
1644 /// [2286] Für jede SEQ+Z18 (Daten der Messlokation) mindestens einmal anzugeben
1645 fn evaluate_2286(&self, ctx: &EvaluationContext) -> ConditionResult {
1646 ctx.any_group_has_qualifier("SEQ", 0, "Z18", &["SG4", "SG8"])
1647 }
1648
1649 /// [2287] Für jede SEQ+Z03 (Zähleinrichtungsdaten) mindestens einmal anzugeben
1650 fn evaluate_2287(&self, ctx: &EvaluationContext) -> ConditionResult {
1651 ctx.any_group_has_qualifier("SEQ", 0, "Z03", &["SG4", "SG8"])
1652 }
1653
1654 /// [2335] Für jede SEQ+Z02 (OBIS-Daten der Marktlokation), welche im PIA+5 die OBIS-Kennzahl 7-20:99.33.17/ 7-0:33.86.0 übermittelt, genau einmal anzugeben
1655 // REVIEW: Condition states: to be given exactly once for each SEQ+Z02 (OBIS-Daten der Marktlokation) where PIA+5 transmits OBIS code 7-20:99.33.17 or 7-0:33.86.0. Per segment structure reference, PIA elements[1][0] (DE7140) holds the OBIS code. Checks message-wide PIA+5 for the target OBIS values, then verifies SEQ+Z02 is present. Since PIA is always nested in the same SG8 as its SEQ, message-wide PIA check is a valid approximation when group navigator is unavailable. (medium confidence)
1656 fn evaluate_2335(&self, ctx: &EvaluationContext) -> ConditionResult {
1657 {
1658 // Check for PIA+5 with specific OBIS codes 7-20:99.33.17 or 7-0:33.86.0 at elements[1][0]
1659 let pias = ctx.find_segments_with_qualifier("PIA", 0, "5");
1660 let has_target_obis = pias.iter().any(|pia| {
1661 pia.elements
1662 .get(1)
1663 .and_then(|e| e.first())
1664 .map(|v| v == "7-20:99.33.17" || v == "7-0:33.86.0")
1665 .unwrap_or(false)
1666 });
1667 if !has_target_obis {
1668 return ConditionResult::False;
1669 }
1670 // Also verify SEQ+Z02 (OBIS-Daten der Marktlokation) is present — PIA is always co-located in the same SG8
1671 ctx.any_group_has_qualifier("SEQ", 0, "Z02", &["SG4", "SG8"])
1672 }
1673 }
1674
1675 /// [2353] Für jede SEQ+Z09 (Mengenumwerter-Daten) mindestens einmal anzugeben
1676 fn evaluate_2353(&self, ctx: &EvaluationContext) -> ConditionResult {
1677 ctx.any_group_has_qualifier("SEQ", 0, "Z09", &["SG4", "SG8"])
1678 }
1679}