"""Weekly digest synthesizer - reads all daily digests and generates a week-level analysis.""" import logging import os import subprocess 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__))) WEEKLY_SYSTEM_PROMPT = """你是一个私人思考伙伴。你的任务是认真阅读用户过去一周的7份日报,写一篇有深度的周度分析报告。 ## 核心原则 你读的是日报——不是原始灵感记录,而是已经经过每日分析提炼的内容。你的工作是: 1. **提炼跨日主线**:7份日报各自都有"今日概览"——把它们串起来。这周的主旋律是什么?从周一到现在,思考的焦点如何演变? 2. **追踪延续与断裂**:日报中反复出现的标签(如 #浙江培训 #交易 #灵感收集器)代表持续性的主题。哪些主题从头贯穿到尾?哪些在中间消失了?为什么? 3. **识别本周的关键跃迁**:在7天的思考中,哪一天出现了认知上的质变?哪一个洞察是本周最核心的收获? 4. **不逐日罗列**:不要"周一...周二...周三..."。要融会贯通,把7天当作一个整体来写。按主题组织,不是按时间组织。 5. **深度优先**:每周分析应该比每日分析更深一层。如果日报是在追问"今天发生了什么",周报应该回答"这一周意味着什么"。 ## 输出格式 纯 Markdown,不要代码块包裹。 ## 附加素材:今日新知 过去7天的日报中,凡是出现了"### 今日新知"章节的,都包含用户本周积累的新知识碎片。这些新知不属于"灵感"(需要AI深挖),而是用户记录下来的外部信息碎片。在周报中,请: - 在适当位置汇总本周新积累的知识领域 - 说明哪些知识是零散信息、哪些已形成体系 - 判断这些新知与用户长期主题(交易、写作、培训等)的潜在关联 结构参考: ### 1. 本周概览 一两句话点出这周的底色,并指出最核心的1-2条暗线(可能不是最显眼的,但是对用户最具启发价值的)。 ### 2. 深入分析 按主题组织,融会贯通地写。可以包含但不限于: - 持续性主题的演变轨迹 - 认知跃迁的关键时刻 - 看似无关事件之间的深层关联 - 本周形成的新规则、新认知、新能力 - 与用户长期目标/身份认同的关系 引用日报内容要自然嵌入行文,让用户知道你在引用哪一天的观察。 ### 3. 待办事项 基于本周分析,提出需要延续到下周的具体行动。要写到"什么时机做什么做到什么程度"的颗粒度。 --- 注意:即便7份日报中有些比较简短,也要从整体中挖掘深度。篇幅不设上限。""" def read_week_digests(week_start, week_end): """Read all daily digest files from week_start to week_end. Returns list of (date_str, digest_text, tags) tuples. """ daily_root = get_output_dir("daily") digests = [] current = week_start while current <= week_end: date_str = current.strftime("%Y-%m-%d") daily_dir = os.path.join(daily_root, date_str) if os.path.isdir(daily_dir): # Find the latest digest file for this day files = sorted([ f for f in os.listdir(daily_dir) if f.endswith(".md") and "digest" in f and not f.endswith(".bak.md") ], reverse=True) if files: filepath = os.path.join(daily_dir, files[0]) with open(filepath, "r", encoding="utf-8") as f: content = f.read() # Extract AI analysis section ai_start = content.find("## AI 分析") annotation_start = content.find("## 我的批注", ai_start) if ai_start != -1 else -1 if ai_start != -1 and annotation_start != -1: ai_body = content[ai_start:annotation_start].strip() elif ai_start != -1: ai_body = content[ai_start:].strip() else: ai_body = content # Extract tags from frontmatter tags = [] if "tags:" in content[:500]: tag_line = content.split("tags:")[1].split("\n")[0] import re tags = re.findall(r"'([^']+)'", tag_line) digests.append((date_str, ai_body, tags)) logger.info( "Read digest %s: %d chars, tags=%s", date_str, len(ai_body), ", ".join(tags[:5]) ) else: logger.info("No digest file found for %s (skipped)", date_str) else: logger.info("No daily dir for %s (skipped)", date_str) current += timedelta(days=1) return digests def build_weekly_prompt(digests): """Build the user prompt containing all 7 daily digest bodies.""" if not digests: return None # Extract week range first_date = digests[0][0] last_date = digests[-1][0] prompt_parts = [ f"以下是我从 {first_date} 到 {last_date} 共7天的日报。", f"共 {len(digests)} 份日报。", "", "请基于这些日报内容,写一篇周度分析报告。" ] # Collect all unique tags across the week all_tags = set() for _, _, tags in digests: all_tags.update(tags) if all_tags: tag_str = "、".join(f"#{t}" for t in sorted(all_tags)) prompt_parts.append(f"\n本周出现的标签:{tag_str}") prompt_parts.append("\n---\n") for date_str, body, tags in digests: # Show a weekday indicator dt = datetime.strptime(date_str, "%Y-%m-%d") weekday = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][dt.weekday()] tag_hint = "" if tags: important_tags = [t for t in tags if t not in ("灵感收集器", "每日总结", "AI分析")] if important_tags: tag_hint = " | 标签:" + ", ".join(f"#{t}" for t in important_tags[:5]) prompt_parts.append(f"\n## {date_str}({weekday}){tag_hint}\n") prompt_parts.append(body) prompt_parts.append("\n") prompt_parts.append("\n---\n") prompt_parts.append("请基于以上所有日报内容,写一篇有深度的周度分析报告。") return "\n".join(prompt_parts) def format_weekly_digest(week_start, week_end, ai_body, tags=None): """Format weekly digest markdown with frontmatter, matching daily digest format.""" week_start_str = week_start.strftime("%Y-%m-%d") week_end_str = week_end.strftime("%m/%d") week_number = week_start.isocalendar()[1] start_month = week_start.strftime("%m/%d") char_count = len(ai_body.replace(" ", "").replace("\n", "")) read_min = max(1, round(char_count / 300)) if tags is None: tags = ["灵感收集器", "每周总结"] tag_str = ", ".join(["'" + t + "'" for t in tags]) lines = [] lines.append("---") lines.append(f"date: {week_start.strftime('%Y')}-W{week_number}") lines.append("type: weekly-digest") lines.append(f"tags: [{tag_str}]") lines.append("---") lines.append("") lines.append(f"# 第{week_number}周 · 周度总结({start_month} - {week_end_str})") lines.append("") lines.append(f"> 本文共 **{char_count}** 字 · 预计阅读 **{read_min}** 分钟") lines.append("") lines.append("> 基于本周7份日报综合生成。") lines.append("") lines.append("## AI 分析(自动生成,请勿编辑)") lines.append("") lines.append(ai_body) lines.append("") lines.append("---") lines.append("") lines.append("## 我的批注") lines.append("") lines.append("> *在这里写下你的想法、质疑、补充*") lines.append("") return "\n".join(lines) def git_push_weekly(filepath): """Commit and push the weekly digest to Gitea.""" try: result = subprocess.run( ["git", "add", filepath], cwd=PROJECT_DIR, capture_output=True, text=True, timeout=15 ) if result.returncode != 0: logger.warning("git add failed: %s", result.stderr.strip()) return False result = subprocess.run( ["git", "status", "--porcelain", "ai-insights/"], cwd=PROJECT_DIR, capture_output=True, text=True, timeout=10 ) if not result.stdout.strip(): logger.info("No changes to commit") return True week_num = datetime.now(TZ_BEIJING).isocalendar()[1] result = subprocess.run( ["git", "commit", "-m", f"weekly digest W{week_num}"], cwd=PROJECT_DIR, capture_output=True, text=True, timeout=15 ) if result.returncode != 0 and "nothing to commit" not in result.stdout: logger.warning("git commit failed: %s", result.stderr.strip()) return False result = subprocess.run( ["git", "push", "origin", "main"], cwd=PROJECT_DIR, capture_output=True, text=True, timeout=30 ) if result.returncode != 0: logger.warning("git push failed: %s", result.stderr.strip()) return False for line in result.stdout.split("\n"): if "->" in line or "remote:" in line: logger.info("Gitea push: %s", line.strip()) logger.info("Pushed weekly digest to Gitea") return True except subprocess.TimeoutExpired: logger.error("git push timed out") return False except Exception as e: logger.error("git push error: %s", e) return False def run(llm_client, date=None): """Run weekly digest synthesis. Reads all daily digests from the past 7 days (Mon-Sun), synthesizes a weekly report via DeepSeek API, and saves to ai-insights/weekly/. """ date = date or datetime.now(TZ_BEIJING) # Calculate week range (Monday to Sunday) weekday = date.weekday() # 0=Monday, 6=Sunday week_start = date - timedelta(days=weekday) # This Monday week_end = week_start + timedelta(days=6) # This Sunday logger.info( "Weekly digest started for W%d (%s - %s)", week_start.isocalendar()[1], week_start.strftime("%Y-%m-%d"), week_end.strftime("%Y-%m-%d") ) # Read all daily digests digests = read_week_digests(week_start, week_end) if not digests: logger.info("No daily digests found for this week, skipping") return None, 0 logger.info("Found %d daily digests to synthesize", len(digests)) # Build prompt user_prompt = build_weekly_prompt(digests) if user_prompt is None: logger.error("Failed to build weekly prompt") return None, 0 # Call DeepSeek API raw_response = llm_client.ask( system_prompt=WEEKLY_SYSTEM_PROMPT, user_prompt=user_prompt, temperature=0.5 ) if not raw_response: logger.error("Empty response from DeepSeek API") return None, 0 # Collect all unique tags from the week all_tags = set() for _, _, tags in digests: all_tags.update(tags) # Add default weekly tags default_tags = ["灵感收集器", "每周总结"] weekly_tags = default_tags + sorted(all_tags) # Format output content = format_weekly_digest(week_start, week_end, raw_response, weekly_tags) # Save to weekly directory output_dir = get_output_dir("weekly") week_start_str = week_start.strftime("%Y-%m-%d") filename = f"W{week_start_str}_digest.md" filepath = os.path.join(output_dir, filename) with open(filepath, "w", encoding="utf-8") as f: f.write(content) logger.info( "Weekly digest written to %s | %d digests | %d chars analysis | %d tags", filepath, len(digests), len(raw_response), len(weekly_tags) ) # Auto-push to Gitea git_push_weekly(filepath) return filepath, len(digests) def main(): """CLI entry point.""" logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" ) try: secrets = load_secrets() except FileNotFoundError as e: print(e) sys.exit(1) llm_client = DeepSeekClient( api_key=secrets["deepseek_api_key"], model=secrets.get("deepseek_model", "deepseek-chat") ) filepath, count = run(llm_client) if filepath: print(f"Done: {filepath} ({count} digests synthesized)") else: print("No digests found, skipped") sys.exit(0) if __name__ == "__main__": main()