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 ConditionResult::from(ctx.count_qualified_in_group("RNG", 0, "Z03", &["SG4", "SG31"]) == 0)
388 }
389
390 /// [94] Wenn RNG+Z04 (unverbindliche Mengenangabe) in dieser SG31 nicht vorhanden
391 fn evaluate_94(&self, ctx: &EvaluationContext) -> ConditionResult {
392 ConditionResult::from(ctx.count_qualified_in_group("RNG", 0, "Z04", &["SG4", "SG31"]) == 0)
393 }
394
395 /// [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.
396 fn evaluate_517(&self, _ctx: &EvaluationContext) -> ConditionResult {
397 // Hinweis: Wert aus BGM+Z93 DE1004 der REQOTE — informational note about value origin, always applies
398 ConditionResult::True
399 }
400
401 /// [518] Hinweis: Angabe gemäß Preisblatt B des MSB.
402 fn evaluate_518(&self, _ctx: &EvaluationContext) -> ConditionResult {
403 // Hinweis: Angabe gemäß Preisblatt B des MSB — informational note, always applies
404 ConditionResult::True
405 }
406
407 /// [519] Hinweis: Wert aus BGM DE1004 der PRICAT mit der das Preisblatt B des MSB, auf dem dieses Angebot basiert, übermittelt wurde.
408 fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
409 // Hinweis: Wert aus BGM DE1004 der PRICAT mit der das Preisblatt B des MSB, auf dem dieses Angebot basiert, übermittelt wurde.
410 ConditionResult::True
411 }
412
413 /// [520] Hinweis: Angabe des Beginnzeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann.
414 fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
415 // Hinweis: Angabe des Beginnzeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann.
416 ConditionResult::True
417 }
418
419 /// [521] Hinweis: Angabe des Endezeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann..
420 fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
421 // Hinweis: Angabe des Endezeitpunkts des voraussichtlich frühesten Umsetzungstermins, der zum Zeitpunkt der Angebotserstellung ermittelt werden kann.
422 ConditionResult::True
423 }
424
425 /// [959] Format: n13-n2
426 fn evaluate_959(&self, ctx: &EvaluationContext) -> ConditionResult {
427 ctx.format_check_qualified("PIA", 0, "Z02", 1, 0, |val| {
428 validate_artikel_pattern(val, &[13, 2])
429 })
430 }
431
432 /// [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ü...
433 /// EXTERNAL: Requires context from outside the message.
434 fn evaluate_2066(&self, ctx: &EvaluationContext) -> ConditionResult {
435 ctx.external
436 .evaluate("smgw_schwellwert_config_product_count")
437 }
438
439 /// [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.
440 fn evaluate_2074(&self, _ctx: &EvaluationContext) -> ConditionResult {
441 // Hinweis: Pro Nachricht ist diese SG27 so oft zu wiederholen, bis für jedes angefragte Produkt
442 // derselben SG27 aus der Anfrage eine Angebotsposition angegeben wurde.
443 // Informational cardinality rule about SG27 repetition — always applies.
444 ConditionResult::True
445 }
446
447 /// [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...
448 fn evaluate_2075(&self, _ctx: &EvaluationContext) -> ConditionResult {
449 // Hinweis: Pro SG27 LIN kann die SG27 PIA+Z02 (Artikel-ID) bis zu zweimal angegeben werden —
450 // einmal für die reguläre Artikel-ID gemäß Preisblatt B und einmal für die Pauschale Kosten-ID
451 // '9991000003030-01'. Ob die zweite Angabe erlaubt ist, hängt vom Preisblatt B des MSB ab (extern).
452 // Informational cardinality rule about max 2 PIA+Z02 occurrences per SG27.
453 ConditionResult::True
454 }
455
456 /// [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...
457 fn evaluate_2076(&self, _ctx: &EvaluationContext) -> ConditionResult {
458 // Hinweis: Pro SG27 LIN kann die SG31 Preisangabe zur Position nur einmal angegeben werden.
459 // Die Preisangabe bezieht sich auf die Artikel-ID, die nicht die '9991000003030-01'
460 // (Pauschale Kosten für das Scheitern der Änderung der Technik) ist.
461 // Informational cardinality rule — SG31 appears at most once per SG27 LIN.
462 ConditionResult::True
463 }
464
465 /// [1] Wenn Position nicht angeboten werden kann, weil rechtliche Regelungen oder Rechte Dritter dem entgegenstehen
466 /// EXTERNAL: Requires context from outside the message.
467 fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
468 ctx.external.evaluate("position_legally_blocked")
469 }
470
471 /// [2] Wenn in derselben SG27 LIN IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Code Z09 (Kann nicht angeboten werden) nicht vorhanden.
472 // 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)
473 fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
474 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
475 match ctx.this_group_has_qualifier("IMD", 1, "Z09", &["SG27"]) {
476 ConditionResult::True => ConditionResult::False,
477 ConditionResult::False => ConditionResult::True,
478 ConditionResult::Unknown => ConditionResult::Unknown,
479 }
480 }
481
482 /// [3] Wenn CCI+++Z64 vorhanden ist
483 fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
484 ctx.has_qualifier("CCI", 2, "Z64")
485 }
486
487 /// [4] Wenn am Gerät vorhanden und abweichend von Gerätenummer
488 /// EXTERNAL: Requires context from outside the message.
489 fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
490 ctx.external
491 .evaluate("device_attribute_differs_from_device_number")
492 }
493
494 /// [5] Wenn in derselben SG27 LIN die Artikelnummer 9990001000649 vorhanden ist
495 // 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)
496 fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
497 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
498 ctx.this_group_has_qualifier("LIN", 2, "9990001000649", &["SG27"])
499 }
500
501 /// [8] Wenn SG28 CCI+++E13 CAV+EHZ vorhanden
502 // 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)
503 fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
504 ctx.any_group_has_co_occurrence("CCI", 2, &["E13"], "CAV", 0, 0, &["EHZ"], &["SG28"])
505 }
506
507 /// [9] Wenn in derselben SG27 LIN die Artikelnummer 9990001000657 vorhanden ist
508 // 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)
509 fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
510 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
511 ctx.this_group_has_qualifier("LIN", 2, "9990001000657", &["SG27"])
512 }
513
514 /// [10] Wenn SG28 CAV+MIW/MPW/MBW vorhanden
515 fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
516 ctx.any_group_has_any_qualifier("CAV", 0, &["MIW", "MPW", "MBW"], &["SG28"])
517 }
518
519 /// [11] Wenn in derselben SG27 LIN die Artikelnummer 9990001000665 vorhanden ist
520 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000665 at elements[2][0]. Same pattern as condition 5. (medium confidence)
521 fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
522 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
523 ctx.this_group_has_qualifier("LIN", 2, "9990001000665", &["SG27"])
524 }
525
526 /// [12] Wenn in derselben SG27 LIN die Artikelnummer 9990001000673 vorhanden ist
527 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000673 at elements[2][0]. Same pattern as condition 5. (medium confidence)
528 fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
529 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
530 ctx.this_group_has_qualifier("LIN", 2, "9990001000673", &["SG27"])
531 }
532
533 /// [13] Wenn am Gerät vorhanden
534 /// EXTERNAL: Requires context from outside the message.
535 fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
536 ctx.external.evaluate("device_attribute_present")
537 }
538
539 /// [15] Wenn in derselben SG27 LIN die Artikelnummer 9990001000772 vorhanden ist
540 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000772 at elements[2][0]. Same pattern as condition 5. (medium confidence)
541 fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
542 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
543 ctx.this_group_has_qualifier("LIN", 2, "9990001000772", &["SG27"])
544 }
545
546 /// [16] Wenn in derselben SG27 LIN die Artikelnummer 9990001000780 vorhanden ist
547 // REVIEW: Checks if any SG27 group has LIN with article number 9990001000780 at elements[2][0]. Same pattern as condition 5. (medium confidence)
548 fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
549 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
550 ctx.this_group_has_qualifier("LIN", 2, "9990001000780", &["SG27"])
551 }
552
553 /// [17] Wenn das Angebot per REQOTE angefragt wurde
554 // 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)
555 fn evaluate_17(&self, ctx: &EvaluationContext) -> ConditionResult {
556 ctx.has_qualifier("RFF", 0, "AAV")
557 }
558
559 /// [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...
560 // 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)
561 fn evaluate_18(&self, ctx: &EvaluationContext) -> ConditionResult {
562 // Check if offer references additional market locations via RFF+Z18
563 // RFF+Z18 = 'Referenz auf ID weiterer Marktlokationen' — presence indicates multiple locations covered
564 // Note: the 'identical connection user' and 'iMS classification' sub-conditions require external context
565 ctx.has_qualifier("RFF", 0, "Z18")
566 }
567
568 /// [20] Wenn IMD++Z33 vorhanden
569 fn evaluate_20(&self, ctx: &EvaluationContext) -> ConditionResult {
570 ctx.has_qualifier("IMD", 1, "Z33")
571 }
572
573 /// [21] Wenn IMD++Z34 vorhanden
574 fn evaluate_21(&self, ctx: &EvaluationContext) -> ConditionResult {
575 ctx.has_qualifier("IMD", 1, "Z34")
576 }
577
578 /// [22] Wenn CCI+++Z75 vorhanden ist
579 fn evaluate_22(&self, ctx: &EvaluationContext) -> ConditionResult {
580 ctx.has_qualifier("CCI", 2, "Z75")
581 }
582
583 /// [25] Wenn SG28 CCI+++E13 CAV+MME vorhanden
584 fn evaluate_25(&self, ctx: &EvaluationContext) -> ConditionResult {
585 ctx.any_group_has_co_occurrence("CCI", 2, &["E13"], "CAV", 0, 0, &["MME"], &["SG28"])
586 }
587
588 /// [26] Wenn DTM+203 nicht vorhanden
589 fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
590 ctx.lacks_qualifier("DTM", 0, "203")
591 }
592
593 /// [27] Wenn DTM+469 nicht vorhanden
594 fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
595 ctx.lacks_qualifier("DTM", 0, "469")
596 }
597
598 /// [28] Wenn RFF+AAV vorhanden
599 fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
600 ctx.has_qualifier("RFF", 0, "AAV")
601 }
602
603 /// [29] Wenn DTM+469 in Anfrage (REQOTE) vorhanden war
604 /// EXTERNAL: Requires context from outside the message.
605 fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
606 ctx.external.evaluate("reqote_had_dtm_469")
607 }
608
609 /// [30] Wenn SG27 IMD+Z09 vorhanden
610 fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
611 ctx.any_group_has_qualifier("IMD", 0, "Z09", &["SG27"])
612 }
613
614 /// [39] MP-ID nur aus Sparte Strom
615 /// EXTERNAL: Requires context from outside the message.
616 fn evaluate_39(&self, ctx: &EvaluationContext) -> ConditionResult {
617 ctx.external.evaluate("mp_id_is_strom")
618 }
619
620 /// [44] Wenn MSBA nicht Eigentümer des Gerätes/der Geräte ist
621 /// EXTERNAL: Requires context from outside the message.
622 fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
623 ctx.external.evaluate("msba_is_not_device_owner")
624 }
625
626 /// [49] Wenn SG27 LIN++Z64 (Erforderliches Produkt Schaltzeitdefinitionen) vorhanden
627 fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
628 ctx.any_group_has_qualifier("LIN", 1, "Z64", &["SG27"])
629 }
630
631 /// [50] Wenn SG27 LIN++Z65 (Erforderliches Produkt Leistungskurvendefinitionen) vorhanden
632 fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
633 ctx.any_group_has_qualifier("LIN", 1, "Z65", &["SG27"])
634 }
635
636 /// [51] Wenn SG27 LIN++Z66 (Erforderliches Produkt Ad-hoc-Steuerkanal) vorhanden
637 fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
638 ctx.any_group_has_qualifier("LIN", 1, "Z66", &["SG27"])
639 }
640
641 /// [52] Wenn SG27 LIN++Z67 (Erforderliches Messprodukt für Werte nach Typ 2 aus Backend) vorhanden
642 fn evaluate_52(&self, ctx: &EvaluationContext) -> ConditionResult {
643 ctx.has_qualifier("LIN", 1, "Z67")
644 }
645
646 /// [53] Wenn SG27 LIN++Z68 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ2 aus SMGW) vorhanden
647 fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
648 ctx.has_qualifier("LIN", 1, "Z68")
649 }
650
651 /// [54] Wenn in der Anfrage zum Angebot einer Konfiguration vorhanden
652 /// EXTERNAL: Requires context from outside the message.
653 fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
654 ctx.external.evaluate("configuration_in_request")
655 }
656
657 /// [55] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Marktlokation angegeben ist.
658 fn evaluate_55(&self, ctx: &EvaluationContext) -> ConditionResult {
659 // Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Marktlokation angegeben ist.
660 // LOC elements[0][0] == "172" (qualifier), elements[1][0] == DE3225 (Identifikator)
661 let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
662 match locs
663 .first()
664 .and_then(|s| s.elements.get(1))
665 .and_then(|e| e.first())
666 {
667 Some(val) => validate_malo_id(val),
668 None => ConditionResult::False, // segment absent → condition not applicable
669 }
670 }
671
672 /// [56] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Messlokation 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 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)
675 fn evaluate_56(&self, ctx: &EvaluationContext) -> ConditionResult {
676 ctx.external.evaluate("loc_172_id_is_messlokation")
677 }
678
679 /// [57] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Netzlokation angegeben ist.
680 /// EXTERNAL: Requires context from outside the message.
681 // 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)
682 fn evaluate_57(&self, ctx: &EvaluationContext) -> ConditionResult {
683 ctx.external.evaluate("loc_172_id_is_netzlokation")
684 }
685
686 /// [58] Wenn in LOC+172 DE3225 (Meldepunkt) die ID einer Steuerbaren Ressource angegeben ist.
687 /// EXTERNAL: Requires context from outside the message.
688 // 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)
689 fn evaluate_58(&self, ctx: &EvaluationContext) -> ConditionResult {
690 ctx.external.evaluate("loc_172_id_is_steuerbare_ressource")
691 }
692
693 /// [60] Es sind nur die Konfigurations-Produkte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.2 „Konfigurationsprodukte Leistungskurvendefinition“ enthalten sind.
694 /// EXTERNAL: Requires context from outside the message.
695 fn evaluate_60(&self, ctx: &EvaluationContext) -> ConditionResult {
696 ctx.external.evaluate("config_product_leistungskurve")
697 }
698
699 /// [61] Es sind nur die Konfigurations-Produkte erlaubt, die in der Codeliste der Konfigurationen im Kapitel 4.3 „Konfigurationsprodukte Ad-hoc-Steuerkanal“ enthalten sind.
700 /// EXTERNAL: Requires context from outside the message.
701 fn evaluate_61(&self, ctx: &EvaluationContext) -> ConditionResult {
702 ctx.external.evaluate("config_product_adhoc_steuerkanal")
703 }
704
705 /// [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.
706 /// EXTERNAL: Requires context from outside the message.
707 fn evaluate_62(&self, ctx: &EvaluationContext) -> ConditionResult {
708 ctx.external.evaluate("messprodukts_typ2_backend_lf_nb")
709 }
710
711 /// [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.
712 /// EXTERNAL: Requires context from outside the message.
713 fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
714 ctx.external.evaluate("messprodukts_typ2_smgw")
715 }
716
717 /// [65] Wenn DTM+203 (Ausführungsdatum) im DE2380 < 202312312300?+00
718 // 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)
719 fn evaluate_65(&self, ctx: &EvaluationContext) -> ConditionResult {
720 let segs = ctx.find_segments_with_qualifier("DTM", 0, "203");
721 match segs.first() {
722 Some(dtm) => {
723 match dtm
724 .elements
725 .first()
726 .and_then(|e| e.get(1))
727 .map(|s| s.as_str())
728 {
729 Some(value) if !value.is_empty() => {
730 ConditionResult::from(value < "202312312300+00")
731 }
732 _ => ConditionResult::Unknown,
733 }
734 }
735 None => ConditionResult::False, // segment absent → condition not applicable
736 }
737 }
738
739 /// [66] Wenn DTM+469 (Beginn zum (nächstmöglichen Termin)) im DE2380 < 202312312300?+00
740 // 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)
741 fn evaluate_66(&self, ctx: &EvaluationContext) -> ConditionResult {
742 let segs = ctx.find_segments_with_qualifier("DTM", 0, "469");
743 match segs.first() {
744 Some(dtm) => {
745 match dtm
746 .elements
747 .first()
748 .and_then(|e| e.get(1))
749 .map(|s| s.as_str())
750 {
751 Some(value) if !value.is_empty() => {
752 ConditionResult::from(value < "202312312300+00")
753 }
754 _ => ConditionResult::Unknown,
755 }
756 }
757 None => ConditionResult::False, // segment absent → condition not applicable
758 }
759 }
760
761 /// [67] Wenn DTM+203 (Ausführungsdatum) im DE2380 ≥ 202312312300?+00
762 // 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)
763 fn evaluate_67(&self, ctx: &EvaluationContext) -> ConditionResult {
764 let segs = ctx.find_segments_with_qualifier("DTM", 0, "203");
765 match segs.first() {
766 Some(dtm) => {
767 match dtm
768 .elements
769 .first()
770 .and_then(|e| e.get(1))
771 .map(|s| s.as_str())
772 {
773 Some(value) if !value.is_empty() => {
774 ConditionResult::from(value >= "202312312300+00")
775 }
776 _ => ConditionResult::Unknown,
777 }
778 }
779 None => ConditionResult::False, // segment absent → condition not applicable
780 }
781 }
782
783 /// [68] Wenn DTM+469 (Beginn zum (nächstmöglichen Termin)) im DE2380 ≥ 202312312300?+00
784 // 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)
785 fn evaluate_68(&self, ctx: &EvaluationContext) -> ConditionResult {
786 let segs = ctx.find_segments_with_qualifier("DTM", 0, "469");
787 if segs.is_empty() {
788 return ConditionResult::Unknown;
789 }
790 for seg in segs {
791 let val = seg
792 .elements
793 .get(0)
794 .and_then(|e| e.get(1))
795 .map(|s| s.as_str())
796 .unwrap_or("");
797 if val >= "202312312300+00" {
798 return ConditionResult::True;
799 }
800 }
801 ConditionResult::False
802 }
803
804 /// [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.
805 /// EXTERNAL: Requires context from outside the message.
806 fn evaluate_69(&self, ctx: &EvaluationContext) -> ConditionResult {
807 ctx.external
808 .evaluate("artikel_id_abrechnung_messstellenbetrieb_strom")
809 }
810
811 /// [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...
812 /// EXTERNAL: Requires context from outside the message.
813 fn evaluate_71(&self, ctx: &EvaluationContext) -> ConditionResult {
814 ctx.external.evaluate("pia5_product_is_threshold_trigger")
815 }
816
817 /// [72] wenn im DE3155 in demselben COM der Code EM vorhanden ist
818 fn evaluate_72(&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| v == "EM")
828 }))
829 }
830
831 /// [73] wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
832 fn evaluate_73(&self, ctx: &EvaluationContext) -> ConditionResult {
833 let segs = ctx.find_segments("COM");
834 if segs.is_empty() {
835 return ConditionResult::Unknown;
836 }
837 ConditionResult::from(segs.iter().any(|seg| {
838 seg.elements
839 .first()
840 .and_then(|e| e.get(1))
841 .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
842 }))
843 }
844
845 /// [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.
846 /// EXTERNAL: Requires context from outside the message.
847 fn evaluate_74(&self, ctx: &EvaluationContext) -> ConditionResult {
848 ctx.external.evaluate("product_in_typ2_backend_codelist")
849 }
850
851 /// [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.
852 /// EXTERNAL: Requires context from outside the message.
853 fn evaluate_75(&self, ctx: &EvaluationContext) -> ConditionResult {
854 ctx.external.evaluate("product_in_typ2_smgw_codelist")
855 }
856
857 /// [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.
858 /// EXTERNAL: Requires context from outside the message.
859 fn evaluate_77(&self, ctx: &EvaluationContext) -> ConditionResult {
860 ctx.external
861 .evaluate("request_contained_typ2_backend_product")
862 }
863
864 /// [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.
865 /// EXTERNAL: Requires context from outside the message.
866 fn evaluate_78(&self, ctx: &EvaluationContext) -> ConditionResult {
867 ctx.external.evaluate("request_contained_typ2_smgw_product")
868 }
869
870 /// [79] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z07 (Kauf) vorhanden.
871 fn evaluate_79(&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 == "Z07")
881 }))
882 }
883
884 /// [80] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z08 (Nutzungsüberlassung) vorhanden.
885 fn evaluate_80(&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 == "Z08")
895 }))
896 }
897
898 /// [81] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z33 (Angebot auf Basis Preisblatt) vorhanden.
899 fn evaluate_81(&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 == "Z33")
909 }))
910 }
911
912 /// [82] Wenn IMD DE7081 (Produkt-/Leistungsbeschreibung) mit Wert Z34 (Individuelles Angebot) vorhanden.
913 fn evaluate_82(&self, ctx: &EvaluationContext) -> ConditionResult {
914 let segs = ctx.find_segments("IMD");
915 if segs.is_empty() {
916 return ConditionResult::Unknown;
917 }
918 ConditionResult::from(segs.iter().any(|seg| {
919 seg.elements
920 .get(1)
921 .and_then(|e| e.first())
922 .is_some_and(|v| v == "Z34")
923 }))
924 }
925
926 /// [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.
927 // 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)
928 fn evaluate_83(&self, ctx: &EvaluationContext) -> ConditionResult {
929 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
930 let segs = ctx.scoped_find_segments_in("SG27", "PIA");
931 if segs.is_empty() {
932 return ConditionResult::Unknown;
933 }
934 ConditionResult::from(segs.iter().any(|seg| {
935 let is_z02 = seg
936 .elements
937 .first()
938 .and_then(|e| e.first())
939 .is_some_and(|v| v == "Z02");
940 let ends_with_01 = seg
941 .elements
942 .get(1)
943 .and_then(|e| e.first())
944 .is_some_and(|v| v.ends_with("01"));
945 is_z02 && ends_with_01
946 }))
947 }
948
949 /// [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.
950 // 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)
951 fn evaluate_84(&self, ctx: &EvaluationContext) -> ConditionResult {
952 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
953 let pias = ctx.scoped_find_segments_in_with_qualifier("SG27", "PIA", 0, "Z02");
954 ConditionResult::from(pias.iter().any(|s| {
955 s.elements
956 .get(1)
957 .and_then(|e| e.first())
958 .map(|v| v.ends_with("02"))
959 .unwrap_or(false)
960 }))
961 }
962
963 /// [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) ...
964 // REVIEW: Checks for PIA+Z02 where DE7140 (elements[1][0]) ends with '03' (Transaktionskosten). Same pattern as condition 84. (medium confidence)
965 fn evaluate_85(&self, ctx: &EvaluationContext) -> ConditionResult {
966 // HAND-EDITED: "in dieser/derselben SG27" — scoped to the enclosing group.
967 let pias = ctx.scoped_find_segments_in_with_qualifier("SG27", "PIA", 0, "Z02");
968 ConditionResult::from(pias.iter().any(|s| {
969 s.elements
970 .get(1)
971 .and_then(|e| e.first())
972 .map(|v| v.ends_with("03"))
973 .unwrap_or(false)
974 }))
975 }
976
977 /// [86] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z01 (Einrichtungspreis) vorhanden ist.
978 fn evaluate_86(&self, ctx: &EvaluationContext) -> ConditionResult {
979 // HAND-EDITED: "in dieser/derselben SG31" — scoped to the enclosing group.
980 ctx.this_group_has_qualifier("PRI", 0, "Z01", &["SG31"])
981 }
982
983 /// [87] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z02 (Transaktionspreis) vorhanden ist.
984 fn evaluate_87(&self, ctx: &EvaluationContext) -> ConditionResult {
985 // HAND-EDITED: "in dieser/derselben SG31" — scoped to the enclosing group.
986 ctx.this_group_has_qualifier("PRI", 0, "Z02", &["SG31"])
987 }
988
989 /// [88] Wenn in derselben SG31 PRI (Preisangabe zur Position) das DE5387 mit dem Wert Z03 (Betriebspreis) vorhanden ist.
990 fn evaluate_88(&self, ctx: &EvaluationContext) -> ConditionResult {
991 // HAND-EDITED: "in dieser/derselben SG31" — scoped to the enclosing group.
992 ctx.this_group_has_qualifier("PRI", 0, "Z03", &["SG31"])
993 }
994
995 /// [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...
996 fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
997 match ctx.resolved_value {
998 Some(val) => is_mesz_utc(val),
999 None => ConditionResult::False,
1000 }
1001 }
1002
1003 /// [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)...
1004 fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
1005 match ctx.resolved_value {
1006 Some(val) => is_mez_utc(val),
1007 None => ConditionResult::False,
1008 }
1009 }
1010
1011 /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
1012 /// EXTERNAL: Requires context from outside the message.
1013 fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
1014 ctx.external.evaluate("recipient_is_electricity_sector")
1015 }
1016
1017 /// [493] wenn MP-ID in NAD+MR aus Sparte Gas
1018 /// EXTERNAL: Requires context from outside the message.
1019 fn evaluate_493(&self, ctx: &EvaluationContext) -> ConditionResult {
1020 ctx.external.evaluate("recipient_is_gas_sector")
1021 }
1022
1023 /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt.
1024 /// EXTERNAL: Requires context from outside the message.
1025 // 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)
1026 fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
1027 ctx.external.evaluate("document_date_is_creation_or_past")
1028 }
1029
1030 /// [500] Hinweis: Angabe eines technischen Ansprechpartners für die Geräteübernahme
1031 fn evaluate_500(&self, _ctx: &EvaluationContext) -> ConditionResult {
1032 // Hinweis: Angabe eines technischen Ansprechpartners für die Geräteübernahme — informational note, always applies
1033 ConditionResult::True
1034 }
1035
1036 /// [501] Hinweis: Verwendung der ID der Marktlokation
1037 fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
1038 // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
1039 ConditionResult::True
1040 }
1041
1042 /// [502] Hinweis: Verwendung der ID der Messlokation
1043 fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
1044 // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
1045 ConditionResult::True
1046 }
1047
1048 /// [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.
1049 fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
1050 // 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.
1051 ConditionResult::True
1052 }
1053
1054 /// [504] Hinweis: Wert aus BGM+311 DE1004 der REQOTE mit der die Angebotsanfrage erfolgt ist.
1055 fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
1056 // Hinweis: Wert aus BGM+311 DE1004 der REQOTE mit der die Angebotsanfrage erfolgt ist.
1057 ConditionResult::True
1058 }
1059
1060 /// [505] Hinweis: Wert aus BGM+Z29 DE1004 der REQOTE, mit der die Anfrage Rechnungsabwicklung erfolgt ist.
1061 fn evaluate_505(&self, _ctx: &EvaluationContext) -> ConditionResult {
1062 // Hinweis: Wert aus BGM+Z29 DE1004 der REQOTE, mit der die Anfrage Rechnungsabwicklung erfolgt ist.
1063 ConditionResult::True
1064 }
1065
1066 /// [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
1067 fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
1068 // Hinweis: Wenn zu einer Position mehrere Gerätenummern existieren, sind die Gerätenummern in derselben Position mittels Wiederholung der SG32 RFF+Z09 anzugeben.
1069 ConditionResult::True
1070 }
1071
1072 /// [507] Hinweis: Verwendung der ID der Tranche
1073 fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
1074 // Hinweis: Verwendung der ID der Tranche
1075 ConditionResult::True
1076 }
1077
1078 /// [511] Hinweis: Wert aus BGM+Z74 (Bestellung eines Angebots einer Konfiguration) DE1004 der REQOTE mit der die Anfrage einer Konfiguration erfolgt ist.
1079 fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
1080 // Hinweis: Wert aus BGM+Z74 DE1004 der REQOTE — informational note, always applies
1081 ConditionResult::True
1082 }
1083
1084 /// [512] Hinweis: Verwendung der ID der Netzlokation
1085 fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
1086 // Hinweis: Verwendung der ID der Netzlokation — informational note, always applies
1087 ConditionResult::True
1088 }
1089
1090 /// [513] Hinweis: Verwendung der ID der Steuerbaren Ressource
1091 fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
1092 // Hinweis: Verwendung der ID der Steuerbaren Ressource — informational note, always applies
1093 ConditionResult::True
1094 }
1095
1096 /// [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.
1097 fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
1098 // Hinweis: Angabe gemäß Preisblatt des MSB — informational note, always applies
1099 ConditionResult::True
1100 }
1101
1102 /// [516] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
1103 fn evaluate_516(&self, _ctx: &EvaluationContext) -> ConditionResult {
1104 // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
1105 ConditionResult::True
1106 }
1107
1108 /// [903] Format: Möglicher Wert: 1
1109 // 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)
1110 fn evaluate_903(&self, ctx: &EvaluationContext) -> ConditionResult {
1111 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, "==", 1.0))
1112 }
1113
1114 /// [906] Format: max. 3 Nachkommastellen
1115 // 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)
1116 fn evaluate_906(&self, ctx: &EvaluationContext) -> ConditionResult {
1117 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 3))
1118 }
1119
1120 /// [908] Format: Mögliche Werte: 1 bis n
1121 // 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)
1122 fn evaluate_908(&self, ctx: &EvaluationContext) -> ConditionResult {
1123 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">=", 1.0))
1124 }
1125
1126 /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend und fortlaufend aufsteigend
1127 fn evaluate_911(&self, _ctx: &EvaluationContext) -> ConditionResult {
1128 // Hinweis: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend
1129 // und fortlaufend aufsteigend — informational annotation about sequential numbering,
1130 // always applies unconditionally
1131 ConditionResult::True
1132 }
1133
1134 /// [912] Format: max. 6 Nachkommastellen
1135 // 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)
1136 fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
1137 ctx.format_check("QTY", 0, 1, |val| validate_max_decimal_places(val, 6))
1138 }
1139
1140 /// [914] Format: Möglicher Wert: > 0
1141 // 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)
1142 fn evaluate_914(&self, ctx: &EvaluationContext) -> ConditionResult {
1143 ctx.format_check("QTY", 0, 1, |val| validate_numeric(val, ">", 0.0))
1144 }
1145
1146 /// [931] Format: ZZZ = +00
1147 // 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)
1148 fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1149 ctx.format_check("DTM", 0, 1, validate_timezone_utc)
1150 }
1151
1152 /// [932] Format: HHMM = 2200
1153 // 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)
1154 fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
1155 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2200"))
1156 }
1157
1158 /// [933] Format: HHMM = 2300
1159 // 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)
1160 fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
1161 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "2300"))
1162 }
1163
1164 /// [934] Format: HHMM = 0400
1165 // 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)
1166 fn evaluate_934(&self, ctx: &EvaluationContext) -> ConditionResult {
1167 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0400"))
1168 }
1169
1170 /// [935] Format: HHMM = 0500
1171 // 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)
1172 fn evaluate_935(&self, ctx: &EvaluationContext) -> ConditionResult {
1173 ctx.format_check("DTM", 0, 1, |val| validate_hhmm_equals(val, "0500"))
1174 }
1175
1176 /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
1177 // 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)
1178 fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
1179 for seg in ctx.find_segments("COM") {
1180 if let Some(elem) = seg.elements.first() {
1181 if let Some(value) = elem.first() {
1182 if value.contains('@') && value.contains('.') {
1183 return ConditionResult::True;
1184 }
1185 }
1186 }
1187 }
1188 ConditionResult::False
1189 }
1190
1191 /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
1192 // 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)
1193 fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
1194 for seg in ctx.find_segments("COM") {
1195 if let Some(elem) = seg.elements.first() {
1196 if let Some(value) = elem.first() {
1197 if value.starts_with('+') && value.chars().skip(1).all(|c| c.is_ascii_digit()) {
1198 return ConditionResult::True;
1199 }
1200 }
1201 }
1202 }
1203 ConditionResult::False
1204 }
1205
1206 /// [942] Format: n1-n2-n1-n3
1207 fn evaluate_942(&self, ctx: &EvaluationContext) -> ConditionResult {
1208 ctx.format_check("PIA", 1, 0, |val| {
1209 validate_artikel_pattern(val, &[1, 2, 1, 3])
1210 })
1211 }
1212
1213 /// [950] Format: Marktlokations-ID
1214 fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
1215 ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
1216 }
1217
1218 /// [951] Format: Zählpunktbezeichnung
1219 fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
1220 ctx.format_check_qualified("LOC", 0, "Z17", 1, 0, validate_zahlpunkt)
1221 }
1222
1223 /// [960] Format: Netzlokations-ID
1224 // HAND-EDITED: a Netzlokations-ID is "E" + ten alphanumerics (`E1688117482`),
1225 // not MaLo-shaped; `validate_malo_id` rejected every real one (#173).
1226 fn evaluate_960(&self, ctx: &EvaluationContext) -> ConditionResult {
1227 ctx.format_check_qualified("LOC", 0, "Z18", 1, 0, validate_nelo_id)
1228 }
1229
1230 /// [961] Format: SR-ID
1231 // 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)
1232 fn evaluate_961(&self, ctx: &EvaluationContext) -> ConditionResult {
1233 ctx.format_check_qualified("LOC", 0, "Z19", 1, 0, validate_sr_id)
1234 }
1235
1236 /// [962] Format: max. 6 Vorkommastellen
1237 // 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)
1238 fn evaluate_962(&self, ctx: &EvaluationContext) -> ConditionResult {
1239 ctx.format_check("PRI", 0, 1, |val| validate_max_integer_digits(val, 6))
1240 }
1241
1242 /// [2042] Innerhalb dieser LIN-Position muss das PIA+Z02 (Artikel-ID) mindestens ein Mal angegeben werden und kann bis zu drei Mal angegeben werden.
1243 // 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)
1244 fn evaluate_2042(&self, ctx: &EvaluationContext) -> ConditionResult {
1245 let count = ctx
1246 .find_segments("PIA")
1247 .iter()
1248 .filter(|s| {
1249 s.elements
1250 .first()
1251 .and_then(|e| e.first())
1252 .is_some_and(|v| v == "Z02")
1253 })
1254 .count();
1255 ConditionResult::from(count >= 1 && count <= 3)
1256 }
1257
1258 /// [2060] Pro Nachricht ist die SG27 LIN+Z64 (Erforderliches Produkt Schaltzeitdefinitionen) maximal einmal anzugeben
1259 fn evaluate_2060(&self, ctx: &EvaluationContext) -> ConditionResult {
1260 let count = ctx
1261 .find_segments("LIN")
1262 .iter()
1263 .filter(|s| {
1264 s.elements
1265 .get(1)
1266 .and_then(|e| e.first())
1267 .is_some_and(|v| v == "Z64")
1268 })
1269 .count();
1270 ConditionResult::from(count <= 1)
1271 }
1272
1273 /// [2061] Pro Nachricht ist die SG27 LIN++Z65 (Erforderliches Produkt Leistungskurvendefinitionen) maximal einmal anzugeben
1274 fn evaluate_2061(&self, ctx: &EvaluationContext) -> ConditionResult {
1275 let count = ctx
1276 .find_segments("LIN")
1277 .iter()
1278 .filter(|s| {
1279 s.elements
1280 .get(1)
1281 .and_then(|e| e.first())
1282 .is_some_and(|v| v == "Z65")
1283 })
1284 .count();
1285 ConditionResult::from(count <= 1)
1286 }
1287
1288 /// [2062] Pro Nachricht ist die SG27 LIN++Z66 (Erforderliches Produkt Ad-hoc-Steuerkanal) maximal einmal anzugeben
1289 fn evaluate_2062(&self, ctx: &EvaluationContext) -> ConditionResult {
1290 let count = ctx.find_segments_with_qualifier("LIN", 1, "Z66").len();
1291 ConditionResult::from(count <= 1)
1292 }
1293
1294 /// [2063] Pro Nachricht ist die SG27 LIN++Z67 (Erforderliches Messprodukt für Werte nach Typ 2 aus Backend) maximal einmal anzugeben
1295 fn evaluate_2063(&self, ctx: &EvaluationContext) -> ConditionResult {
1296 let count = ctx.find_segments_with_qualifier("LIN", 1, "Z67").len();
1297 ConditionResult::from(count <= 1)
1298 }
1299
1300 /// [2064] Pro Nachricht ist die SG27 LIN++Z68 (Erforderliches Produkt Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW) maximal einmal anzugeben
1301 fn evaluate_2064(&self, ctx: &EvaluationContext) -> ConditionResult {
1302 let count = ctx.find_segments_with_qualifier("LIN", 1, "Z68").len();
1303 ConditionResult::from(count <= 1)
1304 }
1305
1306 /// [2068] Pro SG27 LIN ist die SG31 PRI genau einmal anzugeben.
1307 // 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)
1308 fn evaluate_2068(&self, ctx: &EvaluationContext) -> ConditionResult {
1309 let nav = match ctx.navigator() {
1310 Some(n) => n,
1311 None => return ConditionResult::Unknown,
1312 };
1313 let sg27_count = nav.group_instance_count(&["SG27"]);
1314 if sg27_count == 0 {
1315 return ConditionResult::Unknown;
1316 }
1317 for i in 0..sg27_count {
1318 let sg31_count = nav.child_group_instance_count(&["SG27"], i, "SG31");
1319 if sg31_count != 1 {
1320 return ConditionResult::False;
1321 }
1322 }
1323 ConditionResult::True
1324 }
1325
1326 /// [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.
1327 // 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)
1328 fn evaluate_2069(&self, ctx: &EvaluationContext) -> ConditionResult {
1329 // Pro SG27 LIN: SG31 PRI must appear exactly once with H87 (Stück) in DE6411
1330 let pri_count = ctx.count_in_group("PRI", &["SG27", "SG31"]);
1331 if pri_count != 1 {
1332 return ConditionResult::False;
1333 }
1334 // DE6411 is the 5th component of C509 = elements[0][4]
1335 let segs = ctx.find_segments("PRI");
1336 match segs.first() {
1337 Some(pri) => {
1338 let unit = pri
1339 .elements
1340 .first()
1341 .and_then(|e| e.get(4))
1342 .map(|s| s.as_str());
1343 ConditionResult::from(unit == Some("H87"))
1344 }
1345 None => ConditionResult::False, // segment absent → condition not applicable
1346 }
1347 }
1348
1349 /// [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...
1350 // 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)
1351 fn evaluate_2070(&self, ctx: &EvaluationContext) -> ConditionResult {
1352 // Pro SG27 LIN: SG31 PRI must appear exactly twice — one with H87 (Stück), one with DAY (Tag) in DE6411
1353 let pri_count = ctx.count_in_group("PRI", &["SG27", "SG31"]);
1354 if pri_count != 2 {
1355 return ConditionResult::False;
1356 }
1357 // DE6411 is elements[0][4] in standard EDIFACT C509
1358 let segs = ctx.find_segments("PRI");
1359 let has_h87 = segs.iter().any(|pri| {
1360 pri.elements
1361 .first()
1362 .and_then(|e| e.get(4))
1363 .is_some_and(|v| v == "H87")
1364 });
1365 let has_day = segs.iter().any(|pri| {
1366 pri.elements
1367 .first()
1368 .and_then(|e| e.get(4))
1369 .is_some_and(|v| v == "DAY")
1370 });
1371 ConditionResult::from(has_h87 && has_day)
1372 }
1373
1374 /// [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...
1375 fn evaluate_2071(&self, _ctx: &EvaluationContext) -> ConditionResult {
1376 // Hinweis: Cardinality rule — SG31 PRI (Preisangabe) is given up to three times,
1377 // as often as PIA+Z02 (Artikel-ID) appears in the same SG27 LIN (Erforderliches Messprodukt
1378 // für Werte nach Typ2 aus Backend). Informational cardinality annotation, always applies.
1379 ConditionResult::True
1380 }
1381
1382 /// [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...
1383 fn evaluate_2072(&self, _ctx: &EvaluationContext) -> ConditionResult {
1384 // Hinweis: Cardinality rule — SG31 PRI (Preisangabe) is given up to three times,
1385 // as often as PIA+Z02 (Artikel-ID) appears in the same SG27 LIN (Erforderliches Produkt
1386 // Konfigurationserlaubnis für Werte nach Typ 2 aus SMGW). Informational cardinality annotation, always applies.
1387 ConditionResult::True
1388 }
1389
1390 /// [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...
1391 fn evaluate_2073(&self, _ctx: &EvaluationContext) -> ConditionResult {
1392 // Hinweis: Cardinality rule — PIA (OBIS-Kennzahl für Werte nach Typ 2 Backend) must appear
1393 // at least once. It can appear up to 23 times if the other PIA segments in the same SG27 LIN
1394 // appear at most once each. The maximum total number of PIA segments per SG27 LIN is 25.
1395 // Informational cardinality annotation, always applies.
1396 ConditionResult::True
1397 }
1398}