Skip to main content

agentic_time/
deadline.rs

1//! Deadline management with urgency and consequences.
2
3use chrono::{DateTime, Duration as ChronoDuration, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::TemporalId;
7
8/// A deadline with urgency and consequences.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Deadline {
11    /// Unique identifier.
12    pub id: TemporalId,
13
14    /// What this deadline is for.
15    pub label: String,
16
17    /// The deadline timestamp.
18    pub due_at: DateTime<Utc>,
19
20    /// Soft deadline (warning threshold).
21    pub warn_at: Option<DateTime<Utc>>,
22
23    /// Type of deadline.
24    pub deadline_type: DeadlineType,
25
26    /// What happens if missed.
27    pub consequence: Consequence,
28
29    /// Current status.
30    pub status: DeadlineStatus,
31
32    /// Related temporal entities.
33    pub dependencies: Vec<TemporalId>,
34
35    /// When this deadline was created.
36    pub created_at: DateTime<Utc>,
37
38    /// When this deadline was completed (if completed).
39    pub completed_at: Option<DateTime<Utc>>,
40
41    /// Tags for categorization.
42    pub tags: Vec<String>,
43
44    /// Additional context.
45    pub context: Option<String>,
46}
47
48/// Type of deadline.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum DeadlineType {
51    /// Hard deadline — cannot be moved.
52    Hard,
53    /// Soft deadline — can be negotiated.
54    Soft,
55    /// Target — aspirational, not committed.
56    Target,
57    /// Recurring — repeats on schedule.
58    Recurring,
59}
60
61/// Consequence of missing a deadline.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub enum Consequence {
64    /// No significant consequence.
65    None,
66    /// Minor inconvenience.
67    Minor {
68        /// Description of the consequence.
69        description: String,
70    },
71    /// Moderate impact.
72    Moderate {
73        /// Description of the consequence.
74        description: String,
75    },
76    /// Severe impact.
77    Severe {
78        /// Description of the consequence.
79        description: String,
80    },
81    /// Critical failure.
82    Critical {
83        /// Description of the consequence.
84        description: String,
85    },
86    /// Quantified cost.
87    Quantified {
88        /// Description of the consequence.
89        description: String,
90        /// Estimated cost.
91        cost_estimate: f64,
92        /// Cost unit (e.g. "USD", "hours").
93        cost_unit: String,
94    },
95}
96
97/// Status of a deadline.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
99pub enum DeadlineStatus {
100    /// Not yet due.
101    Pending,
102    /// Warning threshold reached.
103    Warning,
104    /// Due now or overdue.
105    Overdue,
106    /// Completed on time.
107    Completed,
108    /// Completed late.
109    CompletedLate,
110    /// Cancelled.
111    Cancelled,
112}
113
114impl Deadline {
115    /// Create a new deadline.
116    pub fn new(label: impl Into<String>, due_at: DateTime<Utc>) -> Self {
117        Self {
118            id: TemporalId::new(),
119            label: label.into(),
120            due_at,
121            warn_at: None,
122            deadline_type: DeadlineType::Soft,
123            consequence: Consequence::None,
124            status: DeadlineStatus::Pending,
125            dependencies: Vec::new(),
126            created_at: Utc::now(),
127            completed_at: None,
128            tags: Vec::new(),
129            context: None,
130        }
131    }
132
133    /// Calculate time remaining until due.
134    pub fn time_remaining(&self) -> Option<ChronoDuration> {
135        if self.status == DeadlineStatus::Completed
136            || self.status == DeadlineStatus::CompletedLate
137            || self.status == DeadlineStatus::Cancelled
138        {
139            return None;
140        }
141        let now = Utc::now();
142        if now < self.due_at {
143            Some(self.due_at - now)
144        } else {
145            None
146        }
147    }
148
149    /// Calculate how long past due.
150    pub fn overdue_by(&self) -> Option<ChronoDuration> {
151        let now = Utc::now();
152        if now > self.due_at
153            && self.status != DeadlineStatus::Completed
154            && self.status != DeadlineStatus::CompletedLate
155            && self.status != DeadlineStatus::Cancelled
156        {
157            Some(now - self.due_at)
158        } else {
159            None
160        }
161    }
162
163    /// Update status based on current time.
164    pub fn update_status(&mut self) {
165        if self.status == DeadlineStatus::Completed
166            || self.status == DeadlineStatus::CompletedLate
167            || self.status == DeadlineStatus::Cancelled
168        {
169            return;
170        }
171
172        let now = Utc::now();
173
174        if now > self.due_at {
175            self.status = DeadlineStatus::Overdue;
176        } else if let Some(warn_at) = self.warn_at {
177            if now > warn_at {
178                self.status = DeadlineStatus::Warning;
179            }
180        }
181    }
182
183    /// Mark as completed.
184    pub fn complete(&mut self) {
185        let now = Utc::now();
186        self.completed_at = Some(now);
187
188        if now > self.due_at {
189            self.status = DeadlineStatus::CompletedLate;
190        } else {
191            self.status = DeadlineStatus::Completed;
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_new_deadline() {
202        let due = Utc::now() + ChronoDuration::hours(24);
203        let d = Deadline::new("Ship v1", due);
204        assert_eq!(d.status, DeadlineStatus::Pending);
205        assert!(d.time_remaining().is_some());
206    }
207
208    #[test]
209    fn test_complete_on_time() {
210        let due = Utc::now() + ChronoDuration::hours(24);
211        let mut d = Deadline::new("Test", due);
212        d.complete();
213        assert_eq!(d.status, DeadlineStatus::Completed);
214        assert!(d.completed_at.is_some());
215    }
216
217    #[test]
218    fn test_overdue_status() {
219        let due = Utc::now() - ChronoDuration::hours(1);
220        let mut d = Deadline::new("Late", due);
221        d.update_status();
222        assert_eq!(d.status, DeadlineStatus::Overdue);
223    }
224}