automapper-validation 0.8.1

AHB condition expression parsing, evaluation, and EDIFACT validation
Documentation
// <auto-generated>
// Generated by automapper-generator generate-conditions
// AHB: xml-migs-and-ahbs/FV2604/UTILMD_AHB_Strom_2_1_Fehlerkorrektur_20260629.xml
// Generated: 2026-09-23T18:15:06Z
// Delegates unchanged conditions to UtilmdStromConditionEvaluatorFV2510 (FV2510).
// </auto-generated>

#![allow(clippy::style, clippy::complexity)]

#[allow(unused_imports)]
use crate::eval::format_validators::*;
use crate::eval::{ConditionEvaluator, ConditionResult, EvaluationContext};
use crate::generated::fv2510::UtilmdStromConditionEvaluatorFV2510;
use mig_types::segment::OwnedSegment;

/// Condition evaluator for UTILMD_Strom FV2604.
///
/// Delegates to [`UtilmdStromConditionEvaluatorFV2510`] for conditions whose AHB description is
/// unchanged from that version. Local overrides: [160], [182], [183], [203], [271].
pub struct UtilmdStromConditionEvaluatorFV2604 {
    fallback: UtilmdStromConditionEvaluatorFV2510,
}

impl Default for UtilmdStromConditionEvaluatorFV2604 {
    fn default() -> Self {
        Self {
            fallback: UtilmdStromConditionEvaluatorFV2510::default(),
        }
    }
}

/// Condition IDs whose implementation differs from the fallback.
const FV2604_OVERRIDES: &[u32] = &[160, 182, 183, 203, 271];

impl ConditionEvaluator for UtilmdStromConditionEvaluatorFV2604 {
    fn message_type(&self) -> &str {
        "UTILMD_Strom"
    }

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

    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
        match condition {
            160 => self.evaluate_160(ctx),
            182 => self.evaluate_182(ctx),
            183 => self.evaluate_183(ctx),
            203 => self.evaluate_203(ctx),
            271 => self.evaluate_271(ctx),
            other => self.fallback.evaluate(other, ctx),
        }
    }

    fn is_external(&self, condition: u32) -> bool {
        !FV2604_OVERRIDES.contains(&condition) && self.fallback.is_external(condition)
    }

    fn is_known(&self, condition: u32) -> bool {
        FV2604_OVERRIDES.contains(&condition) || self.fallback.is_known(condition)
    }
}

/// Whether some SG8 has an SG10 whose CCI and CAV segments satisfy `pred`.
fn sg10_has(
    ctx: &EvaluationContext,
    pred: impl Fn(&[OwnedSegment], &[OwnedSegment]) -> bool,
) -> ConditionResult {
    let Some(nav) = ctx.navigator() else {
        let ccis: Vec<OwnedSegment> = ctx.find_segments("CCI").into_iter().cloned().collect();
        let cavs: Vec<OwnedSegment> = ctx.find_segments("CAV").into_iter().cloned().collect();
        return ConditionResult::from(pred(&ccis, &cavs));
    };
    for i in 0..nav.group_instance_count(&["SG4", "SG8"]) {
        for j in 0..nav.child_group_instance_count(&["SG4", "SG8"], i, "SG10") {
            let ccis = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], i, "SG10", j);
            let cavs = nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], i, "SG10", j);
            if pred(&ccis, &cavs) {
                return ConditionResult::True;
            }
        }
    }
    ConditionResult::False
}

impl UtilmdStromConditionEvaluatorFV2604 {
    /// [160] Wenn der Objektcode im RFF+Z33 (Referenz auf den Objektcode in der Lokationsbündelstruktur) im DE1154 derselben SG8 SEQ+...
    // Message-wide approximation: some two SG8 SEQ+Z58/ZC9/ZD0/ZD6 share Zeitraum-ID
    // (SEQ C286 1050), DE1229 code and the RFF+Z33 Objektcode.
    fn evaluate_160(&self, ctx: &EvaluationContext) -> ConditionResult {
        let nav = match ctx.navigator() {
            Some(n) => n,
            None => return ConditionResult::Unknown,
        };
        let mut seen = std::collections::BTreeSet::new();
        for i in 0..nav.group_instance_count(&["SG4", "SG8"]) {
            let seqs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], i);
            let Some(seq) = seqs.first() else { continue };
            let code = seq
                .elements
                .first()
                .and_then(|e| e.first())
                .cloned()
                .unwrap_or_default();
            if !["Z58", "ZC9", "ZD0", "ZD6"].contains(&code.as_str()) {
                continue;
            }
            let zeitraum = seq
                .elements
                .get(1)
                .and_then(|e| e.first())
                .cloned()
                .unwrap_or_default();
            for rff in
                nav.find_segments_with_qualifier_in_group("RFF", 0, "Z33", &["SG4", "SG8"], i)
            {
                let objekt = rff
                    .elements
                    .first()
                    .and_then(|e| e.get(1))
                    .cloned()
                    .unwrap_or_default();
                if !seen.insert((zeitraum.clone(), code.clone(), objekt)) {
                    return ConditionResult::True;
                }
            }
        }
        ConditionResult::False
    }

    /// [182] Wenn SG10 CCI+++ZA6 (Prognosegrundlage der Marktlokation: Prognose auf Basis von Profilen) CAV+E14 (TLP/TEP) in dieser S...
    fn evaluate_182(&self, ctx: &EvaluationContext) -> ConditionResult {
        sg10_has(ctx, |ccis, cavs| {
            ccis.iter().any(|s| {
                s.elements
                    .get(2)
                    .and_then(|e| e.first())
                    .is_some_and(|v| v == "ZA6")
            }) && cavs.iter().any(|s| {
                s.elements
                    .first()
                    .and_then(|e| e.first())
                    .is_some_and(|v| v == "E14")
            })
        })
    }

    /// [183] Wenn SG10 CCI+Z30++Z07 (Lieferrichtung: Verbrauch) in dieser SG8 vorhanden
    fn evaluate_183(&self, ctx: &EvaluationContext) -> ConditionResult {
        sg10_has(ctx, |ccis, _| {
            ccis.iter().any(|s| {
                s.elements
                    .first()
                    .and_then(|e| e.first())
                    .is_some_and(|v| v == "Z30")
                    && s.elements
                        .get(2)
                        .and_then(|e| e.first())
                        .is_some_and(|v| v == "Z07")
            })
        })
    }

    /// [203] Wenn STS+7++E06 / Z39 / ZC6 / ZC7/ ZT6/ ZT7 / Z02 / ZZD vorhanden
    fn evaluate_203(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualified_value(
            "STS",
            0,
            "7",
            2,
            0,
            &["E06", "Z39", "ZC6", "ZC7", "ZT6", "ZT7", "Z02", "ZZD"],
        )
    }

    /// [271] Wenn das RFF+Z50 (Termine der Marktlokation) aus dieser SG6 (Termine der Marktlokation) auf das gleiche SG5 LOC+Z16 (Mar...
    // As FV2504's [86] (RFF+Z50 of an SG6 references the LOC+Z16 an SG8 RFF+Z18 does),
    // and some SG10 carries CCI+++Z83 with CAV+Z53 (kME/mME).
    fn evaluate_271(&self, ctx: &EvaluationContext) -> ConditionResult {
        let shared = ctx.groups_share_qualified_value(
            "RFF",
            0,
            "Z50",
            0,
            2,
            &["SG4", "SG6"],
            "RFF",
            0,
            2,
            &["SG4", "SG8"],
        );
        let messtechnik = sg10_has(ctx, |ccis, cavs| {
            ccis.iter().any(|s| {
                s.elements
                    .get(2)
                    .and_then(|e| e.first())
                    .is_some_and(|v| v == "Z83")
            }) && cavs.iter().any(|s| {
                s.elements
                    .first()
                    .and_then(|e| e.first())
                    .is_some_and(|v| v == "Z53")
            })
        });
        shared.and(messtechnik)
    }
}