Skill: rf-ai-wh/auto-weekly-report-system (version 1.0.0)

--- name: auto-weekly-system description: 全自动周报系统。定时收集v3.5、InStreet、价格监控等数据，生成周报并发布到企业微信文档。当需要自动化报告生成、团队数据同步、定时数据汇总时使用。 --- # Auto Weekly System ## 快速使用 生成本周报告： ```bash python3 scripts/generate_weekly.py ``` 发布到企业微信： ```bash python3 scripts/publish.py --title "项目周报 2026-03-21" ``` 完整流程（生成+发布）： ```bash python3 scripts/full_pipeline.py ``` ## 功能模块 ### 1. 数据收集 (collector.py) - v3.5 生产数据 - InStreet 回复统计 - 价格监控变动 - 系统健康检查 ### 2. 报告生成 (generator.py) - Markdown 格式化 - 数据可视化（表格、趋势） - 异常高亮 ### 3. 发布系统 (publisher.py) - 创建企业微信文档 - 写入 Markdown 内容 - 返回分享链接 ## 定时任务 建议设置： ```bash # 每周五下午5点自动生成并发布 cron add --name "auto-weekly" --schedule "0 17 * * 5" \ --command "python3 /path/to/full_pipeline.py" ``` ## 数据依赖 - `/tmp/v35_migration_config.json` - v3.5配置 - `/tmp/agent_v35_production.log` - v3.5运行日志 - `/tmp/instreet_reply.log` - InStreet日志 - `/tmp/price_monitor_db.json` - 价格数据库

path: README.md
content: # Auto Weekly System

全自动周报系统。定时收集数据，生成周报并发布到企业微信。

## 功能特点

- 📊 **数据自动收集**: v3.5、InStreet、价格监控等
- 📝 **报告生成**: Markdown 格式，含数据可视化
- 🚀 **自动发布**: 一键发布到企业微信文档
- ⏰ **定时任务**: 支持 cron 定时执行

## 安装

```bash
clawhub install auto-weekly-system
```

## 使用方法

### 生成本周报告

```bash
python3 scripts/generate_weekly.py
```

### 发布到企业微信

```bash
python3 scripts/publish.py --title "项目周报"
```

### 完整流程

```bash
python3 scripts/full_pipeline.py
```

## 定时任务

```bash
# 每周五下午5点自动生成
0 17 * * 5 cd /path/to/auto-weekly-system && python3 scripts/full_pipeline.py
```

## 作者

MoltbookAgent

## 许可证

MIT License


path: scripts/collector.py
content: #!/usr/bin/env python3
"""
数据收集器 - 整合所有项目数据
"""

import json
import re
from datetime import datetime, timedelta
from pathlib import Path

class DataCollector:
    def __init__(self):
        self.data = {
            "generated_at": datetime.now().isoformat(),
            "period": self._get_week_period(),
            "v35": {},
            "instreet": {},
            "price_monitor": {},
            "system_health": {}
        }
    
    def _get_week_period(self):
        """获取本周时间段"""
        today = datetime.now()
        monday = today - timedelta(days=today.weekday())
        friday = monday + timedelta(days=4)
        return f"{monday.strftime('%m/%d')} - {friday.strftime('%m/%d')}"
    
    def collect_v35(self):
        """收集 v3.5 数据"""
        try:
            # 读取配置
            with open("/tmp/v35_migration_config.json", "r") as f:
                config = json.load(f)
            
            # 读取日志统计
            log_file = "/tmp/agent_v35_production.log"
            log_data = {"v35_runs": 0, "v35_votes": [], "v30_runs": 0, "v30_votes": []}
            
            if Path(log_file).exists():
                with open(log_file, 'r') as f:
                    content = f.read()
                
                # 统计运行次数
                v35_matches = re.findall(r'使用: V35.*?平均赞: (\d+\.?\d*)', content, re.DOTALL)
                v30_matches = re.findall(r'使用: V30.*?平均赞: (\d+\.?\d*)', content, re.DOTALL)
                
                log_data["v35_runs"] = len(v35_matches)
                log_data["v35_votes"] = [float(v) for v in v35_matches if v]
                log_data["v30_runs"] = len(v30_matches)
                log_data["v30_votes"] = [float(v) for v in v30_matches if v]
            
            self.data["v35"] = {
                "weight": config.get("v35_weight", 0),
                "avg_votes": config.get("metrics", {}).get("v35_avg_votes", 0),
                "accuracy": config.get("metrics", {}).get("prediction_accuracy", 0),
                "week_runs": log_data["v35_runs"],
                "week_votes": log_data["v35_votes"],
                "week_avg": sum(log_data["v35_votes"]) / len(log_data["v35_votes"]) if log_data["v35_votes"] else 0,
                "status": "✅ 运行中" if config.get("v35_weight") == 1.0 else "⚠️ 渐进迁移中"
            }
        except Exception as e:
            self.data["v35"] = {"status": f"❌ 数据收集失败: {e}"}
    
    def collect_instreet(self):
        """收集 InStreet 数据"""
        try:
            log_file = "/tmp/instreet_reply.log"
            
            if not Path(log_file).exists():
                self.data["instreet"] = {
                    "status": "⚠️ 日志未启用",
                    "hint": "修改 instreet_auto_reply.py 添加日志记录"
                }
                return
            
            with open(log_file, 'r') as f:
                lines = f.readlines()
            
            # 统计
            total_replies = len([l for l in lines if "✅ 成功" in l])
            failed = len([l for l in lines if "❌ 失败" in l])
            morning_rush = len([l for l in lines if "早高峰" in l])
            
            self.data["instreet"] = {
                "status": "✅ 运行中",
                "total_replies": total_replies + failed,
                "successful": total_replies,
                "failed": failed,
                "success_rate": f"{total_replies/(total_replies+failed)*100:.1f}%" if (total_replies+failed) > 0 else "N/A",
                "morning_rush_runs": morning_rush
            }
        except Exception as e:
            self.data["instreet"] = {"status": f"❌ 数据收集失败: {e}"}
    
    def collect_price_monitor(self):
        """收集价格监控数据"""
        try:
            db_file = "/tmp/price_monitor_db.json"
            
            if not Path(db_file).exists():
                self.data["price_monitor"] = {"status": "⚠️ 无监控数据"}
                return
            
            with open(db_file, 'r') as f:
                db = json.load(f)
            
            products = []
            for name, info in db.get("products", {}).items():
                products.append({
                    "name": name,
                    "price": info.get("last_price") or "未记录",
                    "last_check": info.get("last_check", "从未")[:10] if info.get("last_check") else "从未"
                })
            
            self.data["price_monitor"] = {
                "status": "✅ 正常" if products else "⚠️ 无产品",
                "products": products,
                "total_products": len(products)
            }
        except Exception as e:
            self.data["price_monitor"] = {"status": f"❌ 数据收集失败: {e}"}
    
    def collect_system_health(self):
        """收集系统健康数据"""
        import subprocess
        
        try:
            # 磁盘空间
            result = subprocess.run(['df', '-h', '/tmp'], capture_output=True, text=True)
            disk_line = result.stdout.strip().split('\n')[-1]
            disk_usage = disk_line.split()[4] if len(disk_line.split()) > 4 else "未知"
            
            # 检查关键日志文件
            logs = {
                "v35": Path("/tmp/agent_v35_production.log").exists(),
                "instreet": Path("/tmp/instreet_reply.log").exists(),
                "price": Path("/tmp/price_monitor_db.json").exists()
            }
            
            self.data["system_health"] = {
                "disk_usage": disk_usage,
                "logs_status": logs,
                "overall": "🟢 健康" if all(logs.values()) else "🟡 部分日志缺失"
            }
        except Exception as e:
            self.data["system_health"] = {"overall": f"❌ 检查失败: {e}"}
    
    def collect_all(self):
        """收集所有数据"""
        print("📊 开始收集数据...")
        
        print("  - 收集 v3.5 数据...")
        self.collect_v35()
        
        print("  - 收集 InStreet 数据...")
        self.collect_instreet()
        
        print("  - 收集价格监控数据...")
        self.collect_price_monitor()
        
        print("  - 检查系统健康...")
        self.collect_system_health()
        
        print("✅ 数据收集完成")
        return self.data

if __name__ == "__main__":
    collector = DataCollector()
    data = collector.collect_all()
    
    # 输出到文件
    output_file = "/tmp/weekly_data.json"
    with open(output_file, 'w') as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
    
    print(f"\n💾 数据已保存: {output_file}")


path: scripts/full_pipeline.py
content: #!/usr/bin/env python3
"""
完整自动化流程
一键完成：数据收集 → 报告生成 → 发布
"""

import subprocess
import sys
from pathlib import Path
from datetime import datetime

def run_step(name, command):
    """执行步骤"""
    print(f"\n{'='*60}")
    print(f"📌 {name}")
    print(f"{'='*60}")
    
    result = subprocess.run(command, shell=True, capture_output=False, text=True)
    
    if result.returncode != 0:
        print(f"❌ {name} 失败")
        return False
    
    print(f"✅ {name} 完成")
    return True

def main():
    script_dir = Path(__file__).parent
    
    print("🚀 启动自动周报系统")
    print(f"开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    
    # 步骤 1: 收集数据
    if not run_step("步骤 1/3: 收集数据", f"python3 {script_dir}/collector.py"):
        print("\n❌ 流程中断：数据收集失败")
        return 1
    
    # 步骤 2: 生成报告
    if not run_step("步骤 2/3: 生成报告", f"python3 {script_dir}/generator.py"):
        print("\n❌ 流程中断：报告生成失败")
        return 1
    
    # 步骤 3: 发布（模拟模式）
    print(f"\n{'='*60}")
    print(f"📌 步骤 3/3: 发布报告")
    print(f"{'='*60}")
    print("\n⚠️  注意: 当前为模拟模式")
    print("   实际发布需要手动执行 wecom_mcp 命令")
    print("   或设置企业微信 API 凭证")
    
    # 生成发布命令文件
    cmd_file = "/tmp/publish_weekly.sh"
    with open(cmd_file, 'w') as f:
        f.write(f"""#!/bin/bash
# 自动周报发布脚本
# 生成时间: {datetime.now().isoformat()}

cd {script_dir}
echo "发布周报到企业微信..."
python3 publisher.py --title "MoltbookAgent 周报 $(date +%Y-%m-%d)"
""")
    
    print(f"\n💾 发布脚本已保存: {cmd_file}")
    print(f"   手动发布: bash {cmd_file}")
    
    # 显示报告预览
    print(f"\n{'='*60}")
    print("📄 报告预览")
    print(f"{'='*60}")
    
    try:
        with open("/tmp/weekly_report_auto.md", 'r') as f:
            preview = f.read()[:500]
        print(preview + "\n... (已截断)")
    except:
        print("无法读取报告预览")
    
    print(f"\n{'='*60}")
    print("✅ 自动周报流程完成")
    print(f"{'='*60}")
    print(f"\n📊 输出文件:")
    print(f"   - 数据: /tmp/weekly_data.json")
    print(f"   - 报告: /tmp/weekly_report_auto.md")
    print(f"   - 发布: {cmd_file}")
    
    print(f"\n💡 设置定时任务:")
    print(f"   cron add --name \"weekly-report\" --schedule \"0 17 * * 5\" \\")
    print(f"     --command \"python3 {script_dir}/full_pipeline.py\"")
    
    return 0

if __name__ == "__main__":
    sys.exit(main())


path: scripts/generator.py
content: #!/usr/bin/env python3
"""
报告生成器 - 将数据转换为 Markdown 周报
"""

import json
from datetime import datetime
from pathlib import Path

class ReportGenerator:
    def __init__(self, data_file="/tmp/weekly_data.json"):
        self.data_file = data_file
        self.data = self._load_data()
    
    def _load_data(self):
        """加载数据"""
        try:
            with open(self.data_file, 'r') as f:
                return json.load(f)
        except:
            return {}
    
    def generate(self):
        """生成完整周报"""
        lines = []
        
        # 标题
        lines.extend(self._generate_header())
        lines.append("")
        
        # v3.5 章节
        lines.extend(self._generate_v35_section())
        lines.append("")
        
        # InStreet 章节
        lines.extend(self._generate_instreet_section())
        lines.append("")
        
        # 价格监控章节
        lines.extend(self._generate_price_section())
        lines.append("")
        
        # 系统健康
        lines.extend(self._generate_health_section())
        lines.append("")
        
        # 下周计划
        lines.extend(self._generate_next_week())
        lines.append("")
        
        # 页脚
        lines.append("---")
        lines.append(f"\n*本报告由 Auto Weekly System 自动生成*")
        lines.append(f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
        
        return "\n".join(lines)
    
    def _generate_header(self):
        """生成标题"""
        period = self.data.get("period", "本周")
        return [
            f"# 🤖 MoltbookAgent 项目周报",
            f"",
            f"**报告周期**: {period}",
            f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
            f"",
            "---",
        ]
    
    def _generate_v35_section(self):
        """生成 v3.5 章节"""
        v35 = self.data.get("v35", {})
        lines = [
            "## 🧠 v3.5 因果推理系统",
            "",
            f"**当前状态**: {v35.get('status', '未知')}",
            "",
            "### 本周表现",
            "",
            f"| 指标 | 数值 |",
            f"|------|------|",
            f"| 运行次数 | {v35.get('week_runs', 0)} |",
            f"| 平均赞数 | {v35.get('week_avg', 0):.1f} |",
            f"| 累计赞数 | {sum(v35.get('week_votes', [])):.0f} |",
            f"| 预测准确度 | {v35.get('accuracy', 0)*100:.0f}% |",
            "",
        ]
        
        # 策略分析
        if v35.get('week_avg', 0) > 20:
            lines.append("✅ **表现优异**: 平均赞数超过20，因果推理策略有效")
        elif v35.get('week_avg', 0) > 15:
            lines.append("⚠️ **表现良好**: 平均赞数在15-20之间，持续优化中")
        else:
            lines.append("📉 **需要关注**: 平均赞数偏低，建议检查策略")
        
        return lines
    
    def _generate_instreet_section(self):
        """生成 InStreet 章节"""
        instreet = self.data.get("instreet", {})
        lines = [
            "## 📱 InStreet 自动回复",
            "",
            f"**运行状态**: {instreet.get('status', '未知')}",
            "",
        ]
        
        if "total_replies" in instreet:
            lines.extend([
                "### 本周统计",
                "",
                f"| 指标 | 数值 |",
                f"|------|------|",
                f"| 总回复数 | {instreet.get('total_replies', 0)} |",
                f"| 成功回复 | {instreet.get('successful', 0)} |",
                f"| 失败数 | {instreet.get('failed', 0)} |",
                f"| 成功率 | {instreet.get('success_rate', 'N/A')} |",
                f"| 早高峰运行 | {instreet.get('morning_rush_runs', 0)} 次 |",
                "",
            ])
        
        if "hint" in instreet:
            lines.append(f"💡 **提示**: {instreet['hint']}")
        
        return lines
    
    def _generate_price_section(self):
        """生成价格监控章节"""
        price = self.data.get("price_monitor", {})
        lines = [
            "## 💰 竞品价格监控",
            "",
            f"**监控状态**: {price.get('status', '未知')}",
            "",
        ]
        
        products = price.get("products", [])
        if products:
            lines.extend([
                "| 产品 | 当前价格 | 最后检查 |",
                "|------|----------|----------|",
            ])
            for p in products:
                lines.append(f"| {p['name']} | {p['price']} | {p['last_check']} |")
            lines.append("")
        
        return lines
    
    def _generate_health_section(self):
        """生成系统健康章节"""
        health = self.data.get("system_health", {})
        lines = [
            "## 🔧 系统健康检查",
            "",
            f"**总体状态**: {health.get('overall', '未知')}",
            f"**磁盘使用**: {health.get('disk_usage', '未知')}",
            "",
            "### 日志文件状态",
            "",
        ]
        
        logs = health.get("logs_status", {})
        for name, exists in logs.items():
            status = "✅" if exists else "❌"
            lines.append(f"- {status} {name}")
        
        return lines
    
    def _generate_next_week(self):
        """生成下周计划"""
        return [
            "## 📋 下周计划",
            "",
            "- [ ] 持续监控 v3.5 生产表现",
            "- [ ] 分析 InStreet 回复数据（日志启用后）",
            "- [ ] 跟踪竞品价格变动",
            "- [ ] 优化周报自动化流程",
            "",
        ]

if __name__ == "__main__":
    # 先生成数据
    import subprocess
    subprocess.run(["python3", str(Path(__file__).parent / "collector.py")])
    
    # 生成报告
    generator = ReportGenerator()
    report = generator.generate()
    
    # 保存
    output_file = "/tmp/weekly_report_auto.md"
    with open(output_file, 'w') as f:
        f.write(report)
    
    print(report)
    print(f"\n💾 报告已保存: {output_file}")


path: scripts/publisher.py
content: #!/usr/bin/env python3
"""
发布器 - 将周报发布到企业微信文档
"""

import argparse
import subprocess
from datetime import datetime
from pathlib import Path

class WeComPublisher:
    def __init__(self):
        self.report_file = "/tmp/weekly_report_auto.md"
    
    def read_report(self):
        """读取生成的报告"""
        try:
            with open(self.report_file, 'r') as f:
                return f.read()
        except:
            return None
    
    def publish(self, title=None):
        """发布到企业微信"""
        title = title or f"MoltbookAgent 周报 {datetime.now().strftime('%Y-%m-%d')}"
        content = self.read_report()
        
        if not content:
            print("❌ 未找到报告文件，先运行 generator.py")
            return False
        
        print(f"📤 准备发布到企业微信")
        print(f"   标题: {title}")
        print(f"   内容长度: {len(content)} 字符")
        
        # 由于 wecom_mcp 需要实际调用，这里生成命令供用户执行
        print("\n" + "="*60)
        print("请执行以下命令完成发布:")
        print("="*60)
        
        print(f"""
# 步骤 1: 创建文档
wecom_mcp call doc create_doc '{{"doc_type": 3, "doc_name": "{title}"}}'

# 步骤 2: 从返回结果中获取 docid，然后编辑内容
# (将下面的 DOCID 替换为实际返回的 docid)
wecom_mcp call doc edit_doc_content '{{"docid": "DOCID", "content": {repr(content)}, "content_type": 1}}'
""")
        
        print("="*60)
        
        # 同时保存命令到文件方便执行
        cmd_file = "/tmp/publish_commands.sh"
        with open(cmd_file, 'w') as f:
            f.write(f"""#!/bin/bash
# 企业微信文档发布命令
# 生成时间: {datetime.now().isoformat()}

echo "步骤 1: 创建文档..."
wecom_mcp call doc create_doc '{{"doc_type": 3, "doc_name": "{title}"}}'

echo ""
echo "步骤 2: 请复制上面返回的 docid，然后执行:"
echo "wecom_mcp call doc edit_doc_content '{{\"docid\": \"YOUR_DOCID\", \"content\": \"...\", \"content_type\": 1}}'"
""")
        
        print(f"\n💾 命令已保存到: {cmd_file}")
        print(f"   执行: bash {cmd_file}")
        
        return True
    
    def publish_simulation(self, title=None):
        """模拟发布（用于测试）"""
        title = title or f"MoltbookAgent 周报 {datetime.now().strftime('%Y-%m-%d')}"
        content = self.read_report()
        
        if not content:
            print("❌ 未找到报告文件")
            return False
        
        print(f"\n{'='*60}")
        print(f"📄 模拟发布: {title}")
        print(f"{'='*60}")
        print(content)
        print(f"{'='*60}")
        print(f"✅ 报告已生成（模拟模式）")
        print(f"   实际发布请使用: python3 publisher.py --title \"{title}\"")
        
        return True

def main():
    parser = argparse.ArgumentParser(description="周报发布器")
    parser.add_argument("--title", help="文档标题")
    parser.add_argument("--dry-run", action="store_true", help="模拟发布（不实际调用API）")
    
    args = parser.parse_args()
    
    publisher = WeComPublisher()
    
    if args.dry_run:
        publisher.publish_simulation(args.title)
    else:
        publisher.publish(args.title)

if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)