AI优化病例报告表:从字段设计到逻辑校验,如何减少人工返工
CRF返工通常不是发生在“填表”阶段,而是埋在字段口径不一致、跳转逻辑冲突、版本变更未同步这些细节里。本文从技术架构角度复盘一个AI辅助CRF设计与校验服务的实现思路,重点放在字段模板、JSON Schema、规则引擎和变更追踪。本文仅讨论工程流程示例,不提供诊断、治疗、分诊或用药建议,所有校验规则都应由医疗专业人员和机构规范确认后发布。
问题背景:CRF返工为什么会集中爆发
做过病例报告表设计的人都知道,早期字段看起来只是“多几个输入框”,后面却会变成数据管理、质控、统计导出一起返工。
常见问题有几类:
- 同一概念被设计成多个字段,例如“首次发现日期”和“初次记录日期”口径重叠。
- 字段类型不稳定,第一版是文本,第二版改成日期,历史数据迁移困难。
- 逻辑校验只写在需求文档里,前端、后端、数据核查各实现一套。
- 版本修改没有差异说明,导致已采集数据无法判断是否需要回填。
- 必填规则和跳转规则相互冲突,用户在界面上无法完成提交。
AI适合介入的环节不是“自动决定CRF内容”,而是把需求文本、字段清单和规则描述转成结构化候选项,再辅助发现冲突、漏项和版本差异。最终发布口径仍然必须人工确认。
技术目标与边界
本文示例的目标是构建一个轻量CRF优化服务,支持三个能力:
- 根据字段描述生成可审阅的字段模板。
- 使用JSON Schema和规则引擎做结构校验与逻辑校验。
- 记录字段版本差异,输出变更影响提示。
技术栈选择:
- Python:规则处理和AI结果后处理。
- JSON Schema:定义字段结构和基础约束。
- FastAPI:提供模板生成、校验、版本比对接口。
- PostgreSQL:保存CRF版本、字段快照和校验结果。
- rules engine:承载跨字段逻辑,例如日期先后、条件必填、枚举互斥。
边界也要写清楚:示例中的日期范围、风险标记、升级规则都只是可配置工程规则,不代表任何医学标准。真实项目应结合方案、机构SOP、数据管理计划和伦理合规要求确认。
方案概览:把CRF从“文档”变成“可执行配置”
一个可维护的CRF系统,核心不是页面生成器,而是统一的配置模型。
推荐拆成四层:
- 字段层:字段名、编码、类型、单位、枚举、是否必填。
- 表单层:分组、显示顺序、条件展示、填写说明。
- 规则层:跨字段校验、条件必填、范围提示、提交阻断。
- 版本层:字段新增、删除、类型变化、枚举变化、规则变化。
AI可以放在“候选生成”和“冲突提示”位置。例如输入一段CRF设计说明,让模型输出字段草案;系统再用Schema校验结构,用规则引擎跑一致性检查。这样AI输出不会直接进入生产,而是进入审阅队列。
一个字段配置可以设计成下面的形态:
{"form_code":"visit_basic_v1","fields":[{"code":"visit_date","label":"随访日期","type":"date","required":true},{"code":"has_event","label":"是否发生事件","type":"enum","required":true,"options":["是","否"]},{"code":"event_date","label":"事件日期","type":"date","required_if":{"field":"has_event","equals":"是"}}]}这个结构的价值在于:前端渲染、后端校验、质控导出可以共用同一份配置,减少多端口径漂移。
实现步骤:字段模板、Schema校验和规则引擎
先定义CRF字段配置的JSON Schema。它负责检查结构是否完整,例如字段编码是否存在、类型是否合法、枚举字段是否提供选项。
fromjsonschemaimportvalidate,ValidationError field_schema={"type":"object","required":["form_code","fields"],"properties":{"form_code":{"type":"string"},"fields":{"type":"array","items":{"type":"object","required":["code","label","type"],"properties":{"code":{"type":"string","pattern":"^[a-z][a-z0-9_]*$"},"label":{"type":"string"},"type":{"enum":["string","number","date","enum","boolean"]},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"string"}},"required_if":{"type":"object","required":["field","equals"],"properties":{"field":{"type":"string"},"equals":{}}}}}}}}defvalidate_crf_schema(crf_config:dict)->list[str]:try:validate(instance=crf_config,schema=field_schema)return[]exceptValidationErrorase:return[f"Schema错误:{e.message}"]Schema只能解决“格式正确”,不能解决“逻辑合理”。例如条件必填引用了不存在的字段,或者日期字段之间存在不合理顺序,需要规则层继续处理。
下面是一个简化规则检查器:
defcheck_logic_rules(crf_config:dict)->list[str]:errors=[]fields=crf_config.get("fields",[])field_map={f["code"]:fforfinfields}forfieldinfields:code=field["code"]iffield.get("type")=="enum"andnotfield.get("options"):errors.append(f"{code}: 枚举字段缺少options")required_if=field.get("required_if")ifrequired_if:ref_field=required_if["field"]ifref_fieldnotinfield_map:errors.append(f"{code}: required_if引用了不存在的字段{ref_field}")else:ref=field_map[ref_field]ifref.get("type")=="enum":ref_options=ref.get("options",[])ifrequired_if["equals"]notinref_options:errors.append(f"{code}: 条件值{required_if['equals']}不在{ref_field}的枚举范围内")returnerrorsdefvalidate_crf(crf_config:dict)->list[str]:returnvalidate_crf_schema(crf_config)+check_logic_rules(crf_config)在实际项目中,规则不建议全部写死在Python里。可以把规则配置化,例如:
{"rule_code":"event_date_required","when":{"field":"has_event","operator":"eq","value":"是"},"then":{"field":"event_date","operator":"required"},"severity":"block","message":"当是否发生事件为“是”时,事件日期必填"}这样做的好处不是“更智能”,而是规则可以审阅、灰度、回滚,并且能保存每次发布的规则版本。
FastAPI服务:让CRF校验进入研发流程
CRF设计经常在Excel、Word和系统配置之间来回切换。建议尽早提供API,把校验嵌入研发流程,而不是等表单上线后再人工排查。
下面是一个最小FastAPI示例:
fromfastapiimportFastAPIfrompydanticimportBaseModelfromtypingimportAny app=FastAPI(title="CRF Quality Service")classCRFRequest(BaseModel):form_code:strfields:list[dict[str,Any]]@app.post("/crf/validate")defvalidate_crf_api(payload:CRFRequest):crf_config=payload.model_dump()errors=validate_crf(crf_config)return{"form_code":crf_config["form_code"],"passed":len(errors)==0,"errors":errors}本地运行:
pipinstallfastapi uvicorn jsonschema uvicorn main:app--reload前端CRF设计器、配置管理后台、CI脚本都可以调用这个接口。每次提交字段配置时先跑校验,未通过则不允许合并到发布分支。
版本比对:减少“改了哪里没人知道”
字段变更追踪是降低返工的关键。只看最新配置不够,还要知道从v1到v2发生了什么。
建议在PostgreSQL里保存不可变快照:
- crf_version:表单版本、状态、创建人、发布时间。
- crf_field_snapshot:字段code、字段JSON、版本号。
- crf_rule_snapshot:规则code、规则JSON、版本号。
- crf_change_log:变更类型、影响字段、审阅结论。
版本比对逻辑可以先做三类:
- added:新增字段。
- removed:删除字段。
- changed:类型、枚举、必填条件或标签变化。
defdiff_fields(old_fields:list[dict],new_fields:list[dict])->list[dict]:old_map={f["code"]:fforfinold_fields}new_map={f["code"]:fforfinnew_fields}changes=[]forcodeinnew_map.keys()-old_map.keys():changes.append({"type":"added","field":code})forcodeinold_map.keys()-new_map.keys():changes.append({"type":"removed","field":code})forcodeinold_map.keys()&new_map.keys():old_item=old_map[code]new_item=new_map[code]changed_keys=[keyforkeyin["label","type","required","options","required_if"]ifold_item.get(key)!=new_item.get(key)]ifchanged_keys:changes.append({"type":"changed","field":code,"keys":changed_keys})returnchanges这里AI可以辅助生成变更摘要,例如“event_date从普通字段变为条件必填,可能影响历史记录补录策略”。但这类摘要只能作为审阅提示,不能替代数据管理人员的正式变更说明。
AI介入点:生成候选,不直接发布
在CRF场景里,AI比较适合做四件事:
- 从需求描述中抽取候选字段。
- 提示字段命名不一致和语义重复。
- 根据字段关系生成候选校验规则。
- 对版本diff生成影响说明草稿。
不建议让AI直接完成以下动作:
- 自动确认字段是否必须采集。
- 自动决定提交阻断级别。
- 自动发布生产版本。
- 自动给出医学判断或风险结论。
工程上可以把AI输出放入draft状态,再进入人工审阅队列。审阅通过后,系统才生成正式版本号,并触发前端表单、后端校验和质控任务同步更新。
踩坑记录:几个容易忽略的细节
第一,字段code一旦发布就不要轻易修改。标签可以调整,code变化会影响历史数据、导出脚本和统计映射。
第二,枚举值要区分显示文本和值编码。只保存“是/否”看起来简单,但多语言、导出和兼容历史版本时会比较麻烦。
第三,规则要有严重级别。建议至少区分block和warning,前者阻断提交,后者提示核查。具体哪些规则阻断必须按机构规则确认。
第四,AI生成的字段说明容易“看起来合理”。必须用Schema、规则引擎、版本diff和人工审阅共同兜底,不能只靠自然语言描述判断质量。
总结
CRF返工的根源往往不是页面开发慢,而是字段、规则和版本没有形成统一的可执行配置。用Python、JSON Schema、FastAPI、PostgreSQL和规则引擎,可以搭建一个轻量的CRF质量服务,把问题提前暴露在设计和评审阶段。
AI在这个流程中的定位应当是辅助:帮助发现字段漏项、逻辑冲突和版本影响,提升审阅效率。最终CRF发布口径、校验阈值、阻断规则和变更说明,仍然需要由数据管理人员、医疗专业人员和机构规范共同确认。
本文文献检索、文献挖掘以及文献翻译采用的是【超能文献| AI文献检索|AI文档翻译】。