Skip to main content

mofa_foundation/secretary/default/
reporter.rs

1//! 汇报器 - 阶段5: 验收汇报,更新Todo,生成报告
2
3use super::types::*;
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::RwLock;
7
8/// 汇报格式
9#[derive(Debug, Clone)]
10pub enum ReportFormat {
11    /// Markdown格式
12    Markdown,
13    /// 纯文本
14    PlainText,
15    /// JSON格式
16    Json,
17}
18
19/// 汇报配置
20#[derive(Debug, Clone)]
21pub struct ReportConfig {
22    /// 默认格式
23    pub default_format: ReportFormat,
24    /// 是否包含统计信息
25    pub include_statistics: bool,
26    /// 是否包含详细信息
27    pub include_details: bool,
28    /// 最大历史记录数
29    pub max_history: usize,
30}
31
32impl Default for ReportConfig {
33    fn default() -> Self {
34        Self {
35            default_format: ReportFormat::Markdown,
36            include_statistics: true,
37            include_details: true,
38            max_history: 100,
39        }
40    }
41}
42
43/// 汇报器
44pub struct Reporter {
45    /// 配置
46    config: ReportConfig,
47    /// 汇报历史
48    history: Arc<RwLock<Vec<Report>>>,
49    /// 计数器
50    counter: Arc<RwLock<u64>>,
51}
52
53impl Reporter {
54    /// 创建新的汇报器
55    pub fn new(config: ReportConfig) -> Self {
56        Self {
57            config,
58            history: Arc::new(RwLock::new(Vec::new())),
59            counter: Arc::new(RwLock::new(0)),
60        }
61    }
62
63    /// 生成报告ID
64    async fn generate_id(&self) -> String {
65        let mut counter = self.counter.write().await;
66        *counter += 1;
67        format!("report_{}", *counter)
68    }
69
70    /// 保存报告到历史
71    async fn save_report(&self, report: Report) {
72        let mut history = self.history.write().await;
73        history.push(report);
74
75        // 限制历史记录数量
76        while history.len() > self.config.max_history {
77            history.remove(0);
78        }
79    }
80
81    /// 生成任务完成汇报
82    pub async fn generate_completion_report(
83        &self,
84        todo: &TodoItem,
85        result: &ExecutionResult,
86    ) -> Report {
87        let id = self.generate_id().await;
88        let now = std::time::SystemTime::now()
89            .duration_since(std::time::UNIX_EPOCH)
90            .unwrap_or_default()
91            .as_secs();
92
93        let content = match self.config.default_format {
94            ReportFormat::Markdown => self.format_completion_markdown(todo, result),
95            ReportFormat::PlainText => self.format_completion_plain(todo, result),
96            ReportFormat::Json => self.format_completion_json(todo, result),
97        };
98
99        let mut statistics = HashMap::new();
100        statistics.insert(
101            "execution_time_ms".to_string(),
102            serde_json::json!(result.execution_time_ms),
103        );
104        statistics.insert("success".to_string(), serde_json::json!(result.success));
105        statistics.insert(
106            "artifacts_count".to_string(),
107            serde_json::json!(result.artifacts.len()),
108        );
109
110        let report = Report {
111            id,
112            report_type: ReportType::TaskCompletion,
113            todo_ids: vec![todo.id.clone()],
114            content,
115            statistics,
116            created_at: now,
117        };
118
119        self.save_report(report.clone()).await;
120        report
121    }
122
123    fn format_completion_markdown(&self, todo: &TodoItem, result: &ExecutionResult) -> String {
124        let status = if result.success {
125            "✅ 成功"
126        } else {
127            "❌ 失败"
128        };
129
130        let mut content = format!(
131            "# 任务完成汇报\n\n\
132             ## 基本信息\n\
133             - **任务ID**: {}\n\
134             - **状态**: {}\n\
135             - **执行时间**: {}ms\n\n\
136             ## 任务描述\n\
137             {}\n\n\
138             ## 执行结果\n\
139             {}\n",
140            todo.id, status, result.execution_time_ms, todo.raw_idea, result.summary
141        );
142
143        if self.config.include_details && !result.details.is_empty() {
144            content.push_str("\n## 详细信息\n");
145            for (key, value) in &result.details {
146                content.push_str(&format!("- **{}**: {}\n", key, value));
147            }
148        }
149
150        if !result.artifacts.is_empty() {
151            content.push_str("\n## 产出物\n");
152            for artifact in &result.artifacts {
153                content.push_str(&format!(
154                    "- {} ({})\n",
155                    artifact.name, artifact.artifact_type
156                ));
157            }
158        }
159
160        if let Some(ref error) = result.error {
161            content.push_str(&format!("\n## 错误信息\n```\n{}\n```\n", error));
162        }
163
164        content
165    }
166
167    fn format_completion_plain(&self, todo: &TodoItem, result: &ExecutionResult) -> String {
168        let status = if result.success { "成功" } else { "失败" };
169
170        format!(
171            "任务完成汇报\n\
172             ==============\n\
173             任务ID: {}\n\
174             状态: {}\n\
175             执行时间: {}ms\n\
176             任务描述: {}\n\
177             执行结果: {}\n",
178            todo.id, status, result.execution_time_ms, todo.raw_idea, result.summary
179        )
180    }
181
182    fn format_completion_json(&self, todo: &TodoItem, result: &ExecutionResult) -> String {
183        let report = serde_json::json!({
184            "todo_id": todo.id,
185            "success": result.success,
186            "execution_time_ms": result.execution_time_ms,
187            "description": todo.raw_idea,
188            "summary": result.summary,
189            "details": result.details,
190            "artifacts": result.artifacts,
191            "error": result.error,
192        });
193
194        serde_json::to_string_pretty(&report).unwrap_or_default()
195    }
196
197    /// 生成进度汇报
198    pub async fn generate_progress_report(
199        &self,
200        todos: &[TodoItem],
201        statistics: HashMap<String, serde_json::Value>,
202    ) -> Report {
203        let id = self.generate_id().await;
204        let now = std::time::SystemTime::now()
205            .duration_since(std::time::UNIX_EPOCH)
206            .unwrap_or_default()
207            .as_secs();
208
209        let todo_ids: Vec<String> = todos.iter().map(|t| t.id.clone()).collect();
210
211        let content = match self.config.default_format {
212            ReportFormat::Markdown => self.format_progress_markdown(todos, &statistics),
213            ReportFormat::PlainText => self.format_progress_plain(todos, &statistics),
214            ReportFormat::Json => self.format_progress_json(todos, &statistics),
215        };
216
217        let report = Report {
218            id,
219            report_type: ReportType::Progress,
220            todo_ids,
221            content,
222            statistics,
223            created_at: now,
224        };
225
226        self.save_report(report.clone()).await;
227        report
228    }
229
230    fn format_progress_markdown(
231        &self,
232        todos: &[TodoItem],
233        statistics: &HashMap<String, serde_json::Value>,
234    ) -> String {
235        let mut content = "# 进度汇报\n\n".to_string();
236
237        if self.config.include_statistics {
238            content.push_str("## 统计信息\n");
239            for (key, value) in statistics {
240                content.push_str(&format!("- **{}**: {}\n", key, value));
241            }
242            content.push('\n');
243        }
244
245        content.push_str("## 任务列表\n");
246        for todo in todos {
247            let status_icon = match todo.status {
248                TodoStatus::Completed => "✅",
249                TodoStatus::InProgress => "🔄",
250                TodoStatus::Pending => "⏳",
251                TodoStatus::Cancelled => "❌",
252                _ => "📋",
253            };
254            content.push_str(&format!(
255                "- {} [{}] {:?}: {}\n",
256                status_icon,
257                todo.id,
258                todo.priority,
259                todo.raw_idea.chars().take(50).collect::<String>()
260            ));
261        }
262
263        content
264    }
265
266    fn format_progress_plain(
267        &self,
268        todos: &[TodoItem],
269        statistics: &HashMap<String, serde_json::Value>,
270    ) -> String {
271        let mut content = "进度汇报\n========\n\n".to_string();
272
273        if self.config.include_statistics {
274            content.push_str("统计信息:\n");
275            for (key, value) in statistics {
276                content.push_str(&format!("  {}: {}\n", key, value));
277            }
278            content.push('\n');
279        }
280
281        content.push_str("任务列表:\n");
282        for todo in todos {
283            content.push_str(&format!(
284                "  - [{}] {:?} {:?}: {}\n",
285                todo.id,
286                todo.status,
287                todo.priority,
288                todo.raw_idea.chars().take(50).collect::<String>()
289            ));
290        }
291
292        content
293    }
294
295    fn format_progress_json(
296        &self,
297        todos: &[TodoItem],
298        statistics: &HashMap<String, serde_json::Value>,
299    ) -> String {
300        let report = serde_json::json!({
301            "statistics": statistics,
302            "todos": todos.iter().map(|t| {
303                serde_json::json!({
304                    "id": t.id,
305                    "status": format!("{:?}", t.status),
306                    "priority": format!("{:?}", t.priority),
307                    "description": t.raw_idea,
308                })
309            }).collect::<Vec<_>>(),
310        });
311
312        serde_json::to_string_pretty(&report).unwrap_or_default()
313    }
314
315    /// 生成每日总结
316    pub async fn generate_daily_summary(&self, todos: &[TodoItem]) -> Report {
317        let id = self.generate_id().await;
318        let now = std::time::SystemTime::now()
319            .duration_since(std::time::UNIX_EPOCH)
320            .unwrap_or_default()
321            .as_secs();
322
323        let todo_ids: Vec<String> = todos.iter().map(|t| t.id.clone()).collect();
324
325        // 统计
326        let total = todos.len();
327        let completed = todos
328            .iter()
329            .filter(|t| t.status == TodoStatus::Completed)
330            .count();
331        let in_progress = todos
332            .iter()
333            .filter(|t| t.status == TodoStatus::InProgress)
334            .count();
335        let pending = todos
336            .iter()
337            .filter(|t| t.status == TodoStatus::Pending)
338            .count();
339
340        let mut statistics = HashMap::new();
341        statistics.insert("total".to_string(), serde_json::json!(total));
342        statistics.insert("completed".to_string(), serde_json::json!(completed));
343        statistics.insert("in_progress".to_string(), serde_json::json!(in_progress));
344        statistics.insert("pending".to_string(), serde_json::json!(pending));
345
346        let content = format!(
347            "# 每日总结\n\n\
348             ## 概览\n\
349             - 总任务数: {}\n\
350             - 已完成: {}\n\
351             - 进行中: {}\n\
352             - 待处理: {}\n\
353             - 完成率: {:.1}%\n\n\
354             ## 今日完成\n{}\n\
355             ## 进行中\n{}\n",
356            total,
357            completed,
358            in_progress,
359            pending,
360            if total > 0 {
361                (completed as f64 / total as f64) * 100.0
362            } else {
363                0.0
364            },
365            todos
366                .iter()
367                .filter(|t| t.status == TodoStatus::Completed)
368                .map(|t| format!("- {}\n", t.raw_idea.chars().take(50).collect::<String>()))
369                .collect::<String>(),
370            todos
371                .iter()
372                .filter(|t| t.status == TodoStatus::InProgress)
373                .map(|t| format!("- {}\n", t.raw_idea.chars().take(50).collect::<String>()))
374                .collect::<String>(),
375        );
376
377        let report = Report {
378            id,
379            report_type: ReportType::DailySummary,
380            todo_ids,
381            content,
382            statistics,
383            created_at: now,
384        };
385
386        self.save_report(report.clone()).await;
387        report
388    }
389
390    /// 获取汇报历史
391    pub async fn get_history(&self) -> Vec<Report> {
392        let history = self.history.read().await;
393        history.clone()
394    }
395
396    /// 按类型获取汇报
397    pub async fn get_by_type(&self, report_type: ReportType) -> Vec<Report> {
398        let history = self.history.read().await;
399        history
400            .iter()
401            .filter(|r| r.report_type == report_type)
402            .cloned()
403            .collect()
404    }
405
406    /// 获取最近的汇报
407    pub async fn get_recent(&self, count: usize) -> Vec<Report> {
408        let history = self.history.read().await;
409        history.iter().rev().take(count).cloned().collect()
410    }
411}
412
413impl Default for Reporter {
414    fn default() -> Self {
415        Self::new(ReportConfig::default())
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422
423    #[tokio::test]
424    async fn test_generate_completion_report() {
425        let reporter = Reporter::new(ReportConfig::default());
426
427        let todo = TodoItem::new("todo_1", "Build API", TodoPriority::High);
428        let result = ExecutionResult {
429            success: true,
430            summary: "API built successfully".to_string(),
431            details: HashMap::new(),
432            artifacts: vec![],
433            execution_time_ms: 5000,
434            error: None,
435        };
436
437        let report = reporter.generate_completion_report(&todo, &result).await;
438
439        assert_eq!(report.report_type, ReportType::TaskCompletion);
440        assert!(report.content.contains("成功"));
441        assert!(report.content.contains("Build API"));
442    }
443
444    #[tokio::test]
445    async fn test_generate_daily_summary() {
446        let reporter = Reporter::new(ReportConfig::default());
447
448        let mut todos = vec![
449            TodoItem::new("todo_1", "Task 1", TodoPriority::High),
450            TodoItem::new("todo_2", "Task 2", TodoPriority::Medium),
451        ];
452        todos[0].status = TodoStatus::Completed;
453        todos[1].status = TodoStatus::InProgress;
454
455        let report = reporter.generate_daily_summary(&todos).await;
456
457        assert_eq!(report.report_type, ReportType::DailySummary);
458        assert!(report.content.contains("50.0%")); // 50% completion rate
459    }
460}