pub mod ar_coefficients;
pub mod assembly;
pub mod correlation;
pub mod external;
pub mod inflow_history;
pub mod inflow_stats;
pub mod load_factors;
pub mod load_stats;
pub mod noise_openings;
pub mod non_controllable_factors;
pub mod non_controllable_stats;
pub use ar_coefficients::{InflowArCoefficientRow, parse_inflow_ar_coefficients};
pub use assembly::{assemble_inflow_models, assemble_load_models};
pub use correlation::parse_correlation;
pub use external::{ExternalScenarioRow, parse_external_scenarios};
pub use inflow_history::{InflowHistoryRow, parse_inflow_history};
pub use inflow_stats::{InflowSeasonalStatsRow, parse_inflow_seasonal_stats};
pub use load_factors::{BlockFactor, LoadFactorEntry, parse_load_factors};
pub use load_stats::{LoadSeasonalStatsRow, parse_load_seasonal_stats};
pub use noise_openings::{
NoiseOpeningRow, assemble_opening_tree, parse_noise_openings, validate_noise_openings,
};
pub use non_controllable_factors::{NcsFactorEntry, parse_non_controllable_factors};
pub use non_controllable_stats::parse_ncs_stats;
use cobre_core::scenario::{CorrelationModel, InflowModel, LoadModel, NcsModel};
use crate::LoadError;
use crate::validation::structural::FileManifest;
use std::path::Path;
pub fn load_inflow_seasonal_stats(
path: Option<&Path>,
) -> Result<Vec<InflowSeasonalStatsRow>, LoadError> {
match path {
None => Ok(Vec::new()),
Some(p) => parse_inflow_seasonal_stats(p),
}
}
pub fn load_inflow_ar_coefficients(
path: Option<&Path>,
) -> Result<Vec<InflowArCoefficientRow>, LoadError> {
match path {
None => Ok(Vec::new()),
Some(p) => parse_inflow_ar_coefficients(p),
}
}
pub fn load_inflow_history(path: Option<&Path>) -> Result<Vec<InflowHistoryRow>, LoadError> {
match path {
None => Ok(Vec::new()),
Some(p) => parse_inflow_history(p),
}
}
pub fn load_load_seasonal_stats(
path: Option<&Path>,
) -> Result<Vec<LoadSeasonalStatsRow>, LoadError> {
match path {
None => Ok(Vec::new()),
Some(p) => parse_load_seasonal_stats(p),
}
}
pub fn load_load_factors(path: Option<&Path>) -> Result<Vec<LoadFactorEntry>, LoadError> {
match path {
None => Ok(Vec::new()),
Some(p) => parse_load_factors(p),
}
}
pub fn load_non_controllable_factors(
path: Option<&Path>,
) -> Result<Vec<NcsFactorEntry>, LoadError> {
match path {
None => Ok(Vec::new()),
Some(p) => parse_non_controllable_factors(p),
}
}
pub fn load_ncs_stats(path: Option<&Path>) -> Result<Vec<NcsModel>, LoadError> {
match path {
None => Ok(Vec::new()),
Some(p) => parse_ncs_stats(p),
}
}
pub fn load_correlation(path: Option<&Path>) -> Result<CorrelationModel, LoadError> {
match path {
None => Ok(CorrelationModel::default()),
Some(p) => parse_correlation(p),
}
}
pub fn load_external_scenarios(path: Option<&Path>) -> Result<Vec<ExternalScenarioRow>, LoadError> {
match path {
None => Ok(Vec::new()),
Some(p) => parse_external_scenarios(p),
}
}
pub fn load_noise_openings(path: Option<&Path>) -> Result<Vec<NoiseOpeningRow>, LoadError> {
match path {
None => Ok(Vec::new()),
Some(p) => parse_noise_openings(p),
}
}
#[derive(Debug, Clone)]
pub struct ScenarioData {
pub inflow_models: Vec<InflowModel>,
pub load_models: Vec<LoadModel>,
pub ncs_models: Vec<NcsModel>,
pub correlation: CorrelationModel,
pub inflow_history: Vec<InflowHistoryRow>,
pub external_scenarios: Vec<ExternalScenarioRow>,
pub load_factors: Vec<LoadFactorEntry>,
pub noise_openings: Vec<NoiseOpeningRow>,
}
pub fn load_scenarios(
case_root: &Path,
manifest: &FileManifest,
) -> Result<ScenarioData, LoadError> {
let scenarios_dir = case_root.join("scenarios");
let raw_stats = load_inflow_seasonal_stats(
manifest
.scenarios_inflow_seasonal_stats_parquet
.then(|| scenarios_dir.join("inflow_seasonal_stats.parquet"))
.as_deref(),
)?;
let raw_coefficients = load_inflow_ar_coefficients(
manifest
.scenarios_inflow_ar_coefficients_parquet
.then(|| scenarios_dir.join("inflow_ar_coefficients.parquet"))
.as_deref(),
)?;
let inflow_history = load_inflow_history(
manifest
.scenarios_inflow_history_parquet
.then(|| scenarios_dir.join("inflow_history.parquet"))
.as_deref(),
)?;
let raw_load_stats = load_load_seasonal_stats(
manifest
.scenarios_load_seasonal_stats_parquet
.then(|| scenarios_dir.join("load_seasonal_stats.parquet"))
.as_deref(),
)?;
let load_factors = load_load_factors(
manifest
.scenarios_load_factors_json
.then(|| scenarios_dir.join("load_factors.json"))
.as_deref(),
)?;
let correlation = load_correlation(
manifest
.scenarios_correlation_json
.then(|| scenarios_dir.join("correlation.json"))
.as_deref(),
)?;
let external_scenarios = load_external_scenarios(
manifest
.scenarios_external_scenarios_parquet
.then(|| scenarios_dir.join("external_scenarios.parquet"))
.as_deref(),
)?;
let noise_openings = load_noise_openings(
manifest
.scenarios_noise_openings_parquet
.then(|| scenarios_dir.join("noise_openings.parquet"))
.as_deref(),
)?;
let ncs_models = load_ncs_stats(
manifest
.scenarios_non_controllable_stats_parquet
.then(|| scenarios_dir.join("non_controllable_stats.parquet"))
.as_deref(),
)?;
let inflow_models = assemble_inflow_models(raw_stats, raw_coefficients)?;
let load_models = assemble_load_models(raw_load_stats);
Ok(ScenarioData {
inflow_models,
load_models,
ncs_models,
correlation,
inflow_history,
external_scenarios,
load_factors,
noise_openings,
})
}
#[cfg(test)]
#[allow(
clippy::doc_markdown,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::too_many_lines
)]
mod tests {
use super::*;
use crate::validation::structural::FileManifest;
use tempfile::TempDir;
#[test]
fn test_load_inflow_seasonal_stats_none_returns_empty() {
let result = load_inflow_seasonal_stats(None).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_load_inflow_ar_coefficients_none_returns_empty() {
let result = load_inflow_ar_coefficients(None).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_load_inflow_history_none_returns_empty() {
let result = load_inflow_history(None).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_load_load_seasonal_stats_none_returns_empty() {
let result = load_load_seasonal_stats(None).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_load_load_factors_none_returns_empty() {
let result = load_load_factors(None).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_load_correlation_none_returns_default() {
let result = load_correlation(None).unwrap();
assert!(result.profiles.is_empty());
assert!(result.schedule.is_empty());
}
#[test]
fn test_load_external_scenarios_none_returns_empty() {
let result = load_external_scenarios(None).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_load_scenarios_all_flags_false_returns_empty() {
let dir = TempDir::new().unwrap();
let manifest = FileManifest::default();
let data =
load_scenarios(dir.path(), &manifest).expect("empty manifest should always succeed");
assert!(
data.inflow_models.is_empty(),
"inflow_models should be empty when no scenario files present"
);
assert!(
data.load_models.is_empty(),
"load_models should be empty when no scenario files present"
);
assert!(
data.correlation.profiles.is_empty(),
"correlation.profiles should be empty (default) when correlation.json absent"
);
assert!(
data.correlation.schedule.is_empty(),
"correlation.schedule should be empty (default) when correlation.json absent"
);
assert!(
data.inflow_history.is_empty(),
"inflow_history should be empty when file absent"
);
assert!(
data.external_scenarios.is_empty(),
"external_scenarios should be empty when file absent"
);
assert!(
data.load_factors.is_empty(),
"load_factors should be empty when file absent"
);
}
#[test]
fn test_load_noise_openings_none_returns_empty() {
let result = load_noise_openings(None).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_load_scenarios_noise_openings_absent() {
let dir = TempDir::new().unwrap();
let manifest = FileManifest::default();
let data =
load_scenarios(dir.path(), &manifest).expect("empty manifest should always succeed");
assert!(
data.noise_openings.is_empty(),
"noise_openings should be empty when scenarios_noise_openings_parquet flag is false"
);
}
}