feat: 阅读周报 + 工作日志(日报/周报) 报告生成脚本
This commit is contained in:
155
analyzers/daily_work_report.py
Normal file
155
analyzers/daily_work_report.py
Normal file
@ -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")
|
||||
328
analyzers/weekly_reading_report.py
Normal file
328
analyzers/weekly_reading_report.py
Normal file
@ -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)
|
||||
209
analyzers/weekly_work_report.py
Normal file
209
analyzers/weekly_work_report.py
Normal file
@ -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)
|
||||
29
scripts/run_daily_work_report.sh
Executable file
29
scripts/run_daily_work_report.sh
Executable file
@ -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"
|
||||
43
scripts/run_weekly_reading_report.sh
Executable file
43
scripts/run_weekly_reading_report.sh
Executable file
@ -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"
|
||||
29
scripts/run_weekly_work_report.sh
Executable file
29
scripts/run_weekly_work_report.sh
Executable file
@ -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"
|
||||
Reference in New Issue
Block a user