1use crate::network::Network;
9use crate::{RemainingOptions, ScheduleError};
10use dpm_model::{
11 BeyondCalendar, Calendars, MAX_WINDOW_HOURS, Plan, ResolvedCalendar, Timeline, WorkItemId,
12 WorkingTime,
13};
14use std::collections::{BTreeMap, BTreeSet};
15
16const WIDENINGS: usize = 24;
19
20#[derive(Debug)]
22pub(crate) struct Placement {
23 calendars: Calendars,
24 origin: chrono::DateTime<chrono::Utc>,
25 window: (f64, f64),
26 names: Vec<String>,
27 compiled: Vec<WorkingTime>,
28 execution: Vec<usize>,
30 review: Vec<Option<usize>>,
32 pub(crate) resolved: BTreeMap<WorkItemId, ResolvedCalendar>,
34 review_delay: f64,
36}
37
38impl Placement {
39 pub(crate) fn new(
46 plan: &Plan,
47 network: &Network,
48 timeline: &Timeline,
49 awaiting: &BTreeSet<WorkItemId>,
50 horizon: f64,
51 options: RemainingOptions,
52 ) -> Result<Option<Self>, ScheduleError> {
53 let (calendars, reported) = match (&plan.calendars, options.delays_review()) {
54 (Some(calendars), _) => (calendars.clone(), true),
55 (None, true) => (elapsed_calendars(), false),
56 (None, false) => return Ok(None),
57 };
58 let mut names: Vec<String> = Vec::new();
59 let mut index = |name: &str| match names.iter().position(|n| n == name) {
60 Some(found) => found,
61 None => {
62 names.push(name.to_owned());
63 names.len() - 1
64 }
65 };
66 let verifier = index(calendars.verifier_calendar());
67 let mut execution = Vec::with_capacity(network.order().len());
68 let mut review = Vec::with_capacity(network.order().len());
69 let mut resolved = BTreeMap::new();
70 for id in network.order() {
71 let work = plan
72 .work_items
73 .get(id)
74 .ok_or(ScheduleError::MissingWorkItem(*id))?;
75 let calendar = calendars.resolve(work);
76 execution.push(index(&calendar.calendar));
77 review.push(awaiting.contains(id).then_some(verifier));
78 if work.is_executable() && reported {
79 resolved.insert(*id, calendar);
80 }
81 }
82 let mut placement = Self {
83 calendars,
84 origin: timeline.now(),
85 window: (
86 -24.0 * 14.0,
87 horizon.clamp(24.0 * 7.0 * 8.0, MAX_WINDOW_HOURS / 2.0),
88 ),
89 names,
90 compiled: Vec::new(),
91 execution,
92 review,
93 resolved,
94 review_delay: options.review_delay_hours,
95 };
96 placement.compile()?;
97 Ok(Some(placement))
98 }
99
100 fn compile(&mut self) -> Result<(), ScheduleError> {
101 let zone = self
102 .calendars
103 .zone()
104 .map_err(|_| ScheduleError::CalendarRange)?;
105 self.compiled = self
106 .names
107 .iter()
108 .map(|name| {
109 let rules = self
110 .calendars
111 .rules(name)
112 .ok_or(ScheduleError::CalendarRange)?;
113 WorkingTime::compile(rules, zone, self.origin, self.window.0, self.window.1)
114 .ok_or(ScheduleError::CalendarRange)
115 })
116 .collect::<Result<_, _>>()?;
117 Ok(())
118 }
119
120 fn widen(&mut self) -> Result<(), ScheduleError> {
123 let span = self.window.1 - self.window.0;
124 if span >= MAX_WINDOW_HOURS {
125 return Err(ScheduleError::CalendarRange);
126 }
127 let grown = (span * 2.0).min(MAX_WINDOW_HOURS);
128 let before = (grown - span) / 4.0;
129 self.window = (
130 self.window.0 - before,
131 self.window.1 + (grown - span - before),
132 );
133 self.compile()
134 }
135
136 pub(crate) fn fit<T>(
138 &mut self,
139 mut pass: impl FnMut(&Self) -> Result<T, Placed>,
140 ) -> Result<T, ScheduleError> {
141 for _ in 0..WIDENINGS {
142 match pass(self) {
143 Ok(value) => return Ok(value),
144 Err(Placed::Beyond) => self.widen()?,
145 Err(Placed::Schedule(error)) => return Err(error),
146 }
147 }
148 Err(ScheduleError::CalendarRange)
149 }
150
151 fn calendar(&self, index: usize) -> Result<&WorkingTime, Placed> {
152 self.compiled
153 .get(index)
154 .ok_or(Placed::Schedule(ScheduleError::CalendarRange))
155 }
156
157 pub(crate) fn execution(&self, position: usize) -> Result<&WorkingTime, Placed> {
159 let index = self
160 .execution
161 .get(position)
162 .ok_or(Placed::Schedule(ScheduleError::UnknownPosition(position)))?;
163 self.calendar(*index)
164 }
165
166 pub(crate) fn review_delay(&self) -> f64 {
168 self.review_delay
169 }
170
171 pub(crate) fn reviewed_on_own_calendar(&self, position: usize) -> bool {
174 self.review_delay == 0.0
175 && matches!(
176 (self.review.get(position), self.execution.get(position)),
177 (Some(Some(review)), Some(execution)) if review == execution
178 )
179 }
180
181 pub(crate) fn review(&self, position: usize) -> Result<Option<&WorkingTime>, Placed> {
183 match self.review.get(position) {
184 Some(Some(index)) => self.calendar(*index).map(Some),
185 Some(None) => Ok(None),
186 None => Err(Placed::Schedule(ScheduleError::UnknownPosition(position))),
187 }
188 }
189}
190
191#[derive(Debug)]
193pub(crate) enum Placed {
194 Beyond,
195 Schedule(ScheduleError),
196}
197
198impl From<BeyondCalendar> for Placed {
199 fn from(_: BeyondCalendar) -> Self {
200 Self::Beyond
201 }
202}
203
204impl From<ScheduleError> for Placed {
205 fn from(error: ScheduleError) -> Self {
206 Self::Schedule(error)
207 }
208}
209
210fn elapsed_calendars() -> Calendars {
212 let always = || dpm_model::ALWAYS.to_owned();
213 Calendars {
214 kinds: dpm_model::KindCalendars {
215 human: always(),
216 agent: always(),
217 service: always(),
218 },
219 ..Calendars::in_zone("UTC")
220 }
221}
222
223pub fn worked_between(
230 plan: &Plan,
231 work: &dpm_model::WorkItem,
232 started: chrono::DateTime<chrono::Utc>,
233 now: chrono::DateTime<chrono::Utc>,
234) -> Result<f64, ScheduleError> {
235 let hours = ((now - started).num_milliseconds() as f64 / 3_600_000.0).max(0.0);
236 let Some(calendars) = plan.calendars.as_ref() else {
237 return Ok(hours);
238 };
239 let rules = calendars
240 .rules(&calendars.resolve(work).calendar)
241 .ok_or(ScheduleError::CalendarRange)?;
242 let zone = calendars.zone().map_err(|_| ScheduleError::CalendarRange)?;
243 WorkingTime::compile(rules, zone, started, -24.0, hours + 24.0)
244 .ok_or(ScheduleError::CalendarRange)?
245 .between(0.0, hours)
246 .map_err(|_| ScheduleError::CalendarRange)
247}