automapper-validation 0.1.54

AHB condition expression parsing, evaluation, and EDIFACT validation
Documentation
// <auto-generated>
// Generated by automapper-generator generate-conditions
// AHB: xml-migs-and-ahbs/FV2510/INSRPT_AHB_1_1g_außerordentliche_20251211.xml
// Generated: 2026-03-12T11:19:04Z
// </auto-generated>

// LLM-produced bodies aren't idiomatic Rust — silence stylistic lints at
// file scope so `cargo clippy -D warnings` stays green. Correctness,
// suspicious, and perf categories stay on so real bugs still surface.
#![allow(clippy::style, clippy::complexity)]

#[allow(unused_imports)]
use crate::eval::format_validators::*;
use crate::eval::{ConditionEvaluator, ConditionResult, EvaluationContext};

/// Generated condition evaluator for INSRPT FV2510.
pub struct InsrptConditionEvaluatorFV2510 {
    // External condition IDs that require runtime context.
    external_conditions: std::collections::HashSet<u32>,
}

impl Default for InsrptConditionEvaluatorFV2510 {
    fn default() -> Self {
        let mut external_conditions = std::collections::HashSet::new();
        external_conditions.insert(1);
        external_conditions.insert(3);
        external_conditions.insert(4);
        external_conditions.insert(5);
        external_conditions.insert(7);
        external_conditions.insert(9);
        external_conditions.insert(12);
        external_conditions.insert(14);
        Self {
            external_conditions,
        }
    }
}

impl ConditionEvaluator for InsrptConditionEvaluatorFV2510 {
    fn message_type(&self) -> &str {
        "INSRPT"
    }

    fn format_version(&self) -> &str {
        "FV2510"
    }

    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
        match condition {
            1 => self.evaluate_1(ctx),
            2 => self.evaluate_2(ctx),
            3 => self.evaluate_3(ctx),
            4 => self.evaluate_4(ctx),
            5 => self.evaluate_5(ctx),
            6 => self.evaluate_6(ctx),
            7 => self.evaluate_7(ctx),
            8 => self.evaluate_8(ctx),
            9 => self.evaluate_9(ctx),
            10 => self.evaluate_10(ctx),
            11 => self.evaluate_11(ctx),
            12 => self.evaluate_12(ctx),
            13 => self.evaluate_13(ctx),
            14 => self.evaluate_14(ctx),
            494 => self.evaluate_494(ctx),
            495 => self.evaluate_495(ctx),
            506 => self.evaluate_506(ctx),
            507 => self.evaluate_507(ctx),
            508 => self.evaluate_508(ctx),
            509 => self.evaluate_509(ctx),
            510 => self.evaluate_510(ctx),
            511 => self.evaluate_511(ctx),
            512 => self.evaluate_512(ctx),
            513 => self.evaluate_513(ctx),
            514 => self.evaluate_514(ctx),
            515 => self.evaluate_515(ctx),
            908 => self.evaluate_908(ctx),
            931 => self.evaluate_931(ctx),
            950 => self.evaluate_950(ctx),
            951 => self.evaluate_951(ctx),
            _ => ConditionResult::Unknown,
        }
    }

    fn is_external(&self, condition: u32) -> bool {
        self.external_conditions.contains(&condition)
    }
    fn is_known(&self, condition: u32) -> bool {
        matches!(
            condition,
            1 | 2
                | 3
                | 4
                | 5
                | 6
                | 7
                | 8
                | 9
                | 10
                | 11
                | 12
                | 13
                | 14
                | 494
                | 495
                | 506
                | 507
                | 508
                | 509
                | 510
                | 511
                | 512
                | 513
                | 514
                | 515
                | 908
                | 931
                | 950
                | 951
        )
    }
}

impl InsrptConditionEvaluatorFV2510 {
    /// [1] Wenn Nachrichtenabsender vom Kunden informiert wurde.
    /// EXTERNAL: Requires context from outside the message.
    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external
            .evaluate("nachrichtenabsender_vom_kunden_informiert")
    }

    /// [2] Wenn SG7 STS+Z06+Z10+ZC1 vorhanden.
    fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_segment_matching("STS", &[(0, 0, "Z06"), (1, 0, "Z10"), (2, 0, "ZC1")])
    }

    /// [3] Wenn vorhanden.
    /// EXTERNAL: The AHB text ("Wenn vorhanden.") is underspecified — no subject, no segment reference. Attached to DTM+9 (Bearbeitungs-/Verarbeitungsdatum) at `AHB_Status="Soll [3]"`, so the natural reading is "when a processing timestamp is available to include", which is a sender-side decision, not something the validator can derive from the message.
    fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external
            .evaluate("insrpt_bearbeitungsdatum_available")
    }

    /// [4] Wenn MP-ID in SG2 NAD+MR in der Rolle NB
    /// EXTERNAL: Requires context from outside the message.
    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external.evaluate("recipient_is_nb")
    }

    /// [5] Wenn MP-ID in SG2 NAD+MR in der Rolle LF
    /// EXTERNAL: Requires context from outside the message.
    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external.evaluate("recipient_is_lf")
    }

    /// [6] Wenn keine Störung festgestellt werden konnte.
    // REVIEW: "Keine Störung festgestellt" maps to STS Gerätestatus category Z06 with status Z09. Z09 appears in the STS schema as one of the valid DE4405 status codes, and condition [10] explicitly checks 'DE4405 = Z09', strongly suggesting Z09 encodes the no-fault result. Medium confidence because the semantic mapping of Z09 to 'no fault' is inferred from context rather than explicitly stated in the schema. (medium confidence)
    fn evaluate_6(&self, ctx: &EvaluationContext) -> ConditionResult {
        // "Keine Störung festgestellt" — STS+Z06 (Gerätestatus) with status code Z09 (no fault)
        ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z06"), (1, 0, "Z09")], &["SG3", "SG7"])
    }

    /// [7] Wenn keine weitere SG7 mit demselben Meldepunkt und DTM+9 vorhanden
    /// EXTERNAL: The Meldepunkt identifier (LOC+172 DE3225) sits on the enclosing SG3, not on SG7 itself, so the check is a cross-SG3 correlation: "does any other SG3's SG7 carry the same LOC+172 AND the same DTM+9?". Per-field evaluation does not have the parent-SG3 anchor, and introducing `current_group_stack` was deferred (see cluster H notes). Delegated to the operator.
    fn evaluate_7(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external
            .evaluate("insrpt_no_duplicate_sg7_same_meldepunkt_and_dtm9")
    }

    /// [8] Wenn in dieser SG7 STS+Z06+Z10 vorhanden
    fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
        // "Wenn in dieser SG7 STS+Z06+Z10 vorhanden" — STS with Statuskategorie Z06 and Status Z10
        ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z06"), (1, 0, "Z10")], &["SG3", "SG7"])
    }

    /// [9] Wenn eine Störung festgestellt wurde, die durch den MSB selbständig und unverschuldet nicht behoben werden konnte.
    /// EXTERNAL: Requires context from outside the message.
    fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external.evaluate("msb_unrepairable_fault")
    }

    /// [10] Wenn in diesem STS DE4405 = Z09
    fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
        // "Wenn in diesem STS DE4405 = Z09" — Status code Z09 in STS elements[1][0]
        ctx.any_group_has_qualifier("STS", 1, "Z09", &["SG3", "SG7"])
    }

    /// [11] Wenn in diesem STS DE4405 = Z10
    fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_segment_matching("STS", &[(1, 0, "Z10")])
    }

    /// [12] Wenn eine Störung festgestellt wurde, die durch den MSB behoben wurde.
    /// EXTERNAL: Requires context from outside the message.
    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external.evaluate("fault_resolved_by_msb")
    }

    /// [13] Wenn DE2379 = 303
    fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_segment_matching("DTM", &[(0, 2, "303")])
    }

    /// [14] Nur MP-ID aus Sparte Strom
    /// EXTERNAL: Requires context from outside the message.
    fn evaluate_14(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external.evaluate("mp_id_is_strom")
    }

    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
    // REVIEW: This condition constrains a specific date field to be at or before document creation time. Without knowing which DTM qualifier is being constrained at this AHB position in INSRPT, ConditionResult::True is the appropriate approximation — the constraint is unconditional when the field is present. (medium confidence)
    fn evaluate_494(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
        // Informational constraint: the date in this field must be <= document creation time.
        // Without knowing which specific DTM qualifier is being constrained at this AHB position,
        // returning True is the safe approximation (consistent with ordrsp evaluate_494).
        ConditionResult::True
    }

    /// [495] Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein
    // REVIEW: DTM+137 is the document date. The condition requires all SG7 DTMs (qualifiers 9, 163, 164, 292) to be ≤ the document date value. String comparison is valid for EDIFACT format 303 (CCYYMMDDHHMM) timestamps. Medium confidence because the condition says 'Der Zeitpunkt' (singular) but applies to DTMs in SG7 — unclear which specific one without AHB context. (medium confidence)
    fn evaluate_495(&self, ctx: &EvaluationContext) -> ConditionResult {
        // Der Zeitpunkt muss ≤ dem Wert im DE2380 des DTM+137 sein
        // Get the document date from DTM+137, then check all SG7 DTMs are ≤ that value
        let dtm_137_segs = ctx.find_segments_with_qualifier("DTM", 0, "137");
        let threshold = match dtm_137_segs
            .first()
            .and_then(|s| s.elements.first())
            .and_then(|e| e.get(1))
        {
            Some(v) if !v.is_empty() => v.clone(),
            _ => return ConditionResult::Unknown,
        };
        // SG7 DTM qualifiers: 9 (status determined), 163 (start), 164 (end), 292 (state end)
        let sg7_qualifiers = ["9", "163", "164", "292"];
        let mut found_any = false;
        for qual in &sg7_qualifiers {
            let segs = ctx.find_segments_with_qualifier("DTM", 0, qual);
            for seg in segs {
                if let Some(val) = seg.elements.first().and_then(|e| e.get(1)) {
                    if !val.is_empty() {
                        found_any = true;
                        if val.as_str() > threshold.as_str() {
                            return ConditionResult::False;
                        }
                    }
                }
            }
        }
        if found_any {
            ConditionResult::True
        } else {
            ConditionResult::Unknown
        }
    }

    /// [506] Hinweis: Zu nutzen, wenn Behebung der Störung durch den MSB selbständig und unverschuldet nicht möglich ist.
    fn evaluate_506(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Zu nutzen, wenn Behebung der Störung durch den MSB selbständig und unverschuldet nicht möglich ist.
        // Informational note about usage context — always applies
        ConditionResult::True
    }

    /// [507] Hinweis: In SG7 FTX+AAO ist anzugeben, was die übergeordnete Ursache ist, aufgrund derer der MSB nicht in der Lage ist die Störung zu beheben.
    fn evaluate_507(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: In SG7 FTX+AAO ist anzugeben, was die übergeordnete Ursache ist, aufgrund derer der MSB nicht in der Lage ist die Störung zu beheben.
        // Informational note about what content to provide in FTX+AAO — always applies
        ConditionResult::True
    }

    /// [508] Hinweis: Vorgangsnummer aus DOC DE1004.
    fn evaluate_508(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Vorgangsnummer aus DOC DE1004.
        // Informational note indicating the value origin (transaction number from DOC DE1004) — always applies
        ConditionResult::True
    }

    /// [509] Hinweis: Verwendung der ID der Messlokation
    fn evaluate_509(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Verwendung der ID der Messlokation — informational note, always applies
        ConditionResult::True
    }

    /// [510] Hinweis: Verwendung der ID der Marktlokation
    fn evaluate_510(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Verwendung der ID der Marktlokation — informational note, always applies
        ConditionResult::True
    }

    /// [511] Hinweis: Die Nummerierung beginnt in jedem Dokument bei 1
    fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Die Nummerierung beginnt in jedem Dokument bei 1 — informational note, always applies
        ConditionResult::True
    }

    /// [512] Hinweis: Wurde eine Störung festgestellt und durch den MSB behoben, ist die Segmentgruppe mit demselben Meldepunkt zweimal anzugeben
    fn evaluate_512(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Wurde eine Störung festgestellt und durch den MSB behoben, ist die Segmentgruppe mit demselben Meldepunkt zweimal anzugeben — informational note, always applies
        ConditionResult::True
    }

    /// [513] Hinweis: Wurde keine Störung festgestellt, ist die Segmentgruppe genau einmal anzugeben
    fn evaluate_513(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Wurde keine Störung festgestellt, ist die Segmentgruppe genau einmal anzugeben — informational note, always applies
        ConditionResult::True
    }

    /// [514] Hinweis: Wurde eine Störung festgestellt, die nicht durch den MSB behoben werden konnte, ist die Segmentgruppe genau einmal anzugeben
    fn evaluate_514(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Wurde eine Störung festgestellt, die nicht durch den MSB behoben werden konnte, ist die Segmentgruppe genau einmal anzugeben
        // Informational note — always applies
        ConditionResult::True
    }

    /// [515] Hinweis: "≤ dem Wert im DE2380 des DTM+137" bedeutet, dass der dort genannte Tag ≥ dem in diesem DTM genannten Tag sein muss, wenn in DE2379 der Code 102 steht.
    fn evaluate_515(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: "≤ dem Wert im DE2380 des DTM+137" bedeutet, dass der dort genannte Tag ≥ dem in diesem DTM genannten Tag sein muss, wenn in DE2379 der Code 102 steht.
        // Informational note — always applies
        ConditionResult::True
    }

    /// [908] Format: Mögliche Werte: 1 bis n
    // REVIEW: Format condition 'Mögliche Werte: 1 bis n' means the value must be a positive integer (>= 1). In INSRPT, the most likely target is a sequence number element (SEQ DE1050). Implemented using validate_numeric with '>=' 1.0. Medium confidence because the exact segment/element target is not specified in the provided structure reference. (medium confidence)
    fn evaluate_908(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.format_check("SEQ", 1, 0, |val| validate_numeric(val, ">=", 1.0))
    }

    /// [931] Format: ZZZ = +00
    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.format_check_qualified("DTM", 0, "137", 0, 1, validate_timezone_utc)
    }

    /// [950] Format: Marktlokations-ID
    fn evaluate_950(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.format_check_qualified("LOC", 0, "Z16", 1, 0, validate_malo_id)
    }

    /// [951] Format: Zählpunktbezeichnung
    // REVIEW: Format condition for Zählpunktbezeichnung (meter point designation) — 33 alphanumeric characters. Uses validate_zahlpunkt helper. Without the exact INSRPT segment structure reference, the implementation checks LOC segment element 1 (typical location for identifiers) with a fallback to NAD C082. Confidence is medium because the exact segment path for the Zählpunktbezeichnung in INSRPT is not confirmed without the MIG segment structure reference. (medium confidence)
    fn evaluate_951(&self, ctx: &EvaluationContext) -> ConditionResult {
        // Format: Zählpunktbezeichnung — 33 alphanumeric characters
        // Zählpunktbezeichnung is stored in a LOC or reference segment in INSRPT
        // Try LOC segments (common location for Zählpunkt identifiers)
        let loc_segs = ctx.find_segments("LOC");
        for seg in &loc_segs {
            if let Some(val) = seg.elements.get(1).and_then(|e| e.first()) {
                if !val.is_empty() {
                    return validate_zahlpunkt(val);
                }
            }
        }
        // Fallback: check NAD segment identification (C082)
        let nad_segs = ctx.find_segments("NAD");
        for seg in &nad_segs {
            if let Some(val) = seg.elements.get(1).and_then(|e| e.first()) {
                if !val.is_empty() && val.len() == 33 {
                    return validate_zahlpunkt(val);
                }
            }
        }
        ConditionResult::Unknown
    }
}