ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

药物数据治理:统一INN、ATC与化学结构标识的完整方法论

药物数据治理:统一INN、ATC与化学结构标识的完整方法论 做药物数据治理十几年最常被问的一句话是为什么同一个药在三个正规数据库里能出现三套完全不同的身份证拿阿司匹林来说WHO国际非专利名INN叫 acetylsalicylic acidCAS 号是 50-78-2ATC 编码至少能列出来三个——止痛场景下是 N02BA01抗血小板场景下是 B01AC06口腔局部用药场景下是 A01AD05到了化学结构管理员手里又可能写成 SMILES、InChI 或者 InChIKey。药品通用名、ATC、化学结构每套体系都有人管、都很有序但彼此之间没有天然外键合在一起就是一团乱麻。这篇文章想聊的就是我实际项目中基于 WHO INN 数据方法论把这三套标识统一管理起来的完整经验包括 INN 数据怎么获取和清洗、化学结构指纹怎么算不踩坑、ATC 映射怎么设计表结构、映射置信度怎么打分以及最后几个让我加班的真实事故。适合正在做药品主数据、药物警戒数据库、真实世界研究数据治理或者想把研发和临床数据打通的朋友。1. 三套身份证各自的管理逻辑和对不齐的根本原因1.1 通用名WHO INN 是如何给一个分子起名的国际非专利名INNInternational Nonproprietary Name由世界卫生组织下属的 INN 专家委员会审定是面向全球的公共财产命名体系。它解决的第一个问题是让不同国家、不同药典、不同语言的人说起同一个药时至少有一个共同指称。比如中国的对乙酰氨基酚美国药典叫 acetaminophen而 WHO 正式推荐的国际非专利名是 paracetamol三者本质同一物质但名字体系不互通。INN 命名的关键不等于随机造词。它的词干系统本身就是一套药理学分类编码看到后缀 -olol 一般就是 β 受体阻滞剂-pril 往往是 ACE 抑制剂-sartan 是沙坦类药物-gliptin 是 DPP-4 抑制剂-mab 则是单克隆抗体。所以即使一条记录里完全没有 ATC 编码仅凭 INN 词干也可以初步判断药物类别。这里很容易踩的一个坑是INN 名称并不是一锤定音。新药通常会先进入 Proposed INNpINN清单经过一段评审期后才成为 Recommended INNrINN部分 pINN 可能被修改甚至废弃。名称生命周期需要被记录。此外同一个活性成分往往有多个 INN 相关条目比如 metoprolol 和 metoprolol tartrate 各自独立被命名前者是活性碱基后者是盐。做主数据时如果只抓 INN 字符串不做实体拆分后面一定会出乱子。1.2 ATC记住它是一个分类号不是标识符ATCAnatomical Therapeutic Chemical分类体系由 WHO 药物统计方法学合作中心WHOCC位于奥斯陆维护。它的结构是五级层级第一级是解剖学主类比如 A 是消化道和代谢B 是血液和造血器官C 是心血管系统N 是神经系统第二、三、四级逐步细化到治疗亚类、药理亚类、化学亚类第五级才是具体化学物质。问题就出在这个具体化学物质上。ATC 的第五级本质上是在说这个物质在这个治疗语境下属于哪一类而不是在确认这个物质到底是哪个分子。所以同一个 INN 名称对应多个 ATC 编码是常态阿司匹林就是典型。再有ATC/DDD Index 每年都会更新一次2023 年的某个分类到了 2024 年可能被合并、拆分或调整。如果哪位同事图省事在数据库主表里放一列atc_code打算存一个单值那后续每次 ATC 更新都在制造数据事故。用一句话概括ATC 解决的是它被当作什么药用不解决它是什么。1.3 化学结构从 SMILES 到 InChIKey结构指纹不等于物质指纹化学结构是最接近分子物理本质的一层标识。从业者手里常见的结构表示工具有五种CAS 号、SMILES、InChI、InChIKey、Molfile。CAS 号由美国化学会分配检索能力强但属于商业闭源维护SMILES 是人类可读的线性字符串但同一个分子能写出多种合法写法原子顺序、芳香性写法、电荷表示稍有不同字符串就不一致InChI 是 IUPAC 推出的规范表示包含分层信息InChIKey 是 InChI 的定长哈希只有 27 个字符非常适合做数据库索引和跨库比对。但这里有个反直觉的事实即便有了 InChIKey你得到的也只是一个化学结构的确定性字符串它不等于物质身份。同分异构、立体异构、互变异构、盐型、溶剂合物、外消旋体都会让结构指纹分叉或归并。比如氧氟沙星和左氧氟沙星一个是外消旋体、一个是左旋体standard InChIKey 的立体化学层不同绝不能合并而如果一个数据源把 atorvastatin 写成了不带盐的游离酸结构另一个数据源写的是 atorvastatin calcium整分子 InChIKey 又不一致。所以化学结构层的统一首先要定义清楚到底把哪一个化学视图当作标准键。1.4 三者为什么天然对不齐目的不同、粒度不同、更新频率不同把三套体系放在一起看更清楚维度WHO INN 通用名ATC 分类化学结构键管理方WHO INN 专家组WHOCC 奥斯陆中心IUPAC / CAS / 各数据库厂商更新频率每年 1-2 批新清单每年一次随来源库不定期无统一版本表达内容命名与词干语义解剖-治疗-化学分类分子连接性与立体化学唯一性一个基础物质可对应多个盐型名称一个物质可对应多个分类编码一个结构可对应多种标准化表达适用场景标签、注册、临床沟通用药统计、DDD 计算、处方分析化学信息学、化合物比对、知识图谱三者对不齐是体系设计使然命名体系关心法定主体和临床沟通ATC 关心治疗分类和统计口径化学结构关心分子本质。统一管理的目标不是强行把三者拧成一个字符串而是保留各自语义同时建立可追踪、带置信度、可版本回溯的映射关系。这套方法论的内核由此展开结构键负责确定是不是同一个分子INN 负责提供权威可读的名字ATC 负责补充在什么语境下被怎么分类再加上来源和版本字段让每一条映射都说得清来龙去脉。2. WHO INN 数据的获取与加工先有一张干净的权威命名表2.1 数据源选型官方源优先别拿维基百科当字典主数据项目最忌讳的事情是从二手页面批量抄名字。维基百科、部分医药资讯站点的词条质量参差不齐同一个药在不同词条里可能用不同写法还夹杂大量商标名、商品名、不规范缩写用来做人肉搜索可以用来建主数据表就是在给自己埋雷。可用的官方源头主要有三个WHO 官网 INN 栏目可以找到历年发布的 INN List、词干清单Stem List、以及部分 INN 数据库在线检索功能。INN List 通常按批次发布每条记录会标注 pINN/rINN 状态、发布批次、名称变形和特殊备注。WHOCC 官网 ATC/DDD Index每年更新年度版本提供结构化的数据导出文件包括 ATC 编码、层级名称、DDD 值和部分对应成分名。这个文件是后续建立 INN-ATC 映射的关键原料。开放化学数据库辅助源比如 PubChem 的 CID、InChI、InChIKeyDrugBank 的开放版本。它们不负责起 INN 名但能弥补结构指纹字段的缺失。如果你所在团队拿到的 INN 数据是第三方整理好的 CSV 甚至 API务必确认清楚其原始版本日期、是否保留 proposed/recommended 状态、是否包含已废弃条目。没有版本标识的 INN 表我不建议作为入库基础。2.2 清洗与归档不是下载完就算数INN 原始数据拿到手之后第一步是进裸数据区不要直接并入主表。我习惯先做四件事计算原始文件哈希如 SHA-256记录获取时间、来源 URL、文件版本号形成一条数据源记录。解析 INN List把INN 名称、词干、状态proposed / recommended / abandoned 等、发布批次、备注拆成结构化字段。做 Unicode 规范化。这里特别容易出问题INN 原始名称里可能混有全角括号、半角括号、不可见控制字符、不同编码的连字符。推荐用unicodedata.normalize(NFKC, text)统一再统一转小写、去首尾空格。别小看这一步很多明明看起来一样但字符串匹配不上的案子就是坏在不可见字符上。对同一物质可能出现的多语言名称、旧名、异名不直接塞进主字段而是单独建别名表保留来源和语言标签。这里有一个取舍经验不要把别名和被废弃的 pINN 直接删掉即使你的应用场景只需要 rINN。原因有两个历史文献和注册文件里很可能还写着旧名下游系统做数据回溯时需要知道当年这个药叫什么。别名表又不占多少空间删了反而后悔。经过这一步你会得到一张inn_master表核心字段大致包括inn_id自增主键、inn_name规范化后的标准名、inn_status、list_batch、stem、source_version、published_date。2.3 INN 词干表一个免费、但必须小心使用的药理分类引擎WHO 公布过 INN 词干细胞列表这是整条数据链路里最被低估的一手资料。词干的用法在缺失 ATC 或结构匹配失败时尤其有价值一个陌生 INN只要后缀是 -sartan你至少可以判断它属于血管紧张素 II 受体拮抗剂方向像 -mab 家族还能通过整个名称的组成进一步推测是哪种单克隆抗体技术类别-ximab、-zumab、-umab 等。不过必须强调词干只能提供参考不能作为唯一结论。一是因为不少老药命名早于现代词干体系后缀可能不体现现代药理二是因为某些词干本身涵盖的亚类跨度较大三是企业在申请 INN 时的命名倾向不完全统一。我在实际项目中用词干的姿势是把它当成一个候补语义信号在映射置信度打分时词干匹配只给中等权重不作为决定性证据。它真正擅长的事情是帮你在人工抽审时快速发现严重误映射。3. 统一映射的核心方法论以结构指纹 权威名 分类语境三层建模3.1 实体粒度在活性成分层级设计主数据统一管理的第一步不是写代码而是决定一行数据到底代表什么粒度。我和不同团队打过交道最经典的错误是有人把含药物的片剂当成实体有人把盐型当成实体有人把活性碱基当成实体各方在一个表里互相覆盖。正确的做法是先明确分层。我推荐的三层模型如下chemical_entity活性实体代表去掉盐离子、溶剂、结晶水之后的分子活性部分是整条链路里的稳定节点用标准 InChIKey 作为持久身份。一个活性实体可以对应多个名称。ingredient_label名称标签同一个活性实体在官方体系里的各种命名形态包括 INN、USAN、药典名、中文通用名、盐型全称。比如 metoprolol 和 metoprolol tartrate 属于同一个活性实体下的两个名称标签但它们在制剂层面有差异。atc_contextATC 语境活性实体和 ATC 编码之间的一对多关系每条关系里记录适用语境、参考 DDD、来源版本和置信度。这么设计的理由是如果你把实体定在盐型层那么游离酸和钠盐会被拆成两条记录后续做药物暴露统一分析时很难聚合如果你把实体直接定在名称层那么同一个分子的不同命名会被当成不同药。而活性实体层刚好处于一个平衡点结构上可验证临床上可通过多个名称互相引用。另外要注意大分子药物的特例。抗体、融合蛋白这类生物药没有传统小分子意义上的 InChIKey强行计算会失败。此时应以 INN 本身作为主标识并用靶点、蛋白质序列、细胞表达系统等辅助字段做身份确认。方法论不能照搬。3.2 化学结构归一化从多个原料 SMILES 到标准 InChIKey跨库合库时最常见的原料是各种来源的 SMILES 字符串。虽然 SMILES 写法千奇百怪但我们可以统一用 RDKit 把结构转成标准 InChIKey。流程大概如下from rdkit import Chem from rdkit.Chem import inchi from rdkit.Chem.SaltRemover import SaltRemover remover SaltRemover() def structure_to_key(smiles: str) - str: mol Chem.MolFromSmiles(smiles) if mol is None: return # 去除对离子/盐的共价片段 mol remover.StripMol(mol, dontRemoveEverythingTrue) # 用标准 InChIKey 作为持久指纹 return inchi.MolToInchiKey(mol)这段代码只是示意真实环境里还要补充几层策略盐处理策略SaltRemover可以去除共价连接的正负离子片段但你要先确认自己的业务规则到底是保留盐型还是统一到活性部分。我的建议是结构键统一到活性部分盐型信息单独存字段这样既能聚合分析也不丢制剂差异。电荷与 pH 形态同一分子在不同 pH 下可能呈现不同质子化状态建议统一输出为标准中性形态。互变异构标准 InChI 本身能处理相当一部分互变异构差异但如果你的数据源含有非标准表达可能需要先做互变异构归一再用 InChI 计算。立体化学除非有明确说明否则不要把 R/S 或 L/D 异构体强制归一。比如左氧氟沙星与氧氟沙星必须留在不同的活性实体下。一个基础的经验法则入库之前给每条结构记录增加标准化方法版本字段。因为不同版本的 RDKit、不同版本的 InChI 软件可能对同一结构输出略有差异没有版本标记以后你根本不知道哪批数据用的什么算法。3.3 ATC 映射利用 ATC/DDD Index 反向建映射并保留语境ATC/DDD Index 文件里每个 ATC 编码通常会附带一个药物成分名称这个名称往往就是规范化后的 INN 或 INN 的盐型写法。于是可以自动做逆向映射把 ATC Index 里的名称先做同样的 Unicod 规范化和字符串标准化先把精确匹配的条目直接落库置信度给最高档对匹配不上的条目尝试去盐后缀再进行一次匹配比如把hydrochloride、sodium、calcium等盐型标签剥掉后和 INN 表比对仍不匹配的进入人工池用词干 结构辅助判断。这里最反直觉的设计是映射表不要只存一个 ATC 编码而要存多条关系并保留每条关系的来源语境。我们看阿司匹林同一个活性实体在 ATC 表里对应多个编码N02BA01 代表用于止痛B01AC06 代表用于抗血小板A01AD05 代表用于口腔局部症状。如果数据库 schema 里 ATC 是单值字段你等于强迫自己在两个合法编码里二选一掉哪个都是数据损失。每条映射还需要带route_context、source_version、valid_from、valid_to之类的语境信息未来做历史趋势分析才有抓手。3.4 置信度打分与冲突消解别让代码替你偷偷删数据自动映射不能只给是或否我给每条映射打五档置信度置信度含义典型场景S5官方明确对应ATC Index 里明确写了该 INN且与结构键一致S4结构精确一致 名称语义一致两条来源记录的 InChIKey 完全相同且 INN 名称中文/英文语义吻合S3名称去掉盐后缀后精确匹配字符串层面能对应但没有结构键交叉验证S2词干 ATC 药理分类推断没有明确名称对应靠词干引导人工判断S1推测性匹配仅文本相似度较高无可用证据进入待审名单冲突消解的规则很简单永远不要把冲突记录直接覆盖或删除而是保留双方记录在每条关系上标记来源优先级和置信度。例如来源 A 与来源 B 对同一实体给出不同 ATC 编码我们可以都保留用mapping_source区分并让业务侧最终决定在当前用途下以哪个为准。因为一个实体本来就可能合法地拥有多个 ATC 语境冲突不等于错误反而往往是数据比你想的更全。4. 从 0 到 1 落地管线表结构、脚本与版本控制4.1 数据模型设计至少别把 ATC 塞进一列给一个经过实战检验的最小表设计不需要 DBA 也能跑source_record数据源文件记录字段包括source_name、source_version、file_hash、pulled_at。inn_master规范化的 INN 权威表字段包括inn_id、inn_name、inn_status、list_batch、stem、source_version。chemical_entity活性实体表字段包括entity_id、inchi_key、canonical_smiles、molecular_formula、structure_method_version。ingredient_label名称标签表字段包括label_id、entity_id、label_type、label_name、language、source_record_id。atc_relation映射表字段包括relation_id、entity_id、atc_code、route_context、confidence、mapping_source、valid_from、valid_to。alias_dict需要处理的商品名、曾用名、中文异名表尽量与正式主数据分离只做搜索索引用途。核心原则是持久身份只用自增/生成的 entity_id不要用 INN 名称或 ATC 编码当主键。因为名称会变ATC 会变结构键虽然是稳定的但可能发生计算版本的调整只有不承载业务语义的代理主键最安全。4.2 一个最小可用的数据合并脚本参考下面是一个高度化简的脚本骨架展示 INN、ATC、结构三源合并的大致逻辑import pandas as pd inn_df pd.read_csv(inn_master.tsv, sep\t) atc_df pd.read_csv(atc_index_2024.tsv, sep\t) smi_df pd.read_csv(pubchem_smiles_2024q3.tsv, sep\t) # 1. 计算结构键 smi_df[inchi_key] smi_df[smiles].apply(structure_to_key) # 2. 用 INN 规范化名称做第一次匹配 match1 atc_df.merge( inn_df[[inn_id, inn_name]], left_onnormalized_atc_name, right_oninn_name, howinner ) # 3. 用结构键对未匹配记录做第二次匹配 unmatched atc_df[~atc_df.index.isin(match1.index)].copy() unmatched unmatched.merge( smi_df[[canonical_name, inchi_key]], left_onnormalized_atc_name, right_oncanonical_name, howinner ) # 4. 汇总输出 output pd.concat([ match1[[inn_id, atc_code, route_context]], unmatched[[inn_id, atc_code, route_context]] ]).drop_duplicates()注意这段代码里我省略了标准化函数的定义、别名处理、源版本记录和置信度打分真正上线时这些环节一个都不能少。但思路要传达清楚先以规范化的 INN 字符串为主键做一次匹配再用结构键补漏最后用词干和人工把漏网之鱼捡回来。4.3 版本管理INN 一年两次、ATC 一年一次别用覆盖式同步主数据最怕原地更新。如果每次拿到新版本 INN List 就直接 update 主表三个月后你会完全无法回答这条映射当年是怎么来的。我的做法是快照式管理INN 源按批次归档比如inn_list_2024_06、inn_list_2025_01新批次入库时先入候选区与当前主表做 diff。对新增的 INN 条目标记为new进入待审核队列对消失或变更的条目不要物理删除而是把valid_to设为当前版本日期。ATC 源同理每年发布新版本后生成atc_2024、atc_2025两张快照表映射关系里的atc_relation也要记录自己基于哪个 ATC 源版本。全量回刷是最后的手段。每次大版本更新我会先在一个完全独立的候选库里跑全流程生成新的覆盖率报告、冲突清单肉眼确认后再批量 promote 到生产库。很多团队就是省了这一步导致后来为什么映射结果变了变成猜谜游戏。4.4 质量看板三个指标比九百个指标有用数据治理最容易被提出一堆华丽的指标最后没人维护。我建议先盯三个未映射 INN 占比统计inn_master中有多少 INN 完全找不到 ATC 和结构键。成熟库通常控制在 5% 以内新上市药物除外。这个数字一旦明显增长说明 TTL 解析或数据源解析出了问题。结构冲突数统计同一个inchi_key被关联到多个entity_id的记录数。如果大于零赶紧查是不是有异构体、外消旋体或命名误合并。这个指标是发现数据合并事故前端的哨兵。版本新鲜度记录当前正在使用的 INN 批次、ATC 年度版本与官方最新版的差距。落后两个版本以内是正常落后太多会直接影响下游注册和药物警戒工作流。每个指标配一张自动生成的小表每天发到数据群里每周再抽 50 条样本做一次人工比对。这个投入成本不大但因为主数据的变量太多定期人工看样本仍然是不可替代的。5. 踩坑实录我在这套方法上实际遇到过的四个问题5.1 盐与离子的战争结构键匹配到了游离酸名称匹配到了钠盐一个很典型的场景入库时来源 A 给的是 atorvastatin游离酸的 SMILES来源 B 给的是 atorvastatin calcium钙盐的 Molfile两个来源在名称层面都写着阿托伐他汀。如果直接做整分子 InChIKey 匹配两条记录将无法 merge如果只做名称匹配又无法确认是不是同一个化学本质。实际情况比这个更复杂有些数据库会把钠盐、钙盐、游离酸存成三个 CID有些则认为它们是同一个物质。最终解决方案就是前面说的结构键统一到活性部分 盐型单独存字段。对阿托伐他汀这个例子去盐后 InChIKey 一致于是三条来源记录顺利归到同一chemical_entity下而ingredient_label表里保留 atorvastatin、atorvastatin calcium、阿托伐他汀钙等不同名称标签。这样既满足研究者我要钙盐的说明书的需求也不会在到底有几个他汀的统计里产生重复计数。5.2 前药问题名称和后缀看似同源实际上是两个实体奥司他韦oseltamivir是前药在体内水解为活性代谢产物奥司他韦羧酸oseltamivir carboxylate。从命名看两者都带 oseltamivir 字样化学结构却完全不同ATC 里通常也只给前药单独的编码活性代谢产物则需要额外标注。如果把二者合并成同一实体药物相互作用和药代动力学研究会直接算错账。正确的建模方式是给两个chemical_entity再用一张prodrug_of关系表把前药 - 活性产物的连接记录下来。这种关系不该代替身份映射而是成为知识图谱里的边。以后任何人问奥司他韦的活性物是什么都能从关系表直接给出答案。5.3 ATC 里还有复方制剂单一 INN 映射会漏掉一大片很多团队做到单方药就收工了结果遇到 ATC 编码里的复方项比如J05AR类目下会有拉米夫定和阿巴卡韦的复方制剂。复方药物的 ATC 名称往往是lamivudine and abacavir这种组合写法不可能在 INN 单方主表里精确匹配到一条记录硬把它挂到拉米夫定或阿巴卡韦任何一个实体下都会造成误导。我建议为复方建立复合实体一张compound_formulation表用component_id和component_ratio与两个或多个单体实体关联。复方实体自己也有 ATC 编码、DDD 来源、商品名等属性。这虽然增加了一点建模量但换来的是处方分析、剂量学研究和药物相互作用计算的准确性。5.4 中文和多语言别名的坑别名差点被当成新药入库国内数据的一个特色问题是对乙酰氨基酚/扑热息痛/醋氨酚/泰诺林同时出现在文献里。更麻烦的是同一中文名在药典修订后可能调整用字比如某些含酉字旁的药品名改成更规范的译法。如果解析逻辑里没有别名归类这些写法会被当成多个新 INN 实体反复入库覆盖率和冲突指标瞬间爆表。我的处理原则是别名只能出现在alias_dict搜索索引表里不能出现在inn_master主表建立别名时必须记录来源哪篇指南、哪个药典、哪个数据库否则以后没法问责。搜索时可以做全词匹配、前缀匹配、拼音匹配但入库申请必须走规范名映射流程。这个原则还可以延伸到所有多语言场景避免看起来是新实体、实际是旧名字的误判。6. 统一主数据能带来什么实际业务价值从研发到药物警戒6.1 打通结构指纹与真实世界研究真实世界研究RWE中最容易炸的数据问题就是处方药名称和成分无法标准化。电子病历里可能写商品名药房数据里可能写通用名检验报告里可能写化学名。经过统一主数据管线后下游分析团队拿着任意一个名称标签都能定位到唯一entity_id再靠这个实体 ID 拖出全部 ATC 语境、分子结构、剂量形式研究中的药物暴露定义、处方合并、剂量标准化就可以自动完成。以前需要人工逐个核对两个星期的工作变成了一段几十行的 lineage 查询。6.2 药物警戒和信号检测中的去重收益药物警戒数据库里最常见的问题是重复上报同一不良反应事件一个报告写在 INN另一个报告写了盐型全名还有一个报告写了商品名。如果后台不做实体归并同一个药的信号会被拆成好几条信号计算的基数就错了。统一主数据以后报告入口做一层名称标签到entity_id的转译再做 ATC 层面的聚合分析信号检测的灵敏度和特异度都会明显提升。6.3 构建内部药品知识图谱的底座实体层一旦稳定后面的知识图谱就水到渠成以chemical_entity为节点ingredient_label提供多语言名称边atc_relation提供分类语境边prodrug_of提供前药关系边compound_formulation提供复方成分边。扩展开来还可以接靶点、适应症、专利信息、不良反应信号。很多团队一开始只是为了给三个表找个统一主键几个月后发现这顺手就成了跨部门数据整合的底座。这种复利效应是我觉得这部分投入最值得的地方。如果把这套方法浓缩成我个人的体会就四句话命名存历史结构算指纹ATC 存多对多版本留快照。真打算在小团队里落地别急着全量开跑先拿 20 个有代表性的化合物手工把映射跑对一遍确认自己的盐处理规则、立体化学规则、复方规则都能自洽再放开全量。跑完多花一天做冲突样本抽审结果通常会比预期脏得多但这正是主数据项目正常的开局。后面如果遇到生物制品、基因治疗这种传统结构指纹不适用的大分子需要再补一套序列与靶点维度的身份管理有机会我们单独聊。
返回列表