1use serde::{Deserialize, Serialize};
5
6use crate::lint::{LintReport, Severity, lint};
7use crate::metrics::{Scorecard, scorecard};
8use crate::monte_carlo::{MonteCarloRequest, monte_carlo_with_cpm};
9use crate::plan::{MilestoneRow, PlanGraph, PlannerError, ScheduleRow};
10use crate::resource_schedule::{ResourceSchedule, ScheduleRequest, resource_schedule_with_cpm};
11use crate::schedule::{compute_cpm, milestone_rows, schedule_rows};
12
13#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct SimulateRequest {
17 #[serde(default)]
19 pub schedule: Option<ScheduleRequest>,
20 #[serde(default)]
22 pub monte_carlo: Option<MonteCarloRequest>,
23}
24
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub struct SimulationResult {
28 pub lint: LintReport,
29 pub critical_path: Vec<String>,
30 pub critical_path_hours: f32,
31 pub schedule: Vec<ScheduleRow>,
33 pub milestones: Vec<MilestoneRow>,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub resource_schedule: Option<ResourceSchedule>,
36 pub scorecard: Scorecard,
38}
39
40pub fn simulate(
47 graph: &PlanGraph,
48 req: &SimulateRequest,
49) -> Result<SimulationResult, PlannerError> {
50 let lint_report = lint(graph);
51 let errors: Vec<String> = lint_report
52 .findings
53 .iter()
54 .filter(|f| f.severity == Severity::Error)
55 .map(|f| format!("{} [{}]: {}", f.code, f.ids.join(", "), f.message))
56 .collect();
57 if !errors.is_empty() {
58 return Err(PlannerError::InvalidGraph {
59 reason: format!("lint errors: {}", errors.join("; ")),
60 });
61 }
62 crate::planner::validate_graph(graph)?;
65 let cpm = compute_cpm(graph)?;
66 let leveled = req
67 .schedule
68 .as_ref()
69 .map(|s| resource_schedule_with_cpm(graph, s, &cpm))
70 .transpose()?;
71 let summary = req
72 .monte_carlo
73 .as_ref()
74 .map(|m| monte_carlo_with_cpm(graph, m, &cpm))
75 .transpose()?;
76 let mut card = scorecard(graph, &cpm, leveled.as_ref(), &lint_report);
77 card.monte_carlo = summary;
78 Ok(SimulationResult {
79 lint: lint_report,
80 critical_path: cpm.critical_path.clone(),
81 critical_path_hours: cpm.critical_path_duration,
82 schedule: schedule_rows(graph, &cpm),
83 milestones: milestone_rows(graph, &cpm, |_| false),
84 resource_schedule: leveled,
85 scorecard: card,
86 })
87}