use super::super::{ErrorKind, ValidationContext, schema::ParsedData};
use super::CORR_TOLERANCE;
pub(super) fn check_correlation_matrices(data: &ParsedData, ctx: &mut ValidationContext) {
let Some(correlation) = &data.correlation else {
return;
};
for profile in correlation.profiles.values() {
for group in &profile.groups {
let n = group.entities.len();
let group_name = &group.name;
if group.matrix.len() != n {
continue;
}
for i in 0..n {
if group.matrix[i].len() != n {
continue;
}
for j in 0..n {
let val = group.matrix[i][j];
if i == j && (val - 1.0).abs() > CORR_TOLERANCE {
ctx.add_error(
ErrorKind::BusinessRuleViolation,
"scenarios/correlation.json",
Some(format!("CorrelationGroup {group_name}")),
format!(
"CorrelationGroup '{group_name}': diagonal entry matrix[{i}][{i}] \
is {val}, expected 1.0 (±{CORR_TOLERANCE}); \
correlation matrix diagonal must be 1.0"
),
);
}
if i != j && !((-1.0_f64)..=1.0).contains(&val) {
ctx.add_error(
ErrorKind::BusinessRuleViolation,
"scenarios/correlation.json",
Some(format!("CorrelationGroup {group_name}")),
format!(
"CorrelationGroup '{group_name}': off-diagonal entry \
matrix[{i}][{j}] is {val}, outside valid range [-1.0, 1.0]; \
correlation coefficients must be in [-1.0, 1.0]"
),
);
}
if i < j {
let symmetric = group.matrix[j][i];
if (val - symmetric).abs() > CORR_TOLERANCE {
ctx.add_error(
ErrorKind::BusinessRuleViolation,
"scenarios/correlation.json",
Some(format!("CorrelationGroup {group_name}")),
format!(
"CorrelationGroup '{group_name}': correlation matrix is not \
symmetric at ({i},{j}): matrix[{i}][{j}]={val} but \
matrix[{j}][{i}]={symmetric}; tolerance is {CORR_TOLERANCE}"
),
);
}
}
}
}
}
}
}
pub(super) fn check_correlation_same_type(data: &ParsedData, ctx: &mut ValidationContext) {
let Some(correlation) = &data.correlation else {
return;
};
for profile in correlation.profiles.values() {
for group in &profile.groups {
if group.entities.is_empty() {
continue;
}
let first_type = &group.entities[0].entity_type;
for entity in &group.entities[1..] {
if entity.entity_type != *first_type {
ctx.add_error(
ErrorKind::BusinessRuleViolation,
"scenarios/correlation.json",
Some(format!("CorrelationGroup '{}'", group.name)),
format!(
"CorrelationGroup '{}': entity {} has type '{}' but entity {} has \
type '{}'; all entities in a group must share the same entity_type",
group.name,
group.entities[0].id.0,
first_type,
entity.id.0,
entity.entity_type,
),
);
break;
}
}
}
}
}