1use crate::EngineError;
5use chrono::{DateTime, Utc};
6use dpm_model::{
7 Applicability, DependencyId, DependencyKind, Key, Plan, Priority, Timeline, WorkItem,
8 WorkItemId, WorkKind, WorkStatus,
9};
10use dpm_schedule::{
11 ActivityCalendar, ActivitySchedule, Schedule, SimulationSummary, deterministic_remaining_at,
12 simulate_remaining_at,
13};
14use serde::{Deserialize, Serialize};
15use std::collections::BTreeMap;
16
17mod span;
18pub use span::{ScheduleSpan, schedule_span};
19
20#[cfg(test)]
21mod tests;
22
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct ScheduledTimes {
27 pub earliest_start_hours: f64,
29 pub earliest_finish_hours: f64,
31 pub latest_start_hours: f64,
33 pub latest_finish_hours: f64,
35 pub total_float_hours: f64,
37 pub free_float_hours: f64,
39 pub critical: bool,
41}
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct ScheduledWork {
46 pub id: WorkItemId,
48 pub key: Key,
50 pub kind: WorkKind,
52 pub parent: Option<WorkItemId>,
54 pub title: String,
56 pub status: WorkStatus,
58 pub applicability: Applicability,
60 pub priority: Priority,
62 pub span: Option<ScheduleSpan>,
65 pub times: Option<ScheduledTimes>,
69 pub criticality: Option<f64>,
74 pub calendar: Option<ActivityCalendar>,
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub struct ScheduleUncertainty {
81 pub iterations: usize,
83 pub seed: u64,
85 pub p50_finish_hours: f64,
87 pub p80_finish_hours: f64,
89 pub p95_finish_hours: f64,
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct ScheduleProjection {
96 pub project_finish_hours: f64,
98 pub uncertainty: Option<ScheduleUncertainty>,
101 pub work: Vec<ScheduledWork>,
103 pub relations: Vec<ScheduledRelation>,
106}
107
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110pub struct ScheduledRelation {
111 pub id: DependencyId,
113 pub predecessor: WorkItemId,
115 pub successor: WorkItemId,
117 pub kind: DependencyKind,
119 pub slack_hours: f64,
122 pub driving: bool,
124 pub critical: bool,
126}
127
128pub fn schedule_projection(
130 plan: &Plan,
131 probabilistic: bool,
132 now: DateTime<Utc>,
133) -> Result<ScheduleProjection, EngineError> {
134 plan.validate()?;
135 let timeline = Timeline::at(plan, now);
136 let schedule = deterministic_remaining_at(plan, &timeline)?;
137 let config = crate::query::simulation_config();
138 let simulation = if probabilistic
139 && !crate::query::choices::has_open_choices(plan)
140 && plan
141 .work_items
142 .values()
143 .any(|w| w.schedule.estimate.is_some())
144 {
145 Some(simulate_remaining_at(plan, config, &timeline)?)
146 } else {
147 None
148 };
149 let spans = span::spans(plan, &timeline, &schedule);
150 let leaves = span::package_leaves(plan, &timeline, &schedule);
151 let mut work: Vec<ScheduledWork> = plan
152 .work_items
153 .values()
154 .map(|item| {
155 let mut scheduled = row(&timeline, &schedule, simulation.as_ref(), &spans, item);
156 if let Some(leaves) = leaves.get(&item.id) {
157 roll_up(&mut scheduled, &schedule, simulation.as_ref(), leaves);
158 }
159 scheduled
160 })
161 .collect();
162 work.sort_by(|a, b| a.key.natural_cmp(&b.key));
163 Ok(ScheduleProjection {
164 project_finish_hours: schedule.project_finish_hours,
165 uncertainty: simulation.map(|s| ScheduleUncertainty {
166 iterations: s.iterations,
167 seed: config.seed,
168 p50_finish_hours: s.p50_finish_hours,
169 p80_finish_hours: s.p80_finish_hours,
170 p95_finish_hours: s.p95_finish_hours,
171 }),
172 work,
173 relations: relations(plan, &schedule),
174 })
175}
176
177fn relations(plan: &Plan, schedule: &Schedule) -> Vec<ScheduledRelation> {
178 plan.dependencies
179 .iter()
180 .filter_map(|d| {
181 let r = schedule.relations.get(&d.id)?;
182 Some(ScheduledRelation {
183 id: d.id,
184 predecessor: d.predecessor,
185 successor: d.successor,
186 kind: d.kind,
187 slack_hours: r.slack_hours,
188 driving: r.driving,
189 critical: r.critical,
190 })
191 })
192 .collect()
193}
194
195fn roll_up(
198 scheduled: &mut ScheduledWork,
199 schedule: &Schedule,
200 simulation: Option<&SimulationSummary>,
201 leaves: &[WorkItemId],
202) {
203 scheduled.times = span::rolled_up(schedule, leaves).map(|a| times(&a));
204 scheduled.criticality = simulation.and_then(|s| {
205 leaves
206 .iter()
207 .filter_map(|id| s.criticality.get(id).copied())
208 .reduce(f64::max)
209 });
210}
211
212fn times(a: &ActivitySchedule) -> ScheduledTimes {
213 ScheduledTimes {
214 earliest_start_hours: a.earliest_start_hours,
215 earliest_finish_hours: a.earliest_finish_hours,
216 latest_start_hours: a.latest_start_hours,
217 latest_finish_hours: a.latest_finish_hours,
218 total_float_hours: a.total_float_hours,
219 free_float_hours: a.free_float_hours,
220 critical: a.critical,
221 }
222}
223
224fn row(
225 timeline: &Timeline,
226 schedule: &Schedule,
227 simulation: Option<&SimulationSummary>,
228 spans: &BTreeMap<WorkItemId, ScheduleSpan>,
229 item: &WorkItem,
230) -> ScheduledWork {
231 let applicability = timeline.applicability(item.id);
232 let activity = schedule
233 .activities
234 .get(&item.id)
235 .filter(|_| applicability.is_applicable());
236 let mut status = item.execution.status;
237 if !item.is_executable() && timeline.completed_at(item.id).is_some() {
238 status = WorkStatus::Verified;
239 }
240 ScheduledWork {
241 id: item.id,
242 key: item.key.clone(),
243 kind: item.kind,
244 parent: item.parent,
245 title: item.title.clone(),
246 status,
247 applicability: applicability.clone(),
248 priority: item.schedule.priority,
249 span: spans.get(&item.id).copied(),
250 times: activity.map(times),
251 criticality: simulation.and_then(|s| s.criticality.get(&item.id).copied()),
252 calendar: activity.and_then(|a| a.calendar.clone()),
253 }
254}