dpm_engine/query/estimates.rs
1//! Work whose missing estimate the remaining forecast silently counts as zero hours.
2//!
3//! The remaining CPM and Monte Carlo keep a task without an estimate at 0 h rather than guessing a
4//! duration, so a forecast over such work is optimistic. These projections name that work so a
5//! reader can judge the forecast; they never change its arithmetic.
6
7use dpm_model::{Key, Plan, Timeline, WorkItem};
8
9/// Whether `work` enters the remaining forecast at 0 h only because it has no estimate.
10///
11/// It follows the remaining projection, which a test checks: a task (milestones and packages have no
12/// duration of their own) in the active graph (applicable, so not excluded, undecided, awaiting a
13/// choice, stranded or an empty join) that is not yet verified or done (a submitted task still
14/// keeps its full duration until independent verification).
15#[must_use]
16pub fn is_unestimated(timeline: &Timeline, work: &WorkItem) -> bool {
17 work.is_executable()
18 && work.schedule.estimate.is_none()
19 && !work.execution.status.satisfies_dependency()
20 && timeline.applicability(work.id).is_applicable()
21}
22
23/// Keys of every work item [`is_unestimated`] reports, in key order.
24#[must_use]
25pub fn unestimated(plan: &Plan, timeline: &Timeline) -> Vec<Key> {
26 let mut keys: Vec<_> = plan
27 .work_items
28 .values()
29 .filter(|w| is_unestimated(timeline, w))
30 .map(|w| w.key.clone())
31 .collect();
32 keys.sort_by(dpm_model::Key::natural_cmp);
33 keys
34}
35
36#[cfg(test)]
37mod tests;