【Python实战】AI自动生成工作周报:Git日志+日历事件+邮件摘要→专业周报
·
一、项目背景
1.1 痛点分析
写周报是职场必修课,但传统方式效率极低:
| 环节 | 手工方式 | 时间 |
|---|---|---|
| 回忆本周工作 | 翻记录 | 20分钟 |
| 整理工作内容 | 分类归纳 | 15分钟 |
| 撰写周报正文 | 手动编写 | 20分钟 |
| 写下周计划 | 思考规划 | 10分钟 |
| 润色格式 | 调整排版 | 10分钟 |
| 总计 | - | 75分钟 |
一年52周,每周75分钟,一年花65小时写周报。
1.2 技术需求
核心需求:
- 自动采集Git提交记录
- 自动读取日历事件
- 自动提取邮件摘要
- AI智能归纳总结
- 自动生成标准周报格式
- 支持多种输出(Markdown/Word/HTML)
二、技术架构
Git日志 ──┐
日历事件 ──┼──→ 数据聚合 → AI归纳 → 格式输出
邮件记录 ──┘
↑ ↑ ↑ ↑
gitpython icalendar DashScope Jinja2
imaplib Qwen3 python-docx
技术栈:
- **gitpython**:读取Git提交记录
- **icalendar**:解析日历事件
- **imaplib**:读取邮件
- **DashScope/Qwen3**:AI归纳总结
- **Jinja2**:模板渲染
- **python-docx**:Word输出
---
## 三、环境准备
### 3.1 安装依赖
```bash
pip install gitpython icalendar dashscope jinja2 python-docx
3.2 配置
# config.py
DASHSCOPE_API_KEY = "your-api-key-here"
WEEKLY_REPORT_CONFIG = {
'git_repos': [
'/path/to/project-a',
'/path/to/project-b'
],
'author_email': 'your-email@company.com',
'calendar_file': 'calendar.ics',
'imap_server': 'imap.company.com',
'email_account': 'your-email@company.com',
'email_password': 'your-app-password',
'output_format': 'markdown' # markdown / word / html
}
四、核心模块实现
4.1 Git日志采集模块
自动读取本周所有Git提交记录:
from git import Repo
from datetime import datetime, timedelta
class GitCollector:
def __init__(self, repo_paths, author_email):
self.repo_paths = repo_paths
self.author_email = author_email
def collect(self, days=7):
"""采集最近N天的Git提交"""
since = datetime.now() - timedelta(days=days)
all_commits = []
for repo_path in self.repo_paths:
try:
repo = Repo(repo_path)
repo_name = repo_path.split('/')[-1]
for commit in repo.iter_commits(since=since.isoformat()):
if commit.author.email == self.author_email:
all_commits.append({
'repo': repo_name,
'message': commit.message.strip(),
'date': datetime.fromtimestamp(commit.committed_date).strftime('%Y-%m-%d'),
'time': datetime.fromtimestamp(commit.committed_date).strftime('%H:%M'),
'files_changed': commit.stats.total['files'],
'insertions': commit.stats.total['insertions'],
'deletions': commit.stats.total['deletions']
})
except Exception as e:
print(f"读取仓库失败 {repo_path}: {e}")
# 按日期排序
all_commits.sort(key=lambda x: x['date'], reverse=True)
return all_commits
def summarize(self, commits):
"""按项目和日期汇总"""
summary = {}
for commit in commits:
repo = commit['repo']
if repo not in summary:
summary[repo] = {
'commits': 0,
'files_changed': 0,
'messages': []
}
summary[repo]['commits'] += 1
summary[repo]['files_changed'] += commit['files_changed']
summary[repo]['messages'].append(commit['message'])
return summary
4.2 日历事件采集模块
读取本周的会议和事件:
from icalendar import Calendar
from datetime import datetime, timedelta
class CalendarCollector:
def collect_from_file(self, ics_path, days=7):
"""从ICS文件读取日历事件"""
since = datetime.now() - timedelta(days=days)
events = []
with open(ics_path, 'rb') as f:
cal = Calendar.from_ical(f.read())
for component in cal.walk():
if component.name == 'VEVENT':
start = component.get('dtstart')
if start:
start_dt = start.dt
if hasattr(start_dt, 'date'):
event_date = start_dt.date()
else:
event_date = start_dt
if event_date >= since.date():
events.append({
'title': str(component.get('summary', '无标题')),
'date': str(event_date),
'start_time': str(start_dt),
'duration': self._get_duration(component),
'location': str(component.get('location', '')),
'description': str(component.get('description', ''))[:200]
})
events.sort(key=lambda x: x['date'])
return events
def _get_duration(self, component):
"""计算事件时长"""
start = component.get('dtstart')
end = component.get('dtend')
if start and end and hasattr(start.dt, 'hour'):
delta = end.dt - start.dt
hours = delta.seconds // 3600
minutes = (delta.seconds % 3600) // 60
return f"{hours}h{minutes}m"
return "全天"
def categorize(self, events):
"""事件分类"""
categories = {
'会议': [],
'评审': [],
'培训': [],
'其他': []
}
for event in events:
title = event['title'].lower()
if any(kw in title for kw in ['会议', 'meeting', '周会', '日会', '站会']):
categories['会议'].append(event)
elif any(kw in title for kw in ['评审', 'review', '代码审查']):
categories['评审'].append(event)
elif any(kw in title for kw in ['培训', 'training', '分享']):
categories['培训'].append(event)
else:
categories['其他'].append(event)
return categories
4.3 邮件摘要采集模块
读取本周重要邮件:
import imaplib
import email
from email.header import decode_header
from datetime import datetime, timedelta
class EmailCollector:
def __init__(self, server, account, password):
self.server = server
self.account = account
self.password = password
def collect(self, days=7, folder='INBOX'):
"""采集最近N天的邮件"""
since = (datetime.now() - timedelta(days=days)).strftime('%d-%b-%Y')
emails = []
try:
mail = imaplib.IMAP4_SSL(self.server)
mail.login(self.account, self.password)
mail.select(folder)
# 搜索最近的邮件
_, message_ids = mail.search(None, f'(SINCE {since})')
for msg_id in message_ids[0].split():
_, msg_data = mail.fetch(msg_id, '(RFC822)')
msg = email.message_from_bytes(msg_data[0][1])
subject = self._decode_header(msg['Subject'])
sender = self._decode_header(msg['From'])
date = msg['Date']
# 只采集发送的邮件和重要邮件
emails.append({
'subject': subject,
'from': sender,
'date': date,
'is_sent': self.account in str(msg.get('From', '')),
'snippet': self._get_body(msg)[:200]
})
mail.logout()
except Exception as e:
print(f"邮件采集失败: {e}")
return emails
def _decode_header(self, header):
if header is None:
return ''
decoded, encoding = decode_header(header)[0]
if isinstance(decoded, bytes):
return decoded.decode(encoding or 'utf-8', errors='ignore')
return str(decoded)
def _get_body(self, msg):
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == 'text/plain':
return part.get_payload(decode=True).decode('utf-8', errors='ignore')
else:
return msg.get_payload(decode=True).decode('utf-8', errors='ignore')
return ''
4.4 AI周报生成模块
将所有数据交给AI,生成专业周报:
import dashscope
class ReportGenerator:
def __init__(self, api_key):
dashscope.api_key = api_key
def generate(self, git_data, calendar_data, email_data, user_name="张三"):
"""AI生成周报"""
# 组装上下文
context = self._build_context(git_data, calendar_data, email_data)
prompt = f"""
你是一位职场写作专家。请根据以下工作记录,生成一份专业的工作周报。
员工姓名:{user_name}
周报周期:{self._get_week_range()}
{context}
请按以下格式输出:
# 工作周报
**姓名**:{user_name}
**周期**:{self._get_week_range()}
## 一、本周工作完成情况
### 1. [项目/类别名称]
- 具体工作内容(量化成果)
- 具体工作内容(量化成果)
### 2. [项目/类别名称]
- 具体工作内容
## 二、重要会议与协作
- 会议名称 + 关键决策/产出
## 三、问题与风险
- 遇到的问题 + 解决方案/状态
## 四、下周工作计划
- 计划1(预计完成时间)
- 计划2(预计完成时间)
## 五、需要的支持
- 需要协调的资源/支持
要求:
- 语言简洁专业
- 成果要量化(提交了X次代码,修改了X个文件)
- 不编造不存在的工作内容
- 下周计划要基于本周进展合理推断
- 问题与风险要具体,不要写套话
"""
response = dashscope.Generation.call(
model="qwen3-72b",
messages=[{"role": "user", "content": prompt}],
result_format="message"
)
return response.output.choices[0].message.content
def _build_context(self, git_data, calendar_data, email_data):
"""构建AI上下文"""
context = ""
# Git记录
context += "【代码提交记录】\n"
for repo, data in git_data.items():
context += f"\n项目:{repo}(共{data['commits']}次提交,{data['files_changed']}个文件变更)\n"
for msg in data['messages'][:20]:
context += f" - {msg}\n"
# 日历事件
context += "\n【本周会议/事件】\n"
for event in calendar_data:
context += f" - {event['date']} {event['title']}({event['duration']})\n"
# 邮件记录
context += "\n【重要邮件】\n"
for mail in email_data[:10]:
direction = "发送" if mail['is_sent'] else "收到"
context += f" - {direction}:{mail['subject']}\n"
return context
def _get_week_range(self):
from datetime import datetime, timedelta
today = datetime.now()
monday = today - timedelta(days=today.weekday())
friday = monday + timedelta(days=4)
return f"{monday.strftime('%Y.%m.%d')} - {friday.strftime('%Y.%m.%d')}"
4.5 多格式输出模块
支持Markdown、Word、HTML三种输出:
from jinja2 import Template
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
class ReportExporter:
def export_markdown(self, content, output_path):
"""输出Markdown"""
with open(output_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"✅ Markdown周报已生成:{output_path}")
def export_word(self, content, output_path):
"""输出Word文档"""
doc = Document()
# 设置默认字体
style = doc.styles['Normal']
style.font.name = '微软雅黑'
style.font.size = Pt(11)
for line in content.split('\n'):
line = line.strip()
if line.startswith('# '):
p = doc.add_heading(line[2:], level=1)
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
elif line.startswith('## '):
doc.add_heading(line[3:], level=2)
elif line.startswi
...(truncated)...
更多推荐



所有评论(0)