dpm_engine/schedule_view/
span.rs1use dpm_model::{Plan, Timeline, WorkItem, WorkItemId, WorkKind};
4use dpm_schedule::{ActivitySchedule, Schedule};
5use serde::{Deserialize, Serialize};
6use std::collections::{BTreeMap, BTreeSet};
7
8#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
10pub struct ScheduleSpan {
11 pub start_hours: f64,
13 pub finish_hours: f64,
15 pub critical: bool,
17}
18
19type Children<'a> = BTreeMap<WorkItemId, Vec<&'a WorkItem>>;
20
21#[must_use]
24pub fn schedule_span(
25 plan: &Plan,
26 timeline: &Timeline,
27 schedule: &Schedule,
28 item: &WorkItem,
29) -> Option<ScheduleSpan> {
30 if item.kind != WorkKind::WorkPackage {
31 return leaf(timeline, schedule, item);
32 }
33 package(timeline, schedule, &children(plan), item)
34}
35
36pub(super) fn spans(
38 plan: &Plan,
39 timeline: &Timeline,
40 schedule: &Schedule,
41) -> BTreeMap<WorkItemId, ScheduleSpan> {
42 let children = children(plan);
43 plan.work_items
44 .values()
45 .filter_map(|item| {
46 let span = if item.kind == WorkKind::WorkPackage {
47 package(timeline, schedule, &children, item)
48 } else {
49 leaf(timeline, schedule, item)
50 };
51 span.map(|span| (item.id, span))
52 })
53 .collect()
54}
55
56pub(super) fn package_leaves(
63 plan: &Plan,
64 timeline: &Timeline,
65 schedule: &Schedule,
66) -> BTreeMap<WorkItemId, Vec<WorkItemId>> {
67 let children = children(plan);
68 let completed = timeline.completed();
69 plan.work_items
70 .values()
71 .filter(|item| item.kind == WorkKind::WorkPackage)
72 .filter(|item| timeline.applicability(item.id).is_applicable())
73 .filter_map(|item| {
74 let mut leaves = Vec::new();
75 let found = Leaves {
76 timeline,
77 schedule,
78 children: &children,
79 completed: &completed,
80 }
81 .collect(item.id, &mut leaves);
82 found.then_some((item.id, leaves))
83 })
84 .collect()
85}
86
87struct Leaves<'a> {
88 timeline: &'a Timeline,
89 schedule: &'a Schedule,
90 children: &'a Children<'a>,
91 completed: &'a BTreeSet<WorkItemId>,
92}
93
94impl Leaves<'_> {
95 fn collect(&self, package: WorkItemId, out: &mut Vec<WorkItemId>) -> bool {
97 let mut found = false;
98 for child in self.children.get(&package).into_iter().flatten() {
99 if !self.timeline.applicability(child.id).is_applicable() {
100 continue;
101 }
102 if child.kind == WorkKind::WorkPackage {
103 found |= self.collect(child.id, out);
104 } else if self.schedule.activities.contains_key(&child.id) {
105 found = true;
106 if !self.completed.contains(&child.id) {
107 out.push(child.id);
108 }
109 }
110 }
111 found
112 }
113}
114
115pub(super) fn rolled_up(schedule: &Schedule, leaves: &[WorkItemId]) -> Option<ActivitySchedule> {
117 leaves
118 .iter()
119 .filter_map(|id| schedule.activities.get(id))
120 .map(|a| ActivitySchedule {
121 calendar: None,
122 ..a.clone()
123 })
124 .reduce(|a, b| ActivitySchedule {
125 earliest_start_hours: a.earliest_start_hours.min(b.earliest_start_hours),
126 earliest_finish_hours: a.earliest_finish_hours.max(b.earliest_finish_hours),
127 latest_start_hours: a.latest_start_hours.min(b.latest_start_hours),
128 latest_finish_hours: a.latest_finish_hours.max(b.latest_finish_hours),
129 total_float_hours: a.total_float_hours.min(b.total_float_hours),
130 free_float_hours: a.free_float_hours.min(b.free_float_hours),
131 critical: a.critical || b.critical,
132 calendar: None,
133 })
134}
135
136fn children(plan: &Plan) -> Children<'_> {
137 let mut children = Children::new();
138 for item in plan.work_items.values() {
139 if let Some(parent) = item.parent {
140 children.entry(parent).or_default().push(item);
141 }
142 }
143 children
144}
145
146fn leaf(timeline: &Timeline, schedule: &Schedule, item: &WorkItem) -> Option<ScheduleSpan> {
147 if !timeline.applicability(item.id).is_applicable() {
148 return None;
149 }
150 let activity = schedule.activities.get(&item.id)?;
151 Some(ScheduleSpan {
152 start_hours: activity.earliest_start_hours,
153 finish_hours: activity.earliest_finish_hours,
154 critical: activity.critical,
155 })
156}
157
158fn package(
159 timeline: &Timeline,
160 schedule: &Schedule,
161 children: &Children<'_>,
162 item: &WorkItem,
163) -> Option<ScheduleSpan> {
164 if !timeline.applicability(item.id).is_applicable() {
165 return None;
166 }
167 let Some(own) = children.get(&item.id) else {
168 return Some(ScheduleSpan {
169 start_hours: 0.0,
170 finish_hours: 0.0,
171 critical: false,
172 });
173 };
174 own.iter()
175 .filter_map(|child| {
176 if child.kind == WorkKind::WorkPackage {
177 package(timeline, schedule, children, child)
178 } else {
179 leaf(timeline, schedule, child)
180 }
181 })
182 .reduce(|a, b| ScheduleSpan {
183 start_hours: a.start_hours.min(b.start_hours),
184 finish_hours: a.finish_hours.max(b.finish_hours),
185 critical: a.critical || b.critical,
186 })
187}