use serde::{Deserialize, Serialize};
use crate::lint::{LintReport, Severity, lint};
use crate::metrics::{Scorecard, scorecard};
use crate::monte_carlo::{MonteCarloRequest, monte_carlo_with_cpm};
use crate::plan::{MilestoneRow, PlanGraph, PlannerError, ScheduleRow};
use crate::resource_schedule::{ResourceSchedule, ScheduleRequest, resource_schedule_with_cpm};
use crate::schedule::{compute_cpm, milestone_rows, schedule_rows};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SimulateRequest {
#[serde(default)]
pub schedule: Option<ScheduleRequest>,
#[serde(default)]
pub monte_carlo: Option<MonteCarloRequest>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SimulationResult {
pub lint: LintReport,
pub critical_path: Vec<String>,
pub critical_path_hours: f32,
pub schedule: Vec<ScheduleRow>,
pub milestones: Vec<MilestoneRow>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource_schedule: Option<ResourceSchedule>,
pub scorecard: Scorecard,
}
pub fn simulate(
graph: &PlanGraph,
req: &SimulateRequest,
) -> Result<SimulationResult, PlannerError> {
let lint_report = lint(graph);
let errors: Vec<String> = lint_report
.findings
.iter()
.filter(|f| f.severity == Severity::Error)
.map(|f| format!("{} [{}]: {}", f.code, f.ids.join(", "), f.message))
.collect();
if !errors.is_empty() {
return Err(PlannerError::InvalidGraph {
reason: format!("lint errors: {}", errors.join("; ")),
});
}
crate::planner::validate_graph(graph)?;
let cpm = compute_cpm(graph)?;
let leveled = req
.schedule
.as_ref()
.map(|s| resource_schedule_with_cpm(graph, s, &cpm))
.transpose()?;
let summary = req
.monte_carlo
.as_ref()
.map(|m| monte_carlo_with_cpm(graph, m, &cpm))
.transpose()?;
let mut card = scorecard(graph, &cpm, leveled.as_ref(), &lint_report);
card.monte_carlo = summary;
Ok(SimulationResult {
lint: lint_report,
critical_path: cpm.critical_path.clone(),
critical_path_hours: cpm.critical_path_duration,
schedule: schedule_rows(graph, &cpm),
milestones: milestone_rows(graph, &cpm, |_| false),
resource_schedule: leveled,
scorecard: card,
})
}