From 51466aaa1ecc71be6808b88896c0315830f3d727 Mon Sep 17 00:00:00 2001 From: fxy Date: Fri, 19 Jun 2026 10:34:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=98=85=E8=AF=BB=E5=91=A8=E6=8A=A5=20?= =?UTF-8?q?+=20=E5=B7=A5=E4=BD=9C=E6=97=A5=E5=BF=97(=E6=97=A5=E6=8A=A5/?= =?UTF-8?q?=E5=91=A8=E6=8A=A5)=20=E6=8A=A5=E5=91=8A=E7=94=9F=E6=88=90?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analyzers/daily_work_report.py | 155 +++++++++++++ analyzers/weekly_reading_report.py | 328 +++++++++++++++++++++++++++ analyzers/weekly_work_report.py | 209 +++++++++++++++++ scripts/run_daily_work_report.sh | 29 +++ scripts/run_weekly_reading_report.sh | 43 ++++ scripts/run_weekly_work_report.sh | 29 +++ 6 files changed, 793 insertions(+) create mode 100644 analyzers/daily_work_report.py create mode 100644 analyzers/weekly_reading_report.py create mode 100644 analyzers/weekly_work_report.py create mode 100755 scripts/run_daily_work_report.sh create mode 100755 scripts/run_weekly_reading_report.sh create mode 100755 scripts/run_weekly_work_report.sh diff --git a/analyzers/daily_work_report.py b/analyzers/daily_work_report.py new file mode 100644 index 0000000..394834e --- /dev/null +++ b/analyzers/daily_work_report.py @@ -0,0 +1,155 @@ +"""Daily work report generator - extracts work-related Memos and generates a daily work log.""" + +import logging +import os +import sys +from datetime import datetime, timezone, timedelta + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from tools.config import get_output_dir +from tools.memos_client import MemosClient +from tools.config import load_secrets + +logger = logging.getLogger(__name__) + +TZ_BEIJING = timezone(timedelta(hours=8)) + +# Tags that indicate work-related memos +WORK_TAGS = {"工作", "会议", "项目", "出差", "培训", "汇报", "材料", "接待", "调研"} + + +def is_work_memo(tags): + """Check if a memo's tags indicate work content.""" + work_tags = set(tags) & WORK_TAGS + return len(work_tags) > 0 + + +def generate_daily_work_report(target_date=None): + """Generate a daily work report from Memos. + + Args: + target_date: datetime.date or None (defaults to today in Beijing) + """ + if target_date is None: + target_date = datetime.now(TZ_BEIJING).date() + else: + if hasattr(target_date, 'date'): + target_date = target_date.date() + + date_str = target_date.strftime("%Y-%m-%d") + logger.info(f"Generating daily work report for {date_str}") + + # Connect to Memos + secrets = load_secrets() + client = MemosClient(secrets["memos_url"], secrets["memos_token"]) + + # Fetch today's memos + start_dt = datetime.combine(target_date, datetime.min.time(), tzinfo=TZ_BEIJING) + end_dt = datetime.combine(target_date, datetime.max.time(), tzinfo=TZ_BEIJING) + memos = client.list_all_memos_from_range(start_dt, end_dt) + + # Filter work-related + work_memos = [m for m in memos if is_work_memo(m.get("tags", []))] + + logger.info(f"Total memos: {len(memos)}, work-related: {len(work_memos)}") + + if not work_memos: + return _write_empty_report(date_str) + + # Build report + report = _build_report(date_str, work_memos) + _write_report(date_str, report) + + return date_str + + +def _build_report(date_str, work_memos): + """Build markdown report from work memos.""" + lines = [ + f"# 工作日志 · {date_str}", + "", + f"> 共 {len(work_memos)} 条工作记录 · 数据来源:Memos", + "", + ] + + # Group by tag + tag_groups = {} + for m in work_memos: + for tag in m.get("tags", []): + if tag in WORK_TAGS: + if tag not in tag_groups: + tag_groups[tag] = [] + tag_groups[tag].append(m) + + # Summary + lines.append("## 概览") + lines.append("") + for tag, items in sorted(tag_groups.items(), key=lambda x: -len(x[1])): + lines.append(f"- **{tag}**:{len(items)} 条") + lines.append("") + + # Detailed entries + lines.append("## 详情") + lines.append("") + + for m in work_memos: + content = m.get("content", "").strip() + tags = m.get("tags", []) + work_tags = [t for t in tags if t in WORK_TAGS] + tag_str = " · ".join(f"#{t}" for t in work_tags) + + # Remove tag markers from display content for cleaner reading + # Keep the raw content but show tags separately + display_content = content + + created = m.get("created_at", "") + try: + dt = datetime.fromisoformat(created.replace("Z", "+00:00")) + time_str = dt.astimezone(TZ_BEIJING).strftime("%H:%M") + except (ValueError, AttributeError): + time_str = "" + + lines.append(f"### {time_str} {tag_str}") + lines.append("") + lines.append(display_content) + lines.append("") + + return "\n".join(lines) + + +def _write_report(date_str, report_text): + """Write report to ai-insights/daily/YYYY-MM-DD/work/""" + output_root = get_output_dir("daily") + work_dir = os.path.join(output_root, date_str, "work") + os.makedirs(work_dir, exist_ok=True) + + filepath = os.path.join(work_dir, f"工作日志_{date_str}.md") + with open(filepath, "w", encoding="utf-8") as f: + f.write(report_text) + + logger.info(f"Work report saved to {filepath}") + + +def _write_empty_report(date_str): + """Write a minimal report when no work memos found.""" + report = f"# 工作日志 · {date_str}\n\n> 今日无工作相关 Memos\n" + _write_report(date_str, report) + return date_str + + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--date", type=str, help="Target date YYYY-MM-DD, defaults to today") + args = parser.parse_args() + + logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") + + if args.date: + target = datetime.strptime(args.date, "%Y-%m-%d").date() + else: + target = None + + generate_daily_work_report(target) + print("DONE") diff --git a/analyzers/weekly_reading_report.py b/analyzers/weekly_reading_report.py new file mode 100644 index 0000000..cf83067 --- /dev/null +++ b/analyzers/weekly_reading_report.py @@ -0,0 +1,328 @@ +"""Weekly reading report synthesizer - reads daily reading reports and generates weekly analysis.""" + +import logging +import os +import re +import sys +from datetime import datetime, timezone, timedelta + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from tools.config import load_secrets +from tools.llm import DeepSeekClient + +logger = logging.getLogger(__name__) + +TZ_BEIJING = timezone(timedelta(hours=8)) +PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# Reading reports live in weread-notes repo, sibling to inspiration-collector +WEREAD_DIR = os.path.join(os.path.dirname(PROJECT_DIR), "weread-notes") + +READING_WEEKLY_SYSTEM_PROMPT = """你是一个私人阅读分析师。你的任务是阅读用户过去一周的每日阅读报告,生成一篇有深度的周度阅读分析。 + +## 核心原则 + +1. **统计是基础,分析是核心**:先给出本周阅读的量化概览,然后深入分析阅读行为背后的认知模式。 + +2. **追踪阅读主题演变**:本周读了哪些书?它们之间有什么关联?阅读焦点在周初和周尾有什么变化? + +3. **识别认知跃迁**:本周有没有哪本书/哪个概念让用户的思考发生了质变?在哪一天?是什么触发的? + +4. **评估阅读质量**:不是读了多久、划了多少线,而是划线背后反映了什么思考模式?是精读还是泛读?是验证已有认知还是拓展新领域? + +5. **建议下周方向**:基于本周的阅读轨迹,下周建议继续深耕哪本书?或者是否需要补充某个缺失的领域? + +## 输出格式 + +纯 Markdown,不要代码块包裹。 + +### 1. 本周统计 + +用表格呈现: +| 维度 | 数值 | +|:---|:---| +| 阅读天数 | X/7 | +| 累计阅读时长 | X小时Y分钟 | +| 日均阅读时长 | X分钟 | +| 本周在读书籍 | X本 | +| 本周新增划线 | X条 | +| 本周新增想法 | X条 | +| 最投入的书 | 《XXX》· X小时 | +| 划线最多的书 | 《XXX》· X条 | + +### 2. 阅读主题追踪 + +本周的阅读覆盖了哪些主题领域(如投资、哲学、历史、技术……)?每个主题在读哪些书?占了多少时间?哪些主题之间有交叉? + +### 3. 深入分析 + +按主题组织,融会贯通地写: +- 本周阅读的主线是什么(不是逐日罗列,是提炼共同指向) +- 认知跃迁的关键时刻(哪天的什么划线让你意识到用户在想什么) +- 跨书关联(本周读的几本书之间有没有隐含的对话) +- 阅读节奏评价(是持续深耕一本书,还是多线并行?深度如何?) +- 本周最值得注意的一个阅读习惯(比如:划线密度突然变化、某个时段的阅读特别集中) + +### 4. 本周金句 + +从本周的划线和批注中,选出 3-5 条最具代表性的原文或用户批注。每条注明来自哪本书、哪一天。 + +### 5. 下周阅读建议 + +基于本周轨迹,给出 1-3 条具体的阅读方向建议。要写到"继续读哪本书的哪个章节"或"补充哪类书"的颗粒度。 + +--- + +## 补充说明 + +- 如果某天没有阅读报告(用户当天没读书),在统计中注明,但分析中跳过。 +- 统计数字必须从日报原文中提取,不要编造。 +- 分析要有观点,不要求和稀泥。如果用户本周阅读质量明显下降,直接指出。 +- 篇幅不设上限,深度优先。""" + + +def extract_stats_from_daily(daily_text, date_str): + """Extract reading statistics from a daily report. + + Returns dict with: date, books, highlights_count, notes_count, + reading_time_minutes, book_list + """ + stats = { + "date": date_str, + "has_data": False, + "reading_time_minutes": 0, + "highlights_count": 0, + "notes_count": 0, + "books": [], + } + + if not daily_text or len(daily_text.strip()) < 50: + return stats + + # Check if there was any reading today + if "今日没有阅读记录" in daily_text or "暂无阅读" in daily_text: + return stats + + stats["has_data"] = True + + # Extract reading time - look for patterns like "X小时Y分钟", "X分钟" + time_patterns = [ + r'(\d+)\s*小时\s*(\d+)\s*分钟', + r'(\d+)\s*小时', + r'(\d+)\s*分钟', + ] + for pattern in time_patterns: + match = re.search(pattern, daily_text) + if match: + if len(match.groups()) == 2: + stats["reading_time_minutes"] = int(match.group(1)) * 60 + int(match.group(2)) + elif "小时" in pattern: + stats["reading_time_minutes"] = int(match.group(1)) * 60 + else: + stats["reading_time_minutes"] = int(match.group(1)) + break + + # Extract highlight/note counts - look for patterns + highlight_match = re.search(r'划线.*?(\d+)\s*条', daily_text) + if highlight_match: + stats["highlights_count"] = int(highlight_match.group(1)) + + notes_match = re.search(r'想法.*?(\d+)\s*条', daily_text) + if notes_match: + stats["notes_count"] = int(notes_match.group(1)) + + # Extract book titles - look for 《书名》 patterns + book_titles = re.findall(r'《([^》]+)》', daily_text) + # De-duplicate while preserving order + seen = set() + unique_books = [] + for title in book_titles: + if title not in seen and len(title) > 1: + seen.add(title) + unique_books.append(title) + stats["books"] = unique_books[:10] # Top 10 to avoid noise + + return stats + + +def read_week_daily_reports(week_start, week_end): + """Read all daily reading reports from week_start to week_end. + + Args: + week_start, week_end: datetime objects in Beijing time + + Returns: + tuple of (accumulated_stats, full_texts) + """ + daily_dir = os.path.join(WEREAD_DIR, "daily") + all_stats = [] + all_texts = [] + + current = week_start + while current <= week_end: + date_str = current.strftime("%Y-%m-%d") + # Daily reports are named: 每日阅读_YYYY-MM-DD.md + filename = f"每日阅读_{date_str}.md" + filepath = os.path.join(daily_dir, filename) + + if os.path.exists(filepath): + with open(filepath, "r", encoding="utf-8") as f: + text = f.read() + stats = extract_stats_from_daily(text, date_str) + all_stats.append(stats) + all_texts.append((date_str, text)) + logger.info(f" ✓ {date_str}: {len(text)} chars, {len(stats['books'])} books") + else: + all_stats.append({"date": date_str, "has_data": False, "books": []}) + logger.info(f" ✗ {date_str}: no report") + + current += timedelta(days=1) + + return all_stats, all_texts + + +def aggregate_weekly_stats(all_stats): + """Aggregate daily stats into weekly totals.""" + total = { + "reading_days": 0, + "total_minutes": 0, + "total_highlights": 0, + "total_notes": 0, + "all_books": [], + "daily_detail": [], + } + + seen_books = set() + for s in all_stats: + if s.get("has_data"): + total["reading_days"] += 1 + total["total_minutes"] += s.get("reading_time_minutes", 0) + total["total_highlights"] += s.get("highlights_count", 0) + total["total_notes"] += s.get("notes_count", 0) + for book in s.get("books", []): + if book not in seen_books: + seen_books.add(book) + total["all_books"].append(book) + + total["daily_detail"].append({ + "date": s["date"], + "has_data": s.get("has_data", False), + "minutes": s.get("reading_time_minutes", 0), + "books": s.get("books", []), + }) + + return total + + +def build_stats_table(weekly_stats): + """Build the statistics markdown table.""" + avg_minutes = weekly_stats["total_minutes"] // max(weekly_stats["reading_days"], 1) + hours = weekly_stats["total_minutes"] // 60 + minutes = weekly_stats["total_minutes"] % 60 + avg_h = avg_minutes // 60 + avg_m = avg_minutes % 60 + + # Find most engaged book by looking at daily detail + book_mentions = {} + for d in weekly_stats["daily_detail"]: + for book in d.get("books", []): + book_mentions[book] = book_mentions.get(book, 0) + 1 + top_book = max(book_mentions, key=book_mentions.get) if book_mentions else "—" + + return f"""| 维度 | 数值 | +|:---|:---| +| 阅读天数 | {weekly_stats['reading_days']}/7 | +| 累计阅读时长 | {hours}小时{minutes}分钟 | +| 日均阅读时长 | {avg_h}小时{avg_m}分钟 | +| 本周在读书籍 | {len(weekly_stats['all_books'])}本 | +| 本周新增划线 | {weekly_stats['total_highlights']}条 | +| 本周新增想法 | {weekly_stats['total_notes']}条 | +| 最常出现的书 | 《{top_book}》|""" + + +def generate_weekly_report(): + """Main entry: generate weekly reading report.""" + # Determine week range (past 7 days, ending yesterday) + now = datetime.now(TZ_BEIJING) + yesterday = now - timedelta(days=1) + week_end = yesterday.replace(hour=23, minute=59, second=59) + week_start = (week_end - timedelta(days=6)).replace(hour=0, minute=0, second=0) + + logger.info(f"Week range: {week_start.strftime('%Y-%m-%d')} → {week_end.strftime('%Y-%m-%d')}") + + # Step 1: Read all daily reports + all_stats, all_texts = read_week_daily_reports(week_start, week_end) + + if not all_texts: + logger.error("No daily reading reports found this week") + return None + + # Step 2: Aggregate statistics + weekly_stats = aggregate_weekly_stats(all_stats) + stats_md = build_stats_table(weekly_stats) + + # Step 3: Build context for LLM + context_parts = [] + context_parts.append(f"## 本周阅读统计\n\n{stats_md}\n") + + context_parts.append("## 每日阅读报告原文\n") + for date_str, text in all_texts: + # Trim each daily report - keep the key analysis sections, skip repetitive formatting + context_parts.append(f"### {date_str}\n") + # Limit each day to ~3000 chars to avoid token overflow + context_parts.append(text[:3500]) + context_parts.append("\n---\n") + + full_context = "\n".join(context_parts) + + # Step 4: Call DeepSeek API + secrets = load_secrets() + client = DeepSeekClient(secrets["deepseek_api_key"]) + + prompt = READING_WEEKLY_SYSTEM_PROMPT + logger.info(f"Sending to DeepSeek... ({len(full_context)} chars)") + + result = client.chat( + system_prompt=prompt, + user_message=full_context, + temperature=0.5, + ) + + # Step 5: Assemble final report + year = week_start.strftime("%Y") + week_num = week_start.isocalendar()[1] + week_start_str = week_start.strftime("%m%d") + week_end_str = week_end.strftime("%m%d") + + filename = f"每周阅读_W{week_num}_{week_start_str}-{week_end_str}.md" + + header = f"""# 每周阅读报告 · {week_start.strftime('%Y.%m.%d')} — {week_end.strftime('%Y.%m.%d')} + +> 本文基于 {weekly_stats['reading_days']} 天阅读数据生成 · 依托 DeepSeek API 分析 + +""" + + final_report = header + result + + # Step 6: Write and return + output_dir = os.path.join(WEREAD_DIR, "weekly") + os.makedirs(output_dir, exist_ok=True) + output_path = os.path.join(output_dir, filename) + + with open(output_path, "w", encoding="utf-8") as f: + f.write(final_report) + + logger.info(f"Report saved to {output_path}") + logger.info(f"Stats: {weekly_stats['reading_days']} days, {weekly_stats['total_minutes']}min, {len(weekly_stats['all_books'])} books") + + return output_path + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") + path = generate_weekly_report() + if path: + print(f"DONE: {path}") + else: + print("FAILED: No report generated") + sys.exit(1) diff --git a/analyzers/weekly_work_report.py b/analyzers/weekly_work_report.py new file mode 100644 index 0000000..129186c --- /dev/null +++ b/analyzers/weekly_work_report.py @@ -0,0 +1,209 @@ +"""Weekly work report synthesizer - reads daily work logs and generates weekly analysis.""" + +import logging +import os +import sys +from datetime import datetime, timezone, timedelta + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from tools.config import load_secrets, get_output_dir +from tools.llm import DeepSeekClient + +logger = logging.getLogger(__name__) + +TZ_BEIJING = timezone(timedelta(hours=8)) +PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +WORK_WEEKLY_SYSTEM_PROMPT = """你是一个私人工作日志分析师。你的任务是阅读用户过去一周的每日工作日志,生成一篇有深度的周度工作分析。 + +## 核心原则 + +1. **概述先行**:给出本周工作的整体面貌——主要精力投向了哪些领域、总体节奏如何。 + +2. **按主题组织,不按时间罗列**:不要"周一做了X、周二做了Y"。要提炼跨日的工作主线——哪些项目贯穿了整周?哪些任务是单日完成的? + +3. **识别模式和趋势**:本周的工作节奏有什么特点?是否出现了新的工作类型?哪些工作在重复发生?是否有一些事在全周的工作日志中反复出现但没有被解决? + +4. **产出导向**:本周有什么可量化的产出?写了什么材料?参加了什么会议?完成了什么节点? + +5. **前瞻建议**:基于本周工作日志,下周需要关注什么?有什么待办被遗漏了? + +## 输出格式 + +纯 Markdown,不要代码块包裹。 + +### 1. 本周工作概览 + +一段话概括本周工作的底色——主要在忙什么、节奏如何、核心成果。 + +### 2. 工作统计 + +| 维度 | 数值 | +|:---|:---| +| 有工作记录的天数 | X/7 | +| 工作 Memos 总数 | X 条 | +| 涉及标签 | #工作 #会议 ... | +| 单日最高工作量 | 周X · X条 | + +### 3. 深入分析 + +按主题组织: +- 持续性的工作主线(跨日出现的项目/主题) +- 本周重要节点(会议、截止日期、关键决策) +- 工作节奏评价(是否有某天特别忙?是否有连续高压?) +- 新出现的工作类型或关注点 +- 被遗漏或被推迟的事项 + +### 4. 下周关注 + +基于本周工作轨迹,列出下周需要重点关注的事项。具体到事项+时间节点。 + +--- + +注意: +- 如果某天没有工作日志,在统计中注明,但分析中跳过。 +- 分析要有观点——不只是"做了什么",更要回答"这意味着什么"和"接下来该做什么"。 +- 篇幅不设上限。""" + + +def read_week_work_logs(week_start, week_end): + """Read all daily work logs from week_start to week_end.""" + all_texts = [] + daily_root = get_output_dir("daily") + + current = week_start + while current <= week_end: + date_str = current.strftime("%Y-%m-%d") + work_file = os.path.join(daily_root, date_str, "work", f"工作日志_{date_str}.md") + + if os.path.exists(work_file): + with open(work_file, "r", encoding="utf-8") as f: + text = f.read() + all_texts.append((date_str, text)) + logger.info(f" ✓ {date_str}: {len(text)} chars") + else: + logger.info(f" ✗ {date_str}: no work log") + + current += timedelta(days=1) + + return all_texts + + +def extract_weekly_work_stats(all_texts): + """Extract statistics from daily work logs.""" + total_memos = 0 + all_tags = set() + highest_day = ("", 0) + days_with_data = 0 + + for date_str, text in all_texts: + if "今日无工作" in text: + continue + days_with_data += 1 + + # Count memos (each ### header with content) + import re + memo_count = len(re.findall(r'^###\s+\d{2}:\d{2}', text, re.MULTILINE)) + total_memos += memo_count + + if memo_count > highest_day[1]: + highest_day = (date_str, memo_count) + + # Extract tags + tag_matches = re.findall(r'#(\w+)', text) + work_tags = {"工作", "会议", "项目", "出差", "培训", "汇报", "材料", "接待", "调研"} + for tag in tag_matches: + if tag in work_tags: + all_tags.add(tag) + + return { + "days_with_data": days_with_data, + "total_memos": total_memos, + "top_day": highest_day, + "tags": sorted(all_tags), + } + + +def generate_weekly_work_report(): + """Main entry: generate weekly work report.""" + now = datetime.now(TZ_BEIJING) + yesterday = now - timedelta(days=1) + week_end = yesterday.replace(hour=23, minute=59, second=59) + week_start = (week_end - timedelta(days=6)).replace(hour=0, minute=0, second=0) + + logger.info(f"Week range: {week_start.strftime('%Y-%m-%d')} → {week_end.strftime('%Y-%m-%d')}") + + # Step 1: Read daily work logs + all_texts = read_week_work_logs(week_start, week_end) + + if not all_texts: + logger.warning("No work logs found this week") + return None + + # Step 2: Extract stats + stats = extract_weekly_work_stats(all_texts) + + # Step 3: Build context + context_parts = [] + context_parts.append(f"## 本周工作统计\n") + context_parts.append(f"- 有工作记录的天数:{stats['days_with_data']}/7") + context_parts.append(f"- 工作 Memos 总数:{stats['total_memos']} 条") + context_parts.append(f"- 涉及标签:{' '.join('#' + t for t in stats['tags'])}") + if stats['top_day'][0]: + context_parts.append(f"- 单日最高:{stats['top_day'][0]} · {stats['top_day'][1]}条") + context_parts.append("") + + context_parts.append("## 每日工作日志原文\n") + for date_str, text in all_texts: + context_parts.append(f"### {date_str}\n") + context_parts.append(text[:3500]) # Trim each day + context_parts.append("\n---\n") + + full_context = "\n".join(context_parts) + + # Step 4: Call DeepSeek API + secrets = load_secrets() + client = DeepSeekClient(secrets["deepseek_api_key"]) + + logger.info(f"Sending to DeepSeek... ({len(full_context)} chars)") + result = client.chat( + system_prompt=WORK_WEEKLY_SYSTEM_PROMPT, + user_message=full_context, + temperature=0.5, + ) + + # Step 5: Assemble and write + year = week_start.strftime("%Y") + week_num = week_start.isocalendar()[1] + week_start_str = week_start.strftime("%m%d") + week_end_str = week_end.strftime("%m%d") + filename = f"工作周报_W{week_num}_{week_start_str}-{week_end_str}.md" + + header = f"""# 工作周报 · {week_start.strftime('%Y.%m.%d')} — {week_end.strftime('%Y.%m.%d')} + +> 基于 {stats['days_with_data']}/7 天工作日志生成 · 依托 DeepSeek API 分析 + +""" + + final_report = header + result + + output_dir = os.path.join(get_output_dir("weekly"), "work") + os.makedirs(output_dir, exist_ok=True) + output_path = os.path.join(output_dir, filename) + + with open(output_path, "w", encoding="utf-8") as f: + f.write(final_report) + + logger.info(f"Report saved to {output_path}") + return output_path + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") + path = generate_weekly_work_report() + if path: + print(f"DONE: {path}") + else: + print("FAILED: No report generated") + sys.exit(1) diff --git a/scripts/run_daily_work_report.sh b/scripts/run_daily_work_report.sh new file mode 100755 index 0000000..e96e002 --- /dev/null +++ b/scripts/run_daily_work_report.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Daily work report generator +# Reads today's work-related Memos → generates daily work log +# Run: daily, alongside the daily digest (22:00 Beijing = 14:00 UTC) + +set -e + +INSP_DIR="/home/ubuntu/inspiration-collector" +LOG_FILE="/tmp/daily_work_report_$(date +%Y%m%d).log" + +echo "[$(date)] Starting daily work report..." | tee "$LOG_FILE" + +cd "$INSP_DIR" +git pull origin main 2>&1 | tee -a "$LOG_FILE" + +echo "[$(date)] Running analyzer..." | tee -a "$LOG_FILE" +python3 analyzers/daily_work_report.py 2>&1 | tee -a "$LOG_FILE" + +echo "[$(date)] Committing and pushing..." | tee -a "$LOG_FILE" +git add ai-insights/daily/ 2>/dev/null || true + +if git diff --cached --quiet; then + echo "[$(date)] No changes" | tee -a "$LOG_FILE" +else + git commit -m "daily: work report $(date +%Y-%m-%d)" 2>&1 | tee -a "$LOG_FILE" + git push origin main 2>&1 | tee -a "$LOG_FILE" +fi + +echo "[$(date)] Done." | tee -a "$LOG_FILE" diff --git a/scripts/run_weekly_reading_report.sh b/scripts/run_weekly_reading_report.sh new file mode 100755 index 0000000..c301ec1 --- /dev/null +++ b/scripts/run_weekly_reading_report.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Weekly reading report generator +# Reads 7 daily reading reports → DeepSeek API → weekly analysis +# Run: weekly, Sunday 16:30 Beijing time (08:30 UTC) +# +# NOTE: Script lives in inspiration-collector/scripts/ but output +# goes to weread-notes/weekly/. Both repos are siblings on the server. + +set -e + +INSP_DIR="/home/ubuntu/inspiration-collector" +WEREAD_DIR="/home/ubuntu/weread-notes" +LOG_FILE="/tmp/weekly_reading_report_$(date +%Y%m%d_%H%M).log" + +echo "[$(date)] Starting weekly reading report..." | tee "$LOG_FILE" + +# Pull latest in both repos +echo "[$(date)] Pulling weread-notes..." | tee -a "$LOG_FILE" +cd "$WEREAD_DIR" +git pull origin main 2>&1 | tee -a "$LOG_FILE" + +echo "[$(date)] Pulling inspiration-collector..." | tee -a "$LOG_FILE" +cd "$INSP_DIR" +git pull origin main 2>&1 | tee -a "$LOG_FILE" + +# Run the analyzer (it reads from weread-notes/daily/ and writes to weread-notes/weekly/) +echo "[$(date)] Running analyzer..." | tee -a "$LOG_FILE" +cd "$INSP_DIR" +python3 analyzers/weekly_reading_report.py 2>&1 | tee -a "$LOG_FILE" + +# Commit and push weread-notes +echo "[$(date)] Committing weread-notes..." | tee -a "$LOG_FILE" +cd "$WEREAD_DIR" +git add weekly/ 2>/dev/null || true + +if git diff --cached --quiet; then + echo "[$(date)] No changes to commit in weread-notes" | tee -a "$LOG_FILE" +else + git commit -m "weekly: reading report $(date +%Y-%m-%d)" 2>&1 | tee -a "$LOG_FILE" + git push origin main 2>&1 | tee -a "$LOG_FILE" +fi + +echo "[$(date)] Done." | tee -a "$LOG_FILE" diff --git a/scripts/run_weekly_work_report.sh b/scripts/run_weekly_work_report.sh new file mode 100755 index 0000000..15a3c4a --- /dev/null +++ b/scripts/run_weekly_work_report.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Weekly work report generator +# Reads 7 daily work logs → DeepSeek API → weekly work analysis +# Run: weekly, Sunday 17:00 Beijing time (09:00 UTC) + +set -e + +INSP_DIR="/home/ubuntu/inspiration-collector" +LOG_FILE="/tmp/weekly_work_report_$(date +%Y%m%d_%H%M).log" + +echo "[$(date)] Starting weekly work report..." | tee "$LOG_FILE" + +cd "$INSP_DIR" +git pull origin main 2>&1 | tee -a "$LOG_FILE" + +echo "[$(date)] Running analyzer..." | tee -a "$LOG_FILE" +python3 analyzers/weekly_work_report.py 2>&1 | tee -a "$LOG_FILE" + +echo "[$(date)] Committing and pushing..." | tee -a "$LOG_FILE" +git add ai-insights/weekly/ 2>/dev/null || true + +if git diff --cached --quiet; then + echo "[$(date)] No changes" | tee -a "$LOG_FILE" +else + git commit -m "weekly: work report $(date +%Y-%m-%d)" 2>&1 | tee -a "$LOG_FILE" + git push origin main 2>&1 | tee -a "$LOG_FILE" +fi + +echo "[$(date)] Done." | tee -a "$LOG_FILE"