
我算过一笔账我所在的质控组每个月要审核完大约 40 份监查报告。每一份报告从登录 CTMS 找到对应文档到下载、打开再到逐页核对访视日期、签名、AE 记录和方案偏离最后把问题在 Word 里批注给对应的 CRA这一套流程走完平均要花 20 分钟。一个月下来就是 13 个小时以上的纯重复劳动。后来我把这套流程彻底拆开用 AI 桌面智能体跑了一遍最终实现的效果是从 CTMS 下载监查报告到 AI 分析报告内容并生成 Word 批注全程不需要我坐在屏幕前守着。本篇文章就把这套东西从架构设计、技术选型到踩坑经历完整讲一遍希望对正在被各种“下载—核对—批注”循环折磨的临床质控、数据管理、CRA 朋友以及想尝试桌面智能体的自动化爱好者有所帮助。1. 为什么审核监查报告这件事值得交给一个桌面智能体1.1 拆开看之后你会发现审核报告大半是体力活监查报告的审核表面上是“专业判断”实际上拆开来看可以分成三类动作。第一类是检索和定位登录 CTMS找到指定中心、指定访视类型的报告下载到本地。这一步没有任何智力含量纯粹是固定的界面操作熟手做一次大约两分钟。第二类是格式和完整性核对报告有没有填日期、有没有签名、表格有没有空项、附件是否齐全。这类检查完全可以用代码实现因为判定逻辑是确定的比如“监查员签名栏为空”就是一个布尔判断。第三类是语义判断某条 AE 的描述是不是和 CRF 一致、某个方案偏离有没有写清楚补救措施、SAE 的上报时限有没有超期。这类工作以前只能靠人读人判也是真正消耗脑力的部分。问题在于三类动作被混在一起执行导致每一份报告都需要完整打开、逐段读过去。人脑在处理前两类动作时很容易疲劳漏检往往就发生在最无聊的环节而不是最复杂的环节。1.2 试过 API 和传统 RPA为什么最终选了桌面智能体我想过直接走 CTMS 的数据接口。但实际情况是我所在的项目环境里CTMS 的开放接口并不完整报告导出这类操作没有公开 APIIT 部门审批接口权限的周期长到不可接受。此路不通。后来我试了传统 RPA 工具。RPA 确实能做“打开页面、点击下载、打开 Word、加批注”这些机械操作而且很稳。但它的局限同样明显当页面表格结构稍有变化或者需要判断“这段 SAE 描述和结论是否矛盾”时RPA 就抓瞎了。它没有“理解”的能力只能执行完全固定的命令。桌面智能体不一样。它的本质是一个能把“眼睛看到的内容”转换成“结构化信息”、再把“结构化信息”交给规则引擎和大语言模型去判断的程序。用大白话说就是RPA 负责当手和脚AI 负责当眼睛和大脑。这样既保留了自动化操作的稳定性又引入了语义判断的能力正好覆盖监查报告审核的完整闭环。2. 桌面智能体的整体架构眼睛、手脚和大脑怎么各司其职2.1 拆成三个模块之后很多设计决策都变简单了我把这个智能体分成三个模块感知模块、执行模块、判断模块。感知模块负责“看屏幕”。它做的事情包括截取当前界面、读取 CTMS 页面上的文字和按钮位置、把报告里的表格提取成结构化数据。具体实现上界面元素优先用 Windows UI AutomationUIA读取因为它是自带的辅助功能接口不依赖图像识别速度快很多。遇到非标准的控件或者图表区域再辅以 OCR 兜底我用的是 PaddleOCR 的本地模型数据不需要外传。执行模块负责“动手”。它控制鼠标键盘完成点击和输入也负责文件系统操作比如建立目录、重命名文件、调用 Word 程序。执行层我选的是 Playwright 控制浏览器来操作 Web 端 CTMS桌面端的一些辅助操作用 pywinauto。选 Playwright 而不是直接模拟鼠标是因为它对页面元素的定位更可靠而且天然支持等待页面加载完成。判断模块负责“动脑”。它接收感知模块输出的结构化文本先用确定性规则引擎处理比如日期计算、必填项检查、时间窗判断再用本地部署的 LLM 做语义层面的复核。这里有个重要的设计原则能用规则算的绝不用模型猜模型只处理规则覆盖不了的语义判断。这样既控制了成本也大幅降低了误报率。2.2 审核规则从“人脑里的经验”变成“机器可执行的配置”以前审核时我脑子里其实有一套隐性的 checklist比如访视日期有没有超窗、报告日期不能早于监查日期、AE 记录必须完整、方案偏离必须写了措施、监查员签名不能漏。这些经验从来没有被显式写下来过。做自动化倒逼我干了一件事把每条经验写成了结构化的规则存在单独的配置文件里。规则拆成三个层次。第一层是格式类规则对应报告字段的完整性检查。比如“报告编号不能为空”“中心编号必须存在于中心列表”。这类规则对准确性要求最高一旦触发就必须批注。第二层是计算类规则涉及日期和时间窗。比如“报告日期减去监查日期应在 5 个工作日内”“随访日期和上次随访日期的间隔符合方案窗口”。这类规则用 Python 的 datetime 计算就能完成。第三层是语义类规则。比如“SAE 表格中描述的重度不良事件是否在 AE 汇总表中有对应记录”“方案偏离的补救措施描述是否合理”。这类规则我会把相关表格片段截取出来交给 LLM 去判断要求它输出结构化 JSON包含问题定位、原始引用和批注建议。3. 从 CTMS 下载监查报告登录态、列表定位和文件落地细节3.1 登录态维护和验证码介入点是整个流程能否跑通的前提很多人做桌面自动化第一步就挂在登录上。CTMS 通常是内网系统加企业统一登录账号密码不能平铺在脚本里。我的做法是把凭据放到 Windows 凭据管理器里智能体启动时通过命令行读取避免明文出现在代码和日志中。验证码的问题比较麻烦。我一开始想用 OCR 自动识别实测成功率只有六成太不稳定。后来改成混合方案智能体检测到验证码或 MFA 推送时自动截图并通过企业 IM 发送给值守人员人工输入一次后会话状态会持续保持之后所有页面操作不再需要重新登录。另外需要在伪代码里做会话过期的检测。具体做法很朴素每一步操作前判断当前页面 URL 和页面标题是否还停留在“已登录”状态如果发现跳转到登录页就停下来发通知而不是盲目继续点击。3.2 列表定位不能写死选择器文本和相对位置才是稳定解CTMS 的前端升级频率不算低我第一次写完脚本之后不到一个月某个按钮的 CSS 选择器就变了整个流程中断。所以我在列表定位上采用了更抗变化的策略。首先优先用 Playwright 的 get_by_role、get_by_text 这类基于语义的定位方式而不是 XPath 里写死的类名。其次对于“下载”这类按钮我按它们在表格行内的相对位置来定位先按报告编号找到那行再找该行内的下载按钮。这样即使前端重构只要报告编号在功能就能跑。筛选逻辑上我会先按“审核状态 待审核”过滤再按日期区间和中心编号缩小范围。这里有个容易忽略的点系统里同名同编号的报告可能会因为多次上传而存在多个版本。所以我在抓列表时会把报告编号和更新时间一起取出来只处理最新版本。3.3 下载后的文件整理决定了后面所有步骤的效率下载目录如果不整理几十份文件堆在一起后续解析和批注定位会乱成一锅粥。我设定的目录规则是按任务日期建一个主目录里面每个中心一个小目录文件名统一重命名为“中心编号_报告编号_报告日期.docx”。重命名的过程相当于建索引后续批注生成时靠文件名就能把责任人和中心对应上。每份文件下载完我会做一次完整性校验文件大小不为 0能正常打开解析且解析出的段落数大于一个预设阈值。如果校验失败自动重试三次重试仍失败就记录异常跳过该文件并继续后续流程而不是整个任务崩溃。这个环节有个非常值得说的坑有相当一部分报告是老的 .doc 格式python-docx 无法直接读取。我的处理办法是先调用 Word COM 接口把文档转换成 .docx再做后续解析。这一步要放在下载之后立刻做越早统一格式后面越省心。4. 从报告内容到 Word 批注真正体现 AI 价值的核心链路4.1 把 Word 报告变成可检索的结构化文本保留段落映射关系想要在 Word 里加批注前提是得知道问题发生在哪个位置。所以文档解析这一步不能只满足于“能读到文字”还要保留文字与段落的对应关系。我用 python-docx 读取 .docx按顺序遍历所有段落和表格。表格里的每个单元格我会记录它属于哪个表格的第几行第几列并把单元格文本连同行号列号存进一个列表。这样后续如果发现“第二行第一列缺少签名”就能精确回溯到文档中的具体位置。对于 PDF 版本我用了 pdfplumber 提取文字并对提取结果按页组织。但这里要提醒一句如果系统同时提供 Word 版和 PDF 版一定要优先处理 Word 版因为 PDF 的排版信息和 Word 段落不是天然对应的后续定位批注会非常痛苦。PDF 版只在 Word 版缺失时才作为兜底。4.2 审核规则落地确定性规则先用代码算语义判断再交给 LLM我把审核规则全部配置化之后执行起来大概是这样的思路每条规则有一个唯一的 rule_id规则定义了它要检查的字段、判定逻辑以及命中时需要生成的批注模板。例如rule_id: AE_COUNT_MISMATCH 检查对象: AE汇总表与SAE表格中编号为AE-03的记录 判定逻辑: AE汇总表中是否存在该编号且严重程度描述一致 批注模板: 编号 {ae_no} 在SAE表格中描述为{severity_in_sae}但在AE汇总表中为{severity_in_ae}请核实并统一。确定性规则的代码实现很直接就是遍历解析出来的结构化文本。比较典型的包括日期是否缺失、时间窗偏差计算、必填字段是否为空、编号是否在校验字典里。这些规则计算速度快、结果可复现是整个审核的骨架。语义判断的部分我会把相关表格片段截取出来拼进一段 prompt 里打给本地 LLM。这里的关键是 prompt 要给出明确的输出格式约束。我用的输出格式是{ rule_id: PD_ACTION_MISSING, verdict: FAIL, quote: 受试者在2024-03-12发生方案偏离未记录采取的措施, comment: 方案偏离记录缺少补救措施请补充说明。 }为了让 LLM 只做文本内部的逻辑一致性判断而不去脑补外部知识我在 prompt 里反复强调了一句话只根据提供的文本内容判断禁止推测文本未提及的事实。这一条约束极大减少了 LLM 的幻觉问题。4.3 写 Word 批注的三条技术路线我全试过给你一条条对比python-docx 自带的功能里并没有“添加批注”的接口这是很多人做到这一步才发现的问题。我试过三种方案。第一种是 win32com 调 Word 的 COM 接口。这是最直接的方案代码量小批注能真实嵌入 Word 文档办公室环境里最推荐。核心代码大概是这样import win32com.client word win32com.client.Dispatch(Word.Application) word.Visible False doc word.Documents.Open(str(docx_path)) rng doc.Range(start, end) rng.Comments.Add(rng, 批注内容) doc.Save() doc.Close(False) word.Quit()这种方案的优点是简单可靠缺点是必须依赖安装了 Office 的 Windows 环境而且 Office 版本差异会导致 Range 的定位偏移。第二种是直接操作 docx 文件的 XML。docx 本质上是一个 zip 包可以在 word/comments.xml 里写入批注节点并修改 document.xml 中的引用关系。这条路不依赖 Office适合放在服务器上跑但实现复杂度明显更高需要你对 OOXML 规范有比较清楚的了解。我初期就是用这种方法在无 Office 的环境里验证流程后来主要用方案一。第三种是保底方案如果完全不想碰 Word 文件格式就把批注清单导出成 Excel 或 CSV让 CRA 自己在文档里定位修改。这个方案当然用户体验差一些但它能作为自动化流程的降级出口避免因为某个环境问题导致整个任务卡死。5. 跑起来之后真正的考验稳定性和防误报防漏报5.1 三层校验机制给自动化上了一道保险自动化最大的风险不是慢而是悄悄出错。我用三层校验来兜底。第一层是任务级校验开始任务时记录“预期报告数”结束时统计“实际下载并入库的报告数”两者不一致直接报警。这一步能拦截大部分下载遗漏。第二层是文档级校验每份报告必须跑完所有启用的规则并生成一个审核结果文件记录每条规则是 PASS、FAIL 还是 SKIP以及 SKIP 的原因。如果某份报告没跑完规则就中断状态文件里会留下记录断点续跑时优先补跑。第三层是批注级校验智能体生成完批注后脚本会重新打开文档检查批注数量等于本次审核的 FAIL 数量批注文本包含规则编号批注位置落在有效 Range 内。这一步能防住“脚本以为写上了其实没写进去”的尴尬情况。5.2 异常中断处理状态文件、截图留证和通知推送长时间无人值守跑流程难免遇到断网、系统弹窗、CTMS 超时这类意外。我给智能体加了一个统一的状态机每个阶段完成时把当前进度写入一个 JSON 文件。比如“已下载第 23/40 份报告已审核 22 份正在生成批注”。这样下次启动时直接读取状态文件从断点继续跑而不是从头再来。每次操作异常智能体会做三件事捕获当前屏幕截图保存到日志目录、把异常堆栈写入日志、通过企业 IM 的 webhook 推送一条带截图链接的消息给值守人员。这样即使在深夜跑任务第二天早上我也能快速知道昨晚发生了什么。5.3 实测效果40 份报告的审核时长从 13 小时压到 1 小时内在完善了三层校验和异常处理后我连续跑了一个完整月的实测。下载阶段40 份报告从 CTMS 下载到本地并完成重命名耗时约 18 分钟主要时间花在等待页面响应上。审核阶段确定性规则全部跑完加上 LLM 对语义规则的判断耗时约 25 分钟。最终生成 Word 批注并完成批注级校验耗时约 10 分钟。全程合计不到 1 小时。对比人工审核原来的 13 个小时直接压缩到一次早餐的时间。更关键的是确定性规则的漏判率降到了零因为代码不会像人一样疲劳。语义类规则的准确率大约在 92% 左右也就是说还有少量批注需要人工复核时删改。所以我的结论是这套方案消灭的是重复劳动和低端核对真正复杂的判断仍然需要人来兜底但人的时间和精力被解放了出来。6. 半年实测下来这几个坑和心得值得单独说一说先说说我踩过最深的几个坑。第一个坑是 CTMS 前端升级。有一次系统更新之后所有按钮的类名全部变化我的脚本直接停在登录页动弹不得。后来我彻底放弃了写死选择器的做法改成用文本内容和相对位置定位稳定性才真正建立起来。从那以后我再也没有因为页面改版而中断过任务。第二个坑是 Word 表格里合并单元格的处理。合并单元格在读取时会多次出现在行列数据中容易造成重复计数。解决方法是读取表格后做一次去重单元格的坐标以“第一个单元格的绝对位置”为准。第三个坑是 Office 版本差异带来的 Range 偏移。同一段文字在不同版本的 Word 里Range 的起始位置可能差几个字符批注会落到错误的字上。后来我在定位批注位置时不再用纯数字索引而是先查找一段唯一的标识性文本再以它的位置为锚点计算偏移问题迎刃而解。第四个坑比较隐晦Windows 的路径长度限制。报告文件名如果包含中心编号、报告编号、日期和作者名很容易超过 260 个字符导致文件操作报错。解决办法是统一缩短字段文件名里只保留中心编号、报告编号和日期三个要素。再说说我现在的心得。做这类桌面智能体最重要的原则是“规则先行AI 兜底”——凡是能用逻辑和计算解决的问题绝不上模型模型只负责语义判断。第二重要的原则是保留人工介入点。哪怕自动化再成熟验证码、异常确认、最终复核这几个环节都必须有人能接管。第三个经验是尽量把规则配置化不要在代码里手写死规则因为审核标准是会变的我今天就刚改过一个时间窗参数改配置文件比改代码要安全得多。这套东西跑通之后我又把它扩展到了其他文档审核场景比如知情同意书模板检查、中心文档列表核对。这个方向其实还有很多可以挖掘的地方比如把批注结果汇总成周报或者接入更深的医学逻辑判断。但有一点是确定的审核工作的核心价值从来不在“有没有点开文档、有没有看到空值、有没有签名”而在于“这些缺失背后意味着什么风险”。把这些机械的部分交出去人才能真正腾出手来做有价值的判断。