简介:这份源码面向精神医学信息化研究者、Python 开发者及医学数据方向的学生,提供一套基于 Python 构建 DSM-5 精神障碍数据库的完整实现,用于解决精神障碍分类信息标准化存储、查询与管理的需求。压缩包共 22 个文件,约 1.03MB,以 8 个 py 脚本承担数据导入、查询与更新等核心逻辑,辅以 3 个 docx 与 2 个 rst 文档说明项目结构与用法,3 个 json 文件存放障碍分类数据,另有 pyproject.toml、pdm.lock、.pdm-python 等依赖与配置项,以及 license、gitignore 等工程文件。目前已有 86 人学习下载。项目采用 PDM 管理依赖,目录结构清晰,读者可据此理解医学数据建模思路,直接复用数据层代码,快速搭建可查询、可扩展的精神障碍信息库,为后续数据分析与科研应用打下基础。
1. 从一份 20 文件的源码包说起:DSM-5 精神障碍数据库到底能拿来干什么
如果你正在做精神医学相关的数据工具,或者需要把《理解 DSM-5 精神障碍》这类工具书里的分类信息结构化落地,那这份基于 Python 的 DSM-5 精神障碍数据库设计源码值得花时间拆一遍。整个包 20 个文件,7 个 Python 脚本、3 个 JSON 数据文件、3 个文档、2 个 ReStructuredText、1 个 PDM 项目定义、1 个锁定文件、1 个 TOML 配置,外加许可证和 Git 忽略配置。它解决的核心问题很具体:把 DSM-5 里散落在章节和诊断标准中的精神障碍信息,变成可查询、可更新、可扩展的结构化数据。适合谁?做精神医学研究工具的后端开发者、需要本地化诊断分类数据的数据工程师,以及想用 Python 快速搭一个领域数据库原型的从业者。它不是成品软件,是一套可复用的数据库设计骨架。
2. 拆开 odmantic 与 JSON 数据层:模型定义和存储选型为什么这么定
2.1 为什么用 odmantic 而不是裸 MongoDB 驱动
这个项目在依赖里选了 odmantic,而不是 pymongo 或 mongoengine。odmantic 是基于 Pydantic 的异步 MongoDB ODM,它的核心价值在于用 Python 类型注解直接定义文档模型,同时自带校验和序列化。对于 DSM-5 这种字段多、嵌套深、诊断标准条目经常需要校验的数据结构,裸驱动写起来会有一堆重复的 dict 操作,而 odmantic 让模型即 schema,改字段时类型检查能提前暴露问题。
常见做法是先用 Pydantic 定义诊断条目的字段约束,再让 odmantic 接管持久化。比如障碍名称不能为空、诊断编码要符合格式、关联的章节引用必须存在。这些约束写在模型里,插入和更新时自动生效,不用在业务层到处写 if 判断。
# mydsm5/models.py from typing import Optional, List from odmantic import Model, Field class DiagnosticCriterion(Model): """单条诊断标准,对应 DSM-5 中 A/B/C 等条目""" label: str = Field(..., min_length=1) # 条目编号,如 "A1" description: str = Field(..., min_length=5) # 标准正文 required: bool = True # 是否为必备条目 class Disorder(Model): """一个精神障碍的完整记录""" name: str = Field(..., min_length=2) # 障碍名称 code: Optional[str] = None # 诊断编码,可空 chapter: str # 所属章节 criteria: List[DiagnosticCriterion] = [] # 嵌套的诊断标准 notes: Optional[str] = None # 补充说明逻辑说明:DiagnosticCriterion把每条诊断标准拆成独立文档嵌入,Disorder用列表持有它们。参数上,min_length是最小长度约束,...表示必填。这样设计的好处是查询某个障碍时一次取出完整嵌套结构,不需要多次 join。坑在于嵌套列表过大时单文档会膨胀,DSM-5 里个别障碍标准条目多,但通常不超过几十条,MongoDB 单文档 16MB 上限完全够用。
2.2 JSON 数据文件怎么和模型对齐
包里有 3 个 JSON 数据文件,它们承担的是初始数据导入和静态分类信息的角色。常见做法是把 DSM-5 的章节结构、障碍清单、诊断标准分别放在不同 JSON 里,导入脚本读取后批量写入 MongoDB。这里的关键是 JSON 的字段名必须和模型字段一一对应,否则 odmantic 校验会直接报错。
# mydsm5/loader.py import json from pathlib import Path from odmantic import AIOEngine from mydsm5.models import Disorder async def load_disorders(engine: AIOEngine, json_path: str): """从 JSON 文件批量导入障碍数据""" raw = json.loads(Path(json_path).read_text(encoding="utf-8")) for item in raw["disorders"]: disorder = Disorder(**item) # 触发 Pydantic 校验 await engine.save(disorder) # 异步写入 print(f"imported {len(raw['disorders'])} disorders")逻辑说明:Disorder(**item)这一步会把 JSON 字典直接映射成模型实例,字段缺失或类型不对会立刻抛异常。参数上,json_path指向数据文件,engine是 odmantic 的异步引擎。注意 JSON 里的嵌套criteria数组会被自动转成DiagnosticCriterion列表,不需要手动构造。如果 JSON 里多出模型没定义的字段,Pydantic 默认会忽略,但建议在模型配置里设extra = "forbid"来强制对齐,避免数据静默丢失。
2.3 PDM 与 pyproject.toml 在依赖管理里的角色
项目用 PDM 而不是 pip + requirements.txt。PDM 把依赖声明放在pyproject.toml,锁定版本放在pdm.lock。这样做的好处是开发依赖和运行依赖能分组管理,锁定文件保证不同机器上装出来的版本完全一致。对于数据库项目,驱动版本和 ODM 版本的兼容性很敏感,锁定文件就是后悔药。
# 安装依赖并进入虚拟环境 pdm install pdm run python -m mydsm5.loader # 新增一个依赖 pdm add motor逻辑说明:pdm install会读取pdm.lock安装精确版本,pdm run在项目虚拟环境里执行命令,不用手动 activate。参数上,pdm add会同时更新pyproject.toml和锁定文件。注意.pdm-python文件记录的是解释器路径,换机器后可能需要重新pdm use指定 Python 版本。
3. 从零跑通导入与查询:环境、脚本和参数怎么配
3.1 环境准备与 MongoDB 连接配置
跑通这个项目需要 Python 3.9 以上和本地或远程的 MongoDB 实例。连接字符串通常放在环境变量或 TOML 配置里,不要硬编码在脚本中。项目里的 TOML 配置文件就是干这个的,常见做法是定义一个[database]段,把 host、port、dbname 写进去,脚本启动时读取。
# mydsm5/config.py import tomllib from pathlib import Path def load_config(path: str = "config.toml") -> dict: """读取 TOML 配置,返回数据库连接参数""" with open(path, "rb") as f: return tomllib.load(f) # 使用示例 cfg = load_config() mongo_uri = f"mongodb://{cfg['database']['host']}:{cfg['database']['port']}" db_name = cfg["database"]["dbname"]逻辑说明:tomllib是 Python 3.11 内置的 TOML 解析库,低版本需要装tomli。参数上,host默认localhost,port默认27017,dbname建议用dsm5。注意连接字符串里如果带认证信息,不要提交到版本库,用.gitignore排除本地配置文件。
3.2 初始化引擎与批量导入的完整流程
把模型、配置、加载器串起来,就是一个完整的导入流程。odmantic 的AIOEngine需要传入 MongoDB 客户端,异步写入时注意控制并发量,避免一次性打太多请求。
# mydsm5/init_db.py import asyncio from motor.motor_asyncio import AsyncIOMotorClient from odmantic import AIOEngine from mydsm5.config import load_config from mydsm5.loader import load_disorders async def main(): cfg = load_config() client = AsyncIOMotorClient( host=cfg["database"]["host"], port=cfg["database"]["port"] ) engine = AIOEngine(client=client, database=cfg["database"]["dbname"]) await load_disorders(engine, "docs/disorders.json") client.close() if __name__ == "__main__": asyncio.run(main())逻辑说明:AsyncIOMotorClient是 motor 的异步客户端,AIOEngine包装它提供 ODM 能力。参数上,database指定库名,load_disorders的第二个参数是 JSON 路径。注意client.close()要放在所有写入完成后,否则可能丢数据。如果数据量大,建议分批engine.save_all()而不是逐条 save。
3.3 查询与更新:常用操作和索引建议
导入完成后,查询是主要使用场景。odmantic 支持用模型字段构造查询条件,也支持直接写 MongoDB 查询表达式。对于 DSM-5 数据库,按章节查、按名称模糊查、按诊断编码精确查是最常见的三类。
# 按章节查询 disorders = await engine.find(Disorder, Disorder.chapter == "精神分裂症谱系") # 按名称模糊查询(正则) disorders = await engine.find(Disorder, {"name": {"$regex": "抑郁"}}) # 更新单条记录 disorder = await engine.find_one(Disorder, Disorder.code == "F20.9") disorder.notes = "更新说明" await engine.save(disorder)逻辑说明:engine.find返回列表,find_one返回单条或 None。参数上,正则查询用$regex,注意转义特殊字符。建议在name和code上建索引,否则数据量上来后查询会变慢。odmantic 本身不自动建索引,需要在模型里用Field(index=True)或在初始化时手动创建。
4. 避坑与排查:导入失败、字段错位和版本冲突的常见问题
4.1 导入时报 ValidationError,字段类型对不上
现象:运行加载脚本时抛pydantic.ValidationError,提示某个字段期望str但收到int或None。原因通常是 JSON 数据里字段类型和模型定义不一致,比如诊断编码在 JSON 里是数字,模型里定义成字符串。解决:先检查 JSON 对应字段的实际类型,要么改 JSON,要么在模型里用Union或validator做类型转换。我一般会在加载前先跑一遍Disorder(**item)的 dry-run,把报错条目单独打印出来。
4.2 嵌套列表导入后变成空数组
现象:导入成功,但查询时criteria字段是空列表。原因多半是 JSON 里嵌套数组的键名和模型字段名不一致,比如 JSON 用"criteria_list"而模型定义的是criteria。Pydantic 默认忽略多余字段,所以不会报错,但数据也没进去。解决:在模型配置里设extra = "forbid",让多余字段直接报错;或者导入前用脚本做一次键名映射校验。
4.3 PDM 锁定文件与本地 Python 版本不匹配
现象:pdm install报错,提示某个包需要 Python 3.10 但当前是 3.9。原因:pdm.lock里记录的依赖解析结果是基于某个 Python 版本生成的,换版本后部分包不兼容。解决:用pdm use切换到锁定文件对应的 Python 版本,或者删掉pdm.lock重新pdm lock。注意重新锁定会升级依赖版本,可能引入不兼容,生产环境建议固定 Python 版本。
4.4 MongoDB 连接超时或认证失败
现象:脚本卡在连接阶段,或报OperationFailure: Authentication failed。原因:连接字符串里没带认证信息,或者 MongoDB 没启动。解决:先确认 MongoDB 服务在跑,mongosh能连上;如果开了认证,在AsyncIOMotorClient里传username和password。常见做法是把认证信息放环境变量,脚本里用os.environ读取,不要写死在 TOML 里提交。
4.5 异步写入时事件循环冲突
现象:在 Jupyter 或某些框架里调用asyncio.run(main())报RuntimeError: This event loop is already running。原因:当前环境已经有运行中的事件循环,不能再嵌套asyncio.run。解决:改用await main()直接在已有循环里执行,或者用nest_asyncio打补丁。我一般会在脚本入口判断是否已有循环,有就直接 await,没有才asyncio.run。
5. 进阶用法:把 DSM-5 数据库接进检索服务和数据校验流水线
跑通基础导入查询之后,这个项目真正的价值在于它能作为上层应用的数据底座。一个具体技巧是给Disorder模型加一个全文检索字段,把名称、章节、诊断标准描述拼成一个search_text,在 MongoDB 里建文本索引,这样前端搜索框可以直接用$text查询,比正则快一个量级。
# 在模型里增加检索字段 class Disorder(Model): # ... 原有字段 ... search_text: str = "" def build_search_text(self): parts = [self.name, self.chapter] parts += [c.description for c in self.criteria] self.search_text = " ".join(parts) return self # 导入时构建 disorder = Disorder(**item).build_search_text() await engine.save(disorder)逻辑说明:build_search_text把分散字段拼成单一文本,$text查询会走索引。参数上,文本索引需要在 MongoDB 里手动创建:db.disorder.create_index([("search_text", "text")])。注意中文分词 MongoDB 社区版支持有限,如果检索效果不理想,常见做法是外接一个轻量分词库,在写入前把分词结果也存进去。
另一个进阶方向是数据校验流水线。DSM-5 的诊断标准有明确的逻辑关系,比如某些条目是「必备」、某些是「至少满足几条」。可以在模型上加一个validate_criteria方法,导入和更新时自动检查条目组合是否合法,把不合规的数据挡在库外。这样上层应用拿到的数据天然满足业务规则,不用在每个查询里重复判断。
def validate_criteria(self) -> bool: """检查诊断标准组合是否满足最低要求""" required = [c for c in self.criteria if c.required] optional = [c for c in self.criteria if not c.required] if not required: raise ValueError(f"{self.name} 缺少必备诊断条目") if len(optional) < 1: raise ValueError(f"{self.name} 至少需要一条可选条目") return True逻辑说明:这个方法在engine.save前调用,不满足就抛异常。参数上,required和optional的划分来自模型字段,实际业务里可能更复杂,比如「满足任意两条」这种规则,可以用计数器或位掩码实现。我一般会把校验规则单独抽成一个模块,方便后续按 DSM-5 更新版本调整。
从那以后我每次拿到这类领域数据库源码,都会先跑一遍最小导入,确认模型和 JSON 对齐,再去看索引和校验逻辑。这份 DSM-5 数据库源码包结构清晰,odmantic 的选型也合理,适合作为精神医学数据工具的原型底座。希望帮到你。
本文还有配套的精品资源,点击获取