Skip to main content

brainwires_agents/task_manager/
query_ops.rs

1//! Query Operations
2//!
3//! Task query operations: get ready tasks, get all tasks, get tree, format tree, get stats, progress.
4
5use std::collections::HashMap;
6
7use super::TaskManager;
8use super::time_tracking::{TaskStats, TaskTimeInfo, TimeStats};
9use brainwires_core::{Task, TaskPriority, TaskStatus};
10
11impl TaskManager {
12    /// Get all tasks ready to execute (no incomplete dependencies)
13    pub async fn get_ready_tasks(&self) -> Vec<Task> {
14        let tasks = self.tasks.read().await;
15        let mut ready = Vec::new();
16
17        for task in tasks.values() {
18            if task.status == TaskStatus::Pending || task.status == TaskStatus::Blocked {
19                // Check if all dependencies are complete or skipped
20                let deps_complete = task.depends_on.iter().all(|dep_id| {
21                    tasks
22                        .get(dep_id)
23                        .map(|t| {
24                            t.status == TaskStatus::Completed || t.status == TaskStatus::Skipped
25                        })
26                        .unwrap_or(false)
27                });
28
29                if deps_complete {
30                    ready.push(task.clone());
31                }
32            }
33        }
34
35        // Sort by priority (highest first)
36        ready.sort_by(|a, b| b.priority.cmp(&a.priority));
37        ready
38    }
39
40    /// Get all root tasks (tasks without parents)
41    pub async fn get_root_tasks(&self) -> Vec<Task> {
42        let tasks = self.tasks.read().await;
43        tasks.values().filter(|t| t.is_root()).cloned().collect()
44    }
45
46    /// Get task tree starting from a task (or all roots if None)
47    pub async fn get_task_tree(&self, root_id: Option<&str>) -> Vec<Task> {
48        let tasks = self.tasks.read().await;
49        let mut result = Vec::new();
50
51        match root_id {
52            Some(id) => {
53                if let Some(task) = tasks.get(id) {
54                    Self::collect_tree_recursive(&tasks, task, &mut result);
55                }
56            }
57            None => {
58                // Get all root tasks and their trees
59                for task in tasks.values().filter(|t| t.is_root()) {
60                    Self::collect_tree_recursive(&tasks, task, &mut result);
61                }
62            }
63        }
64
65        result
66    }
67
68    /// Recursively collect tasks in tree order
69    fn collect_tree_recursive(tasks: &HashMap<String, Task>, task: &Task, result: &mut Vec<Task>) {
70        result.push(task.clone());
71        for child_id in &task.children {
72            if let Some(child) = tasks.get(child_id) {
73                Self::collect_tree_recursive(tasks, child, result);
74            }
75        }
76    }
77
78    /// Get all tasks
79    pub async fn get_all_tasks(&self) -> Vec<Task> {
80        let tasks = self.tasks.read().await;
81        tasks.values().cloned().collect()
82    }
83
84    /// Get tasks by status
85    pub async fn get_tasks_by_status(&self, status: TaskStatus) -> Vec<Task> {
86        let tasks = self.tasks.read().await;
87        tasks
88            .values()
89            .filter(|t| t.status == status)
90            .cloned()
91            .collect()
92    }
93
94    /// Get summary statistics
95    pub async fn get_stats(&self) -> TaskStats {
96        let tasks = self.tasks.read().await;
97        let mut stats = TaskStats::default();
98
99        for task in tasks.values() {
100            stats.total += 1;
101            match task.status {
102                TaskStatus::Pending => stats.pending += 1,
103                TaskStatus::InProgress => stats.in_progress += 1,
104                TaskStatus::Completed => stats.completed += 1,
105                TaskStatus::Failed => stats.failed += 1,
106                TaskStatus::Blocked => stats.blocked += 1,
107                TaskStatus::Skipped => stats.skipped += 1,
108            }
109        }
110
111        stats
112    }
113
114    /// Get time tracking info for a task
115    pub async fn get_task_time_info(&self, task_id: &str) -> Option<TaskTimeInfo> {
116        let tasks = self.tasks.read().await;
117        tasks.get(task_id).map(|task| TaskTimeInfo {
118            task_id: task.id.clone(),
119            description: task.description.clone(),
120            status: task.status.clone(),
121            started_at: task.started_at,
122            completed_at: task.completed_at,
123            duration_secs: task.duration_secs(),
124            elapsed_secs: task.elapsed_secs(),
125        })
126    }
127
128    /// Get time statistics for all tasks
129    pub async fn get_time_stats(&self) -> TimeStats {
130        let tasks = self.tasks.read().await;
131
132        let mut total_duration: i64 = 0;
133        let mut completed_count: usize = 0;
134        let mut total_elapsed: i64 = 0;
135        let mut in_progress_count: usize = 0;
136
137        for task in tasks.values() {
138            if let Some(duration) = task.duration_secs() {
139                total_duration += duration;
140                completed_count += 1;
141            }
142            if task.status == TaskStatus::InProgress
143                && let Some(elapsed) = task.elapsed_secs()
144            {
145                total_elapsed += elapsed;
146                in_progress_count += 1;
147            }
148        }
149
150        TimeStats {
151            total_duration_secs: total_duration,
152            completed_tasks: completed_count,
153            average_duration_secs: if completed_count > 0 {
154                Some(total_duration / completed_count as i64)
155            } else {
156                None
157            },
158            current_elapsed_secs: total_elapsed,
159            in_progress_tasks: in_progress_count,
160        }
161    }
162
163    /// Calculate progress percentage (0.0 to 1.0) for a task
164    /// For tasks with children, this is based on completed children
165    /// For leaf tasks, returns 1.0 if completed, 0.5 if in progress, 0.0 otherwise
166    pub async fn get_progress(&self, task_id: &str) -> f64 {
167        let tasks = self.tasks.read().await;
168
169        if let Some(task) = tasks.get(task_id) {
170            if task.children.is_empty() {
171                // Leaf task
172                match task.status {
173                    TaskStatus::Completed => 1.0,
174                    TaskStatus::InProgress => 0.5,
175                    _ => 0.0,
176                }
177            } else {
178                // Parent task - calculate based on children
179                let completed = task
180                    .children
181                    .iter()
182                    .filter(|id| {
183                        tasks
184                            .get(*id)
185                            .map(|t| t.status == TaskStatus::Completed)
186                            .unwrap_or(false)
187                    })
188                    .count();
189                let in_progress = task
190                    .children
191                    .iter()
192                    .filter(|id| {
193                        tasks
194                            .get(*id)
195                            .map(|t| t.status == TaskStatus::InProgress)
196                            .unwrap_or(false)
197                    })
198                    .count();
199
200                let total = task.children.len() as f64;
201                if total == 0.0 {
202                    return 0.0;
203                }
204
205                (completed as f64 + (in_progress as f64 * 0.5)) / total
206            }
207        } else {
208            0.0
209        }
210    }
211
212    /// Get overall progress for all tasks
213    pub async fn get_overall_progress(&self) -> f64 {
214        let stats = self.get_stats().await;
215        if stats.total == 0 {
216            return 0.0;
217        }
218
219        let completed = stats.completed as f64;
220        let in_progress = stats.in_progress as f64 * 0.5;
221        let total = stats.total as f64;
222
223        (completed + in_progress) / total
224    }
225
226    /// Get average task duration in seconds (from completed tasks)
227    pub async fn get_average_duration(&self) -> Option<i64> {
228        let tasks = self.tasks.read().await;
229        let durations: Vec<i64> = tasks.values().filter_map(|t| t.duration_secs()).collect();
230
231        if durations.is_empty() {
232            None
233        } else {
234            Some(durations.iter().sum::<i64>() / durations.len() as i64)
235        }
236    }
237
238    /// Estimate remaining time in seconds based on average duration
239    pub async fn estimate_remaining_time(&self) -> Option<i64> {
240        let avg_duration = self.get_average_duration().await?;
241        let stats = self.get_stats().await;
242        let remaining = stats.pending + stats.blocked;
243
244        Some(avg_duration * remaining as i64)
245    }
246
247    /// Format task tree as indented text for display
248    pub async fn format_tree(&self) -> String {
249        let tasks = self.tasks.read().await;
250        let mut output = String::new();
251
252        // Get root tasks
253        let mut roots: Vec<_> = tasks.values().filter(|t| t.is_root()).collect();
254        roots.sort_by(|a, b| b.priority.cmp(&a.priority));
255
256        let root_count = roots.len();
257        for (idx, root) in roots.iter().enumerate() {
258            let is_last = idx == root_count - 1;
259            Self::format_task_recursive(&tasks, root, 0, is_last, "", &mut output);
260        }
261
262        if output.is_empty() {
263            output = "No tasks".to_string();
264        }
265
266        output
267    }
268
269    fn format_task_recursive(
270        tasks: &HashMap<String, Task>,
271        task: &Task,
272        depth: usize,
273        is_last: bool,
274        parent_prefix: &str,
275        output: &mut String,
276    ) {
277        let status_icon = match task.status {
278            TaskStatus::Pending => "○",
279            TaskStatus::InProgress => "◐",
280            TaskStatus::Completed => "●",
281            TaskStatus::Failed => "✗",
282            TaskStatus::Blocked => "◌",
283            TaskStatus::Skipped => "⊘",
284        };
285        let priority_icon = match task.priority {
286            TaskPriority::Urgent => "🔴 ",
287            TaskPriority::High => "🟠 ",
288            TaskPriority::Normal => "",
289            TaskPriority::Low => "🔵 ",
290        };
291
292        // Build the prefix for this task
293        let current_prefix = if depth == 0 {
294            String::new()
295        } else if is_last {
296            format!("{}└── ", parent_prefix)
297        } else {
298            format!("{}├── ", parent_prefix)
299        };
300
301        output.push_str(&format!(
302            "{}{} {}{}\n",
303            current_prefix, status_icon, priority_icon, task.description
304        ));
305
306        // Build prefix for children
307        let child_prefix = if depth == 0 {
308            String::new()
309        } else if is_last {
310            format!("{}    ", parent_prefix)
311        } else {
312            format!("{}│   ", parent_prefix)
313        };
314
315        // Format children
316        let child_count = task.children.len();
317        for (idx, child_id) in task.children.iter().enumerate() {
318            if let Some(child) = tasks.get(child_id) {
319                let child_is_last = idx == child_count - 1;
320                Self::format_task_recursive(
321                    tasks,
322                    child,
323                    depth + 1,
324                    child_is_last,
325                    &child_prefix,
326                    output,
327                );
328            }
329        }
330    }
331}