#![cfg(test)]
#[path = "temporal_contract/drag.rs"]
mod drag;
#[path = "temporal_contract/examples.rs"]
mod examples;
#[path = "temporal_contract/properties.rs"]
mod properties;
#[path = "temporal_contract/textbook.rs"]
mod textbook;
use dpm_model::{
AcceptanceCriterion, Dependency, DependencyKind, Key, Plan, Priority, Project, ProjectId,
ThreePointEstimate, WorkItem, WorkItemId, WorkKind, WorkStatus,
};
use std::collections::{BTreeMap, BTreeSet};
pub(crate) const TOLERANCE: f64 = 1e-8;
pub(crate) const KINDS: [DependencyKind; 4] = [
DependencyKind::FinishStart,
DependencyKind::StartStart,
DependencyKind::FinishFinish,
DependencyKind::StartFinish,
];
pub(crate) fn stable_id(n: u64) -> WorkItemId {
serde_json::from_str(&format!("\"00000000-0000-4000-8000-{n:012x}\"")).expect("stable id")
}
pub(crate) struct Network {
pub plan: Plan,
project: ProjectId,
durations: BTreeMap<WorkItemId, f64>,
}
impl Network {
pub fn new() -> Self {
let mut plan = Plan::empty("temporal-contract");
let project = Project {
id: ProjectId::new(),
key: Key::new("P"),
parent: None,
title: "Temporal contract".into(),
objective: "Exercise temporal bounds".into(),
};
let id = project.id;
plan.projects.insert(id, project);
Self {
plan,
project: id,
durations: BTreeMap::new(),
}
}
fn insert(&mut self, id: WorkItemId, kind: WorkKind, hours: f64) -> WorkItemId {
let key = format!("W{}", self.plan.work_items.len());
let estimate = (kind == WorkKind::Task).then_some(ThreePointEstimate {
optimistic_hours: hours,
likely_hours: hours,
pessimistic_hours: hours,
});
let work = WorkItem {
id,
key: Key::new(key.clone()),
project: self.project,
parent: None,
kind,
title: key.clone(),
order: Default::default(),
contract: dpm_model::WorkContract {
objective: key,
acceptance: vec![AcceptanceCriterion {
text: "observable result exists".into(),
}],
instructions: None,
capabilities: BTreeSet::new(),
requirement_ids: BTreeSet::new(),
condition: None,
join: dpm_model::JoinPolicy::default(),
assets: Vec::new(),
},
execution: dpm_model::ExecutionRecord {
status: WorkStatus::Planned,
reported_progress_percent: 0,
artifact_ids: BTreeSet::new(),
owner: None,
handoffs: Vec::new(),
releases: Vec::new(),
block_reason: None,
events: Default::default(),
last_rejection: None,
attempts: Vec::new(),
basis: Vec::new(),
},
schedule: dpm_model::ScheduleInputs {
priority: Priority::P2,
estimate,
executor: None,
calendar: None,
},
};
self.plan.work_items.insert(id, work);
self.durations.insert(id, hours);
id
}
pub fn task(&mut self, id: WorkItemId, hours: f64) -> WorkItemId {
self.insert(id, WorkKind::Task, hours)
}
pub fn pert_task(&mut self, id: WorkItemId, o: f64, m: f64, p: f64) -> WorkItemId {
self.insert(id, WorkKind::Task, (o + 4.0 * m + p) / 6.0);
if let Some(work) = self.plan.work_items.get_mut(&id) {
work.schedule.estimate = Some(ThreePointEstimate {
optimistic_hours: o,
likely_hours: m,
pessimistic_hours: p,
});
}
id
}
pub fn milestone(&mut self, id: WorkItemId) -> WorkItemId {
self.insert(id, WorkKind::Milestone, 0.0)
}
pub fn link(&mut self, from: WorkItemId, to: WorkItemId, kind: DependencyKind, lag: f64) {
self.plan
.dependencies
.push(Dependency::new(from, to, kind, lag));
}
pub fn durations(&self) -> &BTreeMap<WorkItemId, f64> {
&self.durations
}
}