agentic_time/
write_engine.rs1use chrono::{DateTime, Utc};
4
5use crate::deadline::{Deadline, DeadlineStatus};
6use crate::decay::DecayModel;
7use crate::duration::DurationEstimate;
8use crate::error::{TimeError, TimeResult};
9use crate::file_format::{EntityType, TimeFile};
10use crate::schedule::{Schedule, ScheduleStatus};
11use crate::sequence::Sequence;
12use crate::TemporalId;
13
14pub struct WriteEngine {
16 file: TimeFile,
17}
18
19impl WriteEngine {
20 pub fn new(file: TimeFile) -> Self {
22 Self { file }
23 }
24
25 pub fn into_file(self) -> TimeFile {
27 self.file
28 }
29
30 pub fn file(&self) -> &TimeFile {
32 &self.file
33 }
34
35 pub fn file_mut(&mut self) -> &mut TimeFile {
37 &mut self.file
38 }
39
40 pub fn add_duration(&mut self, duration: DurationEstimate) -> TimeResult<TemporalId> {
44 let id = duration.id;
45 self.file.add(EntityType::Duration, id, &duration)?;
46 self.file.save()?;
47 Ok(id)
48 }
49
50 pub fn update_duration(&mut self, duration: DurationEstimate) -> TimeResult<()> {
52 self.file
53 .add(EntityType::Duration, duration.id, &duration)?;
54 self.file.save()?;
55 Ok(())
56 }
57
58 pub fn add_deadline(&mut self, deadline: Deadline) -> TimeResult<TemporalId> {
62 let id = deadline.id;
63 self.file.add(EntityType::Deadline, id, &deadline)?;
64 self.file.save()?;
65 Ok(id)
66 }
67
68 pub fn update_deadline(&mut self, deadline: Deadline) -> TimeResult<()> {
70 self.file
71 .add(EntityType::Deadline, deadline.id, &deadline)?;
72 self.file.save()?;
73 Ok(())
74 }
75
76 pub fn complete_deadline(&mut self, id: &TemporalId) -> TimeResult<()> {
78 let mut deadline: Deadline = self
79 .file
80 .get(id)?
81 .ok_or_else(|| TimeError::NotFound(id.0.to_string()))?;
82 deadline.complete();
83 self.update_deadline(deadline)
84 }
85
86 pub fn cancel_deadline(&mut self, id: &TemporalId) -> TimeResult<()> {
88 let mut deadline: Deadline = self
89 .file
90 .get(id)?
91 .ok_or_else(|| TimeError::NotFound(id.0.to_string()))?;
92 deadline.status = DeadlineStatus::Cancelled;
93 self.update_deadline(deadline)
94 }
95
96 pub fn add_schedule(&mut self, schedule: Schedule) -> TimeResult<TemporalId> {
100 let conflicts = self.check_schedule_conflicts(&schedule)?;
101 if !conflicts.is_empty() && !schedule.flexible {
102 return Err(TimeError::ScheduleConflict(
103 schedule.label.clone(),
104 conflicts[0].label.clone(),
105 ));
106 }
107
108 let id = schedule.id;
109 self.file.add(EntityType::Schedule, id, &schedule)?;
110 self.file.save()?;
111 Ok(id)
112 }
113
114 pub fn update_schedule(&mut self, schedule: Schedule) -> TimeResult<()> {
116 self.file
117 .add(EntityType::Schedule, schedule.id, &schedule)?;
118 self.file.save()?;
119 Ok(())
120 }
121
122 pub fn reschedule(&mut self, id: &TemporalId, new_start: DateTime<Utc>) -> TimeResult<()> {
124 let mut schedule: Schedule = self
125 .file
126 .get(id)?
127 .ok_or_else(|| TimeError::NotFound(id.0.to_string()))?;
128 schedule.start_at = new_start;
129 schedule.status = ScheduleStatus::Rescheduled;
130 self.update_schedule(schedule)
131 }
132
133 fn check_schedule_conflicts(&self, schedule: &Schedule) -> TimeResult<Vec<Schedule>> {
135 let mut conflicts = Vec::new();
136
137 for block in self.file.list_by_type(EntityType::Schedule) {
138 let existing: Schedule = block.deserialize()?;
139 if existing.id != schedule.id
140 && existing.status == ScheduleStatus::Scheduled
141 && schedule.conflicts_with(&existing)
142 {
143 conflicts.push(existing);
144 }
145 }
146
147 Ok(conflicts)
148 }
149
150 pub fn add_sequence(&mut self, sequence: Sequence) -> TimeResult<TemporalId> {
154 let id = sequence.id;
155 self.file.add(EntityType::Sequence, id, &sequence)?;
156 self.file.save()?;
157 Ok(id)
158 }
159
160 pub fn update_sequence(&mut self, sequence: Sequence) -> TimeResult<()> {
162 self.file
163 .add(EntityType::Sequence, sequence.id, &sequence)?;
164 self.file.save()?;
165 Ok(())
166 }
167
168 pub fn advance_sequence(&mut self, id: &TemporalId) -> TimeResult<()> {
170 let mut sequence: Sequence = self
171 .file
172 .get(id)?
173 .ok_or_else(|| TimeError::NotFound(id.0.to_string()))?;
174 sequence.complete_current_step();
175 self.update_sequence(sequence)
176 }
177
178 pub fn add_decay(&mut self, decay: DecayModel) -> TimeResult<TemporalId> {
182 let id = decay.id;
183 self.file.add(EntityType::Decay, id, &decay)?;
184 self.file.save()?;
185 Ok(id)
186 }
187
188 pub fn update_decay(&mut self, decay: DecayModel) -> TimeResult<()> {
190 self.file.add(EntityType::Decay, decay.id, &decay)?;
191 self.file.save()?;
192 Ok(())
193 }
194
195 pub fn refresh_decay(&mut self, id: &TemporalId) -> TimeResult<f64> {
197 let mut decay: DecayModel = self
198 .file
199 .get(id)?
200 .ok_or_else(|| TimeError::NotFound(id.0.to_string()))?;
201 decay.update();
202 let value = decay.current_value;
203 self.update_decay(decay)?;
204 Ok(value)
205 }
206
207 pub fn update_all_statuses(&mut self) -> TimeResult<UpdateReport> {
211 let mut report = UpdateReport::default();
212
213 let deadline_blocks: Vec<_> = self
215 .file
216 .list_by_type(EntityType::Deadline)
217 .into_iter()
218 .cloned()
219 .collect();
220
221 for block in deadline_blocks {
222 let mut deadline: Deadline = block.deserialize()?;
223 let old_status = deadline.status;
224 deadline.update_status();
225 if deadline.status != old_status {
226 self.file
227 .add(EntityType::Deadline, deadline.id, &deadline)?;
228 report.deadlines_updated += 1;
229 if deadline.status == DeadlineStatus::Overdue {
230 report.new_overdue.push(deadline.id);
231 }
232 }
233 }
234
235 let decay_blocks: Vec<_> = self
237 .file
238 .list_by_type(EntityType::Decay)
239 .into_iter()
240 .cloned()
241 .collect();
242
243 for block in decay_blocks {
244 let mut decay: DecayModel = block.deserialize()?;
245 decay.update();
246 self.file.add(EntityType::Decay, decay.id, &decay)?;
247 report.decays_updated += 1;
248 }
249
250 self.file.save()?;
251 Ok(report)
252 }
253}
254
255#[derive(Debug, Default)]
257pub struct UpdateReport {
258 pub deadlines_updated: usize,
260 pub decays_updated: usize,
262 pub new_overdue: Vec<TemporalId>,
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use crate::deadline::Deadline;
270 use chrono::Duration as ChronoDuration;
271 use tempfile::tempdir;
272
273 #[test]
274 fn test_add_and_complete_deadline() {
275 let dir = tempdir().unwrap();
276 let path = dir.path().join("test.atime");
277 let tf = TimeFile::create(&path).unwrap();
278 let mut engine = WriteEngine::new(tf);
279
280 let d = Deadline::new("Test deadline", Utc::now() + ChronoDuration::hours(24));
281 let id = engine.add_deadline(d).unwrap();
282
283 engine.complete_deadline(&id).unwrap();
284
285 let loaded: Deadline = engine.file().get(&id).unwrap().unwrap();
286 assert_eq!(loaded.status, DeadlineStatus::Completed);
287 }
288}