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