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 断言各种组合。验证方法上我一般会准备三组用例全满足、差一条、排除命中。三组都过才认为判定逻辑可信。这套东西搭完后面接量表、接随访、接统计报表都是顺水推舟。血泪经验就一句判定逻辑和数据存储一定要分家否则标准一改整个系统跟着塌。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

MySQL万年历日历表设计:从建表到插入的完整避坑指南

MySQL万年历日历表设计:从建表到插入的完整避坑指南

简介:一整套覆盖1970年1月1日至2100年12月31日的MySQL万年历数据库SQL脚本,面向需要处理日期、农历、节假日或时间计算的开发者和数据库使用者。压缩包内共1个文件,整体大小2.25MB,文件类型为SQL脚本,内含完整的建表语…

2026/10/13 12:55:20 阅读更多 →
MySql.Data.dll 8.0.13 x86 加载失败根因与修复指南

MySql.Data.dll 8.0.13 x86 加载失败根因与修复指南

简介:本资源为适用于.NET Framework环境的MySQL官方数据库驱动程序集合,面向C#/.NET开发者,解决Windows平台下x86架构项目连接与操作MySQL 8.0数据库的核心依赖问题,尤其适配Entity Framework Core 2.x/3.x及Entity Framework 6.x…

2026/10/14 8:11:06 阅读更多 →
使用three.js导入OBJ模型后切换与隐藏失效?TaoToken统一Key排查渲染器、照相机与灯光配置

使用three.js导入OBJ模型后切换与隐藏失效?TaoToken统一Key排查渲染器、照相机与灯光配置

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

2026/10/14 5:40:16 阅读更多 →

最新新闻

AI辅助量化策略开发:从零搭建ETF轮动回测框架

AI辅助量化策略开发:从零搭建ETF轮动回测框架

1. 为什么说现在是个人做量化的最好时机我接触量化交易差不多有七八年了,最早那会儿,个人想做一套自己的策略,门槛高得离谱。你得自己搭数据库、自己写回测框架、自己处理除权除息、自己搞行情接口,光是数据清洗就能耗掉大半个月。…

2026/10/14 8:10:50 阅读更多 →
M/o/Vfuscator 入门示例:用单条 mov 指令编译 prime.c,与 GCC 输出逐行对比

M/o/Vfuscator 入门示例:用单条 mov 指令编译 prime.c,与 GCC 输出逐行对比

编译器 【免费下载链接】movfuscator The single instruction C compiler 项目地址: https://gitcode.com/gh_mirrors/mo/movfuscator 点击查看 免费下载 本文以 movfuscator 仓库中的 overview/ 目录为例,展示如何用 M/o/Vfuscator 编译器(…

2026/10/14 8:10:50 阅读更多 →
openrig 装配指南:Node.js、tmux 与 Claude Code/Codex 本地工作台搭建

openrig 装配指南:Node.js、tmux 与 Claude Code/Codex 本地工作台搭建

1. 从 openrig 这个名字说起:它到底想解决什么问题第一次看到openrig这个词,我脑子里蹦出来的不是某个具体工具,而是一种"把散装零件拼成一台整机"的直觉。rig 在英文里本意是"装配、搭建",在工程语境里常指把…

2026/10/14 8:10:50 阅读更多 →
ECCV2020三维视觉技术路线图:点云处理与3D检测的范式跃迁

ECCV2020三维视觉技术路线图:点云处理与3D检测的范式跃迁

1. 这不是论文清单,而是一份三维视觉领域的技术路线图ECCV 2020那届会议我全程跟下来了,当时在苏黎世现场听oral报告时,就明显感觉到——三维视觉的范式正在悄悄转移。过去几年大家还在争论“点云该不该用MLP堆叠”,到了2020年&am…

2026/10/14 8:10:50 阅读更多 →
REA 接入前的技术核对:静态分析、后端和 MCP 分别检查什么

REA 接入前的技术核对:静态分析、后端和 MCP 分别检查什么

REA 接入前的技术核对:静态分析、后端和 MCP 分别检查什么 准备把 REA 接到 Agent 时,先区分三个问题:分析输入是什么,对应工作流需要什么依赖,Agent 是否真的连到了这项能力。它们出错时的表现可能相似,处…

2026/10/14 8:10:50 阅读更多 →
照片隐私保护:EXIF元数据清除工具与技术解析

照片隐私保护:EXIF元数据清除工具与技术解析

1. 为什么我们需要关注照片隐私问题上周帮朋友处理一张手机拍摄的证件照时,无意间用专业软件查看了它的EXIF信息,结果发现里面完整记录了拍摄时间、GPS坐标、手机型号甚至系统版本。这让我意识到——我们每天随手分享的照片,可能正在泄露大量…

2026/10/14 8:09:50 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/14 7:53:34 阅读更多 →