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