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