一、项目背景

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)...
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐