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