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