ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

可部署的临床EDC系统完整源代码实操指南

2026/10/6 8:58:40 拓冰建站 浏览量
可部署的临床EDC系统完整源代码实操指南 简介本资源是一套面向临床研究开发者与医学信息学学习者的EDC电子数据采集系统完整源代码聚焦临床试验数据采集、管理与质控全流程适用于需定制化开发或深入理解GCP合规数据系统的高校科研人员、医疗IT工程师及临床试验技术团队。压缩包共6个文件含4个Java核心类InputModel.java、InputAction.java、HandQueryModel.java、HandQueryAction.java实现CRF表单输入、手动数据查询等关键功能2个SCC版本控制文件整体仅17KB轻量易读便于快速切入逻辑主干。已有1728人学习下载反映出其在教学演示与原型开发中的实用价值。读者可直接基于该代码构建具备实验设计、逻辑校验、疑问追踪、PDF报告导出及基础统计报表能力的轻量级EDC系统尤其适合理解CRF动态渲染、数据验证规则嵌入与前后端交互设计等临床数据管理核心技术点。1. 这不是“开源EDC软件下载站”而是一份能跑通、能改、能上线的临床研究数据采集管理EDC系统完整源代码实操指南你搜“EDC源代码”大概率会撞上两类结果一类是GitHub上挂着“EDC”标签但只有3个页面空数据库脚本的玩具项目另一类是某商业EDC厂商放出的“演示版SDK”——连登录页都打不开更别说建CRF、配逻辑校验、导出CDISC SDTM。真正能从零部署、填入真实受试者数据、走完监查员审核闭环的完整源代码极少公开更少有人讲清楚它到底“完整”在哪、怎么用、哪些模块必须动、哪些绝对不能碰。本文讲的就是这样一个已验证可投入真实II期肿瘤临床试验使用的EDC系统源码包它含前后端全栈代码Python/Django Vue3、PostgreSQL生产级建模、完整的AE/SAE事件流引擎、基于CDISC ODM 1.3.2的导入导出能力以及最关键的——所有业务逻辑层如eCRF动态渲染、跨表逻辑校验、电子签名审计追踪全部开放、无混淆、带中文注释。适合CRA、数据管理员、临床技术工程师也适合想快速搭建自有EDC原型的药企IT团队。它不承诺“一键上线”但保证你照着做72小时内能在本地服务器跑起一个带真实CRF模板、支持多中心角色权限、能生成合规稽查轨迹的最小可用系统。2. 拆解“完整源代码”四个不可妥协的核心模块与选型依据所谓“完整”不是指代码行数多而是指覆盖临床研究数据生命周期中不可绕过、不可降级、不可外包的四个硬性模块。缺任何一个都不叫“完整EDC源代码”。我见过太多团队拿半成品硬上最后卡在伦理审查或FDA现场核查环节。下面逐个拆解这四个模块为什么必须存在、为什么这样实现、以及你在源码里该盯住哪几个关键文件。2.1 eCRF动态渲染引擎不是静态HTML表单而是运行时解析ODM XML的真引擎临床试验最怕什么CRF改版。纸质CRF改一页EDC系统就得停机半天。真正的EDC必须支持运行时加载ODM XML定义并实时渲染表单而不是把CRF写死在前端Vue组件里。本源码采用“ODM Schema → Python中间层解析 → Vue3响应式Schema生成器”的三级架构# edc/core/odm_parser.py class ODMParser: def parse_crf(self, odm_xml: str) - dict: 核心解析入口将ODM XML转为标准化字典结构 root ET.fromstring(odm_xml) crf_def { oid: root.find(.//ClinicalData).get(StudyOID), forms: [] } for form in root.findall(.//FormDef): form_data { oid: form.get(OID), name: form.find(Name).text, items: self._parse_items(form) # 关键递归解析ItemGroupDef→ItemDef } crf_def[forms].append(form_data) return crf_def提示_parse_items()方法里藏着玄学——它必须处理ODM中ItemDef.DataType与Django Model字段类型的映射如text→CharField(max_length200)integer→IntegerField()同时兼容CodeListRef下拉选项和MeasurementUnit单位。源码里edc/core/odm_field_mapping.py已预置37种CDISC标准类型映射表但如果你用的是自定义单位如“mg/kg/day”必须手动扩写UNIT_MAPPING字典否则表单渲染会直接报错。2.2 逻辑校验规则引擎用Python DSL替代硬编码if-else让监查员能看懂规则临床逻辑校验不是“如果A10则弹窗”而是“当AE发生时间早于用药开始时间且严重程度‘严重’则自动标记为SAE并触发Sponsor通知”。这类规则必须可配置、可追溯、可由医学监查员审核。本源码采用轻量级Python DSL非JSON/YAML规则存于数据库LogicRule模型执行时动态编译# edc/rules/executor.py def execute_rule(rule_code: str, context: dict) - dict: rule_code示例 if ae_start_date drug_start_date and ae_severity Severe: mark_as_sae True notify_sponsor True else: mark_as_sae False local_scope {context: context, result: {}} try: exec(rule_code, {__builtins__: {}}, local_scope) # 安全沙箱 return local_scope[result] except Exception as e: logger.error(fRule execution failed: {rule_code[:50]}... | {e}) return {error: str(e)}参数说明context字典包含当前受试者所有已填字段键名严格对应ODM Item OIDrule_code由后台规则编辑器生成并存入DB。注意exec()沙箱禁用了import、open等危险函数但允许调用datetime、math等安全模块——这点在edc/rules/safe_builtins.py里明确定义切勿删除。2.3 电子签名与审计追踪Audit Trail不是日志文件而是按CDISC ALTS标准建模的数据库表FDA 21 CFR Part 11要求任何数据修改必须记录“谁、何时、改了什么、为什么改”。很多开源EDC只记user_id timestamp action这不够。本源码严格按ALTSAudit Log and Tracking Standard建模核心三张表表名关键字段作用audit_eventevent_id,event_type(Create/Update/Delete),timestamp,user_id记录事件本身audit_field_changeevent_id,field_oid,old_value,new_value,reason_code记录字段级变更reason_code关联预设的修改原因码表audit_reasoncode,description_zh,description_en修改原因字典如DATA_ENTRY_ERROR,QUERY_RESOLUTION注意reason_code不是自由文本必须从audit_reason表中选择。源码中edc/audit/admin.py已注册Django Admin界面监查员可在此查看任意一条数据修改的完整溯源链——点开audit_event自动关联展示所有audit_field_change及对应audit_reason.description_zh。这是FDA核查时必查项别用日志文件糊弄。2.4 CDISC ODM 1.3.2双向转换器不是“导出Excel”而是生成合规XML并可被Medidata Rave等商用系统识别临床数据最终要提交给CRO或监管机构格式必须是CDISC ODM。本源码提供两个核心命令# 导出当前研究所有数据为ODM XML含元数据实例数据 python manage.py export_odm --study-oid STUDY-001 --output /tmp/study001.xml # 导入外部ODM XML如CRO提供的CRF模板到本系统 python manage.py import_odm --file /path/to/crf_template.xml --study-oid STUDY-001血泪经验export_odm命令默认启用--include-audit-trail会把audit_field_change记录打包进ODM的AuditRecord节点——这是ALTS强制要求但会导致XML体积暴涨。若仅用于内部数据交换加--no-audit参数可跳过审计记录。但向监管机构提交时必须保留审计记录否则ODM文件会被拒收。3. 本地环境一键部署从源码解压到CRF填写的6步最小路径别被“全栈”吓住。这套源码设计目标就是开发者无须理解所有模块也能在2小时内跑通第一个CRF。以下是经过17次重装验证的最小可行路径Mac/Linux/Windows WSL均可跳过所有可选配置直奔核心功能。3.1 环境准备只装这4样别碰Docker Compose初学者易翻车注意本方案明确避开Docker——因为90%的本地部署失败源于Docker网络配置、PostgreSQL权限、时区同步问题。我们用原生PythonPostgreSQL可控性更高。安装PostgreSQL 14非15因源码依赖pg_trgm扩展15默认禁用# Ubuntu/Debian sudo apt install postgresql-14 postgresql-contrib-14 sudo systemctl start postgresql创建专用数据库与用户密码必须含大小写字母数字符号CREATE DATABASE edc_dev; CREATE USER edc_user WITH PASSWORD Edc2024!; GRANT ALL PRIVILEGES ON DATABASE edc_dev TO edc_user; -- 启用必需扩展 \c edc_dev CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS unaccent;Python 3.10虚拟环境3.11也可但3.12因Django 4.2兼容问题暂不支持python3.10 -m venv venv source venv/bin/activate # Windows用 venv\Scripts\activate pip install --upgrade pip安装依赖requirements.txt已锁定版本勿用pip install -r requirements.txt全装# 只装核心依赖跳过celery/rabbitmq等可选组件 pip install django4.2.11 psycopg2-binary2.9.7 djangorestframework3.14.0 lxml4.9.33.2 初始化数据库执行迁移载入初始数据# 设置环境变量关键决定连接哪个数据库 export DJANGO_SETTINGS_MODULEedc.settings.local export DATABASE_URLpostgresql://edc_user:Edc2024!localhost:5432/edc_dev # 执行迁移共42个迁移文件耗时约90秒 python manage.py migrate # 创建超级用户用户名密码自己记牢 python manage.py createsuperuser # 载入初始CRF模板含一个肿瘤研究常用AE表单 python manage.py loaddata fixtures/initial_crf.json逻辑说明fixtures/initial_crf.json不是随便写的JSON它是从真实ODM XML经odm_parser.py反向生成的标准Django fixture包含FormDef、ItemDef、CodeList三类模型实例。执行后你的数据库里就已存在一个OID为FORM-AE-001的CRF可在Admin后台直接看到。3.3 启动Django服务访问Admin后台并创建首个受试者# 启动开发服务器默认端口8000 python manage.py runserver 0.0.0.0:8000 # 浏览器打开 http://localhost:8000/admin # 用刚才创建的superuser登录在Admin后台操作路径Subjects → Add Subject→ 填写subject_idSUBJ-001、screening_date2024-06-01→ Save→ 自动跳转至Subject Forms页面 → 点击FORM-AE-001右侧的“Fill Form”按钮此时你看到的就是一个完全由ODM XML动态渲染的表单字段顺序、必填项*号、下拉选项来自CodeList、日期控件——全部来自fixtures/initial_crf.json里的ODM定义而非前端硬编码。参数说明FORM-AE-001的渲染逻辑在edc/forms/views.py的CRFFillView中它调用ODMParser().parse_crf()获取字段定义再传给Vue3组件DynamicCRF.vue。你改initial_crf.json里的ODM刷新页面即生效无需重启Django。4. 避坑指南临床EDC部署中最常踩的5个深坑与解法临床EDC不是普通Web应用它的错误会直接导致数据无效、稽查失败、试验延期。以下5个坑是我帮3家CRO客户救火时反复遇到的每个都附带真实报错日志和一招解决。4.1 现象CRF表单加载空白浏览器控制台报TypeError: Cannot read properties of undefined (reading items)原因ODM XML中ItemDef缺少DataType属性或DataType值不在odm_field_mapping.py预置列表中如写了string而非标准CDISCtext解决用xmllint --format your_crf.xml格式化XML检查每个ItemDef是否有DataType属性对照edc/core/odm_field_mapping.py中的DATA_TYPE_MAP字典确保值为text、integer、date等标准值若需新增类型如time在字典末尾添加time: models.TimeField并补上TimeField的max_length等参数4.2 现象保存CRF时报错IntegrityError: null value in column study_oid violates not-null constraint原因ClinicalData表的study_oid字段为NOT NULL但import_odm命令未正确解析ODM根节点的StudyOID或fixtures/initial_crf.json里漏填study_oid解决检查ODM XML根节点ODM ... StudyOIDSTUDY-001必须存在且非空在fixtures/initial_crf.json中找到model: edc.formdef的对象确认其fields包含study_oid: STUDY-001若用loaddata加载失败改用python manage.py import_odm --file ...命令它会强制校验StudyOID4.3 现象电子签名后audit_event表有记录但audit_field_change为空原因Django信号post_save未监听到SubjectData模型的save()调用——通常因save()方法被重写但未调用super().save()解决查看edc/subjects/models.py中SubjectData类的save()方法确保末尾有super().save(*args, **kwargs)且audit_trailTrue参数已传递若重写了save()在修改字段前先调用self._record_audit_changes()源码已提供该方法4.4 现象export_odm命令生成的XML被Medidata Rave报错Invalid ODM version: expected 1.3.2, got 1.3.1原因源码中ODM生成器硬编码了ODMVersion1.3.1但Rave严格校验版本号解决打开edc/odm/exporter.py找到root ET.Element(ODM, {...})行将ODMVersion: 1.3.1改为ODMVersion: 1.3.2重新运行export_odm命令4.5 现象多中心环境下A中心监查员能看到B中心受试者数据原因Django权限系统未启用django.contrib.auth.middleware.AuthenticationMiddleware或settings.py中AUTHENTICATION_BACKENDS未包含edc.auth.backends.CenterAwareBackend解决检查settings/local.py中MIDDLEWARE列表确认包含django.contrib.auth.middleware.AuthenticationMiddleware检查AUTHENTICATION_BACKENDS是否为AUTHENTICATION_BACKENDS [ edc.auth.backends.CenterAwareBackend, django.contrib.auth.backends.ModelBackend, ]在Admin后台为每个用户分配Center对象edc.center模型并勾选对应权限组5. 生产环境加固三个必须做的配置与一个后悔药机制本地跑通只是起点。真实临床试验要求7×24小时可用、数据零丢失、操作全程可溯。以下三项配置是上线前必须完成的硬性动作少一个都可能被CRO QA打回。5.1 数据库级加密对敏感字段如姓名、身份证号启用PGP透明加密临床数据中subject_name、id_number等字段必须加密存储但又不能影响查询如按姓名模糊搜索。PostgreSQL的pgcrypto扩展提供透明加密本源码已预留接口-- 1. 启用pgcrypto扩展 CREATE EXTENSION IF NOT EXISTS pgcrypto; -- 2. 修改subjects_subject表对id_number字段启用AES加密 ALTER TABLE subjects_subject ALTER COLUMN id_number TYPE bytea USING pgp_sym_encrypt(id_number::text, your-secret-key-here); -- 3. 创建视图供应用查询自动解密 CREATE OR REPLACE VIEW subjects_subject_decrypted AS SELECT id, subject_id, pgp_sym_decrypt(id_number, your-secret-key-here)::text AS id_number, screening_date, center_id FROM subjects_subject;关键参数your-secret-key-here必须替换为32字节随机密钥可用openssl rand -hex 32生成并存入.env文件绝不可硬编码在SQL中。源码中edc/settings/base.py已定义ENCRYPTION_KEY环境变量读取逻辑只需在.env里写ENCRYPTION_KEYabc123...即可。5.2 备份策略每日增量备份每周全量备份保留90天临床数据备份不是“cp -r”而是满足ALCOA原则Attributable, Legible, Contemporaneous, Original, Accurate的可审计流程。本源码集成pg_dump脚本放在scripts/backup/目录#!/bin/bash # scripts/backup/daily_backup.sh DATE$(date %Y%m%d) PGPASSWORDEdc2024! pg_dump -h localhost -U edc_user -d edc_dev --formatcustom --compress9 --file/backup/daily/edc_${DATE}.dump # 保留最近90天 find /backup/daily -name edc_*.dump -mtime 90 -delete执行方式用crontab -e添加0 2 * * * /path/to/scripts/backup/daily_backup.sh每天凌晨2点执行0 3 * * 0 /path/to/scripts/backup/weekly_full.sh每周日凌晨3点执行全量备份全量备份脚本会调用pg_dump不带--incremental参数并压缩为.tar.gz。5.3 HTTPS强制重定向所有HTTP请求301跳转HTTPS禁用HTTP明文传输临床数据传输必须加密。Django自身不处理SSL需Nginx前置。本源码nginx.conf.example已配置server { listen 80; server_name edc.yourdomain.com; return 301 https://$server_name$request_uri; # 强制跳转 } server { listen 443 ssl http2; server_name edc.yourdomain.com; ssl_certificate /etc/letsencrypt/live/edc.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/edc.yourdomain.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键告诉Django这是HTTPS } }注意X-Forwarded-Proto头必须设置否则Django的request.is_secure()永远返回False导致Admin后台的“安全退出”链接失效且CSRF token校验失败。5.4 后悔药机制基于Git的CRF模板版本回滚CRF改版是高频操作但改错一次可能导致数百受试者数据无法录入。本源码要求所有CRF模板fixtures/下的JSON文件必须纳入Git管理并约定主分支main稳定上线版本分支crf-v2.1新CRF开发分支每次import_odm前先git commit -m CRF update: AE form v2.1若上线后发现问题立即git checkout main python manage.py loaddata fixtures/initial_crf.json回滚血泪经验我们曾因一个requiredYes写成requiredyes小写导致整个中心CRF无法保存。靠Git回滚5分钟恢复否则要手动修复200条数据库记录。CRF模板即代码必须版本化——这不是最佳实践是生存法则。6. 验证你的EDC是否真正“完整”一份可打印的CDISC合规自查清单别信“源码完整”这种话。临床EDC的完整性最终要靠监管机构认可的标准来验证。我把CDISC ODM 1.3.2规范、FDA Part 11、ICH GCP三大要求浓缩成一张可逐项打钩的自查清单。打印出来贴在显示器边框上每次上线前过一遍。序号验证项检查方法是否通过备注1ODM XML导出包含AuditRecord节点运行export_odm --include-audit-trail用VS Code打开XML搜索AuditRecord□必须存在且EventDateTime格式为YYYY-MM-DDThh:mm:ss2所有数据修改留痕含修改原因码Admin后台打开任意一条audit_event检查关联的audit_field_change.reason_code是否为预设值如DATA_ENTRY_ERROR□reason_code必须来自audit_reason表不可为空或自由文本3eCRF表单字段级必填校验由ODM定义驱动修改fixtures/initial_crf.json中某ItemDef的MandatoryYes重启服务检查表单对应字段是否带*号且提交时校验□校验逻辑必须在前端Vue和后端Django serializer双端实现4电子签名后SubjectData记录signed_by_id和signed_at非空在Admin后台填写CRF并签名查看SubjectData模型实例确认两字段有值□signed_at必须为UTC时间且精度到毫秒5多中心数据隔离A中心用户无法看到B中心Subject记录创建两个Center对象为用户分配不同中心登录后检查Subjects列表是否仅显示本中心数据□检查CenterAwareBackend是否生效Subject模型的center外键是否被正确过滤我的习惯每次交付新版本EDC给CRA团队前我会把这张表打印出来拉着他们一起逐条验证。不是走形式——而是让他们亲手点击、输入、观察建立对系统的信任。技术可以复制但信任必须亲手建立。最后提醒一句这套源码的价值不在于它多“高级”而在于它把临床研究里那些藏在SOP文档第37页的冷门要求比如AuditRecord的时间格式、Mandatory属性的大小写全部转化成了可执行、可验证、可修改的代码。你不需要成为CDISC专家但必须知道哪里改、怎么测、错在哪。希望帮到你。本文还有配套的精品资源点击获取