🚀

用 Python + Ollama 搭一个本地周报生成器:从任务记录自动写周报

每天记几行任务,周末让 AI 帮你写成周报。全程本地跑,不传数据,不花钱,格式还能自定义。

👁️ - 次阅读

你有没有这种经历:周五下午快下班了,领导突然说"周报发一下"。

你打开笔记软件,翻了好几天的记录,发现有的写了、有的没写、有的只记了关键词。硬着头皮凑了三百字发出去,心里清楚这周报写得很水。

下周还这样。

今天做一个本地周报生成器。每天随手记几行任务,周末跑一个脚本,AI 自动帮你写成结构清晰的周报。数据完全在本地,不用上传任何内容。

项目背景

周报是打工人的常规动作,但写好一份不容易:

  • 每天的事零零碎碎,汇总时理不清重点
  • 写得太细像流水账,写得太略又像没干活
  • 用在线 AI 写周报,工作内容传到了别人服务器上
  • 每周重复写同样的结构,浪费时间

核心痛点就一个:记录太散,汇总太累。

这个项目的思路是:把"记录"和"写周报"两件事分开。每天花 30 秒记几行,周末让 AI 帮你汇总、提炼、排版。

技术选型

组件选择理由
任务记录JSON 文件结构清晰,Python 直接读
大模型Ollama + qwen2.5:7b本地运行,中文周报质量好
任务解析Python 内置 json 模块零依赖
周报输出Markdown可读性好,方便转其他格式

不需要数据库,不需要 Web 服务。数据就是一个 JSON 文件,脚本跑在本地,AI 也跑在本地。

Ollama 安装:

curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen2.5:7b

实现步骤

第一步:项目结构

mkdir -p weekly_report
cd weekly_report
touch daily_log.json generate_report.py

daily_log.json 用来存你每天的任务记录:

[
  {
    "date": "2026-08-18",
    "items": [
      {"task": "修复用户登录页面的超时问题", "type": "fix", "hours": 2},
      {"task": "完成支付模块单元测试", "type": "feat", "hours": 3},
      {"task": "参加产品评审会", "type": "meeting", "hours": 1}
    ]
  },
  {
    "date": "2026-08-19",
    "items": [
      {"task": "优化搜索接口性能,响应时间从 800ms 降到 200ms", "type": "perf", "hours": 3},
      {"task": "写接口文档", "type": "docs", "hours": 1.5},
      {"task": "代码 review,修复 3 个 bug", "type": "fix", "hours": 1.5}
    ]
  }
]

type 字段用来分类任务类型,可选:feat(新功能)、fix(修复)、perf(性能)、docs(文档)、meeting(会议)、other(其他)。

第二步:写周报生成脚本

创建 generate_report.py:

#!/usr/bin/env python3
"""本地周报生成器 — 从每日任务记录自动生成周报"""

import json
import sys
import ollama
from pathlib import Path
from datetime import datetime, timedelta


# ============ 配置区 ============
LOG_FILE = Path.home() / "weekly_report" / "daily_log.json"
MODEL = "qwen2.5:7b"

# 任务类型中文名映射
TYPE_NAMES = {
    "feat": "新功能",
    "fix": "Bug修复",
    "perf": "性能优化",
    "docs": "文档",
    "meeting": "会议",
    "other": "其他",
}

# 周报生成 prompt
REPORT_PROMPT = """你是一个高效的工作助手。请根据用户提供的本周任务记录,生成一份结构清晰的周报。

## 周报要求

1. **本周工作总结**(200字以内):概括本周主要工作内容和成果
2. **任务分类汇总**:按类型分组展示,每组列出具体任务
3. **本周亮点**:挑选 2-3 个最有价值的工作成果,简要说明
4. **遇到的问题**:如有阻塞或困难,列出并说明当前状态
5. **下周计划**:根据本周进度,推断下周可能的工作重点(3-5条)
6. **工时统计**:各类任务总工时

## 输出格式

使用 Markdown,结构如下:

周报 — YYYY-MM DD 至 YYYY-MM DD

📋 本周总结

(一段话概括)

📊 任务明细

新功能(feat)

  • 任务1
  • 任务2

Bug修复(fix)

  • 任务1

(其他类型同理)

⭐ 本周亮点

  1. …
  2. …
  3. …

⚠️ 遇到的问题

  • 问题描述(状态:进行中/已解决/阻塞)

📅 下周计划

  1. …
  2. …

⏱️ 工时统计

  • 新功能:X小时
  • Bug修复:X小时
  • …
  • 总计:X小时

只输出周报内容,不要解释其他内容。用中文写。

## 本周任务记录

{weekly_data}
"""


def load_daily_log(filepath: Path) -> list:
    """读取每日任务日志"""
    if not filepath.exists():
        print(f"❌ 找不到日志文件: {filepath}")
        print("请先用日记功能记录本周任务")
        sys.exit(1)
    with open(filepath, "r", encoding="utf-8") as f:
        return json.load(f)


def get_current_week_range():
    """获取本周一到周日的日期范围"""
    today = datetime.now()
    monday = today - timedelta(days=today.weekday())
    sunday = monday + timedelta(days=6)
    return monday, sunday


def filter_week_data(data: list, week_start: datetime, week_end: datetime) -> list:
    """筛选本周数据"""
    week_data = []
    for day in data:
        try:
            day_date = datetime.strptime(day["date"], "%Y-%m-%d")
            if week_start <= day_date <= week_end:
                week_data.append(day)
        except (ValueError, KeyError):
            continue
    return week_data


def format_weekly_data(week_data: list) -> str:
    """将本周数据格式化为 prompt 可读的文本"""
    if not week_data:
        return "本周暂无任务记录,请开始记录你的每日工作。"

    lines = []
    total_hours = 0
    for day in week_data:
        date_str = day["date"]
        items = day.get("items", [])
        day_total = sum(item.get("hours", 0) for item in items)
        total_hours += day_total
        lines.append(f"\n📅 {date_str}({day_total}h)")
        for item in items:
            type_name = TYPE_NAMES.get(item.get("type", "other"), "其他")
            lines.append(f"  - [{type_name}] {item['task']}({item.get('hours', 0)}h)")
    lines.append(f"\n💰 本周总工时: {total_hours}h")
    return "\n".join(lines)


def generate_report(weekly_data_str: str) -> str:
    """调用 Ollama 生成周报"""
    prompt = REPORT_PROMPT.format(weekly_data=weekly_data_str)

    response = ollama.chat(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
        options={"temperature": 0.3, "num_predict": 1500}
    )
    return response["message"]["content"]


def main():
    # 获取本周日期范围
    week_start, week_end = get_current_week_range()
    week_label = f"{week_start.strftime('%m/%d')}-{week_end.strftime('%m/%d')}"

    print(f"📅 周报周期: {week_label}")

    # 加载数据
    data = load_daily_log(LOG_FILE)
    week_data = filter_week_data(data, week_start, week_end)

    if not week_data:
        print("⚠️ 本周暂无任务记录,请先生成上周周报或补充数据")
        print("用法: python generate_report.py --last   # 生成上周周报")
        print("用法: python generate_report.py --help    # 查看帮助")
        sys.exit(0)

    # 格式化数据
    weekly_data_str = format_weekly_data(week_data)
    print(f"✅ 已加载 {len(week_data)} 天的任务记录")
    print("🤖 正在生成周报...")

    # 生成周报
    try:
        report = generate_report(weekly_data_str)
    except Exception as e:
        print(f"❌ 生成失败: {e}")
        print("请确认 Ollama 正在运行: ollama list")
        sys.exit(1)

    # 保存周报
    today_str = datetime.now().strftime("%Y-%m-%d")
    output_file = Path.home() / "weekly_report" / f"report_{today_str}.md"
    output_file.parent.mkdir(parents=True, exist_ok=True)

    # 在周报开头加上标题行
    header = f"# 周报 — {week_label}\n\n生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n---\n\n"
    full_report = header + report

    with open(output_file, "w", encoding="utf-8") as f:
        f.write(full_report)

    print(f"✅ 周报已保存到: {output_file}")
    print("\n" + "=" * 50)
    print("📋 周报预览:")
    print("=" * 50)
    print(full_report)


if __name__ == "__main__":
    # 支持 --last 参数生成上周周报
    if len(sys.argv) > 1 and sys.argv[1] == "--last":
        today = datetime.now()
        last_monday = today - timedelta(days=today.weekday() + 7)
        last_sunday = last_monday + timedelta(days=6)

        print(f"📅 上周周报: {last_monday.strftime('%m/%d')}-{last_sunday.strftime('%m/%d')}")

        data = load_daily_log(LOG_FILE)
        week_data = filter_week_data(data, last_monday, last_sunday)

        if not week_data:
            print("⚠️ 上周也无任务记录")
            sys.exit(0)

        weekly_data_str = format_weekly_data(week_data)
        print(f"✅ 已加载 {len(week_data)} 天的任务记录")
        print("🤖 正在生成上周周报...")

        try:
            report = generate_report(weekly_data_str)
        except Exception as e:
            print(f"❌ 生成失败: {e}")
            sys.exit(1)

        today_str = datetime.now().strftime("%Y-%m-%d")
        output_file = Path.home() / "weekly_report" / f"report_last_week_{today_str}.md"
        header = f"# 周报 — {last_monday.strftime('%m/%d')}-{last_sunday.strftime('%m/%d')}\n\n生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n---\n\n"
        full_report = header + report

        with open(output_file, "w", encoding="utf-8") as f:
            f.write(full_report)
        print(f"✅ 上周周报已保存到: {output_file}")
        print("\n" + full_report)
    else:
        main()

第三步:写每日任务记录脚本

为了方便每天记录,再写一个轻量级的日记脚本:

#!/usr/bin/env python3
"""每日任务记录工具"""

import json
import sys
from pathlib import Path
from datetime import datetime

LOG_FILE = Path.home() / "weekly_report" / "daily_log.json"


def load_log():
    if LOG_FILE.exists():
        with open(LOG_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    return []


def save_log(data):
    LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
    with open(LOG_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)


def add_task(date_str, task, task_type="other", hours=1.0):
    """添加一条任务记录"""
    data = load_log()

    # 查找或创建当天的记录
    day_record = None
    for record in data:
        if record.get("date") == date_str:
            day_record = record
            break

    if day_record is None:
        day_record = {"date": date_str, "items": []}
        data.append(day_record)
    else:
        # 更新已存在的记录
        data = [r for r in data if r.get("date") != date_str]
        data.append(day_record)

    day_record["items"].append({
        "task": task,
        "type": task_type,
        "hours": hours
    })

    save_log(data)
    type_name = {"feat": "新功能", "fix": "Bug修复", "perf": "性能优化",
                 "docs": "文档", "meeting": "会议", "other": "其他"}.get(task_type, "其他")
    print(f"✅ 已记录 [{type_name}] {task}({hours}h)— {date_str}")


def list_tasks(date_str=None):
    """列出任务记录"""
    data = load_log()
    if not data:
        print("📭 暂无任务记录")
        return

    if date_str:
        # 只看指定日期
        for record in data:
            if record.get("date") == date_str:
                print(f"\n📅 {record['date']}")
                for item in record.get("items", []):
                    type_name = TYPE_NAMES.get(item.get("type", "other"), "其他")
                    print(f"  - [{type_name}] {item['task']}({item.get('hours', 0)}h)")
                break
        return

    # 显示所有记录(最近7天)
    print("📋 最近的任务记录:\n")
    for record in sorted(data, key=lambda x: x.get("date", ""), reverse=True)[:7]:
        date_str = record.get("date", "未知")
        items = record.get("items", [])
        total = sum(i.get("hours", 0) for i in items)
        print(f"📅 {date_str}({total}h)")
        for item in items:
            type_name = TYPE_NAMES.get(item.get("type", "other"), "其他")
            print(f"  - [{type_name}] {item['task']}")
        print()


# 类型映射(和生成器共用)
TYPE_NAMES = {
    "feat": "新功能", "fix": "Bug修复", "perf": "性能优化",
    "docs": "文档", "meeting": "会议", "other": "其他"
}


def main():
    if len(sys.argv) < 2:
        print("用法:")
        print('  python daily_log.py add "2026-08-22" "完成登录功能" --type feat --hours 3')
        print('  python daily_log.py add "今天" "修复bug" --type fix')
        print('  python daily_log.py list              # 列出最近7天记录')
        print('  python daily_log.py list 2026-08-22   # 查看指定日期')
        print()
        print("类型选项: feat(新功能) fix(Bug修复) perf(性能) docs(文档) meeting(会议) other(其他)")
        sys.exit(0)

    cmd = sys.argv[1]

    if cmd == "add":
        # 解析参数
        date_str = sys.argv[2] if len(sys.argv) > 2 else datetime.now().strftime("%Y-%m-%d")
        # 处理"今天"
        if date_str == "今天":
            date_str = datetime.now().strftime("%Y-%m-%d")

        # 找任务内容("--task" 后面的内容)
        task = ""
        task_type = "other"
        hours = 1.0

        i = 3
        args = sys.argv[3:]
        while i < len(sys.argv):
            if sys.argv[i] == "--task" and i + 1 < len(sys.argv):
                task = sys.argv[i + 1]
                i += 2
            elif sys.argv[i] == "--type" and i + 1 < len(sys.argv):
                task_type = sys.argv[i + 1]
                i += 2
            elif sys.argv[i] == "--hours" and i + 1 < len(sys.argv):
                try:
                    hours = float(sys.argv[i + 1])
                except ValueError:
                    hours = 1.0
                i += 2
            else:
                # 没有 --task 标记时,把剩余内容当任务
                if not task:
                    task = " ".join(sys.argv[i:])
                i += 1

        if not task:
            print("❌ 请提供任务内容")
            sys.exit(1)

        add_task(date_str, task, task_type, hours)

    elif cmd == "list":
        date = sys.argv[2] if len(sys.argv) > 2 else None
        list_tasks(date)

    else:
        print(f"❌ 未知命令: {cmd}")
        print("运行 python daily_log.py 查看帮助")


if __name__ == "__main__":
    main()

第四步:配置 shell 别名

把这两个脚本用起来,加个别名最方便:

# 添加到 ~/.bashrc 或 ~/.zshrc
alias wd='python3 ~/weekly_report/daily_log.py'
alias wg='python3 ~/weekly_report/generate_report.py'

然后生效:

source ~/.bashrc

第五步:日常使用流程

每天下班前,花 30 秒记任务:

# 记今天的任务
wd add "今天" "修复登录超时bug" --type fix --hours 2
wd add "今天" "完成支付模块测试" --type feat --hours 3
wd add "今天" "写接口文档" --type docs --hours 1.5

# 查看所有记录
wd list

周五下午,一键生成周报:

wg

想看上周的周报(如果这周没记):

wg --last

运行效果

# 记录任务
$ wd add "今天" "优化搜索接口性能,响应时间从800ms降到200ms" --type perf --hours 3
✅ 已记录 [性能优化] 优化搜索接口性能,响应时间从800ms降到200ms(3.0h)— 2026-08-22

$ wd add "今天" "完成支付模块单元测试" --type feat --hours 2
✅ 已记录 [新功能] 完成支付模块单元测试(2.0h)— 2026-08-22

$ wd add "今天" "参加产品评审会" --type meeting --hours 1
✅ 已记录 [会议] 参加产品评审会(1.0h)— 2026-08-22

# 生成周报
$ wg
📅 周报周期: 08-18-08-24
✅ 已加载 3 天的任务记录
🤖 正在生成周报...
✅ 周报已保存到: /home/user/weekly_report/report_2026-08-22.md

📋 周报预览:
==================================================
# 周报 — 08-18-08-24

生成时间: 2026-08-22 17:30

---

# 周报 — 08-18-08-24

## 📋 本周总结
本周主要围绕支付模块开发和搜索性能优化展开。完成了支付模块的核心功能开发和单元测试,同时对搜索接口进行了深度优化,将响应时间从800ms降低到200ms,提升了约75%的性能。此外参与了产品评审会议,推进了产品方向的确认。

## 📊 任务明细
### 新功能(feat)
- 完成支付模块单元测试

### Bug修复(fix)
- 修复用户登录页面的超时问题

### 性能优化(perf)
- 优化搜索接口性能,响应时间从800ms降到200ms

### 会议(meeting)
- 参加产品评审会

### 文档(docs)
- 写接口文档

## ⭐ 本周亮点
1. 搜索接口性能优化显著,响应时间降低75%,用户体验大幅提升
2. 支付模块单元测试全覆盖,为后续功能开发打下坚实基础

## ⚠️ 遇到的问题
- 登录超时问题已修复,需观察线上稳定性(状态:进行中)

## 📅 下周计划
1. 推进支付模块上线部署
2. 继续优化其他核心接口性能
3. 完善搜索功能的用户体验
4. 跟进产品评审会确定的需求

## ⏱️ 工时统计
- 新功能:2小时
- Bug修复:2小时
- 性能优化:3小时
- 文档:1.5小时
- 会议:1小时
- 总计:9.5小时

优化方向

脚本已经能用了,想继续打磨可以加这些:

  1. 自动备份:把 daily_log.json 同步到 Git,历史随时可查
  2. 微信推送:周报生成后自动推送到微信/钉钉,不用手动发
  3. 工时统计图:用 matplotlib 生成周工时分布图,汇报时更有说服力
  4. 智能分类:不用手动标记类型,AI 自动判断任务是 feat/fix/perf 哪个
  5. 月度汇总:把每周周报自动汇总成月报,季度总结不再头疼
  6. 语音记录:用 Whisper 把语音转文字,说完任务直接记录,零打字

最简单的一步就是今天开始记。哪怕每天只写一行,周末的周报也不会再是临时抱佛脚了。

你的周报一般写多久?如果能把这时间省下来早点下班,值不值?

这篇文章对你有帮助吗?