CRF 设计返工通常不是因为表单页面做得慢,而是字段定义、取值范围、跳转逻辑和版本变更没有在早期被系统化检查。本文从技术架构角度复盘一个 AI 辅助 CRF 设计与校验服务的实现思路,示例仅用于工程流程说明,不提供诊断、治疗、分诊或用药建议;所有阈值、风险提示和升级规则都应由医疗专业人员及机构规范确认。

问题背景:CRF 返工通常发生在哪里

做过 CRF 或数据管理系统的人都知道,后期返工往往集中在三个点。

第一类是字段设计返工。比如同一个含义的字段在不同表单里出现了多个命名:visit_datevisit_timefollowup_date,后续做数据导出、质控规则和统计口径时都要补映射。

第二类是逻辑冲突。字段 A 要求必填,但它只在字段 B 等于某个选项时才显示;或者某个日期字段允许为空,但下游规则又拿它计算间隔天数。

第三类是版本变更不可追踪。方案调整后,CRF 从 v1.2 改到 v1.3,字段删除、枚举值变更、必填状态修改没有结构化记录,测试环境和生产环境很容易出现口径不一致。

AI 在这里适合做“辅助发现”:根据字段描述生成候选字段、识别重复语义、提示规则冲突、总结版本差异。但是否发布,仍然必须人工确认。

技术目标与边界

本文的目标是构建一个轻量服务,覆盖 CRF 设计中的三个环节:

  • 字段模板:用结构化 JSON 管理字段名称、类型、枚举、必填、显示条件。
  • 逻辑校验:用 JSON Schema 做基础校验,用规则引擎做跨字段校验。
  • 变更追踪:将每次 CRF 版本保存到 PostgreSQL,并生成字段级 diff。

系统不处理真实患者数据,示例字段也只用于说明工程流程。真实项目中应加入权限控制、审计日志、数据脱敏、环境隔离和机构合规流程。

架构设计:把 AI 放在“建议层”

一个比较稳妥的设计是把 AI 输出限制在建议层,而不是直接写入正式版本。

CRF 需求文本

AI 字段建议

人工审核

CRF JSON 模板

JSON Schema 基础校验

规则引擎跨字段校验

校验报告

版本库 PostgreSQL

字段级 Diff

这样做的关键点是:AI 可以生成草稿,但不能绕过审核;规则可以提示风险,但不能替代医学或机构规范判断。

字段模板设计:先统一 Schema 再生成页面

CRF 字段建议不要直接生成前端表单,建议先落到统一 JSON 模型。下面是一个简化字段结构:

{
  "form_code": "baseline_visit",
  "version": "1.0.0",
  "fields": [
    {
      "name": "visit_date",
      "label": "访视日期",
      "type": "date",
      "required": true
    },
    {
      "name": "has_prior_condition",
      "label": "是否有既往相关情况",
      "type": "enum",
      "required": true,
      "options": ["yes", "no", "unknown"]
    },
    {
      "name": "prior_condition_note",
      "label": "既往相关情况说明",
      "type": "text",
      "required": false,
      "visible_when": {
        "field": "has_prior_condition",
        "equals": "yes"
      }
    }
  ]
}

这里的设计原则是字段语义稳定、展示逻辑显式、枚举值机器可读。AI 可以根据需求文本生成类似草稿,但字段命名、枚举含义、是否必填需要数据管理员或项目负责人确认。

用 Python 做字段重复和基础规则检查

下面代码演示一个最小可运行的检查器:检查字段重名、显示条件引用不存在字段、必填字段却依赖隐藏条件等常见问题。

from typing import Dict, List, Any

def validate_crf_template(crf: Dict[str, Any]) -> List[str]:
    errors = []
    fields = crf.get("fields", [])
    names = [f.get("name") for f in fields]
    name_set = set()

    for name in names:
        if not name:
            errors.append("存在未命名字段")
        elif name in name_set:
            errors.append(f"字段重复: {name}")
        else:
            name_set.add(name)

    for field in fields:
        name = field.get("name")
        visible_when = field.get("visible_when")

        if visible_when:
            ref = visible_when.get("field")
            if ref not in name_set:
                errors.append(f"{name} 的显示条件引用了不存在的字段: {ref}")

            if field.get("required") is True:
                errors.append(
                    f"{name} 设置为必填且存在显示条件,需确认隐藏时是否允许为空"
                )

        if field.get("type") == "enum":
            options = field.get("options", [])
            if not options:
                errors.append(f"{name} 是枚举字段,但未配置 options")

    return errors


if __name__ == "__main__":
    crf_template = {
        "form_code": "baseline_visit",
        "version": "1.0.0",
        "fields": [
            {"name": "visit_date", "type": "date", "required": True},
            {
                "name": "prior_condition_note",
                "type": "text",
                "required": True,
                "visible_when": {"field": "has_prior_condition", "equals": "yes"}
            }
        ]
    }

    result = validate_crf_template(crf_template)
    print("\n".join(result) if result else "CRF 模板检查通过")

运行结果会提示:

prior_condition_note 的显示条件引用了不存在的字段: has_prior_condition
prior_condition_note 设置为必填且存在显示条件,需确认隐藏时是否允许为空

这类检查不复杂,但可以把大量人工 review 中容易漏掉的问题提前暴露出来。

FastAPI 服务化:让设计、校验、版本进入同一流程

实际项目里,建议把 CRF 模板检查封装成接口,接入前端配置平台或内部数据管理系统。

from fastapi import FastAPI
from pydantic import BaseModel
from typing import Any, Dict, List

app = FastAPI(title="CRF Template QA Service")

class CRFRequest(BaseModel):
    crf: Dict[str, Any]

@app.post("/crf/validate")
def validate_crf(req: CRFRequest) -> Dict[str, List[str]]:
    errors = validate_crf_template(req.crf)
    return {"errors": errors}

进一步可以增加三个接口:

  • /crf/draft:接收需求文本,调用 AI 生成候选字段草稿。
  • /crf/validate:执行基础校验和跨字段规则检查。
  • /crf/version/diff:比较两个 CRF 版本,输出字段级变更。

AI 生成的草稿应标记为 draft,只有人工审核通过后才允许进入 approved 状态。

版本 Diff:返工成本要在变更时就量化

CRF 版本变更最怕“只改了一点点”这种描述。工程上建议保存完整 JSON,并在提交时生成结构化 diff。

示例 diff 输出可以长这样:

{
  "from_version": "1.0.0",
  "to_version": "1.1.0",
  "changes": [
    {
      "field": "visit_date",
      "change_type": "required_changed",
      "before": true,
      "after": false
    },
    {
      "field": "has_prior_condition",
      "change_type": "option_added",
      "before": ["yes", "no"],
      "after": ["yes", "no", "unknown"]
    }
  ]
}

对于每条变更,可以附加影响范围:

  • 是否影响历史数据导入。
  • 是否影响已配置的质控规则。
  • 是否影响导出字段映射。
  • 是否需要重新确认前端展示逻辑。

这些不是医学判断,而是工程影响分析。它能帮助团队在发布前评估返工范围。

规则引擎:跨字段逻辑不要硬编码在页面里

基础字段类型适合 JSON Schema,跨字段规则建议用独立规则层。比如:

{
  "rule_code": "R_VISIT_DATE_REQUIRED",
  "description": "示例规则:访视日期在基线访视表中必填",
  "when": {
    "form_code": "baseline_visit"
  },
  "then": {
    "field": "visit_date",
    "operator": "not_empty"
  },
  "severity": "error"
}

注意这里写的是“示例规则”。真实项目里,哪些字段必填、哪些规则属于错误、哪些属于警告,都应由机构 SOP、项目方案和数据管理计划确认。

规则引擎的收益是可维护。前端负责展示,后端负责校验,规则仓库负责版本化,避免同一条逻辑散落在页面、导入脚本和质控 SQL 里。

性能与落地建议

在小型 CRF 中,几十到几百个字段的校验通常不是性能瓶颈。更值得关注的是版本治理和规则可解释性。

我在类似项目里更推荐以下做法:

  • 字段模板、规则、版本 diff 都保存结构化 JSON。
  • AI 输出必须带来源提示和置信说明,不能自动发布。
  • 校验报告要给出字段名、规则编号、错误级别和修复建议。
  • PostgreSQL 表中保留 created_byapproved_byapproved_at 等审计字段。
  • CI 流程中加入 CRF 模板校验,阻止明显冲突的配置进入测试环境。

如果后续字段规模扩大,可以把规则执行做成异步任务,并对常用版本的校验结果做缓存。

总结

AI 优化 CRF 的价值不在于“自动生成表单”,而在于把字段漏项、逻辑冲突和版本差异提前暴露出来。工程实现上,可以用 JSON 模板统一字段语义,用 JSON Schema 和规则引擎分层校验,用 PostgreSQL 保存版本并生成 diff。

最后再强调一次:本文是技术架构和工程流程示例,不提供任何诊断、治疗、分诊或用药建议。AI 只能辅助发现冲突和漏项,CRF 的最终发布口径必须由数据管理人员、医疗专业人员和机构规范共同确认。

本文文献检索、文献挖掘以及文献翻译采用的是【超能文献| AI文献检索|AI文档翻译】

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐