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