use std::collections::HashMap;
use cobre_core::{SystemBuilder, scenario::CorrelationModel};
use crate::{
LoadError,
report::{ValidationReport, generate_report},
resolution::{
BoundsEntitySlices, BoundsOverrides, PenaltiesEntitySlices, PenaltiesOverrides,
resolve_bounds, resolve_exchange_factors, resolve_generic_constraint_bounds,
resolve_load_factors, resolve_ncs_bounds, resolve_ncs_factors, resolve_penalties,
},
scenarios::assembly::{assemble_inflow_models, assemble_load_models},
validation::{
ValidationContext,
dimensional::validate_dimensional_consistency,
productivity_resolution::validate_productivity_resolution,
referential::validate_referential_integrity,
scalar_parameters::validate_scalar_parameters,
schema::validate_schema,
semantic::{validate_semantic_hydro_thermal, validate_semantic_stages_penalties_scenarios},
structural::validate_structure,
},
};
use crate::{CaseArtifacts, LoadedCase};
use cobre_core::System;
use std::path::Path;
pub(crate) fn run_pipeline(path: &Path) -> Result<System, LoadError> {
run_pipeline_with_report(path).map(|(system, _report)| system)
}
pub(crate) fn run_pipeline_with_report(
path: &Path,
) -> Result<(System, ValidationReport), LoadError> {
run_pipeline_with_artifacts(path).map(|(loaded, report)| (loaded.system, report))
}
#[allow(clippy::too_many_lines)]
pub(crate) fn run_pipeline_with_artifacts(
path: &Path,
) -> Result<(LoadedCase, ValidationReport), LoadError> {
let mut ctx = ValidationContext::new();
let manifest = validate_structure(path, &mut ctx);
let data = validate_schema(path, &manifest, &mut ctx);
let Some(data) = data else {
return ctx.into_result().and(Err(LoadError::ConstraintError {
description: "schema validation failed but no errors were collected".to_string(),
}));
};
validate_referential_integrity(&data, &mut ctx);
validate_dimensional_consistency(&data, &mut ctx);
validate_semantic_hydro_thermal(&data, &mut ctx);
validate_semantic_stages_penalties_scenarios(&data, &mut ctx);
validate_productivity_resolution(&data, &mut ctx);
let n_study_stages = data.stages.stages.iter().filter(|s| s.id >= 0).count();
validate_scalar_parameters(
&data.scalar_parameters,
&data.hydros,
n_study_stages,
&mut ctx,
);
let report = generate_report(&ctx);
ctx.into_result()?;
let study_stages: Vec<_> = data.stages.stages.iter().filter(|s| s.id >= 0).collect();
let n_stages = study_stages.len();
let stage_index: HashMap<i32, usize> = study_stages
.iter()
.enumerate()
.map(|(idx, s)| (s.id, idx))
.collect();
let penalties = resolve_penalties(
&PenaltiesEntitySlices {
hydros: &data.hydros,
buses: &data.buses,
lines: &data.lines,
ncs_sources: &data.non_controllable_sources,
},
n_stages,
&stage_index,
&PenaltiesOverrides {
hydro: &data.penalty_overrides_hydro,
bus: &data.penalty_overrides_bus,
line: &data.penalty_overrides_line,
ncs: &data.penalty_overrides_ncs,
},
);
let k_max: usize = data
.thermals
.iter()
.filter_map(|t| t.anticipated_config.as_ref())
.map(|c| usize::try_from(c.lead_stages).unwrap_or(usize::MAX))
.max()
.unwrap_or(0);
let bounds = resolve_bounds(
&BoundsEntitySlices {
hydros: &data.hydros,
thermals: &data.thermals,
lines: &data.lines,
pumping_stations: &data.pumping_stations,
contracts: &data.energy_contracts,
},
n_stages,
k_max,
&stage_index,
&BoundsOverrides {
hydro: &data.hydro_bounds,
thermal: &data.thermal_bounds,
line: &data.line_bounds,
pumping: &data.pumping_bounds,
contract: &data.contract_bounds,
},
);
let resolved_generic_bounds = resolve_generic_constraint_bounds(
&data.generic_constraints,
&data.generic_constraint_bounds,
);
let resolved_load_factors =
resolve_load_factors(&data.load_factors, &data.buses, &data.stages.stages);
let resolved_exchange_factors =
resolve_exchange_factors(&data.exchange_factors, &data.lines, &data.stages.stages);
let resolved_ncs_bounds = resolve_ncs_bounds(
&data.ncs_bounds,
&data.non_controllable_sources,
n_stages,
&stage_index,
);
let resolved_ncs_factors = resolve_ncs_factors(
&data.non_controllable_factors,
&data.non_controllable_sources,
&data.stages.stages,
);
let inflow_models = assemble_inflow_models(
data.inflow_seasonal_stats,
data.inflow_ar_coefficients,
data.inflow_annual_components,
)?;
let load_models = assemble_load_models(data.load_seasonal_stats);
let artifacts = CaseArtifacts {
file_manifest: manifest,
hydro_geometry: data.hydro_geometry,
production_models: data.production_models,
hydro_energy_productivity: data.hydro_energy_productivity_rows,
fpha_hyperplanes: data.fpha_hyperplanes,
scalar_parameters: data.scalar_parameters,
};
let system = SystemBuilder::new()
.buses(data.buses)
.lines(data.lines)
.hydros(data.hydros)
.thermals(data.thermals)
.pumping_stations(data.pumping_stations)
.contracts(data.energy_contracts)
.non_controllable_sources(data.non_controllable_sources)
.stages(data.stages.stages)
.policy_graph(data.stages.policy_graph)
.penalties(penalties)
.bounds(bounds)
.resolved_generic_bounds(resolved_generic_bounds)
.resolved_load_factors(resolved_load_factors)
.resolved_exchange_factors(resolved_exchange_factors)
.resolved_ncs_bounds(resolved_ncs_bounds)
.resolved_ncs_factors(resolved_ncs_factors)
.inflow_models(inflow_models)
.load_models(load_models)
.ncs_models(data.ncs_models)
.correlation(data.correlation.unwrap_or_else(CorrelationModel::default))
.initial_conditions(data.initial_conditions)
.generic_constraints(data.generic_constraints)
.inflow_history(data.inflow_history)
.external_scenarios(data.external_scenarios)
.external_load_scenarios(data.external_load_scenarios)
.external_ncs_scenarios(data.external_ncs_scenarios)
.build()
.map_err(|errs| LoadError::ConstraintError {
description: errs
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("\n"),
})?;
Ok((LoadedCase { system, artifacts }, report))
}