automapper_validation/generated/fv2510/quotes_conditions_fv2510.rs
1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2510/QUOTES_AHB_1_1_20250401.xml
4// Generated: 2026-03-12T11:11:29Z
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 QUOTES FV2510.
12pub struct QuotesConditionEvaluatorFV2510 {
13 // External condition IDs that require runtime context.
14 external_conditions: std::collections::HashSet<u32>,
15}
16
17impl Default for QuotesConditionEvaluatorFV2510 {
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(13);
23 external_conditions.insert(29);
24 external_conditions.insert(31);
25 external_conditions.insert(39);
26 external_conditions.insert(44);
27 external_conditions.insert(54);
28 external_conditions.insert(56);
29 external_conditions.insert(57);
30 external_conditions.insert(58);
31 external_conditions.insert(59);
32 external_conditions.insert(60);
33 external_conditions.insert(61);
34 external_conditions.insert(62);
35 external_conditions.insert(63);
36 external_conditions.insert(69);
37 external_conditions.insert(70);
38 external_conditions.insert(71);
39 external_conditions.insert(74);
40 external_conditions.insert(75);
41 external_conditions.insert(77);
42 external_conditions.insert(78);
43 external_conditions.insert(90);
44 external_conditions.insert(91);
45 external_conditions.insert(92);
46 external_conditions.insert(492);
47 external_conditions.insert(493);
48 external_conditions.insert(494);
49 external_conditions.insert(2066);
50 Self {
51 external_conditions,
52 }
53 }
54}
55
56impl ConditionEvaluator for QuotesConditionEvaluatorFV2510 {
57 fn message_type(&self) -> &str {
58 "QUOTES"
59 }
60
61 fn format_version(&self) -> &str {
62 "FV2510"
63 }
64
65 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
66 match condition {
67 1 => self.evaluate_1(ctx),
68 2 => self.evaluate_2(ctx),
69 3 => self.evaluate_3(ctx),
70 4 => self.evaluate_4(ctx),
71 5 => self.evaluate_5(ctx),
72 8 => self.evaluate_8(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 15 => self.evaluate_15(ctx),
79 16 => self.evaluate_16(ctx),
80 17 => self.evaluate_17(ctx),
81 18 => self.evaluate_18(ctx),
82 20 => self.evaluate_20(ctx),
83 21 => self.evaluate_21(ctx),
84 22 => self.evaluate_22(ctx),
85 25 => self.evaluate_25(ctx),
86 26 => self.evaluate_26(ctx),
87 27 => self.evaluate_27(ctx),
88 28 => self.evaluate_28(ctx),
89 29 => self.evaluate_29(ctx),
90 30 => self.evaluate_30(ctx),
91 31 => self.evaluate_31(ctx),
92 39 => self.evaluate_39(ctx),
93 44 => self.evaluate_44(ctx),
94 49 => self.evaluate_49(ctx),
95 50 => self.evaluate_50(ctx),
96 51 => self.evaluate_51(ctx),
97 52 => self.evaluate_52(ctx),
98 53 => self.evaluate_53(ctx),
99 54 => self.evaluate_54(ctx),
100 55 => self.evaluate_55(ctx),
101 56 => self.evaluate_56(ctx),
102 57 => self.evaluate_57(ctx),
103 58 => self.evaluate_58(ctx),
104 59 => self.evaluate_59(ctx),
105 60 => self.evaluate_60(ctx),
106 61 => self.evaluate_61(ctx),
107 62 => self.evaluate_62(ctx),
108 63 => self.evaluate_63(ctx),
109 65 => self.evaluate_65(ctx),
110 66 => self.evaluate_66(ctx),
111 67 => self.evaluate_67(ctx),
112 68 => self.evaluate_68(ctx),
113 69 => self.evaluate_69(ctx),
114 70 => self.evaluate_70(ctx),
115 71 => self.evaluate_71(ctx),
116 72 => self.evaluate_72(ctx),
117 73 => self.evaluate_73(ctx),
118 74 => self.evaluate_74(ctx),
119 75 => self.evaluate_75(ctx),
120 77 => self.evaluate_77(ctx),
121 78 => self.evaluate_78(ctx),
122 79 => self.evaluate_79(ctx),
123 80 => self.evaluate_80(ctx),
124 81 => self.evaluate_81(ctx),
125 82 => self.evaluate_82(ctx),
126 83 => self.evaluate_83(ctx),
127 84 => self.evaluate_84(ctx),
128 85 => self.evaluate_85(ctx),
129 86 => self.evaluate_86(ctx),
130 87 => self.evaluate_87(ctx),
131 88 => self.evaluate_88(ctx),
132 90 => self.evaluate_90(ctx),
133 91 => self.evaluate_91(ctx),
134 92 => self.evaluate_92(ctx),
135 93 => self.evaluate_93(ctx),
136 94 => self.evaluate_94(ctx),
137 490 => self.evaluate_490(ctx),
138 491 => self.evaluate_491(ctx),
139 492 => self.evaluate_492(ctx),
140 493 => self.evaluate_493(ctx),
141 494 => self.evaluate_494(ctx),
142 500 => self.evaluate_500(ctx),
143 501 => self.evaluate_501(ctx),
144 502 => self.evaluate_502(ctx),
145 503 => self.evaluate_503(ctx),
146 504 => self.evaluate_504(ctx),
147 505 => self.evaluate_505(ctx),
148 506 => self.evaluate_506(ctx),
149 507 => self.evaluate_507(ctx),
150 511 => self.evaluate_511(ctx),
151 512 => self.evaluate_512(ctx),
152 513 => self.evaluate_513(ctx),
153 514 => self.evaluate_514(ctx),
154 516 => self.evaluate_516(ctx),
155 517 => self.evaluate_517(ctx),
156 518 => self.evaluate_518(ctx),
157 519 => self.evaluate_519(ctx),
158 520 => self.evaluate_520(ctx),
159 521 => self.evaluate_521(ctx),
160 903 => self.evaluate_903(ctx),
161 906 => self.evaluate_906(ctx),
162 908 => self.evaluate_908(ctx),
163 911 => self.evaluate_911(ctx),
164 912 => self.evaluate_912(ctx),
165 914 => self.evaluate_914(ctx),
166 931 => self.evaluate_931(ctx),
167 932 => self.evaluate_932(ctx),
168 933 => self.evaluate_933(ctx),
169 934 => self.evaluate_934(ctx),
170 935 => self.evaluate_935(ctx),
171 939 => self.evaluate_939(ctx),
172 940 => self.evaluate_940(ctx),
173 942 => self.evaluate_942(ctx),
174 950 => self.evaluate_950(ctx),
175 951 => self.evaluate_951(ctx),
176 959 => self.evaluate_959(ctx),
177 960 => self.evaluate_960(ctx),
178 961 => self.evaluate_961(ctx),
179 962 => self.evaluate_962(ctx),
180 2042 => self.evaluate_2042(ctx),
181 2060 => self.evaluate_2060(ctx),
182 2061 => self.evaluate_2061(ctx),
183 2062 => self.evaluate_2062(ctx),
184 2063 => self.evaluate_2063(ctx),
185 2064 => self.evaluate_2064(ctx),
186 2066 => self.evaluate_2066(ctx),
187 2068 => self.evaluate_2068(ctx),
188 2069 => self.evaluate_2069(ctx),
189 2070 => self.evaluate_2070(ctx),
190 2071 => self.evaluate_2071(ctx),
191 2072 => self.evaluate_2072(ctx),
192 2073 => self.evaluate_2073(ctx),
193 2074 => self.evaluate_2074(ctx),
194 2075 => self.evaluate_2075(ctx),
195 2076 => self.evaluate_2076(ctx),
196 _ => ConditionResult::Unknown,
197 }
198 }
199
200 fn is_external(&self, condition: u32) -> bool {
201 self.external_conditions.contains(&condition)
202 }
203 fn is_known(&self, condition: u32) -> bool {
204 matches!(
205 condition,
206 1 | 2
207 | 3
208 | 4
209 | 5
210 | 8
211 | 9
212 | 10
213 | 11
214 | 12
215 | 13
216 | 15
217 | 16
218 | 17
219 | 18
220 | 20
221 | 21
222 | 22
223 | 25
224 | 26
225 | 27
226 | 28
227 | 29
228 | 30
229 | 31
230 | 39
231 | 44
232 | 49
233 | 50
234 | 51
235 | 52
236 | 53
237 | 54
238 | 55
239 | 56
240 | 57
241 | 58
242 | 59
243 | 60
244 | 61
245 | 62
246 | 63
247 | 65
248 | 66
249 | 67
250 | 68
251 | 69
252 | 70
253 | 71
254 | 72
255 | 73
256 | 74
257 | 75
258 | 77
259 | 78
260 | 79
261 | 80
262 | 81
263 | 82
264 | 83
265 | 84
266 | 85
267 | 86
268 | 87
269 | 88
270 | 90
271 | 91
272 | 92
273 | 93
274 | 94
275 | 490
276 | 491
277 | 492
278 | 493
279 | 494
280 | 500
281 | 501
282 | 502
283 | 503
284 | 504
285 | 505
286 | 506
287 | 507
288 | 511
289 | 512
290 | 513
291 | 514
292 | 516
293 | 517
294 | 518
295 | 519
296 | 520
297 | 521
298 | 903
299 | 906
300 | 908
301 | 911
302 | 912
303 | 914
304 | 931
305 | 932
306 | 933
307 | 934
308 | 935
309 | 939
310 | 940
311 | 942
312 | 950
313 | 951
314 | 959
315 | 960
316 | 961
317 | 962
318 | 2042
319 | 2060
320 | 2061
321 | 2062
322 | 2063
323 | 2064
324 | 2066
325 | 2068
326 | 2069
327 | 2070
328 | 2071
329 | 2072
330 | 2073
331 | 2074
332 | 2075
333 | 2076
334 )
335 }
336}
337
338impl QuotesConditionEvaluatorFV2510 {
339 /// [31] Es sind nur die Artikelnummern erlaubt, die in der Codeliste der Artikelnummern des BDEW mit dem entsprechenden Prüfidentifikator versehen sind.
340 /// EXTERNAL: Requires context from outside the message.
341 fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
342 ctx.external.evaluate("valid_artikel_nummer_for_pid")
343 }
344
345 /// [59] Es sind nur die Konfigurations-Produkte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.1 „Konfigurationsprodukte Schaltzeitdefinition“ enthalten sind.
346 /// EXTERNAL: Requires context from outside the message.
347 fn evaluate_59(&self, ctx: &EvaluationContext) -> ConditionResult {
348 ctx.external
349 .evaluate("valid_konfigurations_produkt_schaltzeitdefinition")
350 }
351
352 /// [70] Es sind nur die Messprodukt-Position-Codes erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.7 „Art der Werte für Messprodukte nach Typ 2“ enthalten sind.
353 /// EXTERNAL: Requires context from outside the message.
354 fn evaluate_70(&self, ctx: &EvaluationContext) -> ConditionResult {
355 ctx.external
356 .evaluate("valid_messprodukt_position_code_typ2")
357 }
358
359 /// [90] Es sind nur die Produkt-Codes erlaubt, die in der Codeliste der Konfigurationen im Kapitel 7 "Produkte zur Bestellung einer Änderung an einer Lokation" die in der Spalte "Ebene" mit dem Wert "Mess...
360 /// EXTERNAL: Requires context from outside the message.
361 fn evaluate_90(&self, ctx: &EvaluationContext) -> ConditionResult {
362 ctx.external
363 .evaluate("valid_produkt_code_aenderung_lokation_messlokation")
364 }
365
366 /// [91] Es sind nur die Produkt-Codes erlaubt, die in der Codeliste der Konfigurationen im Kapitel 7 "Produkte zur Bestellung einer Änderung an einer Lokation" die in der Spalte "Ebene" mit dem Wert "Netz...
367 /// EXTERNAL: Requires context from outside the message.
368 fn evaluate_91(&self, ctx: &EvaluationContext) -> ConditionResult {
369 ctx.external
370 .evaluate("valid_produkt_code_aenderung_lokation_netzlokation")
371 }
372
373 /// [92] Es sind nur die Produkt-Codes erlaubt, die in der Codeliste der Konfigurationen im Kapitel 7 "Produkte zur Bestellung einer Änderung an einer Lokation" die in der Spalte "Ebene" mit dem Wert "Steu...
374 /// EXTERNAL: Requires context from outside the message.
375 fn evaluate_92(&self, ctx: &EvaluationContext) -> ConditionResult {
376 ctx.external
377 .evaluate("product_code_allowed_steuerbare_ressource")
378 }
379
380 /// [93] Wenn RNG+Z03 (verbindliche Mengenangabe) in dieser SG31 nicht vorhanden
381 fn evaluate_93(&self, ctx: &EvaluationContext) -> ConditionResult {
382 ConditionResult::from(ctx.count_qualified_in_group("RNG", 0, "Z03", &["SG4", "SG31"]) == 0)
383 }
384
385 /// [94] Wenn RNG+Z04 (unverbindliche Mengenangabe) in dieser SG31 nicht vorhanden
386 fn evaluate_94(&self, ctx: &EvaluationContext) -> ConditionResult {
387 ConditionResult::from(ctx.count_qualified_in_group("RNG", 0, "Z04", &["SG4", "SG31"]) == 0)
388 }
389
390 /// [517] Hinweis: Wert aus BGM+Z93 (Bestellung eines Angebots Änderung der Technik der Lokation) DE1004 der REQOTE mit der die Anfrage Angebot Änderung Technik erfolgt ist.
391 fn evaluate_517(&self, _ctx: &EvaluationContext) -> ConditionResult {
392 // Hinweis: Wert aus BGM+Z93 DE1004 der REQOTE — informational note about value origin, always applies
393 ConditionResult::True
394 }
395
396 /// [518] Hinweis: Angabe gemäß Preisblatt B des MSB.
397 fn evaluate_518(&self, _ctx: &EvaluationContext) -> ConditionResult {
398 // Hinweis: Angabe gemäß Preisblatt B des MSB — informational note, always applies
399 ConditionResult::True
400 }
401
402 /// [519] Hinweis: Wert aus BGM DE1004 der PRICAT mit der das Preisblatt B des MSB, auf dem dieses Angebot basiert, übermittelt wurde.
403 fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
404 // Hinweis: Wert aus BGM DE1004 der PRICAT mit der das Preisblatt B des MSB, auf dem dieses Angebot basiert, übermittelt wurde.
405 ConditionResult::True
406 }
407
408 /// [520] Hinweis: Angabe des Beginnzeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann.
409 fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
410 // Hinweis: Angabe des Beginnzeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann.
411 ConditionResult::True
412 }
413
414 /// [521] Hinweis: Angabe des Endezeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann..
415 fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
416 // Hinweis: Angabe des Endezeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann.
417 ConditionResult::True
418 }
419
420 /// [959] Format: n13-n2
421 fn evaluate_959(&self, ctx: &EvaluationContext) -> ConditionResult {
422 ctx.format_check_qualified("PIA", 0, "Z02", 1, 0, |val| validate_artikel_pattern(val, &[13, 2]))
423 }
424
425 /// [2066] Diese SG28 ist so oft zu wiederholen, wie zu den unterschiedlichen Messprodukt-Position-Codes zu dem innerhalb derselben SG27 LIN im PIA+5 DE7140 (Erforderliches Produkt Konfigurationserlaubnis fü...
426 /// EXTERNAL: Requires context from outside the message.
427 fn evaluate_2066(&self, ctx: &EvaluationContext) -> ConditionResult {
428 ctx.external
429 .evaluate("smgw_schwellwert_config_product_count")
430 }
431
432 /// [2074] Pro Nachricht ist diese SG27 so oft zu wiederholen, bis für jedes angefragte Produkt derselben SG27 aus der Anfrage eine Angebotsposition angegeben wurde.
433 fn evaluate_2074(&self, _ctx: &EvaluationContext) -> ConditionResult {
434 // Hinweis: Pro Nachricht ist diese SG27 so oft zu wiederholen, bis für jedes angefragte Produkt
435 // derselben SG27 aus der Anfrage eine Angebotsposition angegeben wurde.
436 // Informational cardinality rule about SG27 repetition — always applies.
437 ConditionResult::True
438 }
439
440 /// [2075] Pro SG27 LIN kann die SG27 PIA+Z02 (Artikel-ID) bis zu zweimal angegeben werden. Einmal für die Artikel-ID gemäß Preisblatt B des MSB und einmal für die Artikel-ID "9991000003030-01" (Pauschale...
441 fn evaluate_2075(&self, _ctx: &EvaluationContext) -> ConditionResult {
442 // Hinweis: Pro SG27 LIN kann die SG27 PIA+Z02 (Artikel-ID) bis zu zweimal angegeben werden —
443 // einmal für die reguläre Artikel-ID gemäß Preisblatt B und einmal für die Pauschale Kosten-ID
444 // '9991000003030-01'. Ob die zweite Angabe erlaubt ist, hängt vom Preisblatt B des MSB ab (extern).
445 // Informational cardinality rule about max 2 PIA+Z02 occurrences per SG27.
446 ConditionResult::True
447 }
448
449 /// [2076] Pro SG27 LIN kann die SG31 Preisangabe zur Position nur einmal angegeben werden. Die Angabe der Preisangaben zur Position bezieht sich auf die Artikel-ID bei der es sich nicht um die Artikel-ID "99...
450 fn evaluate_2076(&self, _ctx: &EvaluationContext) -> ConditionResult {
451 // Hinweis: Pro SG27 LIN kann die SG31 Preisangabe zur Position nur einmal angegeben werden.
452 // Die Preisangabe bezieht sich auf die Artikel-ID, die nicht die '9991000003030-01'
453 // (Pauschale Kosten für das Scheitern der Änderung der Technik) ist.
454 // Informational cardinality rule — SG31 appears at most once per SG27 LIN.
455 ConditionResult::True
456 }
457
458 /// [1] Wenn Position nicht angeboten werden kann, weil rechtliche Regelungen oder Rechte Dritter dem entgegenstehen
459 /// EXTERNAL: Requires context from outside the message.
460 fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
461 ctx.external.evaluate("position_legally_blocked")
462 }
463
464 /// [2] Wenn in derselben SG27 LIN IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Code Z09 (Kann nicht angeboten werden) nicht vorhanden.
465 // REVIEW: Negates presence of IMD with DE7081=Z09 (Kann nicht angeboten werden) at elements[1][0] in any SG27 group instance. 'nicht vorhanden' means condition is True when Z09 is absent. any_group_has_qualifier falls back to message-wide if no group navigator is available. (medium confidence)
466 fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
467 match ctx.any_group_has_qualifier("IMD", 1, "Z09", &["SG27"]) {
468 ConditionResult::True => ConditionResult::False,
469 ConditionResult::False => ConditionResult::True,
470 ConditionResult::Unknown => ConditionResult::Unknown,
471 }
472 }
473
474 /// [3] Wenn CCI+++Z64 vorhanden ist
475 fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
476 ctx.has_qualifier("CCI", 2, "Z64")
477 }
478
479 /// [4] Wenn am Gerät vorhanden und abweichend von Gerätenummer
480 /// EXTERNAL: Requires context from outside the message.
481 fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
482 ctx.external
483 .evaluate("device_attribute_differs_from_device_number")
484 }
485
486 /// [5] Wenn in derselben SG27 LIN die Artikelnummer 9990001000649 vorhanden ist
487 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000649 at elements[2][0] (C212 item number identification, first component). LIN is the entry segment of SG27 so each rep has exactly one LIN. (medium confidence)
488 fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
489 ctx.any_group_has_qualifier("LIN", 2, "9990001000649", &["SG27"])
490 }
491
492 /// [8] Wenn SG28 CCI+++E13 CAV+EHZ vorhanden
493 // REVIEW: CCI+++E13 maps to elements[2][0]=E13; CAV+EHZ maps to elements[0][0]=EHZ. Checks that in the same SG28 group instance, CCI with characteristic code E13 AND CAV with code EHZ both occur. Group path SG28 may need parent prefix depending on MIG structure. (medium confidence)
494 fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
495 ctx.any_group_has_co_occurrence("CCI", 2, &["E13"], "CAV", 0, 0, &["EHZ"], &["SG28"])
496 }
497
498 /// [9] Wenn in derselben SG27 LIN die Artikelnummer 9990001000657 vorhanden ist
499 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000657 at elements[2][0] (C212 first component). Same pattern as condition 5. (medium confidence)
500 fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
501 ctx.any_group_has_qualifier("LIN", 2, "9990001000657", &["SG27"])
502 }
503
504 /// [10] Wenn SG28 CAV+MIW/MPW/MBW vorhanden
505 fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
506 ctx.any_group_has_any_qualifier("CAV", 0, &["MIW", "MPW", "MBW"], &["SG28"])
507 }
508
509 /// [11] Wenn in derselben SG27 LIN die Artikelnummer 9990001000665 vorhanden ist
510 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000665 at elements[2][0]. Same pattern as condition 5. (medium confidence)
511 fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
512 ctx.any_group_has_qualifier("LIN", 2, "9990001000665", &["SG27"])
513 }
514
515 /// [12] Wenn in derselben SG27 LIN die Artikelnummer 9990001000673 vorhanden ist
516 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000673 at elements[2][0]. Same pattern as condition 5. (medium confidence)
517 fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
518 ctx.any_group_has_qualifier("LIN", 2, "9990001000673", &["SG27"])
519 }
520
521 /// [13] Wenn am Gerät vorhanden
522 /// EXTERNAL: Requires context from outside the message.
523 fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
524 ctx.external.evaluate("device_attribute_present")
525 }
526
527 /// [15] Wenn in derselben SG27 LIN die Artikelnummer 9990001000772 vorhanden ist
528 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000772 at elements[2][0]. Same pattern as condition 5. (medium confidence)
529 fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
530 ctx.any_group_has_qualifier("LIN", 2, "9990001000772", &["SG27"])
531 }
532
533 /// [16] Wenn in derselben SG27 LIN die Artikelnummer 9990001000780 vorhanden ist
534 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000780 at elements[2][0]. Same pattern as condition 5. (medium confidence)
535 fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
536 ctx.any_group_has_qualifier("LIN", 2, "9990001000780", &["SG27"])
537 }
538
539 /// [17] Wenn das Angebot per REQOTE angefragt wurde
540 // REVIEW: A QUOTES message triggered by a REQOTE contains a reference RFF+AAV in SG1 (Nachrichtennummer der Anfrage, qualifier AAV at elements[0][0]). Presence of RFF+AAV indicates the quote was requested via REQOTE. (medium confidence)
541 fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
542 ctx.has_qualifier("RFF", 0, "AAV")
543 }
544
545 /// [18] Wenn Angebot mehrere Marktlokationen abdeckt, an welchen der identische Anschlussnutzer vorhanden ist und deren messtechnische Einordnung iMS ist (Marktlokation aus SG11 LOC+172 und alle Marktlokat...
546 // REVIEW: RFF+Z18 ('Referenz auf ID weiterer Marktlokationen') is the direct EDIFACT indicator that additional market locations are covered, which is the core structural requirement. However, the additional qualifiers — identical Anschlussnutzer and messtechnische Einordnung iMS — are business attributes about the referenced locations not determinable from message content alone. The RFF+Z18 check is the best message-derivable approximation. (medium confidence)
547 fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
548 // Check if offer references additional market locations via RFF+Z18
549 // RFF+Z18 = 'Referenz auf ID weiterer Marktlokationen' — presence indicates multiple locations covered
550 // Note: the 'identical connection user' and 'iMS classification' sub-conditions require external context
551 ctx.has_qualifier("RFF", 0, "Z18")
552 }
553
554 /// [20] Wenn IMD++Z33 vorhanden
555 fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
556 ctx.has_qualifier("IMD", 1, "Z33")
557 }
558
559 /// [21] Wenn IMD++Z34 vorhanden
560 fn evaluate_21(&self, ctx: &EvaluationContext) -> ConditionResult {
561 ctx.has_qualifier("IMD", 1, "Z34")
562 }
563
564 /// [22] Wenn CCI+++Z75 vorhanden ist
565 fn evaluate_22(&self, ctx: &EvaluationContext) -> ConditionResult {
566 ctx.has_qualifier("CCI", 2, "Z75")
567 }
568
569 /// [25] Wenn SG28 CCI+++E13 CAV+MME vorhanden
570 fn evaluate_25(&self, ctx: &EvaluationContext) -> ConditionResult {
571 ctx.any_group_has_co_occurrence("CCI", 2, &["E13"], "CAV", 0, 0, &["MME"], &["SG28"])
572 }
573
574 /// [26] Wenn DTM+203 nicht vorhanden
575 fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
576 ctx.lacks_qualifier("DTM", 0, "203")
577 }
578
579 /// [27] Wenn DTM+469 nicht vorhanden
580 fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
581 ctx.lacks_qualifier("DTM", 0, "469")
582 }
583
584 /// [28] Wenn RFF+AAV vorhanden
585 fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
586 ctx.has_qualifier("RFF", 0, "AAV")
587 }
588
589 /// [29] Wenn DTM+469 in Anfrage (REQOTE) vorhanden war
590 /// EXTERNAL: Requires context from outside the message.
591 fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
592 ctx.external.evaluate("reqote_had_dtm_469")
593 }
594
595 /// [30] Wenn SG27 IMD+Z09 vorhanden
596 fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
597 ctx.any_group_has_qualifier("IMD", 0, "Z09", &["SG27"])
598 }
599
600 /// [39] MP-ID nur aus Sparte Strom
601 /// EXTERNAL: Requires context from outside the message.
602 fn evaluate_39(&self, ctx: &EvaluationContext) -> ConditionResult {
603 ctx.external.evaluate("mp_id_is_strom")
604 }
605
606 /// [44] Wenn MSBA nicht Eigentümer des Gerätes/der Geräte ist
607 /// EXTERNAL: Requires context from outside the message.
608 fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
609 ctx.external.evaluate("msba_is_not_device_owner")
610 }
611
612 /// [49] Wenn SG27 LIN++Z64 (Erforderliches Produkt Schaltzeitdefinitionen) vorhanden
613 fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
614 ctx.any_group_has_qualifier("LIN", 1, "Z64", &["SG27"])
615 }
616
617 /// [50] Wenn SG27 LIN++Z65 (Erforderliches Produkt Leistungskurvendefinitionen) vorhanden
618 fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
619 ctx.any_group_has_qualifier("LIN", 1, "Z65", &["SG27"])
620 }
621
622 /// [51] Wenn SG27 LIN++Z66 (Erforderliches Produkt Ad-hoc-Steuerkanal) vorhanden
623 fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
624 ctx.any_group_has_qualifier("LIN", 1, "Z66", &["SG27"])
625 }
626
627 /// [52] Wenn SG27 LIN++Z67 (Erforderliches Messprodukt für Werte nach Typ 2 aus Backend) vorhanden
628 fn evaluate_52(&self, ctx: &EvaluationContext) -> ConditionResult {
629 ctx.has_qualifier("LIN", 1, "Z67")
630 }
631
632 /// [53] Wenn SG27 LIN++Z68 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ2 aus SMGW) vorhanden
633 fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
634 ctx.has_qualifier("LIN", 1, "Z68")
635 }
636
637 /// [54] Wenn in der Anfrage zum Angebot einer Konfiguration vorhanden
638 /// EXTERNAL: Requires context from outside the message.
639 fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
640 ctx.external.evaluate("configuration_in_request")
641 }
642
643 /// [55] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Marktlokation angegeben ist.
644 fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
645 // Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Marktlokation angegeben ist.
646 // LOC elements[0][0] == "172" (qualifier), elements[1][0] == DE3225 (Identifikator)
647 let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
648 match locs
649 .first()
650 .and_then(|s| s.elements.get(1))
651 .and_then(|e| e.first())
652 {
653 Some(val) => validate_malo_id(val),
654 None => ConditionResult::False, // segment absent → condition not applicable
655 }
656 }
657
658 /// [56] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Messlokation angegeben ist.
659 /// EXTERNAL: Requires context from outside the message.
660 // REVIEW: Condition asks whether the ID in LOC+172 DE3225 (elements[1][0]) belongs to a Messlokation. While Messlokation IDs have a known 33-char format in Germany, the semantic type assignment (Messlokation vs other location types) ultimately requires external business context or a type registry lookup. Marked external. (medium confidence)
661 fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
662 ctx.external.evaluate("loc_172_id_is_messlokation")
663 }
664
665 /// [57] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Netzlokation angegeben ist.
666 /// EXTERNAL: Requires context from outside the message.
667 // REVIEW: Condition asks whether the ID in LOC+172 DE3225 (elements[1][0]) belongs to a Netzlokation. The ID type cannot be determined from EDIFACT message content alone — requires external type registry or format-based classification beyond the available validators. (medium confidence)
668 fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
669 ctx.external.evaluate("loc_172_id_is_netzlokation")
670 }
671
672 /// [58] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Steuerbaren Ressource angegeben ist.
673 /// EXTERNAL: Requires context from outside the message.
674 // REVIEW: Condition asks whether the ID in LOC+172 DE3225 (elements[1][0]) belongs to a Steuerbare Ressource. Same reasoning as [56] and [57] — semantic type assignment requires external business context. (medium confidence)
675 fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
676 ctx.external.evaluate("loc_172_id_is_steuerbare_ressource")
677 }
678
679 /// [60] Es sind nur die Konfigurations-Produkte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.2 „Konfigurationsprodukte Leistungskurvendefinition“ enthalten sind.
680 /// EXTERNAL: Requires context from outside the message.
681 fn evaluate_60(&self, ctx: &EvaluationContext) -> ConditionResult {
682 ctx.external.evaluate("config_product_leistungskurve")
683 }
684
685 /// [61] Es sind nur die Konfigurations-Produkte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.3 „Konfigurationsprodukte Ad-hoc-Steuerkanal“ enthalten sind.
686 /// EXTERNAL: Requires context from outside the message.
687 fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
688 ctx.external.evaluate("config_product_adhoc_steuerkanal")
689 }
690
691 /// [62] Es sind nur die Messprodukte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.5 „Messprodukte für Werte nach Typ 2 aus Backend für LF und NB“ enthalten sind.
692 /// EXTERNAL: Requires context from outside the message.
693 fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
694 ctx.external.evaluate("messprodukts_typ2_backend_lf_nb")
695 }
696
697 /// [63] Es sind nur die Messprodukte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.4 „Messprodukte mit Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW“ enthalten sind.
698 /// EXTERNAL: Requires context from outside the message.
699 fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
700 ctx.external.evaluate("messprodukts_typ2_smgw")
701 }
702
703 /// [65] Wenn DTM+203 (Ausführungsdatum) im DE2380 < 202312312300?+00
704 // REVIEW: DTM+203 DE2380 is at elements[0][1]. Lexicographic comparison works for YYYYMMDDHHMI+ZZ format since all values use the same timezone offset and format. The EDIFACT escape `?+` in the threshold represents a literal `+`, giving the threshold string "202312312300+00". (medium confidence)
705 fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
706 let segs = ctx.find_segments_with_qualifier("DTM", 0, "203");
707 match segs.first() {
708 Some(dtm) => {
709 match dtm
710 .elements
711 .first()
712 .and_then(|e| e.get(1))
713 .map(|s| s.as_str())
714 {
715 Some(value) if !value.is_empty() => {
716 ConditionResult::from(value < "202312312300+00")
717 }
718 _ => ConditionResult::Unknown,
719 }
720 }
721 None => ConditionResult::False, // segment absent → condition not applicable
722 }
723 }
724
725 /// [66] Wenn DTM+469 (Beginn zum (nächstmöglichen Termin)) im DE2380 < 202312312300?+00
726 // REVIEW: DTM+469 DE2380 is at elements[0][1]. Same lexicographic comparison as condition 65, applied to the Beginn zum nächstmöglichen Termin qualifier 469. (medium confidence)
727 fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
728 let segs = ctx.find_segments_with_qualifier("DTM", 0, "469");
729 match segs.first() {
730 Some(dtm) => {
731 match dtm
732 .elements
733 .first()
734 .and_then(|e| e.get(1))
735 .map(|s| s.as_str())
736 {
737 Some(value) if !value.is_empty() => {
738 ConditionResult::from(value < "202312312300+00")
739 }
740 _ => ConditionResult::Unknown,
741 }
742 }
743 None => ConditionResult::False, // segment absent → condition not applicable
744 }
745 }
746
747 /// [67] Wenn DTM+203 (Ausführungsdatum) im DE2380 ≥ 202312312300?+00
748 // REVIEW: DTM+203 DE2380 is at elements[0][1]. Logical complement of condition 65 — True when Ausführungsdatum >= 2023-12-31 23:00 UTC. Lexicographic comparison is valid for this date format. (medium confidence)
749 fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
750 let segs = ctx.find_segments_with_qualifier("DTM", 0, "203");
751 match segs.first() {
752 Some(dtm) => {
753 match dtm
754 .elements
755 .first()
756 .and_then(|e| e.get(1))
757 .map(|s| s.as_str())
758 {
759 Some(value) if !value.is_empty() => {
760 ConditionResult::from(value >= "202312312300+00")
761 }
762 _ => ConditionResult::Unknown,
763 }
764 }
765 None => ConditionResult::False, // segment absent → condition not applicable
766 }
767 }
768
769 /// [68] Wenn DTM+469 (Beginn zum (nächstmöglichen Termin)) im DE2380 ≥ 202312312300?+00
770 // REVIEW: DTM+469 DE2380 (elements[0][1]) compared lexicographically — works for ISO datetime strings with fixed-width format. EDIFACT ?+ escape resolves to literal + in parsed value. (medium confidence)
771 fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
772 let segs = ctx.find_segments_with_qualifier("DTM", 0, "469");
773 if segs.is_empty() {
774 return ConditionResult::Unknown;
775 }
776 for seg in segs {
777 let val = seg
778 .elements
779 .get(0)
780 .and_then(|e| e.get(1))
781 .map(|s| s.as_str())
782 .unwrap_or("");
783 if val >= "202312312300+00" {
784 return ConditionResult::True;
785 }
786 }
787 ConditionResult::False
788 }
789
790 /// [69] Es sind nur Werte aus der EDI@Energy Codeliste der Artikelnummern und Artikel-ID erlaubt, die im Kapitel 3.5 "Abrechnung Messstellenbetrieb für die Sparte Strom" genannt sind.
791 /// EXTERNAL: Requires context from outside the message.
792 fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
793 ctx.external
794 .evaluate("artikel_id_abrechnung_messstellenbetrieb_strom")
795 }
796
797 /// [71] Wenn innerhalb derselben SG27 LIN im PIA+5 DE7140 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW) ein Produkt angegeben ist, das in der Codeliste der Konfigurationen...
798 /// EXTERNAL: Requires context from outside the message.
799 fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
800 ctx.external.evaluate("pia5_product_is_threshold_trigger")
801 }
802
803 /// [72] wenn im DE3155 in demselben COM der Code EM vorhanden ist
804 fn evaluate_72(&self, ctx: &EvaluationContext) -> ConditionResult {
805 let segs = ctx.find_segments("COM");
806 if segs.is_empty() {
807 return ConditionResult::Unknown;
808 }
809 ConditionResult::from(segs.iter().any(|seg| {
810 seg.elements
811 .first()
812 .and_then(|e| e.get(1))
813 .is_some_and(|v| v == "EM")
814 }))
815 }
816
817 /// [73] wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
818 fn evaluate_73(&self, ctx: &EvaluationContext) -> ConditionResult {
819 let segs = ctx.find_segments("COM");
820 if segs.is_empty() {
821 return ConditionResult::Unknown;
822 }
823 ConditionResult::from(segs.iter().any(|seg| {
824 seg.elements
825 .first()
826 .and_then(|e| e.get(1))
827 .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
828 }))
829 }
830
831 /// [74] Es sind nur die Messprodukte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.6.1 "Werte nach Typ 2 aus Backend" enthalten sind.
832 /// EXTERNAL: Requires context from outside the message.
833 fn evaluate_74(&self, ctx: &EvaluationContext) -> ConditionResult {
834 ctx.external.evaluate("product_in_typ2_backend_codelist")
835 }
836
837 /// [75] Es sind nur die Messprodukte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.6.2 "Werte nach Typ 2 aus SMGW" enthalten sind.
838 /// EXTERNAL: Requires context from outside the message.
839 fn evaluate_75(&self, ctx: &EvaluationContext) -> ConditionResult {
840 ctx.external.evaluate("product_in_typ2_smgw_codelist")
841 }
842
843 /// [77] Wenn in der Anfrage nach Werten ein Messprodukt enthalten war, das in der Codeliste der Konfigurationen im Kapitel 4.6.1 "Werte nach Typ 2 aus Backend" enthalten ist.
844 /// EXTERNAL: Requires context from outside the message.
845 fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
846 ctx.external
847 .evaluate("request_contained_typ2_backend_product")
848 }
849
850 /// [78] Wenn in der Anfrage nach Werten ein Messprodukt enthalten war, das in der Codeliste der Konfigurationen im Kapitel 4.6.2 "Werte nach Typ 2 aus SMWG" enthalten ist.
851 /// EXTERNAL: Requires context from outside the message.
852 fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
853 ctx.external.evaluate("request_contained_typ2_smgw_product")
854 }
855
856 /// [79] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z07 (Kauf) vorhanden.
857 fn evaluate_79(&self, ctx: &EvaluationContext) -> ConditionResult {
858 let segs = ctx.find_segments("IMD");
859 if segs.is_empty() {
860 return ConditionResult::Unknown;
861 }
862 ConditionResult::from(segs.iter().any(|seg| {
863 seg.elements
864 .get(1)
865 .and_then(|e| e.first())
866 .is_some_and(|v| v == "Z07")
867 }))
868 }
869
870 /// [80] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z08 (Nutzungsüberlassung) vorhanden.
871 fn evaluate_80(&self, ctx: &EvaluationContext) -> ConditionResult {
872 let segs = ctx.find_segments("IMD");
873 if segs.is_empty() {
874 return ConditionResult::Unknown;
875 }
876 ConditionResult::from(segs.iter().any(|seg| {
877 seg.elements
878 .get(1)
879 .and_then(|e| e.first())
880 .is_some_and(|v| v == "Z08")
881 }))
882 }
883
884 /// [81] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z33 (Angebot auf Basis Preisblatt) vorhanden.
885 fn evaluate_81(&self, ctx: &EvaluationContext) -> ConditionResult {
886 let segs = ctx.find_segments("IMD");
887 if segs.is_empty() {
888 return ConditionResult::Unknown;
889 }
890 ConditionResult::from(segs.iter().any(|seg| {
891 seg.elements
892 .get(1)
893 .and_then(|e| e.first())
894 .is_some_and(|v| v == "Z33")
895 }))
896 }
897
898 /// [82] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z34 (Individuelles Angebot) vorhanden.
899 fn evaluate_82(&self, ctx: &EvaluationContext) -> ConditionResult {
900 let segs = ctx.find_segments("IMD");
901 if segs.is_empty() {
902 return ConditionResult::Unknown;
903 }
904 ConditionResult::from(segs.iter().any(|seg| {
905 seg.elements
906 .get(1)
907 .and_then(|e| e.first())
908 .is_some_and(|v| v == "Z34")
909 }))
910 }
911
912 /// [83] Wenn in derselben SG27 LIN ein PIA+Z02 (Artikel-ID) mit einer Artikel-ID im DE7140 bei der die letzten beiden Stellen mit dem Wert "01" (Kosten für das Einrichten des Messprodukts) vorhanden ist.
913 // REVIEW: PIA+Z02 where elements[0][0]=="Z02" (Artikel-ID qualifier) and elements[1][0] ends with "01" (Kosten für Einrichten). Group-scoped to SG27 but falls back to message-wide search; medium confidence due to lack of SG27 navigator API for flat groups. (medium confidence)
914 fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
915 let segs = ctx.find_segments("PIA");
916 if segs.is_empty() {
917 return ConditionResult::Unknown;
918 }
919 ConditionResult::from(segs.iter().any(|seg| {
920 let is_z02 = seg
921 .elements
922 .first()
923 .and_then(|e| e.first())
924 .is_some_and(|v| v == "Z02");
925 let ends_with_01 = seg
926 .elements
927 .get(1)
928 .and_then(|e| e.first())
929 .is_some_and(|v| v.ends_with("01"));
930 is_z02 && ends_with_01
931 }))
932 }
933
934 /// [84] Wenn in derselben SG27 LIN ein PIA+Z02 (Artikel-ID) mit einer Artikel-ID im DE7140 bei der die letzten beiden Stellen mit dem Wert "02" (Kosten für den Betrieb des Messprodukts) vorhanden ist.
935 // REVIEW: Checks for PIA+Z02 where DE7140 (elements[1][0]) ends with '02' (Betriebskosten). Falls back to message-wide search since group-scoped navigator API has no direct find_segments_in_group for top-level SG27 — semantically equivalent for this condition. (medium confidence)
936 fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
937 let pias = ctx.find_segments_with_qualifier("PIA", 0, "Z02");
938 ConditionResult::from(pias.iter().any(|s| {
939 s.elements
940 .get(1)
941 .and_then(|e| e.first())
942 .map(|v| v.ends_with("02"))
943 .unwrap_or(false)
944 }))
945 }
946
947 /// [85] Wenn in derselben SG27 LIN ein PIA+Z02 (Artikel-ID) mit einer Artikel-ID im DE7140 bei der die letzten beiden Stellen mit dem Wert "03" (Kosten für die anfallenden Transaktionen des Messprodukts) ...
948 // REVIEW: Checks for PIA+Z02 where DE7140 (elements[1][0]) ends with '03' (Transaktionskosten). Same pattern as condition 84. (medium confidence)
949 fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
950 let pias = ctx.find_segments_with_qualifier("PIA", 0, "Z02");
951 ConditionResult::from(pias.iter().any(|s| {
952 s.elements
953 .get(1)
954 .and_then(|e| e.first())
955 .map(|v| v.ends_with("03"))
956 .unwrap_or(false)
957 }))
958 }
959
960 /// [86] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z01 (Einrichtungspreis) vorhanden ist.
961 fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
962 ctx.any_group_has_qualifier("PRI", 0, "Z01", &["SG31"])
963 }
964
965 /// [87] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z02 (Transaktionspreis) vorhanden ist.
966 fn evaluate_87(&self, ctx: &EvaluationContext) -> ConditionResult {
967 ctx.any_group_has_qualifier("PRI", 0, "Z02", &["SG31"])
968 }
969
970 /// [88] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z03 (Betriebspreis) vorhanden ist.
971 fn evaluate_88(&self, ctx: &EvaluationContext) -> ConditionResult {
972 ctx.any_group_has_qualifier("PRI", 0, "Z03", &["SG31"])
973 }
974
975 /// [490] wenn Wert in diesem DE, an der Stelle CCYYMMDDHHMM ein Zeitpunkt aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Übersicht gesetzliche deutsche Sommerzeit (MESZ)“ der Spalten: „Sommerzei...
976 fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
977 let dtm_segs = ctx.find_segments("DTM");
978 match dtm_segs
979 .first()
980 .and_then(|s| s.elements.first())
981 .and_then(|e| e.get(1))
982 {
983 Some(val) => is_mesz_utc(val),
984 None => ConditionResult::False, // segment absent → condition not applicable
985 }
986 }
987
988 /// [491] wenn Wert in diesem DE, an der Stelle CCYYMMDDHHMM ein Zeitpunkt aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Übersicht gesetzliche deutsche Zeit (MEZ)“ der Spalten: „Winterzeit (MEZ)...
989 fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
990 let dtm_segs = ctx.find_segments("DTM");
991 match dtm_segs
992 .first()
993 .and_then(|s| s.elements.first())
994 .and_then(|e| e.get(1))
995 {
996 Some(val) => is_mez_utc(val),
997 None => ConditionResult::False, // segment absent → condition not applicable
998 }
999 }
1000
1001 /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
1002 /// EXTERNAL: Requires context from outside the message.
1003 fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1004 ctx.external.evaluate("recipient_is_electricity_sector")
1005 }
1006
1007 /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
1008 /// EXTERNAL: Requires context from outside the message.
1009 fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1010 ctx.external.evaluate("recipient_is_gas_sector")
1011 }
1012
1013 /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt.
1014 /// EXTERNAL: Requires context from outside the message.
1015 // REVIEW: Validating that a date is the document creation date or earlier requires knowing the actual current/creation timestamp at validation time. This is a temporal constraint that depends on runtime context outside the message. (medium confidence)
1016 fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
1017 ctx.external.evaluate("document_date_is_creation_or_past")
1018 }
1019
1020 /// [500] Hinweis: Angabe eines technischen Ansprechpartners für die Geräteübernahme
1021 fn evaluate_500(&self, _ctx: &EvaluationContext) -> ConditionResult {
1022 // Hinweis: Angabe eines technischen Ansprechpartners für die Geräteübernahme — informational note, always applies
1023 ConditionResult::True
1024 }
1025
1026 /// [501] Hinweis: Verwendung der ID der Marktlokation
1027 fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1028 // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1029 ConditionResult::True
1030 }
1031
1032 /// [502] Hinweis: Verwendung der ID der Messlokation
1033 fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1034 // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1035 ConditionResult::True
1036 }
1037
1038 /// [503] Hinweis: Es ist die MP-ID des Eigentümers (MSB) zur bilateralen Klärung anzugeben, wenn z. B. der MSBA das Gerät / die Geräte selbst gepachtet hat.
1039 fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
1040 // Hinweis: Es ist die MP-ID des Eigentümers (MSB) zur bilateralen Klärung anzugeben, wenn z. B. der MSBA das Gerät / die Geräte selbst gepachtet hat.
1041 ConditionResult::True
1042 }
1043
1044 /// [504] Hinweis: Wert aus BGM+311 DE1004 der REQOTE mit der die Angebotsanfrage erfolgt ist.
1045 fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1046 // Hinweis: Wert aus BGM+311 DE1004 der REQOTE mit der die Angebotsanfrage erfolgt ist.
1047 ConditionResult::True
1048 }
1049
1050 /// [505] Hinweis: Wert aus BGM+Z29 DE1004 der REQOTE, mit der die Anfrage Rechnungsabwicklung erfolgt ist.
1051 fn evaluate_505(&self, _ctx: &EvaluationContext) -> ConditionResult {
1052 // Hinweis: Wert aus BGM+Z29 DE1004 der REQOTE, mit der die Anfrage Rechnungsabwicklung erfolgt ist.
1053 ConditionResult::True
1054 }
1055
1056 /// [506] Hinweis: Wenn zu einer Position (z. B. Messwandlersatz) mehrere Gerätenummern existieren, sind die Gerätenummern in derselben Position (LIN-Segment) mittels Wiederholung der SG32 RFF+Z09 anzugeben
1057 fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
1058 // Hinweis: Wenn zu einer Position mehrere Gerätenummern existieren, sind die Gerätenummern in derselben Position mittels Wiederholung der SG32 RFF+Z09 anzugeben.
1059 ConditionResult::True
1060 }
1061
1062 /// [507] Hinweis: Verwendung der ID der Tranche
1063 fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
1064 // Hinweis: Verwendung der ID der Tranche
1065 ConditionResult::True
1066 }
1067
1068 /// [511] Hinweis: Wert aus BGM+Z74 (Bestellung eines Angebots einer Konfiguration) DE1004 der REQOTE mit der die Anfrage einer Konfiguration erfolgt ist.
1069 fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
1070 // Hinweis: Wert aus BGM+Z74 DE1004 der REQOTE — informational note, always applies
1071 ConditionResult::True
1072 }
1073
1074 /// [512] Hinweis: Verwendung der ID der Netzlokation
1075 fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
1076 // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
1077 ConditionResult::True
1078 }
1079
1080 /// [513] Hinweis: Verwendung der ID der Steuerbaren Ressource
1081 fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
1082 // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
1083 ConditionResult::True
1084 }
1085
1086 /// [514] Hinweis: Angabe gemäß Preisblatt des MSB. Bis zu einmal für die Parametrierung, bis zu einmal für den Betrieb und bis zu einmal für die Transaktion, sofern im Preisblatt des MSB vorhanden.
1087 fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
1088 // Hinweis: Angabe gemäß Preisblatt des MSB — informational note, always applies
1089 ConditionResult::True
1090 }
1091
1092 /// [516] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
1093 fn evaluate_516(&self, _ctx: &EvaluationContext) -> ConditionResult {
1094 // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
1095 ConditionResult::True
1096 }
1097
1098 /// [903] Format: Möglicher Wert: 1
1099 // REVIEW: Format condition requiring the value to equal exactly 1. Applies to a numeric field — most likely QTY[0][1] in QUOTES context. validate_numeric with == 1.0 covers this. Medium confidence because the exact segment binding isn't specified in the condition text alone. (medium confidence)
1100 fn evaluate_903(&self, ctx: &EvaluationContext) -> ConditionResult {
1101 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "==", 1.0))
1102 }
1103
1104 /// [906] Format: max. 3 Nachkommastellen
1105 // REVIEW: Format condition for maximum 3 decimal places. Uses validate_max_decimal_places helper. Targeting QTY[0][1] as the most likely numeric value field in QUOTES. Medium confidence because the specific data element binding is not stated in the condition text. (medium confidence)
1106 fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
1107 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 3))
1108 }
1109
1110 /// [908] Format: Mögliche Werte: 1 bis n
1111 // REVIEW: Format condition for values in range 1 to n (any positive integer). validate_numeric with >= 1.0 captures the lower bound; the upper bound n is open-ended. Targeting QTY as the most common numeric field. Medium confidence due to unspecified exact segment. (medium confidence)
1112 fn evaluate_908(&self, ctx: &EvaluationContext) -> ConditionResult {
1113 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">=", 1.0))
1114 }
1115
1116 /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend und fortlaufend aufsteigend
1117 fn evaluate_911(&self, _ctx: &EvaluationContext) -> ConditionResult {
1118 // Hinweis: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend
1119 // und fortlaufend aufsteigend — informational annotation about sequential numbering,
1120 // always applies unconditionally
1121 ConditionResult::True
1122 }
1123
1124 /// [912] Format: max. 6 Nachkommastellen
1125 // REVIEW: Format condition for maximum 6 decimal places. Uses validate_max_decimal_places helper with limit 6. Targeting QTY[0][1] as the primary numeric value field in QUOTES. Medium confidence because the exact data element binding is not given in the condition text. (medium confidence)
1126 fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
1127 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 6))
1128 }
1129
1130 /// [914] Format: Möglicher Wert: > 0
1131 // REVIEW: Format condition requiring value > 0. In QUOTES context this most likely applies to QTY element[0][1] (the quantity value). Medium confidence because the exact segment is not specified in the condition text — it depends on which data element this condition is attached to in the AHB. (medium confidence)
1132 fn evaluate_914(&self, ctx: &EvaluationContext) -> ConditionResult {
1133 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">", 0.0))
1134 }
1135
1136 /// [931] Format: ZZZ = +00
1137 // REVIEW: Format condition validating that the timezone portion (ZZZ) of a DTM value equals +00 (UTC). Uses validate_timezone_utc helper. Medium confidence because the specific DTM qualifier this applies to is not stated in the condition text — the AHB attaches this to a specific DTM element. (medium confidence)
1138 fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1139 ctx.format_check("DTM", 0, 1, validate_timezone_utc)
1140 }
1141
1142 /// [932] Format: HHMM = 2200
1143 // REVIEW: Format condition requiring the HHMM portion of a DTM value to equal 2200. Uses validate_hhmm_equals helper. Medium confidence because the specific DTM qualifier (e.g., 163, 164, 137) this applies to is not stated in the condition text alone — it depends on which DTM element in the QUOTES AHB carries this constraint. (medium confidence)
1144 fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
1145 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2200"))
1146 }
1147
1148 /// [933] Format: HHMM = 2300
1149 // REVIEW: Format condition requiring the HHMM portion of a DTM value to equal 2300. Uses validate_hhmm_equals helper. Medium confidence for the same reason as 932 — specific DTM qualifier not identified from condition text alone. (medium confidence)
1150 fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
1151 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2300"))
1152 }
1153
1154 /// [934] Format: HHMM = 0400
1155 // REVIEW: Format condition requiring the HHMM portion of a DTM value to equal 0400. Uses validate_hhmm_equals helper. Medium confidence for the same reason as 932/933 — specific DTM qualifier not identified from condition text alone. In QUOTES context, 0400 likely represents a gas day start time. (medium confidence)
1156 fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
1157 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0400"))
1158 }
1159
1160 /// [935] Format: HHMM = 0500
1161 // REVIEW: Format condition checking HHMM = 0500 on a DTM segment. Without a specific qualifier reference provided for this QUOTES condition, falls back to the first DTM segment. validate_hhmm_equals extracts the HHMM portion of the datetime value and compares it to '0500'. (medium confidence)
1162 fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
1163 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0500"))
1164 }
1165
1166 /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
1167 // REVIEW: Checks COM segments for email-format values (must contain '@' and '.'). COM is the standard EDIFACT segment for communication channels where email addresses appear (qualifier EM). Scans all COM values since context doesn't specify which occurrence to validate. (medium confidence)
1168 fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
1169 for seg in ctx.find_segments("COM") {
1170 if let Some(elem) = seg.elements.first() {
1171 if let Some(value) = elem.first() {
1172 if value.contains('@') && value.contains('.') {
1173 return ConditionResult::True;
1174 }
1175 }
1176 }
1177 }
1178 ConditionResult::False
1179 }
1180
1181 /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
1182 // REVIEW: Checks COM segments for international phone number format (starts with '+' followed only by digits). COM is the standard segment for telephone contacts. Scans all COM values since context doesn't specify which occurrence to validate. (medium confidence)
1183 fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
1184 for seg in ctx.find_segments("COM") {
1185 if let Some(elem) = seg.elements.first() {
1186 if let Some(value) = elem.first() {
1187 if value.starts_with('+') && value.chars().skip(1).all(|c| c.is_ascii_digit()) {
1188 return ConditionResult::True;
1189 }
1190 }
1191 }
1192 }
1193 ConditionResult::False
1194 }
1195
1196 /// [942] Format: n1-n2-n1-n3
1197 fn evaluate_942(&self, ctx: &EvaluationContext) -> ConditionResult {
1198 ctx.format_check("PIA", 1, 0, |val| validate_artikel_pattern(val, &[1, 2, 1, 3]))
1199 }
1200
1201 /// [950] Format: Marktlokations-ID
1202 fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
1203 ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
1204 }
1205
1206 /// [951] Format: Zählpunktbezeichnung
1207 fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
1208 ctx.format_check_qualified("LOC", 0, "Z17", 1, 0, validate_zahlpunkt)
1209 }
1210
1211 /// [960] Format: Netzlokations-ID
1212 // REVIEW: Netzlokations-ID is associated with LOC+Z18 (Netzlokation qualifier). The Netzlokations-ID shares the same 11-digit structure with Luhn check as the Marktlokations-ID. No dedicated validate_netzlokation_id helper exists, so validate_malo_id is used as the structurally equivalent validator. (medium confidence)
1213 fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
1214 ctx.format_check_qualified("LOC", 0, "Z18", 1, 0, validate_malo_id)
1215 }
1216
1217 /// [961] Format: SR-ID
1218 // REVIEW: Format: SR-ID — 11-digit Luhn check digit number. In QUOTES context, Steuerbare Ressource IDs typically appear in LOC+Z19 at elements[1][0]. validate_sr_id checks 11 digits with Luhn validation. Medium confidence because QUOTES segment structure may differ from UTILMD. (medium confidence)
1219 fn evaluate_961(&self, ctx: &EvaluationContext) -> ConditionResult {
1220 ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_sr_id)
1221 }
1222
1223 /// [962] Format: max. 6 Vorkommastellen
1224 // REVIEW: Uses validate_max_integer_digits(value, 6) on the PRI price amount value. Standard EDIFACT PRI C509 places DE5118 (price amount) at elements[0][1]. PRI segment structure is not explicitly listed in the provided MIG reference so medium confidence on the element index. (medium confidence)
1225 fn evaluate_962(&self, ctx: &EvaluationContext) -> ConditionResult {
1226 ctx.format_check("PRI", 0, 1, |val| validate_max_integer_digits(val, 6))
1227 }
1228
1229 /// [2042] Innerhalb dieser LIN-Position muss das PIA+Z02 (Artikel-ID) mindestens ein Mal angegeben werden und kann bis zu drei Mal angegeben werden.
1230 // REVIEW: Counts PIA segments where elements[0][0] == 'Z02' (DE4347 Artikel-ID qualifier per segment structure reference) and verifies the count is between 1 and 3 inclusive. Medium confidence because the condition is scoped to 'within this LIN position' (SG27) but we count message-wide; in REQOTE the PIA+Z02 segments appear within SG27 position groups. (medium confidence)
1231 fn evaluate_2042(&self, ctx: &EvaluationContext) -> ConditionResult {
1232 let count = ctx
1233 .find_segments("PIA")
1234 .iter()
1235 .filter(|s| {
1236 s.elements
1237 .first()
1238 .and_then(|e| e.first())
1239 .is_some_and(|v| v == "Z02")
1240 })
1241 .count();
1242 ConditionResult::from(count >= 1 && count <= 3)
1243 }
1244
1245 /// [2060] Pro Nachricht ist die SG27 LIN+Z64 (Erforderliches Produkt Schaltzeitdefinitionen) maximal einmal anzugeben
1246 fn evaluate_2060(&self, ctx: &EvaluationContext) -> ConditionResult {
1247 let count = ctx
1248 .find_segments("LIN")
1249 .iter()
1250 .filter(|s| {
1251 s.elements
1252 .get(1)
1253 .and_then(|e| e.first())
1254 .is_some_and(|v| v == "Z64")
1255 })
1256 .count();
1257 ConditionResult::from(count <= 1)
1258 }
1259
1260 /// [2061] Pro Nachricht ist die SG27 LIN++Z65 (Erforderliches Produkt Leistungskurvendefinitionen) maximal einmal anzugeben
1261 fn evaluate_2061(&self, ctx: &EvaluationContext) -> ConditionResult {
1262 let count = ctx
1263 .find_segments("LIN")
1264 .iter()
1265 .filter(|s| {
1266 s.elements
1267 .get(1)
1268 .and_then(|e| e.first())
1269 .is_some_and(|v| v == "Z65")
1270 })
1271 .count();
1272 ConditionResult::from(count <= 1)
1273 }
1274
1275 /// [2062] Pro Nachricht ist die SG27 LIN++Z66 (Erforderliches Produkt Ad-hoc-Steuerkanal) maximal einmal anzugeben
1276 fn evaluate_2062(&self, ctx: &EvaluationContext) -> ConditionResult {
1277 let count = ctx.find_segments_with_qualifier("LIN", 1, "Z66").len();
1278 ConditionResult::from(count <= 1)
1279 }
1280
1281 /// [2063] Pro Nachricht ist die SG27 LIN++Z67 (Erforderliches Messprodukt für Werte nach Typ 2 aus Backend) maximal einmal anzugeben
1282 fn evaluate_2063(&self, ctx: &EvaluationContext) -> ConditionResult {
1283 let count = ctx.find_segments_with_qualifier("LIN", 1, "Z67").len();
1284 ConditionResult::from(count <= 1)
1285 }
1286
1287 /// [2064] Pro Nachricht ist die SG27 LIN++Z68 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW) maximal einmal anzugeben
1288 fn evaluate_2064(&self, ctx: &EvaluationContext) -> ConditionResult {
1289 let count = ctx.find_segments_with_qualifier("LIN", 1, "Z68").len();
1290 ConditionResult::from(count <= 1)
1291 }
1292
1293 /// [2068] Pro SG27 LIN ist die SG31 PRI genau einmal anzugeben.
1294 // REVIEW: Checks that every SG27 instance has exactly one SG31 child group (PRI). Uses group navigator to count SG31 children per SG27 instance. Returns True only when all SG27s have exactly one SG31. (medium confidence)
1295 fn evaluate_2068(&self, ctx: &EvaluationContext) -> ConditionResult {
1296 let nav = match ctx.navigator() {
1297 Some(n) => n,
1298 None => return ConditionResult::Unknown,
1299 };
1300 let sg27_count = nav.group_instance_count(&["SG27"]);
1301 if sg27_count == 0 {
1302 return ConditionResult::Unknown;
1303 }
1304 for i in 0..sg27_count {
1305 let sg31_count = nav.child_group_instance_count(&["SG27"], i, "SG31");
1306 if sg31_count != 1 {
1307 return ConditionResult::False;
1308 }
1309 }
1310 ConditionResult::True
1311 }
1312
1313 /// [2069] Pro SG27 LIN ist die SG31 PRI genau einmal anzugeben. Dabei muss in der SG31 PRI im DE5284 eine Mengenangabe mit dem Code H87 (Stück) im DE6411 angegeben sein.
1314 // REVIEW: Checks that count_in_group for PRI in SG31 equals exactly 1 and that the PRI contains H87 in DE6411 (C509 index 4 = elements[0][1] is price amount; index 4 is unit of measure per standard EDIFACT C509 layout: DE5125, DE5118, DE5375, DE5419, DE6411). PRI structure not in the provided MIG reference so medium confidence on the DE6411 index. (medium confidence)
1315 fn evaluate_2069(&self, ctx: &EvaluationContext) -> ConditionResult {
1316 // Pro SG27 LIN: SG31 PRI must appear exactly once with H87 (Stück) in DE6411
1317 let pri_count = ctx.count_in_group("PRI", &["SG27", "SG31"]);
1318 if pri_count != 1 {
1319 return ConditionResult::False;
1320 }
1321 // DE6411 is the 5th component of C509 = elements[0][4]
1322 let segs = ctx.find_segments("PRI");
1323 match segs.first() {
1324 Some(pri) => {
1325 let unit = pri
1326 .elements
1327 .first()
1328 .and_then(|e| e.get(4))
1329 .map(|s| s.as_str());
1330 ConditionResult::from(unit == Some("H87"))
1331 }
1332 None => ConditionResult::False, // segment absent → condition not applicable
1333 }
1334 }
1335
1336 /// [2070] Pro SG27 LIN ist die SG31 PRI zweimal anzugeben. Dabei muss genau eine SG31 PRI mit Preis- und Mengenangabe und dem Code H87 (Stück) im DE6411 und genau eine SG31 PRI mit Preis- und Mengenangabe u...
1337 // REVIEW: Checks that SG31 PRI appears exactly twice and that among those two entries one has H87 and one has DAY in DE6411 (C509 index 4 = elements[0][4]). Same caveat as 2069 — PRI structure not in the provided MIG reference so medium confidence on the component index for DE6411. (medium confidence)
1338 fn evaluate_2070(&self, ctx: &EvaluationContext) -> ConditionResult {
1339 // Pro SG27 LIN: SG31 PRI must appear exactly twice — one with H87 (Stück), one with DAY (Tag) in DE6411
1340 let pri_count = ctx.count_in_group("PRI", &["SG27", "SG31"]);
1341 if pri_count != 2 {
1342 return ConditionResult::False;
1343 }
1344 // DE6411 is elements[0][4] in standard EDIFACT C509
1345 let segs = ctx.find_segments("PRI");
1346 let has_h87 = segs.iter().any(|pri| {
1347 pri.elements
1348 .first()
1349 .and_then(|e| e.get(4))
1350 .is_some_and(|v| v == "H87")
1351 });
1352 let has_day = segs.iter().any(|pri| {
1353 pri.elements
1354 .first()
1355 .and_then(|e| e.get(4))
1356 .is_some_and(|v| v == "DAY")
1357 });
1358 ConditionResult::from(has_h87 && has_day)
1359 }
1360
1361 /// [2071] Diese SG31 PRI (Preisangabe zur Position) ist bis zu dreimal anzugeben. Es ist so oft anzugeben, wie innerhalb derselben SG27 LIN (Erforderliches Messprodukt für Werte nach Typ2 aus Backend) das P...
1362 fn evaluate_2071(&self, _ctx: &EvaluationContext) -> ConditionResult {
1363 // Hinweis: Cardinality rule — SG31 PRI (Preisangabe) is given up to three times,
1364 // as often as PIA+Z02 (Artikel-ID) appears in the same SG27 LIN (Erforderliches Messprodukt
1365 // für Werte nach Typ2 aus Backend). Informational cardinality annotation, always applies.
1366 ConditionResult::True
1367 }
1368
1369 /// [2072] Diese SG31 PRI (Preisangabe zur Position) ist bis zu dreimal anzugeben. Es ist so oft anzugeben, wie innerhalb derselben SG27 LIN (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ...
1370 fn evaluate_2072(&self, _ctx: &EvaluationContext) -> ConditionResult {
1371 // Hinweis: Cardinality rule — SG31 PRI (Preisangabe) is given up to three times,
1372 // as often as PIA+Z02 (Artikel-ID) appears in the same SG27 LIN (Erforderliches Produkt
1373 // Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW). Informational cardinality annotation, always applies.
1374 ConditionResult::True
1375 }
1376
1377 /// [2073] Das PIA (OBIS-Kennzahl für Werte nach Typ 2 Backend) ist mindestens einmal anzugeben. Es kann in Abhängigkeit zu den anderen PIA Segmenten innerhalb derselben SG27 LIN (Erforderliches Messprodukt...
1378 fn evaluate_2073(&self, _ctx: &EvaluationContext) -> ConditionResult {
1379 // Hinweis: Cardinality rule — PIA (OBIS-Kennzahl für Werte nach Typ 2 Backend) must appear
1380 // at least once. It can appear up to 23 times if the other PIA segments in the same SG27 LIN
1381 // appear at most once each. The maximum total number of PIA segments per SG27 LIN is 25.
1382 // Informational cardinality annotation, always applies.
1383 ConditionResult::True
1384 }
1385}