309 lines
10 KiB
Python
309 lines
10 KiB
Python
#!/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())
|