登记表格填写助手 (Registration Form Assistant)
根据简历自动填写登记表格/应聘登记表。核心原则:不删字段,缺失留白,禁止编造。
适用对象:
- 普通用户:上传简历 + 表单模板,AI 自动完成解析、填写、验证
- 技术用户:可基于 fill_form_core.py 自定义解析规则或批量处理
快速开始
1. 上传简历(PDF / DOCX / DOC)
2. 上传 DOCX 表单模板
3. 说"帮我填表"或"根据简历填写"
4. AI 自动完成:解析 → 分析模板 → 填写 → 验证 → 交付
AI 会主动处理:工作经历选择、字段缺失留白、字体格式、合并单元格。用户只需确认关键决策。
工作流程
第 1 步:读取简历
支持三种格式,自动识别:
- PDF:
pdfplumber提取全文字符串 - DOCX:
python-docx的Document类提取段落和表格文本 - DOC(旧版 WPS/Word 格式):
olefile读取 WordDocument 二进制流提取中文文本
> DOC 格式处理要点:.doc 是 OLE 二进制格式,python-docx 无法直接读取。需用 olefile 打开 WordDocument stream,按 UTF-16LE 逐双字节扫描,筛选 Unicode CJK 区段字符拼接成文本。
# DOCX 简历提取
from docx import Document
doc = Document("简历.docx")
texts = []
for p in doc.paragraphs:
if p.text.strip():
texts.append(p.text.strip())
for table in doc.tables:
for row in table.rows:
texts.append(' | '.join(c.text.strip() for c in row.cells if c.text.strip()))
resume_text = '\n'.join(texts)
第 2 步:解析结构化字段
用正则规则从简历文本中提取关键字段,存入 dict:
| 字段 | 匹配规则 |
|---|---|
| 姓名 | 首行或"姓名:"后第一个词 |
| 性别 | "男"/"女",或 男/YYYY 格式 |
| 出生年月 | YYYY.MM / YYYY年MM月 等 |
| 民族 | "民族:"后内容 |
| 政治面貌 | "政治面貌:"后内容 |
| 健康状况 | 含"健康"字样的行 |
| 参加工作时间 | "参加工作"或首段年份 |
| 手机号码 | 11位手机号正则 |
| 电子邮箱 | 标准邮箱正则 |
| 教育经历 | 时间+院校+专业+学历(最多3段) |
| 工作经历 | 时间段+公司名+岗位(全部段落,按时间倒序) |
| 技术职称 | 如"中级经济师" |
| 职业资格 | 证券/基金/CPA/CFA 等 |
| 英语等级 | CET-4/CET-6 |
> 重要规则:简历中未出现的字段(如籍贯、身份证号、推荐人等),一律留空白,不推断、不猜测。
第 3 步:询问用户关键决策
在填写前,必须主动询问以下问题:
① 工作经历的选择(必问)
如果简历有多段工作经历,而表单模板只有有限的行数,必须告知用户:
发现简历中有 N 段工作经历:
1. [公司名A] [时间]
2. [公司名B] [时间]
3. [公司名C] [时间]
请告诉我要填哪几段(或填序号)
如果表单有多个工作经历行,则按时间倒序填入,不足则留空。
② 补充信息文件
如果表单有简历中未提供的字段(身份证号、籍贯、家庭成员等),询问用户是否有补充信息文件可提供。
第 4 步:分析表单模板结构
这是最关键的步骤。 每个表单模板的单元格布局都不同,必须先分析再填写。
使用 python-docx 读取模板,分析表格结构:
from docx import Document
doc = Document("模板.docx")
table = doc.tables[0]
# 分析每行的唯一单元格(排除合并重复)
for ri, row in enumerate(table.rows):
seen = {}
for ci, cell in enumerate(row.cells):
eid = id(cell._element)
if eid not in seen:
seen[eid] = ci
txt = cell.text.strip().replace('\n', '|')
if txt:
print(f'R{ri}C{ci}: [{txt[:60]}]')
> 合并单元格要点:python-docx 中,合并单元格的所有位置都返回同一个 cell 对象(id(cell._element) 相同)。必须通过 id 去重才能识别真实单元格数。合并区域只有左上角位置的 cell 是"主单元格",写入此位置即可。
第 5 步:构建 cell_map 并填写
根据第 4 步的分析结果,为每个模板构建 cell_map(字段名 → (行号, 列号) 的映射),然后用 set_cell_text 逐项写入。
from docx import Document
from docx.oxml.ns import qn
doc = Document("模板.docx")
table = doc.tables[0]
def set_cell_text(cell, text, font_name='仿宋'):
"""在单元格中设置文本,字体默认仿宋"""
for p in cell.paragraphs:
for run in p.runs:
run.text = ''
if cell.paragraphs:
cell.paragraphs[0].clear()
run = cell.paragraphs[0].add_run(text)
run.font.name = font_name
rPr = run._element.get_or_add_rPr()
rFonts = rPr.find(qn('w:rFonts'))
if rFonts is None:
rFonts = rPr.makeelement(qn('w:rFonts'), {})
rPr.insert(0, rFonts)
rFonts.set(qn('w:eastAsia'), font_name)
# 示例 cell_map(每个模板不同,必须单独构建)
cell_map = {
'姓名': (2, 3),
'性别': (2, 9),
'出生年月': (3, 3),
...
}
# 逐项填写
for field, (ri, ci) in cell_map.items():
value = parsed.get(field, '')
if value:
set_cell_text(table.rows[ri].cells[ci], value)
print(f'[OK] {field} = {value}')
else:
print(f'[SKIP] {field}: 简历未提供,留空')
doc.save("输出.docx")
格式要求:字体 仿宋(不是 仿宋_GB2312),颜色黑色,无字间距调整。
> AI 应为每个模板单独编写填写脚本:因为每个模板的行/列布局完全不同,cell_map 无法通用。AI 应根据分析结果编写一个临时 Python 脚本,定义 cell_map 并调用 set_cell_text,然后运行该脚本完成填写。
第 6 步:交付前验证(强制,不可跳过)
填写完成后,必须重新读取简历原文,逐项验证关键字段。 这是硬规则,永远不要在填表后直接交付而不验证。
验证方法——用脚本读取已填写的 DOCX,打印以下关键字段值,与简历原文逐行对照:
from docx import Document
doc = Document("已填写.docx")
table = doc.tables[0]
# 验证清单
for field, (ri, ci) in cell_map.items():
actual = table.rows[ri].cells[ci].text.strip()
expected = parsed.get(field, '')
status = '✓' if actual == expected else '✗ MISMATCH'
print(f'{field}: [{actual}] vs [{expected}] → {status}')
# 特别关注教育经历:学校名和专业名逐字对照
# 特别关注工作经历:公司名和时间段逐字对照
发现任何偏差立即修正,修正后重新验证通过才可交付。
> 事故警示(2026-05-29):填写财达证券登记表时,教育经历被编造成"北京科技大学/矿物加工工程",而简历实际为"中国人民大学/金融学"和"北京工商大学/金融工程"。原因:未从提取文本中确认教育经历,凭主观臆断填入。在任何情况下都禁止编造数据,未提取到的值必须留空。
第 7 步:根据补充文件二次填写
如果用户提供了补充信息文件(如已填过的登记表、个人信息表),可以从中提取之前留空的字段值,补充填写。
流程:
1. 读取补充文件,提取字段值
2. 对照已填写表单中的留空字段
3. 填入补充值,再次执行第 6 步验证
第 8 步:清理记忆文件中的个人信息(强制,不可跳过)
填表任务完成后,必须检查并清理所有记忆文件中的个人及家庭信息。 这是硬规则。
清理范围:
| 记忆文件 | 位置 |
|---|---|
| 项目日志 | .workbuddy/memory/YYYY-MM-DD.md |
| 项目长期记忆 | .workbuddy/memory/MEMORY.md |
| 跨项目记忆 | ~/.workbuddy/MEMORY.md |
需要删除的个人信息包括但不限于:
- 姓名、曾用名
- 身份证号、手机号、邮箱
- 地址、籍贯、户口所在地
- 家庭成员姓名、电话、工作单位
- 教育经历(学校名、专业名)
- 工作单位、职位、薪酬
- 证明人信息
清理原则:记忆文件只保留技术流程描述(用什么工具、什么方法、遇到什么问题、如何解决),不含任何个人数据值。如果日志中出现了姓名,替换为"用户"或直接删除;如果出现了具体数据值,删除该值,保留技术描述。
可靠性设计(强制要求)
Skill 运行时必须处理以下异常场景,不能崩溃或抛出用户看不懂的报错:
文件读取前验证
| 检查项 | 失败时的友好提示 |
|---|---|
| 文件路径是否存在 | "未找到文件:[路径],请确认已上传" |
| 文件扩展名是否支持 | "仅支持 PDF、DOCX、DOC 格式的简历,当前文件:[扩展名]" |
| 文件是否为空(0KB) | "文件内容为空,请重新上传" |
| DOCX 是否为合法 zip 结构 | "DOCX 文件损坏,请用 Word 重新保存后再上传" |
| DOC 是否为合法 OLE 结构 | "DOC 文件无法识别,请转换为 DOCX 后再试" |
| PDF 是否可打开 | "PDF 文件损坏或加密,无法提取文本" |
解析异常处理
- 简历文本为空:提示"未能从简历中提取到文本,请检查文件是否可正常打开"
- 关键字段缺失:不编造,自动留空,并提示"以下字段简历中未提供,已留空:[字段列表]"
- 工作经历数量 > 表单行数:列出所有经历,让用户选择,而不是默认丢弃
- 输出文件被 Word 占用:自动换文件名(如
输出_1.docx)保存,不崩溃
错误输出规范
所有报错信息必须:
1. 用中文说明问题
2. 告诉用户下一步该做什么
3. 不暴露 Python 堆栈(除非用户明确要求调试)
反面示例:FileNotFoundError: [Errno 2] No such file or directory: 'xxx.docx'
正面示例:未找到简历文件:xxx.docx。请确认文件已上传,或重新选择文件。
填写示例
示例:基本信息 cell_map
cell_map_basic = {
'姓名': (2, 3),
'性别': (2, 9),
'出生年月': (3, 3),
'民族': (3, 9),
'政治面貌': (4, 3),
'参加工作时间': (5, 3),
'手机号码': (6, 3),
'电子邮箱': (6, 9),
}
示例:教育经历多行 cell_map
cell_map_edu = [
{'起止时间': (8, 1), '毕业院校': (8, 3), '系及专业': (8, 7), '学历、学位': (8, 10)},
{'起止时间': (9, 1), '毕业院校': (9, 3), '系及专业': (9, 7), '学历、学位': (9, 10)},
]
示例:工作经历多行 cell_map
cell_map_work = [
{'起止时间': (11, 0), '工作单位': (11, 2), '岗位': (11, 6)},
{'起止时间': (12, 0), '工作单位': (12, 2), '岗位': (12, 6)},
{'起止时间': (13, 0), '工作单位': (13, 2), '岗位': (13, 6)},
]
> 每个模板的行/列号不同,AI 必须先用第 4 步分析模板,再按实际结构填写。
字段匹配策略
表单字段名 → 简历内容映射
| 表单字段 | 简历匹配关键词 | 备选 |
|---|---|---|
| 姓名 | 首行、姓名: | - |
| 性别 | 男/女 | 简历中含"男/"或"女/" |
| 出生年月 | 出生年月:/YYYY.MM | 首行数字 |
| 民族 | 民族: | 常见民族名称 |
| 政治面貌 | 政治面貌: | 党员/群众/团员 |
| 健康状况 | 健康 | 良好/一般 |
| 参加工作时间 | 参加工作: | 首段年份 |
| 手机号码 | 手机:/1开头11位 | 直接手机号 |
| 电子邮箱 | 邮箱:/@ | - |
| 技术职称 | 中级经济师/CPA/CFA | 证书栏 |
| 职业资格 | 从业资格 | 证券/基金/本币 |
| 英语等级 | CET/英语四六级 | - |
| 工作单位 | 公司名 | 含"证券/信托/银行/评级" |
| 起止时间 | YYYY/M—YYYY/M | - |
| 岗位 | 经理/研究员/分析师 | - |
表单有、简历无的字段
直接留空,不做任何推断。这是最重要的一条原则。
错误处理
- 文件不存在/格式不支持:提前检查,给出中文提示,不触发异常
- 文件损坏:捕获具体异常,提示用户重新保存或转换格式
- 简历文本为空:提示检查文件,不继续解析
- 关键字段缺失:自动留空并汇总提示
- 工作经历 > 表单行数:主动询问用户选择
- 单元格已有内容:跳过,避免覆盖
- 合并单元格写入:只写左上角位置(主单元格)
.doc格式文件:使用 olefile 特殊处理,不使用 python-docx- 输出文件被占用:自动换文件名保存,如
输出_1.docx - 填写过程异常:已填写的内容仍保存,告知用户哪些字段成功、哪些失败
踩坑经验
- 合并单元格:python-docx 中合并区域的所有位置返回同一 cell 对象,必须通过
id(cell._element)去重才能识别真实单元格数。写入时只需写左上角位置。 - 教育经历数据行 vs 表头行:
起止时间/毕业院校/系及专业/学历学位是数据行,不是表头行;表头行的标签文字在<w:t>中,不要混淆。 - 工作经历定位:如果模板中有多个"起止时间"标签(教育和工作都有),定位时必须区分。
- 字体统一:使用
仿宋(不是仿宋_GB2312),通过w:rFonts的w:eastAsia属性设置中文字体。 - DOC 格式:
.doc是 OLE 二进制格式,python-docx 无法读取。用 olefile 打开 WordDocument stream,按 UTF-16LE 逐双字节扫描提取中文。 - 控制台编码:Windows 控制台输出中文可能乱码,建议将验证结果写入文件而非直接打印到控制台。
- 文件占用:如果输出文件正在被 Word 打开,
doc.save()会失败。换一个文件名保存。 - 二次填写:用户可能提供补充信息文件,用于填写简历中未提供的字段(身份证号、籍贯、家庭成员等)。流程是读取补充文件 → 提取留空字段值 → 填入 → 再次验证。
- 隐私保护:填表任务完成后,必须执行第 8 步清理记忆文件中的所有个人及家庭信息(姓名、身份证号、手机号、邮箱、地址、家庭成员姓名/电话/工作单位、教育经历、工作单位等),只保留技术流程描述。
- 可靠性优先:所有文件读取和字段解析都要加异常处理,失败时给用户中文提示和下一步操作建议,不让 Skill 直接崩溃。
- 简化技术门槛:普通用户只需上传简历和模板;AI 负责分析模板、构建 cell_map、填写、验证。复杂情况才需要用户确认。