// <auto-generated>
// Generated by automapper-generator generate-conditions
// AHB: xml-migs-and-ahbs/FV2610/UTILTS_AHB_1_1_20260401.xml
// Generated: 2026-04-22T18:06:18Z
// Delegates unchanged conditions to UtiltsConditionEvaluatorFV2504 (FV2504).
// </auto-generated>
#![allow(clippy::style, clippy::complexity)]
#[allow(unused_imports)]
use crate::eval::format_validators::*;
use crate::eval::{ConditionEvaluator, ConditionResult, EvaluationContext};
use crate::generated::fv2504::UtiltsConditionEvaluatorFV2504;
/// Condition evaluator for UTILTS FV2610.
///
/// Delegates to [`UtiltsConditionEvaluatorFV2504`] for conditions whose AHB description is
/// unchanged from that version. Local overrides: [15, 63, 511, 2005].
pub struct UtiltsConditionEvaluatorFV2610 {
fallback: UtiltsConditionEvaluatorFV2504,
}
impl Default for UtiltsConditionEvaluatorFV2610 {
fn default() -> Self {
Self {
fallback: UtiltsConditionEvaluatorFV2504::default(),
}
}
}
/// Condition IDs whose implementation differs from the fallback.
const FV2610_OVERRIDES: &[u32] = &[15, 63, 511, 2005];
impl ConditionEvaluator for UtiltsConditionEvaluatorFV2610 {
fn message_type(&self) -> &str {
"UTILTS"
}
fn format_version(&self) -> &str {
"FV2610"
}
fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
match condition {
15 => self.evaluate_15(ctx),
63 => self.evaluate_63(ctx),
511 => self.evaluate_511(ctx),
2005 => self.evaluate_2005(ctx),
other => self.fallback.evaluate(other, ctx),
}
}
fn is_external(&self, condition: u32) -> bool {
matches!(condition, 63) || self.fallback.is_external(condition)
}
fn is_known(&self, condition: u32) -> bool {
FV2610_OVERRIDES.contains(&condition) || self.fallback.is_known(condition)
}
}
impl UtiltsConditionEvaluatorFV2610 {
/// [15] Wenn in einem SG5 IDE+24 nur eine SEQ+Z37 mit einer SG8 RFF+Z19 (Messlokation) und derselben Zeitraum-ID vorhanden ist
// REVIEW: Navigates SG5 groups to find IDE+24, then counts SG8 child instances where SEQ+Z37 and RFF+Z19 coexist with a matching Zeitraum-ID. 'Derselben Zeitraum-ID' is interpreted as: the identifier in SEQ elements[1][0] must equal the reference value in RFF C506 elements[0][1]. True if exactly one such paired SG8 is found. Element positions for the Zeitraum-ID in SEQ and RFF assume standard BDEW UTILTS structure (RFF+Z19:id → elements[0][1], SEQ+Z37+id → elements[1][0]) — no segment structure reference was provided to confirm. (medium confidence)
fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
let nav = match ctx.navigator() {
Some(n) => n,
None => return ConditionResult::Unknown,
};
let sg5_count = nav.group_instance_count(&["SG5"]);
for sg5_i in 0..sg5_count {
let ide_segs = nav.find_segments_in_group("IDE", &["SG5"], sg5_i);
let has_ide_24 = ide_segs.iter().any(|s| {
s.elements
.first()
.and_then(|e| e.first())
.is_some_and(|v| v == "24")
});
if !has_ide_24 {
continue;
}
let sg8_count = nav.child_group_instance_count(&["SG5"], sg5_i, "SG8");
let mut matching_count = 0usize;
for sg8_j in 0..sg8_count {
let seq_segs =
nav.find_segments_in_child_group("SEQ", &["SG5"], sg5_i, "SG8", sg8_j);
let seq_zeitraum_id = seq_segs
.iter()
.find(|s| {
s.elements
.first()
.and_then(|e| e.first())
.is_some_and(|v| v == "Z37")
})
.and_then(|s| s.elements.get(1).and_then(|e| e.first()).cloned());
let Some(seq_id) = seq_zeitraum_id else {
continue;
};
let rff_segs =
nav.find_segments_in_child_group("RFF", &["SG5"], sg5_i, "SG8", sg8_j);
let has_z19_with_id = rff_segs.iter().any(|s| {
s.elements
.first()
.and_then(|e| e.first())
.is_some_and(|v| v == "Z19")
&& s.elements
.first()
.and_then(|e| e.get(1))
.is_some_and(|v| v == &seq_id)
});
if has_z19_with_id {
matching_count += 1;
}
}
if matching_count > 0 {
return ConditionResult::from(matching_count == 1);
}
}
ConditionResult::Unknown
}
/// [63] Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle NB
/// EXTERNAL: Requires context from outside the message.
fn evaluate_63(&self, ctx: &EvaluationContext) -> ConditionResult {
ctx.external.evaluate("recipient_is_nb")
}
/// [511] Hinweis: Der Zählzeitänderungszeitpunkt (SG8 DTM+Z33) dieser SG8 darf in keiner anderen SG8 „Zählzeitdefinition“ wiederholt werden
fn evaluate_511(&self, _ctx: &EvaluationContext) -> ConditionResult {
// Hinweis: DTM+Z33 Zählzeitänderungszeitpunkt must not repeat across SG8 Zählzeitdefinition groups — informational annotation
ConditionResult::True
}
/// [2005] Segment ist genau einmal für jede Zeitraum-ID aus dem DE9012 der SG5 STS+E01 ("Status der Antwort") anzugeben, wenn im selben SG5 STS+E01 im DE9013 der Code A99 ("Sonstiges") enthalten ist
// REVIEW: Finds SG5 STS+E01 segments where DE9013 (C556 comp 0, elements[2][0]) = 'A99' and collects their Zeitraum-IDs from DE9012 (C556 comp 3, elements[2][3]). The annotated segment is FTX+ACB (UTILTS 25010 FV2610 Number=00018), which carries the Zeitraum-ID at C107.DE4441 (elements[2][0]). For each qualifying ID, exactly one FTX+ACB must reference it.
fn evaluate_2005(&self, ctx: &EvaluationContext) -> ConditionResult {
// Collect Zeitraum-IDs from SG5 STS+E01 where DE9013 = A99.
let sts_e01 = ctx.find_segments_with_qualifier("STS", 0, "E01");
let zeitraum_ids: Vec<String> = sts_e01
.iter()
.filter(|s| {
s.elements
.get(2)
.and_then(|e| e.first())
.is_some_and(|v| v == "A99")
})
.filter_map(|s| s.elements.get(2).and_then(|e| e.get(3)).cloned())
.filter(|id| !id.is_empty())
.collect();
if zeitraum_ids.is_empty() {
return ConditionResult::Unknown;
}
// For each Zeitraum-ID, exactly one FTX+ACB (C107.DE4441 at elements[2][0]) must reference it.
let ftx_acb = ctx.find_segments_with_qualifier("FTX", 0, "ACB");
for zid in &zeitraum_ids {
let count = ftx_acb
.iter()
.filter(|s| {
s.elements
.get(2)
.and_then(|e| e.first())
.is_some_and(|v| v == zid)
})
.count();
if count != 1 {
return ConditionResult::False;
}
}
ConditionResult::True
}
}