Skip to main content

automapper_validation/generated/fv2504/
pricat_conditions_fv2504.rs

1// <auto-generated>
2// Generated by automapper-generator generate-conditions
3// AHB: xml-migs-and-ahbs/FV2504/PRICAT_AHB_2_0e_Fehlerkorrektur_20250623.xml
4// Generated: 2026-03-12T10:05:58Z
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 PRICAT FV2504.
12pub struct PricatConditionEvaluatorFV2504 {
13    // External condition IDs that require runtime context.
14    external_conditions: std::collections::HashSet<u32>,
15}
16
17impl Default for PricatConditionEvaluatorFV2504 {
18    fn default() -> Self {
19        let mut external_conditions = std::collections::HashSet::new();
20        external_conditions.insert(1);
21        external_conditions.insert(14);
22        external_conditions.insert(19);
23        external_conditions.insert(22);
24        external_conditions.insert(30);
25        external_conditions.insert(36);
26        external_conditions.insert(39);
27        external_conditions.insert(40);
28        external_conditions.insert(41);
29        external_conditions.insert(42);
30        external_conditions.insert(45);
31        external_conditions.insert(46);
32        external_conditions.insert(54);
33        external_conditions.insert(492);
34        Self {
35            external_conditions,
36        }
37    }
38}
39
40impl ConditionEvaluator for PricatConditionEvaluatorFV2504 {
41    fn message_type(&self) -> &str {
42        "PRICAT"
43    }
44
45    fn format_version(&self) -> &str {
46        "FV2504"
47    }
48
49    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
50        match condition {
51            1 => self.evaluate_1(ctx),
52            2 => self.evaluate_2(ctx),
53            3 => self.evaluate_3(ctx),
54            4 => self.evaluate_4(ctx),
55            5 => self.evaluate_5(ctx),
56            6 => self.evaluate_6(ctx),
57            7 => self.evaluate_7(ctx),
58            9 => self.evaluate_9(ctx),
59            10 => self.evaluate_10(ctx),
60            12 => self.evaluate_12(ctx),
61            14 => self.evaluate_14(ctx),
62            19 => self.evaluate_19(ctx),
63            22 => self.evaluate_22(ctx),
64            24 => self.evaluate_24(ctx),
65            26 => self.evaluate_26(ctx),
66            27 => self.evaluate_27(ctx),
67            28 => self.evaluate_28(ctx),
68            29 => self.evaluate_29(ctx),
69            30 => self.evaluate_30(ctx),
70            31 => self.evaluate_31(ctx),
71            32 => self.evaluate_32(ctx),
72            33 => self.evaluate_33(ctx),
73            34 => self.evaluate_34(ctx),
74            36 => self.evaluate_36(ctx),
75            37 => self.evaluate_37(ctx),
76            38 => self.evaluate_38(ctx),
77            39 => self.evaluate_39(ctx),
78            40 => self.evaluate_40(ctx),
79            41 => self.evaluate_41(ctx),
80            42 => self.evaluate_42(ctx),
81            43 => self.evaluate_43(ctx),
82            44 => self.evaluate_44(ctx),
83            45 => self.evaluate_45(ctx),
84            46 => self.evaluate_46(ctx),
85            47 => self.evaluate_47(ctx),
86            48 => self.evaluate_48(ctx),
87            49 => self.evaluate_49(ctx),
88            50 => self.evaluate_50(ctx),
89            51 => self.evaluate_51(ctx),
90            52 => self.evaluate_52(ctx),
91            53 => self.evaluate_53(ctx),
92            54 => self.evaluate_54(ctx),
93            55 => self.evaluate_55(ctx),
94            64 => self.evaluate_64(ctx),
95            490 => self.evaluate_490(ctx),
96            491 => self.evaluate_491(ctx),
97            492 => self.evaluate_492(ctx),
98            494 => self.evaluate_494(ctx),
99            495 => self.evaluate_495(ctx),
100            502 => self.evaluate_502(ctx),
101            503 => self.evaluate_503(ctx),
102            504 => self.evaluate_504(ctx),
103            511 => self.evaluate_511(ctx),
104            512 => self.evaluate_512(ctx),
105            513 => self.evaluate_513(ctx),
106            519 => self.evaluate_519(ctx),
107            520 => self.evaluate_520(ctx),
108            521 => self.evaluate_521(ctx),
109            902 => self.evaluate_902(ctx),
110            908 => self.evaluate_908(ctx),
111            909 => self.evaluate_909(ctx),
112            911 => self.evaluate_911(ctx),
113            912 => self.evaluate_912(ctx),
114            926 => self.evaluate_926(ctx),
115            929 => self.evaluate_929(ctx),
116            931 => self.evaluate_931(ctx),
117            932 => self.evaluate_932(ctx),
118            933 => self.evaluate_933(ctx),
119            937 => self.evaluate_937(ctx),
120            939 => self.evaluate_939(ctx),
121            940 => self.evaluate_940(ctx),
122            941 => self.evaluate_941(ctx),
123            942 => self.evaluate_942(ctx),
124            946 => self.evaluate_946(ctx),
125            948 => self.evaluate_948(ctx),
126            949 => self.evaluate_949(ctx),
127            957 => self.evaluate_957(ctx),
128            959 => self.evaluate_959(ctx),
129            968 => self.evaluate_968(ctx),
130            _ => ConditionResult::Unknown,
131        }
132    }
133
134    fn is_external(&self, condition: u32) -> bool {
135        self.external_conditions.contains(&condition)
136    }
137    fn is_known(&self, condition: u32) -> bool {
138        matches!(
139            condition,
140            1 | 2
141                | 3
142                | 4
143                | 5
144                | 6
145                | 7
146                | 9
147                | 10
148                | 12
149                | 14
150                | 19
151                | 22
152                | 24
153                | 26
154                | 27
155                | 28
156                | 29
157                | 30
158                | 31
159                | 32
160                | 33
161                | 34
162                | 36
163                | 37
164                | 38
165                | 39
166                | 40
167                | 41
168                | 42
169                | 43
170                | 44
171                | 45
172                | 46
173                | 47
174                | 48
175                | 49
176                | 50
177                | 51
178                | 52
179                | 53
180                | 54
181                | 55
182                | 64
183                | 490
184                | 491
185                | 492
186                | 494
187                | 495
188                | 502
189                | 503
190                | 504
191                | 511
192                | 512
193                | 513
194                | 519
195                | 520
196                | 521
197                | 902
198                | 908
199                | 909
200                | 911
201                | 912
202                | 926
203                | 929
204                | 931
205                | 932
206                | 933
207                | 937
208                | 939
209                | 940
210                | 941
211                | 942
212                | 946
213                | 948
214                | 949
215                | 957
216                | 959
217                | 968
218        )
219    }
220}
221
222impl PricatConditionEvaluatorFV2504 {
223    /// [10] Wenn eine weitere Zone für diese Gruppenartikel-ID vorhanden
224    fn evaluate_10(&self, _ctx: &EvaluationContext) -> ConditionResult {
225        // TODO: Condition [10] requires manual implementation
226        // Reason: Requires checking if another zone instance exists for the same Gruppenartikel-ID within the PRICAT SG17 group structure. This needs cross-group navigation to correlate zone entries to a specific Gruppenartikel-ID, but the exact PRICAT FV2504 segment structure for zone representation (which segment/element carries the zone discriminator relative to Gruppenartikel-ID) is not available in the provided schema context. Cannot implement correctly without the PID schema.
227        ConditionResult::Unknown
228    }
229
230    /// [45] Es sind nur Werte aus der EDI@Energy Codeliste der Artikelnummern und Artikel-ID erlaubt, die in dieser in Kapitel "Abrechnung Messstellenbetrieb für die Sparte Strom" genannt sind
231    /// EXTERNAL: Requires context from outside the message.
232    fn evaluate_45(&self, ctx: &EvaluationContext) -> ConditionResult {
233        ctx.external
234            .evaluate("pricat_artikel_abrechnung_messstellenbetrieb_strom")
235    }
236
237    /// [46] Es sind nur Werte aus der EDI@Energy Codeliste der Artikelnummern und Artikel-ID erlaubt, die in dieser in Kapitel "Artikel-ID für den Universalbestellprozess für die Sparte Strom" genannt sind
238    /// EXTERNAL: Requires context from outside the message.
239    fn evaluate_46(&self, ctx: &EvaluationContext) -> ConditionResult {
240        ctx.external
241            .evaluate("pricat_artikel_universalbestellprozess_strom")
242    }
243
244    /// [54] Falls der Preis des Artikels gezont ist
245    /// EXTERNAL: Requires context from outside the message.
246    // REVIEW: 'Falls der Preis des Artikels gezont ist' describes whether the article has zone-based pricing — a product/business configuration attribute. While zone pricing might leave structural traces in the message (multiple zone segments), whether a price is fundamentally 'gezont' is a business model characteristic of the article that is external to the EDIFACT message content itself. (medium confidence)
247    fn evaluate_54(&self, ctx: &EvaluationContext) -> ConditionResult {
248        ctx.external.evaluate("article_price_is_zoned")
249    }
250
251    /// [55] Wenn eine weitere Zone für diese Artikel-ID vorhanden
252    fn evaluate_55(&self, _ctx: &EvaluationContext) -> ConditionResult {
253        // TODO: Condition [55] requires manual implementation
254        // Reason: Requires checking if another zone instance exists for the same Artikel-ID within the PRICAT structure. Similar to condition 10 but for Artikel-ID (likely LIN/PIA segment). Requires cross-group navigation to count zone instances per Artikel-ID, but the exact PRICAT FV2504 zone segment structure (which element discriminates zones within an article's price group) is not available without the PID schema.
255        ConditionResult::Unknown
256    }
257
258    /// [490] wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Prozesszeitpunkt bei MESZ mit UTC“ ist
259    fn evaluate_490(&self, ctx: &EvaluationContext) -> ConditionResult {
260        let dtm_segs = ctx.find_segments("DTM");
261        match dtm_segs
262            .first()
263            .and_then(|s| s.elements.first())
264            .and_then(|e| e.get(1))
265        {
266            Some(val) => is_mesz_utc(val),
267            None => ConditionResult::False, // segment absent → condition not applicable
268        }
269    }
270
271    /// [491] wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Prozesszeitpunkt bei MEZ mit UTC“ ist
272    fn evaluate_491(&self, ctx: &EvaluationContext) -> ConditionResult {
273        let dtm_segs = ctx.find_segments("DTM");
274        match dtm_segs
275            .first()
276            .and_then(|s| s.elements.first())
277            .and_then(|e| e.get(1))
278        {
279            Some(val) => is_mez_utc(val),
280            None => ConditionResult::False, // segment absent → condition not applicable
281        }
282    }
283
284    /// [1] Wenn Vorgängerversion vorhanden
285    /// EXTERNAL: Requires context from outside the message.
286    // REVIEW: Whether a Vorgängerversion (predecessor version) exists is a business context question — it depends on whether a prior PRICAT was sent for the same catalog/articles and is tracked outside the EDIFACT message itself. Cannot be determined from the current message content alone. (medium confidence)
287    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
288        ctx.external.evaluate("previous_version_exists")
289    }
290
291    /// [2] Wenn in dieser SG36 LIN in DE7140 9990001000813 vorhanden
292    fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
293        let values = ctx.collect_group_values("LIN", 2, 0, &["SG36"]);
294        ConditionResult::from(values.iter().any(|(_, v)| v == "9990001000813"))
295    }
296
297    /// [3] Wenn IMD+X vorhanden
298    fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
299        ctx.has_qualifier("IMD", 0, "X")
300    }
301
302    /// [4] Wenn SG36 IMD+C in diesem IMD vorhanden
303    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
304        ctx.any_group_has_qualifier("IMD", 0, "C", &["SG36"])
305    }
306
307    /// [5] Wenn SG36 IMD+X in diesem IMD vorhanden
308    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
309        ctx.any_group_has_qualifier("IMD", 0, "X", &["SG36"])
310    }
311
312    /// [6] Wenn in dieser SG36 LIN in DE7140 9990001000798 vorhanden
313    fn evaluate_6(&self, ctx: &EvaluationContext) -> ConditionResult {
314        let values = ctx.collect_group_values("LIN", 2, 0, &["SG36"]);
315        ConditionResult::from(values.iter().any(|(_, v)| v == "9990001000798"))
316    }
317
318    /// [7] Wenn in dieser SG36 LIN in DE7140 9990001000798 nicht vorhanden
319    fn evaluate_7(&self, ctx: &EvaluationContext) -> ConditionResult {
320        let values = ctx.collect_group_values("LIN", 2, 0, &["SG36"]);
321        ConditionResult::from(!values.iter().any(|(_, v)| v == "9990001000798"))
322    }
323
324    /// [9] Wenn BGM DE1373 =11 nicht vorhanden
325    fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
326        match ctx.find_segment("BGM") {
327            None => ConditionResult::False, // segment absent → condition not applicable
328            Some(seg) => {
329                let val = seg
330                    .elements
331                    .get(4)
332                    .and_then(|e| e.first())
333                    .map(|s| s.as_str())
334                    .unwrap_or("");
335                ConditionResult::from(val != "11")
336            }
337        }
338    }
339
340    /// [12] je UNB ist nur eine Nachricht mit BGM+Z04 in der Übertragungsdatei erlaubt (nur eine Nachricht je Übertragungsdatei)
341    // REVIEW: The notation-resolved hint says elements[0]=Z04. BGM C002.DE1001 is at elements[0][0]. has_qualifier checks elements[0][0]=='Z04'. The 'only one per UNB' structural constraint is an AHB rule consuming this condition — within a single message, we can only check if this IS a Z04 message type. (medium confidence)
342    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
343        ctx.has_qualifier("BGM", 0, "Z04")
344    }
345
346    /// [14] je UNB ist maximal je Code aus DE1001 eine Nachricht in der Übertragungsdatei erlaubt
347    /// EXTERNAL: Requires context from outside the message.
348    // REVIEW: This is a transmission-level cardinality constraint: per UNB envelope, at most one message per DE1001 message type code is allowed. Enforcing this requires visibility into all messages within the same UNB envelope, which is outside the scope of evaluating a single message's EvaluationContext. Must be handled externally at the interchange/envelope level. (medium confidence)
349    fn evaluate_14(&self, ctx: &EvaluationContext) -> ConditionResult {
350        ctx.external.evaluate("max_one_message_per_type_in_unb")
351    }
352
353    /// [19] Nur MP-ID aus Sparte Strom
354    /// EXTERNAL: Requires context from outside the message.
355    // REVIEW: Nur MP-ID aus Sparte Strom — checking whether a market participant ID belongs to the electricity (Strom) sector requires a lookup against an external market participant registry (Marktstammdatenregister or similar). The sector classification cannot be derived from the EDIFACT message content alone. (medium confidence)
356    fn evaluate_19(&self, ctx: &EvaluationContext) -> ConditionResult {
357        ctx.external.evaluate("mp_id_is_strom_sector")
358    }
359
360    /// [22] Wenn die Artikel-ID aus dieser SG36 LIN DE7140 in der EDI@Energy Codeliste der Artikelnummern und Artikel-ID in der Spalte "PRICAT Preisangabe" ein X hat
361    /// EXTERNAL: Requires context from outside the message.
362    // REVIEW: This condition requires a lookup of the Artikel-ID from LIN DE7140 (elements[2][0]) against the EDI@Energy code list of article numbers, specifically checking whether the 'PRICAT Preisangabe' column has an X for that entry. Code list membership checks against external EDI@Energy publications cannot be determined from EDIFACT message content alone — they require an external code list resolver. (medium confidence)
363    fn evaluate_22(&self, ctx: &EvaluationContext) -> ConditionResult {
364        ctx.external.evaluate("article_id_has_pricat_price_flag")
365    }
366
367    /// [24] Wenn in dieser SG36 Wert von LIN DE7140 im Format n1-n2-n1-n8-n2-n1
368    fn evaluate_24(&self, ctx: &EvaluationContext) -> ConditionResult {
369        let values = ctx.collect_group_values("LIN", 2, 0, &["SG36"]);
370        ConditionResult::from(values.iter().any(|(_, val)| {
371            let parts: Vec<&str> = val.split('-').collect();
372            if parts.len() != 6 {
373                return false;
374            }
375            let expected_lens = [1usize, 2, 1, 8, 2, 1];
376            parts
377                .iter()
378                .zip(expected_lens.iter())
379                .all(|(p, &len)| p.len() == len && p.chars().all(|ch| ch.is_ascii_digit()))
380        }))
381    }
382
383    /// [26] Wenn BGM DE1001 = Z70 vorhanden
384    fn evaluate_26(&self, ctx: &EvaluationContext) -> ConditionResult {
385        ctx.has_qualifier("BGM", 0, "Z70")
386    }
387
388    /// [27] Wenn BGM DE1001 = Z70 nicht vorhanden
389    fn evaluate_27(&self, ctx: &EvaluationContext) -> ConditionResult {
390        ctx.lacks_qualifier("BGM", 0, "Z70")
391    }
392
393    /// [28] Wenn die zugehörige Artikel-ID in der letzten Stelle eine 1 ist
394    // REVIEW: Checks the last character of the article ID (Artikel-ID) in the PIA segment. The PIA segment's additional product identification is at elements[1][0]. Last digit '1' → True, any other digit → False. Medium confidence because the exact PIA element path for Artikel-ID in PRICAT is assumed from convention; no segment structure reference provided. (medium confidence)
395    fn evaluate_28(&self, ctx: &EvaluationContext) -> ConditionResult {
396        // Wenn die zugehörige Artikel-ID in der letzten Stelle eine 1 ist
397        // Artikel-ID is in PIA segment, elements[1][0]
398        let segs = ctx.find_segments("PIA");
399        match segs
400            .first()
401            .and_then(|s| s.elements.get(1))
402            .and_then(|e| e.first())
403        {
404            Some(val) => match val.chars().last() {
405                Some('1') => ConditionResult::True,
406                Some(c) if c.is_ascii_digit() => ConditionResult::False,
407                Some(_) => ConditionResult::False,
408                None => ConditionResult::False, // segment absent → condition not applicable
409            },
410            None => ConditionResult::False, // segment absent → condition not applicable
411        }
412    }
413
414    /// [29] Wenn die zugehörige Artikel-ID in der letzten Stelle &gt; 1 ist
415    // REVIEW: Checks the last character of the article ID in PIA elements[1][0]. Last digit > '1' (i.e., '2'–'9') → True, last digit <= '1' → False. Same assumptions as condition 28 regarding PIA element path. (medium confidence)
416    fn evaluate_29(&self, ctx: &EvaluationContext) -> ConditionResult {
417        // Wenn die zugehörige Artikel-ID in der letzten Stelle > 1 ist
418        // Artikel-ID is in PIA segment, elements[1][0]
419        let segs = ctx.find_segments("PIA");
420        match segs
421            .first()
422            .and_then(|s| s.elements.get(1))
423            .and_then(|e| e.first())
424        {
425            Some(val) => match val.chars().last() {
426                Some(c) if c.is_ascii_digit() && c > '1' => ConditionResult::True,
427                Some(c) if c.is_ascii_digit() => ConditionResult::False,
428                Some(_) => ConditionResult::False,
429                None => ConditionResult::False, // segment absent → condition not applicable
430            },
431            None => ConditionResult::False, // segment absent → condition not applicable
432        }
433    }
434
435    /// [30] wenn MP-ID in SG2 NAD+MR in der Rolle LF
436    /// EXTERNAL: Requires context from outside the message.
437    fn evaluate_30(&self, ctx: &EvaluationContext) -> ConditionResult {
438        ctx.external.evaluate("recipient_is_lf")
439    }
440
441    /// [31] wenn BGM DE1001 = Z32
442    fn evaluate_31(&self, ctx: &EvaluationContext) -> ConditionResult {
443        ctx.has_qualifier("BGM", 0, "Z32")
444    }
445
446    /// [32] wenn der Zeitpunkt im DTM+157 DE2380 &lt; 01.01.2024 00:00 Uhr gesetzlicher deutscher Zeit
447    // REVIEW: DTM+157 (Gültigkeitsbeginn) has DE2380 at elements[0][1] in format 303 (YYYYMMDDHHMM). String comparison is valid for this zero-padded numeric format. Assumes German local time encoding as per EDI@Energy convention. Returns Unknown if DTM+157 is absent. (medium confidence)
448    fn evaluate_32(&self, ctx: &EvaluationContext) -> ConditionResult {
449        {
450            let dtms = ctx.find_segments_with_qualifier("DTM", 0, "157");
451            match dtms.first() {
452                Some(dtm) => {
453                    match dtm
454                        .elements
455                        .first()
456                        .and_then(|e| e.get(1))
457                        .map(|s| s.as_str())
458                    {
459                        Some(value) if !value.is_empty() => {
460                            // Format 303: YYYYMMDDHHMM — string comparison valid for numeric date strings
461                            // 01.01.2024 00:00 German legal time → "202401010000"
462                            ConditionResult::from(value < "202401010000")
463                        }
464                        _ => ConditionResult::Unknown,
465                    }
466                }
467                None => ConditionResult::False, // segment absent → condition not applicable
468            }
469        }
470    }
471
472    /// [33] wenn der Zeitpunkt im DTM+157 DE2380 ≥ 01.01.2024 00:00 Uhr gesetzlicher deutscher Zeit
473    // REVIEW: Mirror of condition 32 with >= comparison. DTM+157 DE2380 at elements[0][1], format 303. Returns Unknown if segment absent. (medium confidence)
474    fn evaluate_33(&self, ctx: &EvaluationContext) -> ConditionResult {
475        {
476            let dtms = ctx.find_segments_with_qualifier("DTM", 0, "157");
477            match dtms.first() {
478                Some(dtm) => {
479                    match dtm
480                        .elements
481                        .first()
482                        .and_then(|e| e.get(1))
483                        .map(|s| s.as_str())
484                    {
485                        Some(value) if !value.is_empty() => {
486                            // Format 303: YYYYMMDDHHMM — string comparison valid for numeric date strings
487                            // 01.01.2024 00:00 German legal time → "202401010000"
488                            ConditionResult::from(value >= "202401010000")
489                        }
490                        _ => ConditionResult::Unknown,
491                    }
492                }
493                None => ConditionResult::False, // segment absent → condition not applicable
494            }
495        }
496    }
497
498    /// [34] wenn BGM DE1001 = Z77
499    fn evaluate_34(&self, ctx: &EvaluationContext) -> ConditionResult {
500        ctx.has_qualifier("BGM", 0, "Z77")
501    }
502
503    /// [36] Wenn MP-ID in SG2 NAD+MR in der Rolle NB
504    /// EXTERNAL: Requires context from outside the message.
505    fn evaluate_36(&self, ctx: &EvaluationContext) -> ConditionResult {
506        ctx.external.evaluate("recipient_is_nb")
507    }
508
509    /// [37] Wenn im DE3155 in demselben COM der Code EM vorhanden ist
510    // REVIEW: COM segment C076 has DE3148 (number) at elements[0][0] and DE3155 (channel code) at elements[0][1]. The condition checks if DE3155 == "EM" (email). COM segment structure is standard EDIFACT and not ambiguous despite not being in the provided reference. 'Demselben COM' at message level means any COM with EM qualifier. (medium confidence)
511    fn evaluate_37(&self, ctx: &EvaluationContext) -> ConditionResult {
512        {
513            let coms = ctx.find_segments("COM");
514            ConditionResult::from(coms.iter().any(|s| {
515                s.elements
516                    .first()
517                    .and_then(|e| e.get(1))
518                    .is_some_and(|v| v == "EM")
519            }))
520        }
521    }
522
523    /// [38] Wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
524    // REVIEW: Same COM segment structure as condition 37. Checks DE3155 for any of TE (telephone), FX (fax), AJ, AL channel codes. (medium confidence)
525    fn evaluate_38(&self, ctx: &EvaluationContext) -> ConditionResult {
526        {
527            let coms = ctx.find_segments("COM");
528            ConditionResult::from(coms.iter().any(|s| {
529                s.elements
530                    .first()
531                    .and_then(|e| e.get(1))
532                    .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
533            }))
534        }
535    }
536
537    /// [39] Es sind nur Werte aus der EDI@Energy Codeliste der Artikelnummern und Artikel-ID erlaubt, die in der Spalte MaBiS ein X haben
538    /// EXTERNAL: Requires context from outside the message.
539    fn evaluate_39(&self, ctx: &EvaluationContext) -> ConditionResult {
540        ctx.external.evaluate("article_id_in_mabis_codelist")
541    }
542
543    /// [40] Es sind nur Werte aus der EDI@Energy Codeliste der Artikelnummern und Artikel-ID erlaubt, die in der Spalte H ein X haben
544    /// EXTERNAL: Requires context from outside the message.
545    fn evaluate_40(&self, ctx: &EvaluationContext) -> ConditionResult {
546        ctx.external.evaluate("article_id_in_h_codelist")
547    }
548
549    /// [41] Es sind nur Werte aus der EDI@Energy Codeliste der Artikelnummern und Artikel-ID erlaubt, die in der Spalte "PRICAT Codeverwendung" ein X haben
550    /// EXTERNAL: Requires context from outside the message.
551    fn evaluate_41(&self, ctx: &EvaluationContext) -> ConditionResult {
552        ctx.external.evaluate("article_id_in_pricat_codelist")
553    }
554
555    /// [42] Es sind nur Werte erlaubt, die die Bildungsvorschrift der EDI@Energy Codeliste der Artikelnummern und Artikel-ID erfüllen, und die in der Spalte "PRICAT Codeverwendung" ein X haben
556    /// EXTERNAL: Requires context from outside the message.
557    fn evaluate_42(&self, ctx: &EvaluationContext) -> ConditionResult {
558        ctx.external
559            .evaluate("article_id_valid_pricat_formation_rule")
560    }
561
562    /// [43] Wenn BGM DE1001 = Z32 und LIN DE7140 im Format n1-n2-n1-n3, dann muss der hier genannte Zeitpunkt ≥ 01.01.2024 00:00 Uhr gesetzlicher deutscher Zeit sein
563    // REVIEW: Checks BGM DE1001==Z32, LIN DE7140 contains hyphens (n1-n2-n1-n3 format like '1-01-6-005'), and DTM+157 (Gültigkeitsbeginn) >= 20240101. Medium confidence because 'der hier genannte Zeitpunkt' is assumed to be DTM+157. (medium confidence)
564    fn evaluate_43(&self, ctx: &EvaluationContext) -> ConditionResult {
565        let bgm = match ctx.find_segment("BGM") {
566            Some(s) => s,
567            None => return ConditionResult::Unknown,
568        };
569        if bgm
570            .elements
571            .first()
572            .and_then(|e| e.first())
573            .map(|s| s.as_str())
574            != Some("Z32")
575        {
576            return ConditionResult::False;
577        }
578        let lin_segments = ctx.find_segments("LIN");
579        let has_hyphen_format = lin_segments.iter().any(|s| {
580            s.elements
581                .get(2)
582                .and_then(|e| e.first())
583                .map_or(false, |v| v.contains('-'))
584        });
585        if !has_hyphen_format {
586            return ConditionResult::False;
587        }
588        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "157");
589        match dtm_segs.first() {
590            Some(dtm) => {
591                let value = dtm
592                    .elements
593                    .first()
594                    .and_then(|e| e.get(1))
595                    .map(|s| s.as_str())
596                    .unwrap_or("");
597                if value.len() >= 8 {
598                    ConditionResult::from(&value[..8] >= "20240101")
599                } else {
600                    ConditionResult::Unknown
601                }
602            }
603            None => ConditionResult::False, // segment absent → condition not applicable
604        }
605    }
606
607    /// [44] Wenn BGM DE1001 = Z77, dann muss der hier genannte Zeitpunkt ≥ 01.10.2023 00:00 Uhr gesetzlicher deutscher Zeit sein.
608    // REVIEW: Checks BGM DE1001==Z77 then verifies DTM+157 value >= 20231001 (01.10.2023). Medium confidence because the referenced time point is assumed to be DTM+157 (Gültigkeitsbeginn). (medium confidence)
609    fn evaluate_44(&self, ctx: &EvaluationContext) -> ConditionResult {
610        let bgm = match ctx.find_segment("BGM") {
611            Some(s) => s,
612            None => return ConditionResult::Unknown,
613        };
614        if bgm
615            .elements
616            .first()
617            .and_then(|e| e.first())
618            .map(|s| s.as_str())
619            != Some("Z77")
620        {
621            return ConditionResult::False;
622        }
623        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "157");
624        match dtm_segs.first() {
625            Some(dtm) => {
626                let value = dtm
627                    .elements
628                    .first()
629                    .and_then(|e| e.get(1))
630                    .map(|s| s.as_str())
631                    .unwrap_or("");
632                if value.len() >= 8 {
633                    ConditionResult::from(&value[..8] >= "20231001")
634                } else {
635                    ConditionResult::Unknown
636                }
637            }
638            None => ConditionResult::False, // segment absent → condition not applicable
639        }
640    }
641
642    /// [47] Wenn BGM DE1001 = Z32 und LIN DE7140 im Format n13, dann muss der hier genannte Zeitpunkt &lt; 01.01.2024 00:00 Uhr gesetzlicher deutscher Zeit sein
643    // REVIEW: Checks BGM DE1001==Z32, LIN DE7140 is exactly 13 digits with no hyphens (legacy EAN/GLN format), and DTM+157 < 20240101. Complement of condition 43. (medium confidence)
644    fn evaluate_47(&self, ctx: &EvaluationContext) -> ConditionResult {
645        let bgm = match ctx.find_segment("BGM") {
646            Some(s) => s,
647            None => return ConditionResult::Unknown,
648        };
649        if bgm
650            .elements
651            .first()
652            .and_then(|e| e.first())
653            .map(|s| s.as_str())
654            != Some("Z32")
655        {
656            return ConditionResult::False;
657        }
658        let lin_segments = ctx.find_segments("LIN");
659        let has_n13_format = lin_segments.iter().any(|s| {
660            s.elements
661                .get(2)
662                .and_then(|e| e.first())
663                .map_or(false, |v| {
664                    !v.contains('-') && v.len() == 13 && v.chars().all(|c| c.is_ascii_digit())
665                })
666        });
667        if !has_n13_format {
668            return ConditionResult::False;
669        }
670        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "157");
671        match dtm_segs.first() {
672            Some(dtm) => {
673                let value = dtm
674                    .elements
675                    .first()
676                    .and_then(|e| e.get(1))
677                    .map(|s| s.as_str())
678                    .unwrap_or("");
679                if value.len() >= 8 {
680                    ConditionResult::from(&value[..8] < "20240101")
681                } else {
682                    ConditionResult::Unknown
683                }
684            }
685            None => ConditionResult::False, // segment absent → condition not applicable
686        }
687    }
688
689    /// [48] Wenn in dieser SG36 LIN in DE7140 einer der Codes 1-01-6-005 / 1-01-9-001 / 1-01-9-002 / 1-02-0-015 / 1-03-8-001 / 1-03-8-002 / 1-03-8-003 / 1-03-8-004 / 1-03-9-001 / 1-03-9-002 / 1-03-9-003 / 1-03...
690    fn evaluate_48(&self, ctx: &EvaluationContext) -> ConditionResult {
691        const CODES: &[&str] = &[
692            "1-01-6-005",
693            "1-01-9-001",
694            "1-01-9-002",
695            "1-02-0-015",
696            "1-03-8-001",
697            "1-03-8-002",
698            "1-03-8-003",
699            "1-03-8-004",
700            "1-03-9-001",
701            "1-03-9-002",
702            "1-03-9-003",
703            "1-03-9-004",
704            "1-07-4-001",
705        ];
706        let lin_segments = ctx.find_segments("LIN");
707        ConditionResult::from(lin_segments.iter().any(|s| {
708            s.elements
709                .get(2)
710                .and_then(|e| e.first())
711                .map_or(false, |v| CODES.contains(&v.as_str()))
712        }))
713    }
714
715    /// [49] Wenn in dieser SG36 LIN in DE7140 keiner der Codes 1-01-6-005 / 1-01-9-001 / 1-01-9-002 / 1-02-0-015 / 1-03-8-001 / 1-03-8-002 / 1-03-8-003 / 1-03-8-004 / 1-03-9-001 / 1-03-9-002 / 1-03-9-003 / 1-0...
716    fn evaluate_49(&self, ctx: &EvaluationContext) -> ConditionResult {
717        const CODES: &[&str] = &[
718            "1-01-6-005",
719            "1-01-9-001",
720            "1-01-9-002",
721            "1-02-0-015",
722            "1-03-8-001",
723            "1-03-8-002",
724            "1-03-8-003",
725            "1-03-8-004",
726            "1-03-9-001",
727            "1-03-9-002",
728            "1-03-9-003",
729            "1-03-9-004",
730            "1-07-4-001",
731        ];
732        let lin_segments = ctx.find_segments("LIN");
733        ConditionResult::from(!lin_segments.iter().any(|s| {
734            s.elements
735                .get(2)
736                .and_then(|e| e.first())
737                .map_or(false, |v| CODES.contains(&v.as_str()))
738        }))
739    }
740
741    /// [50] Wenn MP-ID aus RFF+Z56 mit MP-ID aus NAD+MS identisch ist
742    fn evaluate_50(&self, ctx: &EvaluationContext) -> ConditionResult {
743        let rff_segs = ctx.find_segments_with_qualifier("RFF", 0, "Z56");
744        let nad_segs = ctx.find_segments_with_qualifier("NAD", 0, "MS");
745        let rff_mp_id = rff_segs
746            .first()
747            .and_then(|s| s.elements.first())
748            .and_then(|e| e.get(1))
749            .map(|s| s.as_str());
750        let nad_mp_id = nad_segs
751            .first()
752            .and_then(|s| s.elements.get(1))
753            .and_then(|e| e.first())
754            .map(|s| s.as_str());
755        match (rff_mp_id, nad_mp_id) {
756            (Some(rff), Some(nad)) if !rff.is_empty() && !nad.is_empty() => {
757                ConditionResult::from(rff == nad)
758            }
759            _ => ConditionResult::Unknown,
760        }
761    }
762
763    /// [51] Wenn BGM+Z54 vorhanden
764    fn evaluate_51(&self, ctx: &EvaluationContext) -> ConditionResult {
765        ctx.has_qualifier("BGM", 0, "Z54")
766    }
767
768    /// [52] Wenn BGM+Z54 nicht vorhanden
769    fn evaluate_52(&self, ctx: &EvaluationContext) -> ConditionResult {
770        ctx.lacks_qualifier("BGM", 0, "Z54")
771    }
772
773    /// [53] Diese SG40 darf genau einmal in der SG36 angegeben werden
774    // REVIEW: Validates that SG40 appears exactly once within each SG36 instance. Uses the group navigator to count child SG40 instances per SG36 parent. PRICAT tx_group is SG17; SG36 is a nested group within SG17. Falls back to Unknown when no navigator is available. Medium confidence because the exact parent path ["SG17", "SG36"] is inferred from PRICAT structure conventions without an explicit segment reference. (medium confidence)
775    fn evaluate_53(&self, ctx: &EvaluationContext) -> ConditionResult {
776        // Diese SG40 darf genau einmal in der SG36 angegeben werden
777        // For each SG36 instance, verify SG40 appears exactly once as a child group
778        let nav = match ctx.navigator() {
779            Some(n) => n,
780            None => return ConditionResult::Unknown,
781        };
782        let sg36_count = nav.group_instance_count(&["SG17", "SG36"]);
783        if sg36_count == 0 {
784            return ConditionResult::Unknown;
785        }
786        for i in 0..sg36_count {
787            let sg40_count = nav.child_group_instance_count(&["SG17", "SG36"], i, "SG40");
788            if sg40_count != 1 {
789                return ConditionResult::False;
790            }
791        }
792        ConditionResult::True
793    }
794
795    /// [64] Wenn der Zeitpunkt im DTM+157 DE2380 ≥ 01.01.2026, 00:00 Uhr gesetzlicher deutscher Zeit
796    fn evaluate_64(&self, ctx: &EvaluationContext) -> ConditionResult {
797        let dtm_segs = ctx.find_segments_with_qualifier("DTM", 0, "157");
798        match dtm_segs.first() {
799            Some(dtm) => {
800                let value = dtm
801                    .elements
802                    .first()
803                    .and_then(|e| e.get(1))
804                    .map(|s| s.as_str())
805                    .unwrap_or("");
806                if value.len() >= 8 {
807                    ConditionResult::from(&value[..8] >= "20260101")
808                } else {
809                    ConditionResult::Unknown
810                }
811            }
812            None => ConditionResult::False, // segment absent → condition not applicable
813        }
814    }
815
816    /// [492] wenn MP-ID in NAD+MR aus Sparte Strom
817    /// EXTERNAL: Requires context from outside the message.
818    // REVIEW: Checks if the NAD+MR market participant ID belongs to the electricity (Strom) sector. Sector membership cannot be determined from the EDIFACT message alone — requires external business context. (medium confidence)
819    fn evaluate_492(&self, ctx: &EvaluationContext) -> ConditionResult {
820        ctx.external.evaluate("recipient_is_strom")
821    }
822
823    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
824    fn evaluate_494(&self, _ctx: &EvaluationContext) -> ConditionResult {
825        // Hinweis: Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument
826        // erstellt wurde, oder ein Zeitpunkt, der davor liegt. Informational annotation
827        // about the semantic meaning of the date field — always applies unconditionally.
828        ConditionResult::True
829    }
830
831    /// [495] Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein
832    // REVIEW: The condition checks that the Betrachtungszeitintervall (DTM+492) value is <= the Nachrichtendatum (DTM+137) value. Both DTM qualifiers appear in the segment reference for PRICAT. We extract DTM+137's DE2380 value as the threshold and use dtm_le to compare. Medium confidence because 'Der Zeitpunkt' could refer to DTM+157 (Gültigkeitsbeginn) instead of DTM+492 — the exact field being validated is not explicit in the description. (medium confidence)
833    fn evaluate_495(&self, ctx: &EvaluationContext) -> ConditionResult {
834        // Der Zeitpunkt (DTM+492 Betrachtungszeitintervall) muss <= DTM+137 (Nachrichtendatum) sein
835        let dtm_137_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
836        let dtm_137 = match dtm_137_segs.first() {
837            Some(s) => s,
838            None => return ConditionResult::Unknown,
839        };
840        let threshold = match dtm_137.elements.first().and_then(|e| e.get(1)) {
841            Some(v) => v.clone(),
842            None => return ConditionResult::Unknown,
843        };
844        ctx.dtm_le("492", &threshold)
845    }
846
847    /// [502] Hinweis: Preis in Euro je MWh
848    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
849        ConditionResult::True
850    }
851
852    /// [503] Hinweis: Hier ist immer der Wert 1000 einzutragen, da in DE5118 der Preis in €/MWh angegeben wird.
853    fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
854        ConditionResult::True
855    }
856
857    /// [504] Hinweis: Dokumentennummer der PRICAT
858    fn evaluate_504(&self, _ctx: &EvaluationContext) -> ConditionResult {
859        ConditionResult::True
860    }
861
862    /// [511] Hinweis: 1. Der genannte Wert gehört nicht zum Intervall.  2. Wenn in dieser SG36 die letzte Ziffer von LIN DE7140 &gt;1, dann ist der Wert identisch mit dem Wert des DE6152 im RNG-Segment, in der...
863    fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
864        // Hinweis: informational annotation about RNG value semantics and ordering rules across Artikel-IDs
865        ConditionResult::True
866    }
867
868    /// [512] Hinweis: Der genannte Wert gehört zum Intervall
869    fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
870        ConditionResult::True
871    }
872
873    /// [513] Hinweis: Die zum Preis gehörende Einheit ist in der Codeliste definiert
874    fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
875        ConditionResult::True
876    }
877
878    /// [519] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
879    fn evaluate_519(&self, _ctx: &EvaluationContext) -> ConditionResult {
880        ConditionResult::True
881    }
882
883    /// [520] Hinweis: Falls der Preis des Artikels gezont ist, ist diese SG40 so oft zu wiederholen, bis alle Preise zu diesem Artikel genannt sind
884    fn evaluate_520(&self, _ctx: &EvaluationContext) -> ConditionResult {
885        ConditionResult::True
886    }
887
888    /// [521] Hinweis: Je Artikel-ID muss in einem RNG der Wert dieses DE = 0 sein und in allen anderen RNG zu dieser Artikel-ID muss der Wert dieses DE mit dem Wert des DE6152 eines anderen RNG zu dieser Artike...
889    fn evaluate_521(&self, _ctx: &EvaluationContext) -> ConditionResult {
890        // Hinweis: informational annotation about RNG DE value relationships — one RNG must have value 0,
891        // all others must equal DE6152 of another RNG for the same Artikel-ID
892        ConditionResult::True
893    }
894
895    /// [902] Format: Möglicher Wert: ≥ 0
896    fn evaluate_902(&self, _ctx: &EvaluationContext) -> ConditionResult {
897        ConditionResult::True
898    }
899
900    /// [908] Format: Mögliche Werte: 1 bis n
901    fn evaluate_908(&self, _ctx: &EvaluationContext) -> ConditionResult {
902        ConditionResult::True
903    }
904
905    /// [909] Format: Mögliche Werte: 0 bis n
906    // REVIEW: Format condition specifying possible values 0 to n (non-negative). Applied to LIN DE1082 (Positionsnummer) as the most likely numeric element in PRICAT that can take values starting from 0. Medium confidence because the AHB context specifying which exact element this condition applies to is not included in the description. (medium confidence)
907    fn evaluate_909(&self, ctx: &EvaluationContext) -> ConditionResult {
908        // Format: Mögliche Werte 0 bis n — numeric value must be >= 0
909        // Applied to LIN DE1082 (Positionsnummer) which can start from 0
910        let lin_segs = ctx.find_segments("LIN");
911        if lin_segs.is_empty() {
912            return ConditionResult::Unknown;
913        }
914        for seg in &lin_segs {
915            let val = match seg.elements.first().and_then(|e| e.first()) {
916                Some(v) => v,
917                None => return ConditionResult::Unknown,
918            };
919            match validate_numeric(val, ">=", 0.0) {
920                ConditionResult::False => return ConditionResult::False,
921                ConditionResult::Unknown => return ConditionResult::Unknown,
922                ConditionResult::True => {}
923            }
924        }
925        ConditionResult::True
926    }
927
928    /// [911] Format: Mögliche Werte: 1 bis n, je Nachricht oder Segmentgruppe bei 1 beginnend und fortlaufend aufsteigend
929    // REVIEW: Format condition specifying values 1 to n, starting at 1 and continuously ascending per message or segment group. This describes sequential position numbering, which in PRICAT is DE1082 in the LIN segment. The implementation collects all LIN segments and verifies their position numbers are 1, 2, 3, ... in order. Medium confidence because the specific element and scope (per-message vs per-SG17 group) are not explicit in the condition description alone. (medium confidence)
930    fn evaluate_911(&self, ctx: &EvaluationContext) -> ConditionResult {
931        // Format: Mögliche Werte 1 bis n, sequential per message/segment group starting at 1
932        // Validates LIN DE1082 (Positionsnummer) is sequential starting from 1
933        let lin_segs = ctx.find_segments("LIN");
934        if lin_segs.is_empty() {
935            return ConditionResult::Unknown;
936        }
937        let mut expected: u64 = 1;
938        for seg in &lin_segs {
939            let pos_str = match seg.elements.first().and_then(|e| e.first()) {
940                Some(v) => v,
941                None => return ConditionResult::Unknown,
942            };
943            let pos: u64 = match pos_str.parse() {
944                Ok(n) => n,
945                Err(_) => return ConditionResult::False,
946            };
947            if pos != expected {
948                return ConditionResult::False;
949            }
950            expected += 1;
951        }
952        ConditionResult::True
953    }
954
955    /// [912] Format: max. 6 Nachkommastellen
956    // REVIEW: Max 6 decimal places format check. In PRICAT context, price values (PRI segment, element 0, component 1) are the primary numeric values with decimal places. Medium confidence because the exact segment this applies to depends on context — it could also apply to QTY or other numeric fields. (medium confidence)
957    fn evaluate_912(&self, ctx: &EvaluationContext) -> ConditionResult {
958        // Format: max. 6 Nachkommastellen — applies to PRI price value in PRICAT
959        let segs = ctx.find_segments("PRI");
960        match segs
961            .first()
962            .and_then(|s| s.elements.first())
963            .and_then(|e| e.get(1))
964        {
965            Some(val) => validate_max_decimal_places(val, 6),
966            None => ConditionResult::False, // segment absent → condition not applicable
967        }
968    }
969
970    /// [926] Format: Möglicher Wert: 0
971    // REVIEW: Value must be exactly 0. Applies to a numeric data element — QTY is most common in PRICAT for this kind of constraint (e.g., minimum order quantity of 0). Medium confidence because the specific segment depends on where this condition is referenced in the AHB. (medium confidence)
972    fn evaluate_926(&self, ctx: &EvaluationContext) -> ConditionResult {
973        // Format: Möglicher Wert: 0 — value must equal 0
974        let segs = ctx.find_segments("QTY");
975        match segs
976            .first()
977            .and_then(|s| s.elements.first())
978            .and_then(|e| e.get(1))
979        {
980            Some(val) => validate_numeric(val, "==", 0.0),
981            None => ConditionResult::False, // segment absent → condition not applicable
982        }
983    }
984
985    /// [929] Format: Möglicher Wert: 1000
986    // REVIEW: Value must be exactly 1000. In PRICAT, 1000 is a common reference quantity (e.g., price per 1000 units in QTY). Medium confidence because the exact target segment depends on where this condition is used in the AHB. (medium confidence)
987    fn evaluate_929(&self, ctx: &EvaluationContext) -> ConditionResult {
988        // Format: Möglicher Wert: 1000 — value must equal 1000
989        let segs = ctx.find_segments("QTY");
990        match segs
991            .first()
992            .and_then(|s| s.elements.first())
993            .and_then(|e| e.get(1))
994        {
995            Some(val) => validate_numeric(val, "==", 1000.0),
996            None => ConditionResult::False, // segment absent → condition not applicable
997        }
998    }
999
1000    /// [931] Format: ZZZ = +00
1001    // REVIEW: DTM timezone check: EDIFACT DTM format 303 (CCYYMMDDHHMMzzz) or 719 encodes timezone offset. The condition requires ZZZ=+00 (UTC offset). elements[0][1] holds the datetime value string; checking it ends with '+00' covers standard EDIFACT timezone encoding. Medium confidence because the exact DTM qualifier and format code context is unknown without the segment structure reference. (medium confidence)
1002    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
1003        // ZZZ = +00: timezone offset in DTM value must be +00
1004        // DTM format 303: YYYYMMDDHHMMzzz, timezone encoded as last part of elements[0][1]
1005        let dtm_segments = ctx.find_segments("DTM");
1006        for seg in &dtm_segments {
1007            if let Some(value) = seg.elements.first().and_then(|e| e.get(1)) {
1008                if !value.is_empty() {
1009                    // Timezone +00 appears at end of value string
1010                    if value.ends_with("+00") {
1011                        return ConditionResult::True;
1012                    } else {
1013                        return ConditionResult::False;
1014                    }
1015                }
1016            }
1017        }
1018        ConditionResult::Unknown
1019    }
1020
1021    /// [932] Format: HHMM = 2200
1022    // REVIEW: DTM time check: extracts HHMM portion from DTM value. Format 203 (CCYYMMDDHHmm) places hours at positions 8-11. Format 401 (HHmm) starts at position 0. Condition requires exactly 2200. Medium confidence because the specific DTM qualifier context in PRICAT is unknown without the segment structure reference. (medium confidence)
1023    fn evaluate_932(&self, ctx: &EvaluationContext) -> ConditionResult {
1024        // HHMM = 2200: time value in DTM must be 2200
1025        // In DTM format 203 (YYYYMMDDHHMM) or 401 (HHMM), the time portion is HHMM
1026        let dtm_segments = ctx.find_segments("DTM");
1027        for seg in &dtm_segments {
1028            if let Some(value) = seg.elements.first().and_then(|e| e.get(1)) {
1029                let format_code = seg
1030                    .elements
1031                    .first()
1032                    .and_then(|e| e.get(2))
1033                    .map(|s| s.as_str())
1034                    .unwrap_or("");
1035                let time_part = match format_code {
1036                    "203" if value.len() >= 12 => &value[8..12],
1037                    "401" if value.len() >= 4 => &value[0..4],
1038                    _ if value.len() >= 12 => &value[8..12],
1039                    _ => continue,
1040                };
1041                if time_part == "2200" {
1042                    return ConditionResult::True;
1043                } else {
1044                    return ConditionResult::False;
1045                }
1046            }
1047        }
1048        ConditionResult::Unknown
1049    }
1050
1051    /// [933] Format: HHMM = 2300
1052    // REVIEW: Same structure as condition 932 but checks for 2300 instead of 2200. Extracts HHMM portion from DTM value using format code to determine position. (medium confidence)
1053    fn evaluate_933(&self, ctx: &EvaluationContext) -> ConditionResult {
1054        // HHMM = 2300: time value in DTM must be 2300
1055        let dtm_segments = ctx.find_segments("DTM");
1056        for seg in &dtm_segments {
1057            if let Some(value) = seg.elements.first().and_then(|e| e.get(1)) {
1058                let format_code = seg
1059                    .elements
1060                    .first()
1061                    .and_then(|e| e.get(2))
1062                    .map(|s| s.as_str())
1063                    .unwrap_or("");
1064                let time_part = match format_code {
1065                    "203" if value.len() >= 12 => &value[8..12],
1066                    "401" if value.len() >= 4 => &value[0..4],
1067                    _ if value.len() >= 12 => &value[8..12],
1068                    _ => continue,
1069                };
1070                if time_part == "2300" {
1071                    return ConditionResult::True;
1072                } else {
1073                    return ConditionResult::False;
1074                }
1075            }
1076        }
1077        ConditionResult::Unknown
1078    }
1079
1080    /// [937] Format: keine Nachkommastelle
1081    // REVIEW: No decimal places allowed — integer values only. Implemented as validate_max_decimal_places with 0. In PRICAT, this typically applies to QTY (quantity must be whole number). Medium confidence because the exact target segment depends on AHB context. (medium confidence)
1082    fn evaluate_937(&self, ctx: &EvaluationContext) -> ConditionResult {
1083        // Format: keine Nachkommastelle — no decimal places allowed (max 0)
1084        let segs = ctx.find_segments("QTY");
1085        match segs
1086            .first()
1087            .and_then(|s| s.elements.first())
1088            .and_then(|e| e.get(1))
1089        {
1090            Some(val) => validate_max_decimal_places(val, 0),
1091            None => ConditionResult::False, // segment absent → condition not applicable
1092        }
1093    }
1094
1095    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
1096    // REVIEW: Email address format validation: the string must contain both '@' and '.'. This pattern matches COM segment (communication address) with channel code EM (Electronic Mail). elements[0][0] holds the address, elements[0][1] holds the channel qualifier. Checks channel=EM first, then falls back to any non-empty COM value. Medium confidence because PRICAT segment structure reference not provided. (medium confidence)
1097    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
1098        // Email format: string must contain both '@' and '.'
1099        // Typically applies to COM segment (communication channel EM = email)
1100        let com_segments = ctx.find_segments("COM");
1101        for seg in &com_segments {
1102            // COM: elements[0][0] = communication address, elements[0][1] = channel code
1103            let channel = seg
1104                .elements
1105                .first()
1106                .and_then(|e| e.get(1))
1107                .map(|s| s.as_str())
1108                .unwrap_or("");
1109            if channel == "EM" {
1110                if let Some(address) = seg.elements.first().and_then(|e| e.first()) {
1111                    let contains_at = address.contains('@');
1112                    let contains_dot = address.contains('.');
1113                    return ConditionResult::from(contains_at && contains_dot);
1114                }
1115            }
1116        }
1117        // Fallback: check any COM value for @ and .
1118        for seg in &com_segments {
1119            if let Some(address) = seg.elements.first().and_then(|e| e.first()) {
1120                if !address.is_empty() {
1121                    return ConditionResult::from(address.contains('@') && address.contains('.'));
1122                }
1123            }
1124        }
1125        ConditionResult::Unknown
1126    }
1127
1128    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
1129    // REVIEW: Phone number format: must start with '+' then only digits (international E.164 format). Applies to COM segment with telephone/fax channel codes (TE, FX, AJ). elements[0][0] = number, elements[0][1] = channel code. Medium confidence because PRICAT segment structure reference not provided. (medium confidence)
1130    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
1131        // Phone number format: must start with '+' followed only by digits
1132        // Typically applies to COM segment (TEL/FAX channel)
1133        let com_segments = ctx.find_segments("COM");
1134        for seg in &com_segments {
1135            let channel = seg
1136                .elements
1137                .first()
1138                .and_then(|e| e.get(1))
1139                .map(|s| s.as_str())
1140                .unwrap_or("");
1141            if matches!(channel, "TE" | "FX" | "AJ") {
1142                if let Some(number) = seg.elements.first().and_then(|e| e.first()) {
1143                    if !number.is_empty() {
1144                        let valid = number.starts_with('+')
1145                            && number.len() > 1
1146                            && number[1..].chars().all(|c| c.is_ascii_digit());
1147                        return ConditionResult::from(valid);
1148                    }
1149                }
1150            }
1151        }
1152        // Fallback: check any COM value for +digit format
1153        for seg in &com_segments {
1154            if let Some(number) = seg.elements.first().and_then(|e| e.first()) {
1155                if !number.is_empty() {
1156                    let valid = number.starts_with('+')
1157                        && number.len() > 1
1158                        && number[1..].chars().all(|c| c.is_ascii_digit());
1159                    return ConditionResult::from(valid);
1160                }
1161            }
1162        }
1163        ConditionResult::Unknown
1164    }
1165
1166    /// [941] Format: Artikelnummer
1167    // REVIEW: Article number format validation on PIA segment (Additional Product ID), element 1, component 0. The pattern n1-n2-n1-n3 is a common PRICAT Artikelnummer format in the German energy market. Medium confidence because the exact digit-segment pattern may differ — should be verified against the specific AHB table for this condition reference. (medium confidence)
1168    fn evaluate_941(&self, ctx: &EvaluationContext) -> ConditionResult {
1169        // Format: Artikelnummer — validate article number format in PIA segment
1170        let segs = ctx.find_segments("PIA");
1171        match segs
1172            .first()
1173            .and_then(|s| s.elements.get(1))
1174            .and_then(|e| e.first())
1175        {
1176            Some(val) => validate_artikel_pattern(val, &[1, 2, 1, 3]),
1177            None => ConditionResult::False, // segment absent → condition not applicable
1178        }
1179    }
1180
1181    /// [942] Format: n1-n2-n1-n3
1182    fn evaluate_942(&self, ctx: &EvaluationContext) -> ConditionResult {
1183        let segs = ctx.find_segments("PIA");
1184        match segs
1185            .first()
1186            .and_then(|s| s.elements.get(1))
1187            .and_then(|e| e.first())
1188        {
1189            Some(val) => validate_artikel_pattern(val, &[1, 2, 1, 3]),
1190            None => ConditionResult::False, // segment absent → condition not applicable
1191        }
1192    }
1193
1194    /// [946] Format: max. 11 Nachkommastellen
1195    // REVIEW: Format: max. 11 Nachkommastellen — validates that a numeric value has at most 11 decimal places. In PRICAT, price values appear in PRI segment element 0 component 1. Using PRI as the most likely price segment in PRICAT context. (medium confidence)
1196    fn evaluate_946(&self, ctx: &EvaluationContext) -> ConditionResult {
1197        let segs = ctx.find_segments("PRI");
1198        match segs
1199            .first()
1200            .and_then(|s| s.elements.first())
1201            .and_then(|e| e.get(1))
1202        {
1203            Some(val) => validate_max_decimal_places(val, 11),
1204            None => ConditionResult::False, // segment absent → condition not applicable
1205        }
1206    }
1207
1208    /// [948] Format: n1-n2-n1-n8-n2
1209    fn evaluate_948(&self, ctx: &EvaluationContext) -> ConditionResult {
1210        let segs = ctx.find_segments("PIA");
1211        match segs
1212            .first()
1213            .and_then(|s| s.elements.get(1))
1214            .and_then(|e| e.first())
1215        {
1216            Some(val) => validate_artikel_pattern(val, &[1, 2, 1, 8, 2]),
1217            None => ConditionResult::False, // segment absent → condition not applicable
1218        }
1219    }
1220
1221    /// [949] Format: n1-n2-n1-n8-n2-n1
1222    fn evaluate_949(&self, ctx: &EvaluationContext) -> ConditionResult {
1223        let segs = ctx.find_segments("PIA");
1224        match segs
1225            .first()
1226            .and_then(|s| s.elements.get(1))
1227            .and_then(|e| e.first())
1228        {
1229            Some(val) => validate_artikel_pattern(val, &[1, 2, 1, 8, 2, 1]),
1230            None => ConditionResult::False, // segment absent → condition not applicable
1231        }
1232    }
1233
1234    /// [957] Format: n1-n2-n1-n8
1235    fn evaluate_957(&self, ctx: &EvaluationContext) -> ConditionResult {
1236        let segs = ctx.find_segments("PIA");
1237        match segs
1238            .first()
1239            .and_then(|s| s.elements.get(1))
1240            .and_then(|e| e.first())
1241        {
1242            Some(val) => validate_artikel_pattern(val, &[1, 2, 1, 8]),
1243            None => ConditionResult::False, // segment absent → condition not applicable
1244        }
1245    }
1246
1247    /// [959] Format: n13-n2
1248    // REVIEW: Format: n13-n2 is an Artikelnummer pattern — 13 digits, dash, 2 digits. In PRICAT, article identifiers appear in PIA element 1 component 0. Using validate_artikel_pattern with &[13, 2]. Medium confidence because the exact target segment/element depends on AHB context not provided here. (medium confidence)
1249    fn evaluate_959(&self, ctx: &EvaluationContext) -> ConditionResult {
1250        let segs = ctx.find_segments("PIA");
1251        match segs
1252            .first()
1253            .and_then(|s| s.elements.get(1))
1254            .and_then(|e| e.first())
1255        {
1256            Some(val) => validate_artikel_pattern(val, &[13, 2]),
1257            None => ConditionResult::False, // segment absent → condition not applicable
1258        }
1259    }
1260
1261    /// [968] Format: Möglicher Wert: ≤ 0
1262    // REVIEW: Format: Möglicher Wert: ≤ 0 means the numeric value must be less than or equal to zero. In PRICAT, PRI segment element 0 component 1 holds the price amount. Using validate_numeric with "<=", 0.0. Medium confidence because the exact target segment depends on AHB context — could also be MOA or another numeric field. (medium confidence)
1263    fn evaluate_968(&self, ctx: &EvaluationContext) -> ConditionResult {
1264        let segs = ctx.find_segments("PRI");
1265        match segs
1266            .first()
1267            .and_then(|s| s.elements.first())
1268            .and_then(|e| e.get(1))
1269        {
1270            Some(val) => validate_numeric(val, "<=", 0.0),
1271            None => ConditionResult::False, // segment absent → condition not applicable
1272        }
1273    }
1274}