版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不推荐任何规避地区限制的访问方式,亦不对任何收益结果作承诺。转载请注明出处。
一、QA 桌上那摞纸:六类文件,为什么偏偏批记录抽不准
在制药企业做质量保证,签字是要负责的动作。一批产品从配料到入库,我手上过的纸大致六类:批生产记录、批检验记录、偏差报告、变更控制、放行审核记录,以及托着它们的那套 GMP 文件体系——工艺规程、质量标准、SOP、验证方案。
想把这些活交一部分给大模型,念头很朴素:翻记录、对参数、做大趋势,都是体力活。真动手时撞上的第一个坎,不是模型不够聪明,是我把「一张表」当成「一段文字」扔了进去。
六类文件喂给同一个模型,表现差别很大。
| 文件类型 | 结构特点 | 抽的时候卡在哪 | 抽错的后果 |
|---|---|---|---|
| 偏差报告 | 叙述体,一段一段写 | 时间线、根因、CAPA 混在一段里 | 根因归错类,同类偏差反复发生 |
| 变更控制 | 表单加影响评估段落 | 评估结论是自然语言,边界模糊 | 影响范围漏项 |
| 批检验记录 | 一张主表加多张图谱附件 | 检验项、标准、结果、结论同行 | 结论与数据对不上 |
| 批生产记录 | 多表关联、跨页续表、合并单元格 | 见第二到第五章 | 参数归属错、行缺失 |
| 放行审核 | 逐项勾选的核对单 | 勾选项要与批记录跨文件对齐 | 该拦的没拦住 |
| GMP 文件体系 | 层级目录加版本 | 版本与生效日期一对多 | 引用了作废版本 |
偏差报告那种散文,模型反而处理得好:时间、地点、现象、原因,本来就写在句子里。批记录正相反,它是最像「数据」的一份,也是最容易抽错的一份。
原因只有一句:越像表格的东西,越不能直接当文本喂。
二、模型看到的不是表格,是一串 token
2.1 合并单元格不是一个值,是一段区域
在 xlsx 里,合并单元格由 mergeCells 记录:一个区域,加一个左上角的值,其余格子是空的。人眼看过去,是「制粒」管着六行;模型读到的 csv 是——第一行有「制粒」,后面五行是逗号。
- 行列关系,扁平化之后只剩分隔符;
- 层级关系(哪个值管着哪几行),扁平化之后完全消失;
- 跨页关系(这一页接着上一页),连分隔符都没有。
模型拿到一串 token,要还原的是一张有层级的表。这一步它在猜,而猜是不报错的。
2.2 三个必然失真点
| 失真点 | 纸面或 Excel 里长什么样 | 扁平化后模型看到什么 | 下游后果 |
|---|---|---|---|
| 合并单元格父级丢失 | 工序列「制粒」跨六行 | 只有第一行有值,其余为空 | 干燥参数被挂到制粒机名下 |
| 跨页续表被当新表 | 第三页重复表头,批号只在首页 | 两段互不相干的表 | 行数少一截,一个批次被拆成两个 |
| 同名列重名 | 「实际值」「结论」各出现多次 | 一张宽表里三个同名列 | 取到包装数量当投料量 |
三个点里,最危险的不是抽不到,是抽错还抽得很像。模型不会提示「这里父级缺了」,它会顺着上文补一个最顺的值。这种错,只看输出看不出来。
三、失真点一:合并单元格的父级只在首行出现
3.1 一张真实记录在纸面上长什么样
制粒工序那张记录,人眼看到的是三层:
- 工序列「制粒」,合并六行;
- 设备列前三行「制粒机」,后三行「沸腾干燥机」;
- 项目列六行各不相同:搅拌转速、制粒时间、粘合剂用量、进风温度、干燥时间、水分。
转成 csv 之后:第一行是「制粒,制粒机,搅拌转速,……」,第二、三行工序与设备两列为空,第四行只有设备有值,第五、六行两列全空。
3.2 那三个空格,模型有四种处理方式
- 当「同上」——多数时候对,但在工序切换那一行会沿错;
- 当「未填」——直接判这份记录不完整,把有效行丢掉;
- 按项目名反推——「干燥时间」推成干燥工序,「进风温度」推成总混,推得有理,但没有根据;
- 按训练里见过的批记录格式补——最像人,也最不可控。
第三种和第四种最难发现,因为输出读起来很顺。第四种尤其麻烦:它会给你一个格式完全正确、内容凭空捏造的归属。
3.3 归属错一次,后面全歪
一个具体的连锁:如果「进风温度 62℃」被归到「制粒机」,年度产品质量回顾里按设备统计参数分布,沸腾干燥机就少一组、制粒机多一组。下一次同品种出现水分超标偏差,调查会先去看制粒机——方向从一开始就偏了。
这不是模型的问题。喂进去的东西里,本来就没有「这台设备管这几行」这条信息。
3.4 填之前先分清列的角色
向下填充不是「空格都填上」。填错列,等于造数据。我现在的分法是这样:
| 列的角色 | 判断依据 | 处理方式 | 不这么做的后果 |
|---|---|---|---|
| 层级父级(工序、设备、房间、班次) | 值跨多行重复,且是下一级的容器 | 向下填充,并标记继承来源 | 归属串行 |
| 叶节点值(项目、标准、实际值、单位) | 每行独立,重复才是异常 | 不填,空就报缺失 | 造出假数据 |
| 汇总行(小计、合计、平均) | 行首含小计或合计,或整行加粗 | 单独摘出,不进明细 | 汇总被当成一条记录参与统计 |
| 签字列(操作人、复核人、日期) | 通常只在段末出现一次 | 按段落归属,不逐行填 | 把复核人算成操作人 |
| 备注列 | 自由文本,常有「同上」「见附页」 | 保留原文,不做语义改写 | 改写后失去原始表述 |
最后一条要守住:记录里的「同上」是有意义的表述,转成具体值就不再是原文。放行审核看的是原始记录,不是我们整理后的版本。
四、失真点二:跨页续表被当成第二张表
4.1 纸面证据只有三处
批记录按页打印、逐页签字。第三页顶上重复一遍表头,页脚印着「第 3 页 共 5 页」,批号只在首页右上角写一次。除此之外,第三页和一张新表没有任何区别。
按页抽,最常见的两种错:
- 每页各抽一张表,最后拼起来——表头重复出现,行数看着不少,但每张表都缺上下文;
- 只抽首页——后面几页整段消失。行数少一截比抽错更麻烦,因为没人会发现。
4.2 续表的第一行,父级在上一页
这是跨页最坑的地方:第三页第一行是「总混」工序的第二条参数,工序与设备两列都是空的。它的父级不在本页。单独看这一页,怎么填都填不出来。
所以顺序只有一个:先并表,再向下填充。反过来做,这一行的父级永久丢失。第六章的脚本按这个顺序写。
4.3 什么时候不能合并
反过来也有坑:同一批里真有两张同结构的表。两台压片机各一张记录,表头一模一样。这时合并,会把两台设备的参数混成一条线。
| 信号 | 指向续表 | 指向新表 | 说明 |
|---|---|---|---|
| 表头指纹(列名与顺序) | 完全一致 | 完全一致 | 只能排除,不能确认 |
| 页码 | 连续,如 3/5 接 4/5 | 从 1 重新开始 | 最强信号 |
| 上一页末行 | 明细行,无合计 | 合计行加签字 | 次强信号 |
| 批号出现位置 | 只在首页 | 每页都印 | 每页都印通常是独立表 |
| 表标题 | 含「续表」,或页眉带品名 | 完整标题 | 有则加分,无则不扣 |
| 行号 | 跨页连续 | 从 1 重新开始 | 与页码互相印证 |
我的做法是:页码连续、且上一页末行是明细行,两条同时成立才自动合并,其余一律进人工待核。宁可多一批待核,不要少一批行——待核有人看,丢的行没人看。
五、失真点三:同一张记录里,「结论」出现了三次
5.1 重名不是巧合,是行业习惯
一张批生产记录里,「实际值」至少出现三次:工艺参数的实际值、称量的实际数量、包装的实际数量。「结论」也出现三次:工序结论、中间产品结论、成品结论。批检验记录里还有一个「结论」。
抽成宽表时,同名列表头会被自动改名加后缀,或者被后一个直接覆盖。覆盖的那种最要命:投料 480 kg,包装数量 12000 片,两个值都进了「实际值」,覆盖之后物料平衡按 12000 除以 480 算,出来 25 倍。
数字荒谬,但如果校验只判「是否为空」,它不会报任何问题,一路进到汇总表。
5.2 消歧靠路径,不靠模型猜
做法很土:给每个字段加路径前缀。前缀来自表头层级——哪张表、哪一段、哪一列。
| 原列名 | 出现位置 | 消歧后的字段名 | 单位 | 校验规则 |
|---|---|---|---|---|
| 实际值 | 制粒与干燥参数段 | process.granulation.actual | 按项目定 | 落在标准上下限内 |
| 实际值 | 称量段 | dispense.actual_qty | kg | 与投料总量勾稽 |
| 实际值 | 包装段 | pack.actual_qty | 片 | 与理论产量勾稽 |
| 结论 | 工序段 | process.conclusion | 无 | 枚举,符合或不符合 |
| 结论 | 成品段 | product.conclusion | 无 | 与检验结论一致 |
| 判定 | 批检验记录 | qc.judgement | 无 | 映射到同一枚举 |
最后一行是另一个坑:中文同义不同名。「结论」「判定」「符合性」,很多厂里混着用,跨表对齐时对不上。这个不能靠模型现场猜,得先建一份字段别名字典,把同义词收敛到一个枚举值上。字典是死的,猜是活的,活的那个每次都不一样。
这份资料是什么:一张把批记录常见列名按「原列名 / 出现位置 / 路径前缀 / 校验规则」对齐的字段对照空白表,和本章的消歧做法配套。放在资料包里,扫码即可获取:
六、把结构写回去:先并表,再向下填充
6.1 三个动作,顺序不能换
- 并表:按表头指纹加页码连续性,把续表接回主表;
- 填充:对层级列向下填充,并给继承值打标记;
- 消歧:列名加路径前缀,也就是第五章那张表。
脚本只做前两步,二十八行,纯标准库。输入是两页导出的 csv,列结构与第三章那张记录一致。
importcsvimportsysdefread_page(path):withopen(path,newline="",encoding="utf-8-sig")asf:return[rforrincsv.reader(f)ifany(c.strip()forcinr)]defstitch(pages):out,seen=[],set()forpageinpages:head=tuple(x.strip()forxinpage[0])ifheadinseen:out.extend(page[1:])continueseen.add(head)out.append(page[0])out.extend(page[1:])returnoutdeffill_down(rows,cols):carry={c:""forcincols}out=[rows[0]]forrinrows[1:]:r=list(r)forcincols:ifr[c].strip():carry[c]=r[c].strip()continuer[c]=carry[c]+"*"out.append(r)returnoutif__name__=="__main__":rows=fill_down(stitch([read_page(p)forpinsys.argv[1:]]),[0,1])print("并表后数据行 %d"%(len(rows)-1))forrinrows:print(" | ".join(xifxelse"_"forxinr))跑法一行,页数不限,按页序传进去:
python 批记录还原.py p1.csv p3.csv本机输出,未做任何修饰,星号是我加的继承标记:
并表后数据行 11 工序 | 设备 | 项目 | 标准 | 实际值 | 结论 制粒 | 制粒机 | 搅拌转速 | 300±20 rpm | 312 | 符合 制粒* | 制粒机* | 制粒时间 | 10±1 min | 10 | 符合 制粒* | 制粒机* | 粘合剂用量 | 4.0 kg | 4.0 | 符合 制粒* | 沸腾干燥机 | 进风温度 | 60±5 ℃ | 62 | 符合 制粒* | 沸腾干燥机* | 干燥时间 | 30±5 min | 28 | 符合 制粒* | 沸腾干燥机* | 水分 | ≤3.0 % | 2.4 | 符合 总混 | 三维混合机 | 转速 | 12 rpm | 12 | 符合 总混* | 三维混合机* | 混合时间 | 15±2 min | 15 | 符合 压片 | 压片机 | 片重 | 0.250±0.010 g | 0.253 | 符合 压片* | 压片机* | 硬度 | 60±20 N | 72 | 符合 压片* | 压片机* | 崩解时限 | ≤15 min | 9 | 符合6.2 输出里有两处值得看
第一处是第八行:「总混* | 三维混合机* | 混合时间」。这一行在原文件里是第三页的第一行,父级两列都是空的,靠上一页末尾的「总混」和「三维混合机」接上。如果没有先并表,这一行填出来就是空的。
第二处是行数:7 加 4 等 11。按页分别抽,两张表加起来也是 11 行,行数对得上,但表头多一份,第三页首行没有父级。行数对得上,不代表结构对,这是我在第一批样本上吃过的亏。
星号的用处正在这里:后面做校验时,「记录里真写了」和「我们补上去的」必须能分开。补上去的值一旦出错,追责要能追到脚本,而不是记到记录上。
6.3 这一段不解决第三个失真点
重名要在表头解析阶段解决,也就是拿到列名的那一刻就加前缀。等数据都成行了再改名,路径信息就丢了。
七、抽完不算完:三道交叉校验兜住安静的错误
7.1 抽取准确不等于记录自洽
模型的活是把字认对,QA 的活是判断这份记录自己跟自己对不对。抽得再准,如果参数归属错了、行少了,记录还是不能用。
三道校验,全是规则,不依赖模型。
| 校验 | 规则 | 失败时怎么做 | 兜住哪个失真点 |
|---|---|---|---|
| 批号一致 | 每行批号等于封面批号,继承来的标 inherited | 该行进待核清单,不静默修正 | 跨页拆分 |
| 工序顺序 | 抽出序列是工艺规程序列的子序列,且不逆序 | 报缺失工序与逆序位置 | 行缺失、并表错 |
| 数量勾稽 | 投料总量到理论产量到实际产量,相对偏差按工艺规程限度 | 超出即拦,不进汇总 | 同名列取值错 |
| 签字完整 | 操作人、复核人、日期三段齐全,日期不倒序 | 标记缺签段落 | 段落归属错 |
第三条最有效。参数归属错了,数量往往还对;但取值张冠李戴,勾稽一定崩。一个数从 kg 变成片,量级差几十倍,规则一眼就能看出来。
7.2 阈值不要抄别人的
物料平衡的偏差限度写在各家的工艺规程里,品种之间不一样。我一开始照搬了一个通用阈值,结果颗粒剂那几批全被拦下来,白折腾两天。阈值从自己的工艺规程里取,脚本里做成配置项。
7.3 转行的人,优势在这一节
写提示词谁都能学,这三道校验的规则学不来。它要求你知道哪一列必须对、两个数之间为什么必须相等、签字缺一个意味着什么。这些是从偏差调查和放行审核里长出来的,不是从教程里。
同样做批记录抽取,纯算法背景的人容易把字段准确率当终点;质量岗出身的人会追问:剩下的部分错在哪些列,这些列错了,这批产品还能不能放。前一个问题模型能答,后一个问题只有懂放行的人能答。
7.4 落地顺序
- 先拿二十批做回归样本,人工逐行判一遍,错的按「归属错、缺行、取值错」三类归档;
- 三类错对应三个修法:归属错改填充规则,缺行改并表判据,取值错改列名路径;
- 修完再跑同一批样本,看三类错是否各自下降,别只看一个总数;
- 稳定之后再谈接进放行流程,接之前把待核清单的人工环节留住。
这份资料是什么:一张把「批号一致、工序顺序、数量勾稽、签字完整」四项写成可填规则的三道校验清单,改一改就能贴到自己厂的批记录上。放在资料包里,扫码即可获取:
附表 A:本文引用事实与出处对照表
| 事实 | 出处 | 核验方式 | 本文位置 |
|---|---|---|---|
| 批记录由多张表互相关联,同表跨页延续,层级用合并单元格表达 | 制药企业批记录的实际格式,本文归纳 | 本人经手的批生产记录、批检验记录样本 | 一、二 |
| 《药品生产质量管理规范(2010年修订)》文件名,以及「第八章 文件管理」章名 | 该规范正文 | 文件公开发布,章名可查;具体条号未逐条核对,故全文不引条号 | 一、七 |
| xlsx 中合并单元格由一个区域加左上角值记录 | OOXML 表格结构,mergeCells 元素 | 结构定义,与具体软件版本无关 | 二、三 |
| 三个失真点的分类与后果 | 本文归纳 | 基于本人经手的记录样本,非统计结论 | 二至五 |
| 并表后 11 行、星号标记的继承值 | 本机实测,脚本见 work 目录下的批记录还原脚本,Python 3.13.14,输入两页 csv | 本机运行,输出原样引用,未改数 | 六 |
| 表头指纹、页码、上一页末行三条续表判据 | 本文归纳 | 现场样本试用,非行业标准 | 四 |
| 字段路径前缀命名与别名字典 | 本文做法 | 自定规范,可替换 | 五 |
| 批号一致、工序顺序、数量勾稽、签字完整四项校验 | 本文归纳,源自放行审核与偏差调查流程 | 规则自定,阈值取自工艺规程 | 七 |
| 模型对空白父级的四种处理方式 | 本文观察 | 试用记录,非对照实验 | 三 |
| 具体条号、保存年限、物料平衡限度数值 | 未采用 | 未核到可引用的现行原文,故不写 | 全文 |
附表 B:术语速查表
| 术语 | 含义 | 出现位置 |
|---|---|---|
| 批生产记录 | 一批产品从配料到包装的全过程操作记录,随批流转 | 全文 |
| 批检验记录 | 该批样品的检验项目、数据、图谱与结论的原始记录 | 一、五 |
| 偏差报告 | 记录实际执行与规定不符的事件、原因与纠正措施的文档 | 一、七 |
| 变更控制 | 对工艺、设备、文件等变更做评估、批准、跟踪的流程 | 一 |
| 放行审核 | 出厂前对批记录、检验结果、偏差关闭情况的逐项核查 | 一、七 |
| GMP 文件体系 | 工艺规程、质量标准、SOP、验证文件构成的层级文件集 | 一 |
| 合并单元格 | 电子表格中把一块区域并成一个格,只有左上角保存值 | 二、三 |
| 续表 | 一张表跨页后在新页重复表头继续书写的部分 | 四 |
| 扁平化 | 把带层级和版式的表格压成一行一行的纯文本或 csv | 二 |
| 向下填充 | 把层级列的首行值填到其下属空行,并标记来源 | 三、六 |
| 表头指纹 | 列名及其顺序构成的字符串元组,用来判断两页是否同表 | 四、六 |
| 物料平衡 | 投入量与产出量之间的数量关系核算 | 五、七 |
| 待核清单 | 规则判不准、交人工确认的记录行集合 | 四、七 |
写在最后:这篇用到的资料
写这篇文章时,把相关的官方文档和源码又翻了一遍,顺手也整理了几份配套的东西:
- 大模型学习路线图:从零基础到能自己动手做 Agent,按阶段说明每一步该学什么、哪些可以先跳过
- 《LangChain + LangGraph + MCP 智能体开发实战》视频课:7 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
- AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
- 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
- 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多
资料是我自己整理的,放在下面这个码上,扫码即可获取:
如果这篇对你有用,评论区说说你卡在哪一步,我按留言再补后续的拆解。