DSM-5精神障碍数据库设计:从表结构到诊断判定的工程实践

发布时间:2026/10/9 19:53:50

DSM-5精神障碍数据库设计:从表结构到诊断判定的工程实践 简介这份源码面向精神医学信息化开发者、医疗数据分析人员及Python数据库设计学习者提供基于DSM-5精神障碍分类体系的数据库构建方案解决精神障碍数据标准化存储与查询的问题。资源包共22个文件约1.03MB以8个Python脚本为核心配合3个docx文档、3个JSON数据文件、2个rst说明及pdm-python、toml、lock等工程配置文件覆盖数据导入、查询、更新等核心逻辑与依赖管理。项目采用PDM进行包管理通过锁定文件保证构建一致性并附带许可证与Git忽略配置目录结构清晰便于二次开发与维护。目前已有86人学习下载。读者可获取完整的数据模型设计思路、JSON数据组织方式及项目工程化配置范例适合作为精神医学数据平台或课程设计的参考起点。1. 从一份 DSM-5 精神障碍数据库说起为什么临床数据结构比算法更难写很多做医疗信息化的开发者第一次接触 DSM-5 精神障碍数据库设计时都会下意识觉得“不就是建几张表、录点诊断条目吗”。真动手才发现DSM-5 的诊断体系本身是多轴、层级、带时间维度的一个障碍类别下有若干诊断条目每个条目又由一组症状标准、病程标准、功能损害标准、排除标准共同约束还要记录每次评估的变化。用一张扁平表硬塞三个月后必然翻车。这个方向适合三类人一是做临床科研数据管理的开发者需要把量表、访谈、诊断结论结构化二是做心理健康类应用的后端工程师要给用户建立可追溯的评估档案三是医学信息方向的学生想找一个真实够复杂的领域练手数据库设计。它解决的核心问题只有一个——让“诊断”这件事从自由文本变成可查询、可统计、可回溯的结构化数据同时不丢失 DSM-5 原有的临床语义。下面我按自己实际搭过的一套方案把表结构、约束、查询和踩坑讲清楚。2. 先想清楚 DSM-5 的数据模型五类实体与三种关系2.1 为什么不能把诊断标准直接塞进一个字段最常见的错误做法是建一张diagnosis表字段写成name、criteria_text、note。这样做的直接后果是你无法回答“有多少患者满足 A 类症状中的至少 5 条”也无法做跨诊断的症状共现统计。DSM-5 的诊断标准本质上是可计数的条目集合必须拆成独立实体。我一般把整个模型拆成五类实体实体作用关键字段示例disorder障碍类别顶层分类如抑郁障碍、焦虑障碍code, name, chapterdiagnosis具体诊断某个可下结论的诊断条目code, name, disorder_idcriterion诊断标准某诊断下的一条标准diagnosis_id, criterion_type, min_countsymptom症状条目标准下可勾选的具体症状criterion_id, text, order_noassessment评估记录某患者某次评估的结果patient_ref, assessed_at, status三种关系分别是障碍类别到诊断是一对多诊断到标准是一对多标准到症状是一对多。评估记录则通过一张关联表assessment_symptom与症状多对多挂钩记录“这次评估里该症状是否命中”。2.2 用 SQLAlchemy 定义核心表结构下面这段是核心模型的骨架用 SQLAlchemy 声明式写法SQLite 和 PostgreSQL 都能跑。注意criterion_type用枚举区分“症状计数型”“病程型”“排除型”这是后面查询逻辑的分水岭。from sqlalchemy import ( Column, Integer, String, Text, ForeignKey, DateTime, Enum, Boolean ) from sqlalchemy.orm import declarative_base, relationship import enum Base declarative_base() class CriterionType(enum.Enum): SYMPTOM_COUNT symptom_count # 需满足若干条症状 DURATION duration # 病程时长要求 IMPAIRMENT impairment # 功能损害 EXCLUSION exclusion # 排除标准 class Disorder(Base): __tablename__ disorder id Column(Integer, primary_keyTrue) code Column(String(16), uniqueTrue, nullableFalse) name Column(String(128), nullableFalse) chapter Column(String(64)) diagnoses relationship(Diagnosis, back_populatesdisorder) class Diagnosis(Base): __tablename__ diagnosis id Column(Integer, primary_keyTrue) code Column(String(16), uniqueTrue, nullableFalse) name Column(String(128), nullableFalse) disorder_id Column(Integer, ForeignKey(disorder.id)) disorder relationship(Disorder, back_populatesdiagnoses) criteria relationship(Criterion, back_populatesdiagnosis) class Criterion(Base): __tablename__ criterion id Column(Integer, primary_keyTrue) diagnosis_id Column(Integer, ForeignKey(diagnosis.id)) criterion_type Column(Enum(CriterionType), nullableFalse) min_count Column(Integer, default0) # 症状计数型才有意义 description Column(Text) diagnosis relationship(Diagnosis, back_populatescriteria) symptoms relationship(Symptom, back_populatescriterion) class Symptom(Base): __tablename__ symptom id Column(Integer, primary_keyTrue) criterion_id Column(Integer, ForeignKey(criterion.id)) text Column(Text, nullableFalse) order_no Column(Integer, default0) criterion relationship(Criterion, back_populatessymptoms)逻辑说明Criterion是整套设计的枢纽。min_count只在SYMPTOM_COUNT类型下参与判定其他类型留 0 即可。order_no保证症状展示顺序和量表原文一致别小看这个字段临床上顺序错乱会直接影响访谈节奏。参数说明code建议用 DSM-5 官方编码体系长度 16 足够chapter存章节名便于按大类聚合Enum在 SQLite 里会存成字符串迁移到 PostgreSQL 时记得同步建枚举类型否则 Alembic 迁移会报类型不匹配。2.3 评估记录表把“某次评估”变成可回溯事件评估表是很多人漏掉的一环。没有它数据库只能存“标准”不能存“某个人在某个时间点的状态”。class Assessment(Base): __tablename__ assessment id Column(Integer, primary_keyTrue) patient_ref Column(String(64), indexTrue) # 外部患者标识不存真实身份 diagnosis_id Column(Integer, ForeignKey(diagnosis.id)) assessed_at Column(DateTime, nullableFalse) is_met Column(Boolean, defaultFalse) # 该诊断是否成立 note Column(Text) class AssessmentSymptom(Base): __tablename__ assessment_symptom id Column(Integer, primary_keyTrue) assessment_id Column(Integer, ForeignKey(assessment.id)) symptom_id Column(Integer, ForeignKey(symptom.id)) hit Column(Boolean, defaultFalse)patient_ref刻意用外部标识而非自增主键关联患者表是为了让这套库能嵌进已有系统不强制接管患者主数据。hit字段记录单条症状是否命中判定逻辑放在应用层数据库只负责存事实。3. 把诊断判定写成可复现的查询从症状计数到排除标准3.1 症状计数型标准的判定 SQLDSM-5 里大量标准是“满足以下 9 条中的至少 5 条”。这条规则落到 SQL 就是一个分组计数加阈值比较。-- 给定 assessment_id判断某条 symptom_count 型标准是否满足 SELECT c.id AS criterion_id, c.min_count, COUNT(*) FILTER (WHERE a_s.hit TRUE) AS hit_count, (COUNT(*) FILTER (WHERE a_s.hit TRUE) c.min_count) AS is_satisfied FROM criterion c JOIN symptom s ON s.criterion_id c.id LEFT JOIN assessment_symptom a_s ON a_s.symptom_id s.id AND a_s.assessment_id :assessment_id WHERE c.id :criterion_id AND c.criterion_type symptom_count GROUP BY c.id, c.min_count;逻辑说明FILTER (WHERE ...)是 PostgreSQL 语法SQLite 不支持需要改成SUM(CASE WHEN a_s.hit THEN 1 ELSE 0 END)。LEFT JOIN保证即使一条症状都没勾选也能返回hit_count 0而不是空行这对前端展示“未满足”状态很关键。参数说明:assessment_id和:criterion_id是绑定参数别用字符串拼接评估数据涉及敏感信息注入风险不值得冒。min_count直接来自标准表不要硬编码在 SQL 里否则改标准就得改代码。3.2 排除标准为什么要单独处理排除标准比如“排除物质使用所致”在逻辑上是否决项只要命中整个诊断不成立无论症状计数多高。所以判定顺序必须是先查排除再查计数。def evaluate_diagnosis(session, assessment_id, diagnosis_id): criteria session.query(Criterion).filter_by(diagnosis_iddiagnosis_id).all() # 第一步排除标准一票否决 for c in criteria: if c.criterion_type CriterionType.EXCLUSION: if _any_symptom_hit(session, assessment_id, c.id): return False # 第二步计数型标准逐条判定 for c in criteria: if c.criterion_type CriterionType.SYMPTOM_COUNT: if not _count_satisfied(session, assessment_id, c.id, c.min_count): return False return True逻辑说明把排除标准放在最前面是因为临床上排除项一旦成立后续症状再多也没有诊断意义。这个顺序如果写反会出现“物质所致症状被误判为独立障碍”的严重问题。参数说明_any_symptom_hit和_count_satisfied是两个内部函数前者查是否存在hitTrue后者复用 3.1 的计数逻辑。判定函数保持纯逻辑不碰事务方便单元测试。3.3 用 Alembic 管理表结构演进DSM-5 文本会随修订更新标准条目可能增删。用 Alembic 做迁移别手动改表。alembic init migrations alembic revision --autogenerate -m add criterion_type enum alembic upgrade head逻辑说明--autogenerate会对比模型和数据库差异生成迁移脚本但枚举类型变更它经常识别不全生成后必须人工检查。upgrade head前先在测试库跑一遍评估数据一旦迁移失败回滚成本很高。参数说明迁移脚本里对已有数据的criterion_type新增枚举值要写server_default否则已有行会因非空约束报错。4. 避坑与排查五个真实踩过的坑4.1 症状顺序错乱导致访谈结果偏差现象前端展示的症状顺序和量表原文不一致访谈者按屏幕顺序提问漏掉了靠后的关键条目。原因symptom表插入时没写order_no查询默认按主键排序而批量导入的顺序和原文顺序不一致。解决导入脚本里显式写入order_no查询一律ORDER BY order_no。导入后跑一次校验比对条目数和顺序。4.2 枚举类型在 SQLite 与 PostgreSQL 间不兼容现象本地用 SQLite 开发一切正常部署到 PostgreSQL 后插入criterion_type报类型不存在。原因SQLAlchemy 的Enum在 SQLite 存字符串在 PostgreSQL 会尝试建原生枚举类型而迁移脚本没同步创建。解决迁移脚本里显式CREATE TYPE或改用String加应用层校验。我一般选后者跨库省心。4.3 评估记录时间戳时区混乱现象同一患者两次评估的先后顺序在报表里颠倒。原因assessed_at有的写入本地时间有的写入 UTC混在一起排序就乱。解决统一存 UTC展示层再转本地时区。字段类型用带时区的DateTime(timezoneTrue)别用裸DateTime。4.4 排除标准被当成普通症状计数现象某诊断明明有排除项命中系统仍判定成立。原因判定函数先跑了计数逻辑排除逻辑写在后面或者根本没实现。解决按 3.2 的顺序排除优先。加一条单元测试构造“排除命中 计数满足”的用例断言结果为 False。4.5 患者标识直接存了真实信息现象审计时发现patient_ref里存了姓名拼音加出生日期。原因开发图方便直接把业务系统的展示名塞进来了。解决patient_ref只存不可逆的哈希或业务系统内部 ID真实身份留在原系统。这条不是技术问题是红线问题返工代价极大。5. 进阶技巧把诊断判定做成可测试的纯函数写到这儿数据库能跑、查询能出结果但真正决定这套设计能不能长期维护的是判定逻辑有没有被隔离成可测试的纯函数。我见过太多项目把判定散落在视图、模板、存储过程里改一条标准要翻五个文件最后没人敢动。我的习惯是数据库只存事实症状命中与否判定逻辑全部收敛到一个evaluator模块输入是评估 ID 和诊断 ID输出是布尔值和一份“哪条标准没过”的明细。这样带来三个好处一是可以写单元测试构造各种边界组合二是前端能展示“差 1 条症状就满足”的提示三是标准修订时只改数据不改代码。def evaluate_with_detail(session, assessment_id, diagnosis_id): detail [] criteria session.query(Criterion).filter_by(diagnosis_iddiagnosis_id).all() for c in criteria: if c.criterion_type CriterionType.EXCLUSION: hit _any_symptom_hit(session, assessment_id, c.id) detail.append({criterion: c.id, type: exclusion, passed: not hit}) if hit: return False, detail elif c.criterion_type CriterionType.SYMPTOM_COUNT: cnt _count_hit(session, assessment_id, c.id) passed cnt c.min_count detail.append({criterion: c.id, type: count, hit: cnt, need: c.min_count, passed: passed}) if not passed: return False, detail return True, detail逻辑说明返回(bool, detail)二元组detail里带上每条标准的命中数和阈值前端可以直接渲染成进度条。排除标准命中时立即返回不再继续判定符合临床逻辑。参数说明_count_hit只统计hitTrue的行数和 3.1 的 SQL 保持一致避免两处逻辑漂移。测试时用内存 SQLite 建库构造最小数据集跑 pytest 断言各种组合。验证方法上我一般会准备三组用例全满足、差一条、排除命中。三组都过才认为判定逻辑可信。这套东西搭完后面接量表、接随访、接统计报表都是顺水推舟。血泪经验就一句判定逻辑和数据存储一定要分家否则标准一改整个系统跟着塌。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/9 19:53:50

DPU深度解析:数据中心第三颗主力芯片的原理、落地与避坑指南

1. 从一个真实困惑说起:为什么突然所有人都在聊DPU如果你最近半年逛过技术社区、刷过架构师群聊,或者看过几场数据中心相关的发布会,大概率会被一个词反复砸中——DPU。我第一次听到这个词的时候,第一反应是"又一个新造的概念…

2026/10/9 19:48:48

Windows 10硬盘装机:企业级系统交付的工程化实践

1. 为什么“硬盘装机”不是懒人捷径,而是老手的压箱底技能“Windows 10 安装(硬盘装机)”这八个字,在绝大多数人的认知里,等同于“不会用U盘”“没刻录机”“临时救急”。我见过太多人把它当成万不得已的备选方案——直…

2026/10/9 20:54:07

用Anaconda搞定Python多环境:告别依赖冲突与版本灾难

如果你电脑里同时躺着几个Python项目——一个老项目必须用TensorFlow 2.14,另一个新项目要求PyTorch 2.x,还有一个AI编程智能体刚生成的脚本依赖一堆库——你迟早会遇到同一个问题:环境崩了。今天这篇是“AI 编程智能体”系列的第06篇&#x…

2026/10/9 20:54:07

Jedis 实战指南:从连接池到 Spring Boot 整合的 Redis 客户端入门

1. 为什么 Jedis 是理解 Redis 客户端的最佳起点很多人学 Redis 的路径是这样的:装好服务端,用命令行敲几个SET、GET,觉得挺简单,然后打开项目准备用 Java 连一下,结果第一步就卡住了——到底该用 Jedis、Lettuce 还是…

2026/10/9 20:54:07

pstack-claude:本地化Claude代码调试工作流

1. 项目概述:pstack-claude 是什么,它解决的是哪类开发者的实际痛点?pstack-claude 这个名字乍看像一个工具组合词,但拆解后非常有信息量:“pstack”是 Linux 系统中一个真实存在的诊断命令,用于打印指定进…

2026/10/9 20:54:07

impeccable:一套把代码质量自查变成开发默认动作的工作流

“impeccable”这个单词,是我做过最拧巴的一个项目代号。做工程的人都清楚,市面上从来就不缺“质量工具”:静态检查、代码规范、单测覆盖率、构建门禁,一抓一大把,每个单拎出来都能讲出十几页的“最佳实践”。但真正把…

2026/10/9 20:49:07

视频会议系统建设方案:架构选型、带宽计算与验收避坑指南

简介:一份视频会议系统建设方案文档,面向信息化建设人员、系统集成工程师及项目管理者,可作为远程集中监控与管理系统规划、投标或实施时的参考蓝本。文档结合视频监控系统IVMS-8700及视频报警监控等应用场景,强调各子系统&#x…

2026/10/8 10:03:18

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/9 20:15:56

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/8 6:05:44

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 0:04:27

毕业论文初稿完成后首次进行AIGC疑似度自查的摸底与分流策略

毕业论文初稿完成后首次进行AIGC疑似度自查的摸底与分流策略当数万字的学位论文初稿经历开题、实验、问卷与多轮文献梳理最终成形时,绝大多数研究生都会面临一道全新的形式审查关卡:AIGC 疑似度排查。在高校毕业审核流程中,盲审前的文本检测通…

2026/10/9 0:04:27

食堂节能改造源头工厂,商用厨房设备焕新方案广受好评

商用厨房作为餐饮经营、单位供餐的核心后勤阵地,其设备配置、动线规划与运维体系直接决定后厨作业效率、运营成本与合规性。从基础的灶具、制冷存储设备,到油烟净化、水处理等配套系统,每一个环节的合理性都与食品安全、能耗管控、消防安全挂…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑