use std::collections::BTreeMap;
use automapper_validation::eval::{
ConditionResult, EvaluationContext, GroupScope, MapExternalProvider,
};
use automapper_validation::evaluator_factory::evaluator_for;
use mig_assembly::assembler::{
AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
};
use mig_assembly::navigator::AssembledTreeNavigator;
use mig_types::segment::OwnedSegment;
use ConditionResult::{False, True, Unknown};
fn seg(text: &str) -> AssembledSegment {
let mut parts = text.split('+');
let tag = parts.next().unwrap().to_string();
AssembledSegment {
tag,
elements: parts
.map(|e| e.split(':').map(str::to_string).collect())
.collect(),
mig_number: None,
segment_number: None,
}
}
fn inst(segments: &[&str], child_groups: Vec<AssembledGroup>) -> AssembledGroupInstance {
AssembledGroupInstance {
segments: segments.iter().map(|s| seg(s)).collect(),
child_groups,
entry_mig_number: None,
variant_mig_numbers: vec![],
skipped_segments: vec![],
skipped_positions: Vec::new(),
}
}
fn grp(id: &str, repetitions: Vec<AssembledGroupInstance>) -> AssembledGroup {
AssembledGroup {
group_id: id.to_string(),
repetitions,
}
}
fn tree(groups: Vec<AssembledGroup>) -> AssembledTree {
AssembledTree {
segments: vec![],
groups,
post_group_start: 0,
inter_group_segments: BTreeMap::new(),
}
}
fn flat(tree: &AssembledTree) -> Vec<OwnedSegment> {
fn walk(groups: &[AssembledGroup], out: &mut Vec<OwnedSegment>) {
for group in groups {
for instance in &group.repetitions {
for s in &instance.segments {
out.push(OwnedSegment {
id: s.tag.clone(),
elements: s.elements.clone(),
segment_number: out.len() as u32,
});
}
walk(&instance.child_groups, out);
}
}
}
let mut out = Vec::new();
walk(&tree.groups, &mut out);
out
}
type Case = (u32, &'static [(&'static str, usize)], ConditionResult);
fn check(
variant: &str,
fvs: &[&str],
tree: &AssembledTree,
cases: &[Case],
wrong: &mut Vec<String>,
) {
let navigator = AssembledTreeNavigator::new(tree);
let external = MapExternalProvider::new(Default::default());
let segments = flat(tree);
let ctx = EvaluationContext::with_navigator("00000", &external, &segments, &navigator);
for fv in fvs {
let evaluator = evaluator_for(variant, fv).unwrap_or_else(|| panic!("{variant} {fv}"));
for &(condition, path, expected) in cases {
let got = evaluator.evaluate(condition, &ctx.with_scope(GroupScope { path }));
if got != expected {
wrong.push(format!(
"{variant} {fv} [{condition}] at {path:?}: {got:?}, expected {expected:?}"
));
}
}
}
}
#[test]
fn each_instance_is_compared_with_its_siblings() {
let mut wrong = Vec::new();
let sg15 = |sts: &str, acw: &str| inst(&[sts, acw], vec![]);
let iftsta = tree(vec![grp(
"SG14",
vec![
inst(
&["CNI+1"],
vec![grp(
"SG15",
vec![
sg15("STS+Z27", "RFF+ACW:A1"),
sg15("STS+Z28", "RFF+ACW:A2"),
sg15("STS+Z29", "RFF+ACW:A1"),
sg15("STS+Z30", "RFF+ACW:A3"),
sg15("STS+Z20", "RFF+ACW:A1"),
sg15("STS+Z20", "RFF+ACW:A9"),
],
)],
),
inst(
&["CNI+2"],
vec![grp("SG15", vec![sg15("STS+Z20", "RFF+ACW:A9")])],
),
],
)]);
let at = |i: usize, j: usize| -> &'static [(&'static str, usize)] {
Box::leak(vec![("SG14", i), ("SG15", j)].into_boxed_slice())
};
check(
"IFTSTA",
&["FV2504", "FV2510"],
&iftsta,
&[
(56, at(0, 4), False),
(56, at(0, 5), True),
(56, at(1, 0), Unknown),
(57, at(0, 4), True),
(57, at(0, 1), False),
(58, at(0, 4), False),
(58, at(0, 5), True),
(59, at(0, 4), True),
(59, at(0, 3), False),
],
&mut wrong,
);
let sg10 = |dtms: &[&str]| {
let mut segs = vec!["QTY+220:1"];
segs.extend_from_slice(dtms);
inst(&segs, vec![])
};
let mscons = tree(vec![grp(
"SG5",
vec![inst(
&["NAD+DP"],
vec![grp(
"SG6",
vec![inst(
&["LOC+172+DE0001"],
vec![grp(
"SG9",
vec![
inst(
&["LIN+1", "PIA+5+OBIS1"],
vec![grp(
"SG10",
vec![
sg10(&["DTM+163:20250101:102", "DTM+164:20250102:102"]),
sg10(&["DTM+163:20250102:102", "DTM+164:20250103:102"]),
],
)],
),
inst(
&["LIN+2", "PIA+5+OBIS1"],
vec![grp(
"SG10",
vec![sg10(&["DTM+163:20241231:102", "DTM+164:20250105:102"])],
)],
),
inst(
&["LIN+3", "PIA+5+OBIS2"],
vec![grp(
"SG10",
vec![sg10(&["DTM+163:20240101:102", "DTM+164:20260101:102"])],
)],
),
],
)],
)],
)],
)],
)]);
let m = |i: usize, j: usize| -> &'static [(&'static str, usize)] {
Box::leak(vec![("SG5", 0), ("SG6", 0), ("SG9", i), ("SG10", j)].into_boxed_slice())
};
check(
"MSCONS",
&["FV2504", "FV2510", "FV2604"],
&mscons,
&[
(87, m(1, 0), True),
(87, m(0, 0), False),
(87, m(2, 0), True),
(88, m(1, 0), True),
(88, m(0, 1), False),
(88, m(2, 0), True),
],
&mut wrong,
);
let zone = |rng: &str| inst(&["PRI+CAL:1", rng], vec![]);
let article =
|lin: &str, zones: Vec<AssembledGroupInstance>| inst(&[lin], vec![grp("SG40", zones)]);
let pricat = tree(vec![grp(
"SG17",
vec![
inst(
&["PGI+9"],
vec![grp(
"SG36",
vec![
article(
"LIN+1++A-01:Z12",
vec![zone("RNG+10+KWH:0:100"), zone("RNG+10+KWH:100:200")],
),
article("LIN+2++A-02:Z12", vec![zone("RNG+10+KWH:0:150")]),
article("LIN+3++A-03:Z12", vec![zone("RNG+10+KWH:150:300")]),
article("LIN+4++A-04:Z12", vec![zone("RNG+10+KWH:999:1000")]),
],
)],
),
inst(
&["PGI+9"],
vec![grp(
"SG36",
vec![article("LIN+1++B-01:Z12", vec![zone("RNG+10+KWH:0:10")])],
)],
),
],
)]);
let p = |g: usize, a: usize, z: usize| -> &'static [(&'static str, usize)] {
Box::leak(vec![("SG17", g), ("SG36", a), ("SG40", z)].into_boxed_slice())
};
check(
"PRICAT",
&["FV2510"],
&pricat,
&[
(10, p(0, 0, 0), True),
(10, p(0, 3, 0), False),
(10, p(1, 0, 0), False),
(55, p(0, 0, 0), True),
(55, p(0, 0, 1), False),
(66, p(0, 1, 0), True),
(66, p(0, 2, 0), True),
(66, p(0, 3, 0), False),
(67, p(0, 0, 0), True),
(67, p(0, 3, 0), False),
(72, p(0, 2, 0), True),
(72, p(0, 3, 0), False),
(72, p(0, 0, 0), True),
],
&mut wrong,
);
let utilts = tree(vec![grp(
"SG5",
vec![inst(
&["IDE+24+1"],
vec![grp(
"SG8",
vec![
inst(
&["SEQ+Z42", "DTM+Z33:202501010000:303"],
vec![grp("SG9", vec![inst(&["CCI+Z39++ZZ1"], vec![])])],
),
inst(&["SEQ+Z42", "DTM+Z33:202501010000:303"], vec![]),
inst(&["SEQ+Z42", "DTM+Z33:202502010000:303"], vec![]),
inst(&["SEQ+Z43", "RFF+Z27:ZZ1"], vec![]),
inst(&["SEQ+Z43", "RFF+Z27:ZZ1"], vec![]),
inst(&["SEQ+Z43", "RFF+Z27:ZZ2"], vec![]),
],
)],
)],
)]);
let u = |i: usize| -> &'static [(&'static str, usize)] {
Box::leak(vec![("SG5", 0), ("SG8", i)].into_boxed_slice())
};
check(
"UTILTS",
&["FV2504"],
&utilts,
&[
(511, u(0), False),
(511, u(2), True),
(2002, u(3), True),
(2002, u(5), False),
],
&mut wrong,
);
assert!(wrong.is_empty(), "{wrong:#?}");
}