自动填表或更新表技能(table-form-builder)
> 中文名由用户定为自动填表或更新表技能,简称「自动填表」「更新表」。
> 干的事就两件:往基准表里填新内容,或用新信息更新旧表——
> 版式不动,内容填准,交出去之前过一遍机器校验。
始终有一份基准表(空白模板或老版),把新信息填进去,版式原样保留。
基准表的几种形态
| 基准表形态 | 先做什么 | 用哪个脚本 |
|---|---|---|
| 已有 .docx | 直接填 | fill_template.py |
| 已有 .xlsx | 直接填 | fill_xlsx.py |
| 照片 / 纸质表 / 截图 | 先数字化成 .docx 或 .xlsx,再填 | create_base_table.py / create_base_xlsx.py |
照片流程(一次性建表载体,之后这张文件就是基准表):
读图逐格抄录 → 写 base_spec.json → create_*.py 建成基准表
→ 写 fill_spec.json → fill_*.py 填内容 → verify_*.py 校验
> create_*.py 不识别图片,只把已读出的 spec 变成结构规范的文件。
> 所以读图必须逐格读准,行列数、文字有疑问先问用户,不要猜。
docx 与 xlsx 的差异(写脚本时注意)
- 行号:docx 的 header_row 从 0 起,xlsx 从 1 起
- 合并:docx 是逐格 merge(),xlsx 是区域(E5:E7);两者都不能靠它定位新写入的格
- 增删行:xlsx 的 insert_rows/delete_rows 不会调整合并区定义,必须重建
- 样式:xlsx 的 cell.font 返回 StyleProxy 不可哈希,复制样式要用 copy()
能力边界:分清「基准表」与「信息来源」
网格表(课程表、座位表、成绩单模板)是行×列交叉填充结构,每格独立成表,
没有「一行一条记录」和键列。关键在于它扮演什么角色:
| 角色 | 网格表能否胜任 | 说明 |
|---|---|---|
| 信息来源(新内容的来源) | ✅ 完全可以 | 参照课表、座位表把内容读出来,整理成一条条记录,填进基准表 |
| 基准表(被填的对象) | ❌ 不行 | 没有键列可匹配行,硬填会错位串行,脚本会直接拒绝 |
所以「网格表不适用」指的是不能当基准表,不是不能当资料。
比如「照着这张课表,把任课老师填进班主任联系表」是完全正常的请求——
课表是资料,联系表才是基准表。
信息来源:两条渠道
填什么,只有两个来处:用户直接提供,或用户指定的数据库。
两条渠道的防错标准完全一样(都要过 expect_total 和校验脚本),
但开工前要确认的东西不同。
A. 用户直接提供
截图、手写稿照片、纸质扫描件、文字、文件。按 Step 1 逐行抄录即可。
字迹不清、行列数有疑、两份资料冲突 → 标出来问,不猜。
B. 用户指定的数据库 / 数据源
知识库检索、既有表格文件、业务系统导出、MCP 数据源等。
用之前必须先确认四件事,不许默认:
- 字段映射:库里的字段分别对应基准表哪一列。对不上的先问,
不许按名字相近硬套(「近1年」≠「近一年」时要确认是不是同一个口径)。 - 取值时点:随时间变的数(近一周、近一年、余额、排名……)
必须确认是哪一天的快照。同一张表内所有行必须同一时点,
不能一半今天、一半上周。 - 筛选范围:库里通常还有别的记录,取哪些由用户定
(全部?某个分类?某区间?),不要自行圈定。 - 缺失值怎么处理:库里查不到的字段是留空,还是标「未取到」。
不许填 0、不许猜、不许拿别处的值补位。
取数之后照旧:写 spec → 跑 fill → 跑 verify。
渠道变了,防错标准不变。
适用:基准表是信息表(一行一条记录)
| 类别 | 典型表 | 键列(匹配用) |
|---|---|---|
| 行政用表 | 班主任电话表、巡查安排表、请假条、名单花名册、教职工通讯录 | 序号 / 班级号 / 工号 |
| 排班表 | 教师值班表、考场安排表、周值班表、节假日值班安排 | 日期 / 星期 |
| 台账记录表 | 巡查记录表、器材借还表、班级日志、加班统计、签到表 | 日期 / 序号 |
| 分栏信息表 | 一张纸印多份名单(见下) | 各栏的序号/班级号 |
| 手写录入 | 手写名单拍照、纸质签到表、纸质登记表扫描件 | 视原资料而定 |
分栏重复信息表(同一行装多条记录)
不是网格表,仍是信息表——一张纸横排印多份,省纸而已。
典型表头:序号|班级|负责人|序号|班级|负责人|序号|班级|负责人
怎么认:表头是某个字段块成组重复,且每行每栏都是一条完整记录。
处理方式(脚本已内置,spec 与普通表完全一样):
- 每栏视为独立的记录,各有各的键值。改 801 只动第 1 栏,不会串到 802
- 某栏的记录未提及 → 只清空那一栏,保留整行(同行其他栏还在用)
- 记录数按栏位数统计,不是物理行数;expect_total 填记录总数
- 有空栏位时,新记录填进空栏,行数不变;全满才新增整行
回显会标注「分栏表:只清该栏,保留整行」,便于核对。
工作流
读资料 → 确认键列 → 明确增删改意图 → 填表 → 脚本校验 → 交付
Step 1 读资料,建立「完整清单」
> 资料两条渠道:用户直接提供 / 用户指定的数据库。
> 后者开工前先过一遍「字段映射 · 取值时点 · 筛选范围 · 缺失值」四问,见上一节。
用 Read 工具打开图片/文档(手写稿、纸质照、截图、课表等均可),逐行抄录成纯文本清单。
- 抄录时对每行编号,行数不确定时宁多勿少
- 分清哪些是数据行、哪些是表头/合计/签字等非数据格
- 字迹不清或资料冲突的,标出来请用户确认,不要猜着填
手写 / 纸质表额外注意:
- 格子可能一行挤很多字,或一格写了两行内容——逐格拆清,别把两行并成一行
- 末尾常有"合计""签字""日期"等格,不要当成数据行填进去
- 字迹不清的格子标出来请用户确认,不要猜着填
- 横线表/无框表:先数清有几行几列,再对应到 spec 的 rows
当资料是网格表(课表、座位表等)时:
网格表不能直接当 spec 的 rows,要先读成一条条记录,再填进基准表。
做法:把行列交叉的内容摊平成行。例如课表——
课表原样(行=节次,列=星期) 摊平后(一行一条记录)
周一 周三 周五 节次 星期 科目
第1节 语文 数学 语文 → 第1节 周一 语文
第2节 数学 语文 数学 第1节 周三 数学
...
- 键列必须选摊平后真正唯一的列。上例中「节次」不唯一(第1节出现两次),
得用「节次+星期」这类组合,或选本身唯一的列(序号、日期等)——
否则脚本会因键值重复而中止 - 空格子是「无安排」还是「漏填」有歧义时,问用户,不要自行推断
- 摊平后条目数要能对上:课表 4 节 × 3 天 = 12 条,一个都不能少
Step 2 确认基准表形态,必要时先数字化
基准表是 .docx / .xlsx → 直接进 Step 4。
基准表是照片 / 纸质表 / 截图 → 先数字化:
- 逐格读出:表头有哪些列、几行数据、有无分组标题行、有无纵向合并的长文本列、
表头是否成组重复(分栏表) - 写
base_spec.json(结构见脚本文件头),生成基准表:
- 要 Word:python scripts/create_base_table.py base_spec.json 基准表.docx
- 要 Excel:python scripts/create_base_xlsx.py base_spec.json 基准表.xlsx - 回显会显示各组条目数与首末值,逐项核对是否与图一致
- 生成的文件即为基准表,此后每次更新都基于它,不用重复数字化
> ⚠️ 参考表数据区全空时,别指望 fill_*.py:它按"键列有值"识别数据行,
> 全空的模板会被判为「未在基准表中找到数据行」而中止。
> 这种情况直接用 create_*.py 按列结构重建——列取自参考表,
> 行由新信息决定——产出的文件才是此后可供 fill 更新的基准表。
Step 3 打开基准表,确认键列与可改列
用 Read 工具打开基准表,看清表头文字,确认:
- 哪一列是键列(用于匹配行,通常是序号/编号/班级号/日期)
- 哪些列允许改写(其余列绝不改动)
- 表头在第几行(docx 从 0 起,xlsx 从 1 起)
- 有哪些分组标题行(「八年级」这类整行合并的行)
- 表头是否成组重复(分栏表,见上)
判断不出键列时,问用户哪一列是标识列,不要猜。
> 键列必须唯一。 键列有重复值时脚本会直接中止——
> 因为无法确定该改哪一行,硬改会覆盖错行。
> 若单个列都不唯一(如「节次」在课表摊平后每节出现多次),
> 说明这张表不适合按单列更新,请与用户确认改用什么标识。
Step 4 明确「保留 / 更新 / 删除」
- 判断原则:已有且未变的保留;变了的重写;新增的补充;用户没提的不动
- 用户说"没有的补充上,已有的不必更新" → 只增不改,不要"顺手修正"原有数据
- 用户说"以新的为准" → 按新信息删掉多余旧行
- 数据存疑(两份资料冲突)时主动指出并请确认,不要默默选一个
若用户给了两张表(一张版式参照 + 一张最新数据),把参照表当基准表。
Step 5 写 spec.json,跑 fill 脚本
{
"key_col": "序号",
"fill_cols": ["班级", "负责人", "联系电话"],
"header_row": 0,
"expect_total": 12,
"rows": [
{"分组": "八年级", "序号": "801", "班级": "八1", "负责人": "王楠", "联系电话": "13800000001"},
{"分组": "八年级", "序号": "810", "班级": "八10", "负责人": "新增甲", "联系电话": "13800008010"}
]
}
python scripts/fill_template.py 基准表.docx spec.json 输出.docx
# Excel 版只换脚本名
python scripts/fill_xlsx.py 基准表.xlsx spec.json 输出.xlsx
排班表(键列是日期):
{
"key_col": "日期",
"fill_cols": ["星期", "值班人", "备注"],
"expect_total": 30,
"rows": [
{"日期": "10月8日", "星期": "周三", "值班人": "李静", "备注": "早班"},
{"日期": "10月12日", "星期": "周一", "值班人": "陈晨", "备注": ""}
]
}
匹配与增删规则:
- 命中键值 → 保留格式,只改文字
- 未命中 → 复制同组数据行的格式,插入新行并填写
- 基准表有、新信息未提及 → 删除该行(以新信息为准)
- 纵向合并列(如「要求/说明」)→ 不参与增删,整列保持原样,
增删完成后按新的分组行数自动重建合并区
- 分栏表:按「行+栏位」定位记录;未提及的只清那一栏、保留整行;
新记录先填进空栏,栏位全满才新增整行;expect_total 填记录总数
脚本行为:
- fill_cols 必须声明,未声明时拒绝改动任何列(防止误改版式)
- 只改 fill_cols 列的文字,不触碰 run 的 rPr → 字体/字号/加粗/对齐全部保留
- expect_total 不符 → 报错中止,不产出半成品
- 网格表 → 拒绝处理并说明原因(见能力边界)
- 键值重复(基准表里或新信息里)→ 中止,防止覆盖错行
- 回显逐行"改/增/删"统计,交接时可核对
Step 6 校验(强制,退出码非 0 不得交付)
# Word 版
python scripts/verify_rows.py 成品.docx --expect "八年级=801-812"
python scripts/verify_rows.py 值班表.docx --col-header 日期 --expect "(未分组)=30"
# Excel 版
python scripts/verify_xlsx.py 成品.xlsx --sheet 巡查表 --header-row 2 --expect "八年级=801-812"
脚本自动判断键列类型:数字走"连续性/缺号/重复"检查,日期星期走"空值/重复"检查。
- 数字模式拦截:缺号、重复、空值、与预期起止不符
- 文本模式拦截:键值重复、键值为空、条数与预期不符,并逐项打印键值供与原资料核对
> ⚠️ 数字键必须带 --expect。 实测:只抄到 809、末尾整齐截断时,801–809 本身连续,
> 纯连续性检查会放行——而这正是原事故的形态。
> 只跑不带 --expect 的校验,等于没校验。
>
> 文本键(如日期)没有"连续"概念,不要硬套区间——脚本会打印全部键值,
> 逐条与原资料比对即可。
不通过就回原资料重数、改 spec、重新跑脚本,不要手改成品绕过校验。
Step 7 交付
逐项过 references/checklist.md,用 present_files 展示成品,
回复中列出改动清单(新增/修改/删除了哪些)+ 校验结论。
铁律
铁律 1:逐行扫到表格最后一行
读图片资料时从第一行连续读到物理末行,不许因为"看起来差不多了"就停。
- ❌ 按预期数量截断("八年级应该 9 个班吧")
- ✅ 逐行点数:1,2,3... 一直到没有下一行为止
> 真实事故:一张班主任电话表八年级共 12 个班(801–812),因按"十班左右"的预期只读到 809,
> 漏掉 810/811/812 三个班;且代码中写死 range(9),把错误固化,直到用户指出才发现。
铁律 2:数量交给数据,不写死
# ❌ 把错误数量固化进代码
rows += [(f"八{i+1}班", g8[i]) for i in range(9)]
# ✅ 让数据本身决定行数
rows += [(f"八{i+1}班", g8[i]) for i in range(len(g8))]
spec 里用 expect_total 声明总数,与实际不符时脚本直接报错——
把"条数"变成机器可校验的事,而不是靠记性。
> 行数由数据决定 = 增删行是常规动作,不必向用户汇报或请示。
> 参考表只印了 4 行空位而实际 7 条记录,直接按 7 条产出即可,
> 不用解释、不用确认——用户不关心行数,只关心内容有没有填准。
铁律 3:格式必须原样保留
只改文字,不碰 run 的 rPr(字体/字号/加粗/对齐)。
脚本已按此实现,交付前仍应抽查 2–3 个单元格确认。
> 「保留」= 不破坏基准表已有的格式,不等于精细复刻。
> 列宽、冻结窗格、边框、对齐、底纹这类版式项,用户没有特殊要求就
> 不主动调整、不为它多跑校验、回复里也别提——省算力,
> 本技能的价值在把内容填准,不在版式。
铁律 4:列以基准表为准,绝不擅自加列
- 表头的列与列序只认基准表里有的。资料里多出来的字段一律不收录——
如 App 截图同时给了「近1周/近1月/近1年/近2年」,而基准表只要
「近一周/近一年」,多出的两列就不能进去。 - 认为确实该加列:先按原列把产出做完交付,再在回复末尾询问。
不得先斩后奏,也不得因为"反正有数据"就顺手加进去。 - 行的增删随意(铁律 2),列的增删必须过用户这一关。
文字细节规范
- 两字词中间不加空格:"保安""王楠""王帅",不是"保 安""王 帅"
- 标题是否带括号年月,问清或沿用用户最后确认的版本
- 人名以原始资料为准,不凭记忆填写;同名不同班时按班级号对应
沟通要点
- 用户偏好极简直接:说了改哪处就改哪处,不要展开解释
- 一次只改用户说的,改完列一行清单
- 行数变化不必汇报;加列必须先问(先产出、后询问,见铁律 4)
- 版式无特殊要求时不调、不说(见铁律 3)
- 基准表有、新信息没提到的行,按用户意图决定去留(默认以新信息为准)
- 网格表当基准表时说明不适用;当资料来源时正常读取填入,别一概拒绝
- 键列是基准表里的标识列(序号/班级号/日期),不是资料里的
资源
Word 版(.docx)
- scripts/fill_template.py —— 基准表填充器(日常主用)。保格式改文字、未命中自动增行、
多余旧行删除、纵向合并列自动重建、分栏表支持、网格表拒绝处理、键值重复拦截
- scripts/create_base_table.py —— 基准表数字化器。照片/纸质表 → .docx,不识别图片
- scripts/verify_rows.py —— 反向校验器。自动判断键列类型:数字键查连续性/缺号/重复,
文本键(日期星期)查空值/重复并逐项打印键值。数字键必须带 --expect
Excel 版(.xlsx) —— 与 Word 版行为一致
- scripts/fill_xlsx.py —— 基准表填充器。保留样式/合并区/列宽/冻结窗格,
自动重建合并区(增删行后必须重建)
- scripts/create_base_xlsx.py —— 基准表数字化器。照片/纸质表 → .xlsx
- scripts/verify_xlsx.py —— 反向校验器。支持多工作表、自动定位键列
通用
- references/checklist.md —— 交付前逐项检查清单,Step 7 必过
技能维护(重要)
本技能由用户维护。工作中若发现可改进点(新表型、新坑、脚本缺陷、流程不顺等):
- 先照常完成手头的活,交付成品
- 在回复末尾简要说明发现的改进点,征求用户许可
- 得到明确同意后才修改本技能文件(SKILL.md / 脚本 / checklist)
- 未获许可不得擅自改动——包括"看起来明显该改"的情况
理由:技能内容体现用户的实际偏好与工作习惯,未经确认的改动可能与用户意图不符。