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