Files
openclaw-config/workspace-resume/weekly-reports/summarize_reports.py
T

309 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
周报汇总工具 - 从归档的周报中提取指定时间范围的工作内容
用于月度、年度考核时快速生成工作总结
用法:
python3 summarize_reports.py --week 2026-W31
python3 summarize_reports.py --month 2026-07
python3 summarize_reports.py --year 2026
python3 summarize_reports.py --range 2026-07-01 2026-09-30
"""
import os
import re
import glob
import argparse
from datetime import datetime, timedelta
from pathlib import Path
# 周报归档目录
REPORTS_DIR = Path(__file__).parent
def parse_week_number(week_str):
"""解析周数格式,如 2026-W31"""
match = re.match(r'(\d{4})-W(\d{2})', week_str)
if not match:
return None
year, week = int(match.group(1)), int(match.group(2))
# 计算该周的大致日期范围(简化处理,取第 1 周的周一)
jan_first = datetime(year, 1, 1)
week_start = jan_first + timedelta(weeks=week - 1)
# 调整到最近的周一
week_start = week_start - timedelta(days=week_start.weekday())
week_end = week_start + timedelta(days=4) # 周五
return week_start, week_end
def parse_month_range(month_str):
"""解析月份格式,如 2026-07"""
match = re.match(r'(\d{4})-(\d{2})', month_str)
if not match:
return None
year, month = int(match.group(1)), int(match.group(2))
month_start = datetime(year, month, 1)
if month == 12:
next_month = datetime(year + 1, 1, 1)
else:
next_month = datetime(year, month + 1, 1)
month_end = next_month - timedelta(days=1)
return month_start, month_end
def parse_year_range(year_str):
"""解析年份格式,如 2026"""
match = re.match(r'(\d{4})', year_str)
if not match:
return None
year = int(match.group(1))
year_start = datetime(year, 1, 1)
year_end = datetime(year, 12, 31)
return year_start, year_end
def parse_date_range(start_str, end_str):
"""解析日期范围,如 2026-07-01 2026-09-30"""
try:
start = datetime.strptime(start_str, '%Y-%m-%d')
end = datetime.strptime(end_str, '%Y-%m-%d')
return start, end
except ValueError:
return None
def find_reports_in_range(start_date, end_date):
"""在指定日期范围内查找所有周报文件"""
reports = []
# 遍历所有年份目录
for year_dir in REPORTS_DIR.iterdir():
if not year_dir.is_dir() or year_dir.name.startswith('.'):
continue
# 查找该年份下的所有周报文件
for report_file in glob.glob(str(year_dir / '*.md')):
if report_file.endswith('README.md'):
continue
try:
with open(report_file, 'r', encoding='utf-8') as f:
content = f.read()
# 尝试从文件内容中提取日期范围
date_match = re.search(r'(\d{4}-\d{2}-\d{2})\s*~\s*(\d{4}-\d{2}-\d{2})', content)
if date_match:
file_start = datetime.strptime(date_match.group(1), '%Y-%m-%d')
file_end = datetime.strptime(date_match.group(2), '%Y-%m-%d')
# 检查是否在查询范围内
if file_start <= end_date and file_end >= start_date:
reports.append({
'file': report_file,
'start': file_start,
'end': file_end,
'content': content
})
except Exception as e:
print(f"读取文件失败 {report_file}: {e}")
continue
# 按日期排序
reports.sort(key=lambda x: x['start'])
return reports
def extract_work_content(report_content):
"""从周报内容中提取工作内容"""
sections = {
'project': '',
'task': '',
'daily_work': {},
'problems': ''
}
lines = report_content.split('\n')
current_section = None
current_day = None
for line in lines:
# 项目名称
if line.startswith('项目名称:'):
sections['project'] = line.replace('项目名称:', '').strip()
# 主要任务
elif line.startswith('主要任务:'):
sections['task'] = line.replace('主要任务:', '').strip()
# 本周工作内容
elif '本周工作内容' in line:
current_section = 'daily_work'
# 存在问题
elif line.startswith('存在问题'):
current_section = 'problems'
current_day = None
# 日期行(周一、周二等)
elif current_section == 'daily_work' and re.match(r'^周 [一二三四五六日]\d{4}-\d{2}-\d{2}', line):
match = re.match(r'^周 [一二三四五六日](\d{4}-\d{2}-\d{2})', line)
current_day = match.group(1)
sections['daily_work'][current_day] = []
# 工作内容项
elif current_section == 'daily_work' and line.strip().startswith('- '):
if current_day:
sections['daily_work'][current_day].append(line.strip())
# 问题内容
elif current_section == 'problems' and line.strip() and not line.startswith('存在问题'):
sections['problems'] += line + '\n'
return sections
def generate_summary(reports, summary_type, date_range):
"""生成汇总报告"""
output = []
# 汇总头部
start_str = date_range[0].strftime('%Y-%m-%d')
end_str = date_range[1].strftime('%Y-%m-%d')
output.append(f"# 工作总结报告")
output.append(f"\n**时间范围:** {start_str} ~ {end_str}")
output.append(f"**生成时间:** {datetime.now().strftime('%Y-%m-%d %H:%M')}")
output.append(f"**包含周报数:** {len(reports)} 周")
output.append("")
# 按项目分组
projects = {}
all_problems = []
all_daily_work = {}
for report in reports:
sections = extract_work_content(report['content'])
project = sections['project'] or '未指定项目'
if project not in projects:
projects[project] = {
'tasks': [],
'weeks': []
}
if sections['task']:
projects[project]['tasks'].append({
'week': f"{report['start'].strftime('%Y-W%W')}",
'task': sections['task']
})
projects[project]['weeks'].append(report['start'].strftime('%Y-W%W'))
# 汇总问题
if sections['problems'].strip():
all_problems.append({
'week': report['start'].strftime('%Y-W%W'),
'problems': sections['problems'].strip()
})
# 汇总每日工作
for day, work_items in sections['daily_work'].items():
if day not in all_daily_work:
all_daily_work[day] = []
all_daily_work[day].extend(work_items)
# 输出项目汇总
output.append("## 📊 项目汇总\n")
for project, data in projects.items():
output.append(f"### {project}")
weeks = sorted(set(data['weeks']))
output.append(f"**参与周次:** {', '.join(weeks)}")
output.append(f"**主要任务:**")
for item in data['tasks']:
output.append(f"- {item['week']}: {item['task']}")
output.append("")
# 输出存在问题汇总
if all_problems:
output.append("## ⚠️ 存在问题汇总\n")
for item in all_problems:
output.append(f"### {item['week']}")
output.append(item['problems'])
output.append("")
# 输出所有周报原文(可选)
output.append("\n---\n")
output.append("## 📋 原始周报记录\n")
for report in reports:
week_str = report['start'].strftime('%Y-W%W')
output.append(f"\n### {week_str} ({report['start'].strftime('%Y-%m-%d')} ~ {report['end'].strftime('%Y-%m-%d')})")
output.append(f"文件:{report['file']}\n")
# 提取周报的前 20 行作为摘要
preview_lines = report['content'].split('\n')[:20]
output.append('\n'.join(preview_lines))
output.append("\n... (更多内容见原文件)\n")
return '\n'.join(output)
def main():
parser = argparse.ArgumentParser(description='周报汇总工具')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--week', type=str, help='指定周数,如 2026-W31')
group.add_argument('--month', type=str, help='指定月份,如 2026-07')
group.add_argument('--year', type=str, help='指定年份,如 2026')
group.add_argument('--range', type=str, nargs=2, metavar=('START', 'END'), help='指定日期范围,如 2026-07-01 2026-09-30')
args = parser.parse_args()
# 解析日期范围
date_range = None
summary_type = None
if args.week:
date_range = parse_week_number(args.week)
summary_type = 'week'
elif args.month:
date_range = parse_month_range(args.month)
summary_type = 'month'
elif args.year:
date_range = parse_year_range(args.year)
summary_type = 'year'
elif args.range:
date_range = parse_date_range(args.range[0], args.range[1])
summary_type = 'range'
if not date_range:
print("❌ 日期格式错误")
return 1
print(f"🔍 查找范围:{date_range[0].strftime('%Y-%m-%d')} ~ {date_range[1].strftime('%Y-%m-%d')}")
# 查找周报
reports = find_reports_in_range(date_range[0], date_range[1])
if not reports:
print("❌ 未找到符合条件的周报")
return 1
print(f"✅ 找到 {len(reports)} 份周报")
# 生成汇总
summary = generate_summary(reports, summary_type, date_range)
# 输出到文件
output_file = f"summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
with open(output_file, 'w', encoding='utf-8') as f:
f.write(summary)
print(f"📄 汇总报告已生成:{output_file}")
print("\n" + "="*50)
print(summary[:2000]) # 预览前 2000 字符
if len(summary) > 2000:
print(f"\n... (共 {len(summary)} 字符,完整内容见 {output_file})")
return 0
if __name__ == '__main__':
exit(main())