2026-10-06 · 喵窝资源铺
开会开得热闹,纪要发到群里就沉底——真正要跟的是里面散落的 action items:- [ ] 张伟负责:输出引导流程初稿,10月10日前 这种行。人一多,没人记得谁欠什么。这篇给一个 60 行脚本,从 markdown 纪要里把 - [ ] / - [x] 行全部提出来,解析出负责人 + 事项 + 期限,输出一份 CSV 待办表和一份按人分组的 markdown 清单。代码实跑验证,真实输出贴在下面。
输入一份典型周会纪要(含已完成项、四种期限写法、同一人两种署名),跑完:
提取完成:共 7 条 action items,待办 5 条,已完成 2 条 张伟: 2 项待办,期限分布 ['10月10日', '明天'] 李娜: 1 项待办,期限分布 ['周五'] 王强: 1 项待办,期限分布 ['下周三'] 赵敏: 1 项待办,期限分布 ['月底']
markdown 输出按人分组:
# 待办清单(自动提取自会议纪要) ## 张伟(2 项待办) - [ ] 输出新用户引导流程初稿,10月10日前(期限:10月10日) - [ ] 和老刘对齐 API 字段口径,明天前(期限:明天) ## 李娜(1 项待办) - [x] 完成竞品调研报告,已同步群内(期限:未定期限) - [ ] 补齐竞品价格对比表,周五前(期限:周五) ……
7 条全部解析成功,四种期限写法(具体日期/周五/下周三/月底)都抓到了。
# -*- coding: utf-8 -*-
"""从会议纪要文本提取 action items:责任人+事项+期限 → 待办表(csv/markdown)。"""
import sys, re, csv
def parse(text):
todos = []
for line in text.splitlines():
s = line.strip()
if not s:
continue
# action 行:- [ ] 任务 | - [x] 任务
m = re.match(r'-\s*\[( |x|X)\]\s*(.+)', s)
if not m:
continue
done = m.group(1).lower() == 'x'
body = m.group(2)
owner = re.search(r'[@@]?([\u4e00-\u9fa5A-Za-z0-9]{1,6})(?:负责|:|:)', body)
owner = owner.group(1).replace('负责', '') if owner else '未指派'
# 期限:X月X日前 / 本周五前 / 周X / 底前 / 明天
dl = re.search(r'(\d{1,2}月\d{1,2}日|下?周[一二三四五六日天]|明天|后天|月底)前?', body)
due = dl.group(1) if dl else '未定期限'
item = re.sub(r'[@@]?[\u4e00-\u9fa5A-Za-z0-9]{1,6}(?:负责|:|:)\s*', '', body).strip(' 。;;,')
todos.append({'done': done, 'owner': owner, 'item': item, 'due': due})
return todos
def main(src, dst_csv, dst_md):
todos = parse(open(src, encoding='utf-8').read())
with open(dst_csv, 'w', encoding='utf-8-sig', newline='') as f:
w = csv.writer(f)
w.writerow(['状态', '负责人', '待办事项', '期限'])
for t in todos:
w.writerow(['已完成' if t['done'] else '进行中', t['owner'], t['item'], t['due']])
by_owner = {}
for t in todos:
by_owner.setdefault(t['owner'], []).append(t)
with open(dst_md, 'w', encoding='utf-8') as f:
f.write('# 待办清单(自动提取自会议纪要)\n\n')
for owner, ts in sorted(by_owner.items()):
f.write(f'## {owner}({sum(1 for t in ts if not t["done"])} 项待办)\n')
for t in ts:
mark = 'x' if t['done'] else ' '
f.write(f'- [{mark}] {t["item"]}(期限:{t["due"]})\n')
f.write('\n')
open_cnt = sum(1 for t in todos if not t['done'])
print(f'提取完成:共 {len(todos)} 条 action items,待办 {open_cnt} 条,已完成 {len(todos)-open_cnt} 条')
for owner, ts in sorted(by_owner.items()):
pend = sum(1 for t in ts if not t['done'])
print(f' {owner}: {pend} 项待办,期限分布 {sorted(set(t["due"] for t in ts if not t["done"]))}')
if __name__ == '__main__':
main(sys.argv[1], sys.argv[2], sys.argv[3])
[\u4e00-\u9fa5]{1,6},「张伟负责:」会把「张伟负责」整个吞进名字里——第一版输出的人名全是「张伟负责」「李娜负责」。修法:取到后 replace('负责', ''),或者捕获组里用「负责」做前瞻而不是消费它。更稳的写法是 ([\u4e00-\u9fa5A-Za-z0-9]{1,6}?)(?=负责|:|:) 非贪婪+前瞻。- [x] 李娜:完成调研,新任务写 - [ ] 李娜负责:补齐对比表。不归一的话 markdown 里会出现「李娜」和「李娜负责」两个组,每人名下都缺一半活。归一后李娜的完成项和新任务才能进同一组,负责人名下的待办计数才有意义。weekday() 的映射(周一=0),以及「下周三」是「下周一+2」不是「本周三+7」的口径要先和团队对齐。这份脚本先原样保留相对期限,落地跟单场景建议输出两列:原文期限 + 换算后的绝对日期。这份脚本吃的是 - [ ] 负责人:事项,期限 这种规整写法。如果纪要是自由文本(「会上说让王强去压测」),正则会漏——那一步得先靠 LLM 把纪要转成规整的 action item 列表,再做本脚本这种确定性解析。顺序别反:LLM 负责格式转换,代码负责解析和汇总,让 LLM 直接输出 CSV 容易在转义和编码上翻车。
相关文章:手机号列清洗去重 · 邮箱列清洗去重 · Word 批量替换
📖 相邻文章:聊天记录批量提取「待办+承诺日期」 · 两份流水账自动对账 · | 期限还跟日程表打架?看续篇 待办期限×日程表核对 · 博客目录