> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirobody.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 标准化详解

> ② 转译详解：别名的分层、随包发布的 LOINC 版本，以及这份词表覆盖与不覆盖的范围。

export const OssSource = ({path, lang = "en"}) => {
  const href = "https://github.com/thetahealth/mirobody/blob/c1aae297b4f3fad5cb87c5fc5bffc5163f7758c9/" + path;
  return <p className="text-sm text-gray-500 dark:text-gray-400">
      {lang === "zh" ? "对应 mirobody " : "For mirobody "}
      <code>1.5.3</code>
      {lang === "zh" ? " · 源文件 " : " · source "}
      <a href={href}>
        <code>{path}</code>
      </a>
    </p>;
};

<OssSource path="docs/standardization.zh-CN.md" lang="zh" />

README 里 **② 转译 Translate（标准化）** 那一阶段的长版本：随包发布的词表到底是
什么、它刻意不做什么、切自哪个 LOINC 版本、为什么停在那里，以及那个默认关闭的
语义层。这里每一个精确数字都和 README 引用的是同一个，而 README 里的数字由它们
来源的产物守着，这一页跟着它走。

<p align="center">
  <img alt="收集、转译、智能体：三个阶段，从左到右" width="920" src="https://mintcdn.com/thetahealth/ypMLkjCdwPelODUf/images/oss/docs/images/collect-translate-agent.zh-CN.svg?fit=max&auto=format&n=ypMLkjCdwPelODUf&q=85&s=7abb042afdd5a8dbcd4fe08661948cb2" className="block dark:hidden" data-path="images/oss/docs/images/collect-translate-agent.zh-CN.svg" />

  <img alt="收集、转译、智能体：三个阶段，从左到右" width="920" src="https://mintcdn.com/thetahealth/ypMLkjCdwPelODUf/images/oss/docs/images/collect-translate-agent-dark.zh-CN.svg?fit=max&auto=format&n=ypMLkjCdwPelODUf&q=85&s=efc995f25db85a4fafda178c394fd29f" className="hidden dark:block" data-path="images/oss/docs/images/collect-translate-agent-dark.zh-CN.svg" />
</p>

<h2 id="这一层提供什么">
  这一层提供什么
</h2>

这里说的标准化不是一张查找表，而是一套完整的术语归一系统：

* **63,416 个码**，由一条规则一趟从 LOINC 2.83 切出：实验室与临床观察，
  叙述与文档量表丢掉，面板只留化验子类和生命体征子类。
  **692,577 条折叠过的称谓**指向它们，来自组分、长名与短名、相关名，
  以及发行版自带的 21 份语言变体。
* **英文为主，中文在旁。** 22,578 条中文别名，加上本项目自己人工收录的那些，
  排在索引前面，所以 `hemoglobin`、`血红蛋白`、`血紅素` 都落在 LOINC 718-7 上。
  其他语言走 LOINC 自己发布的那 21 份语言变体（它们本来就是别名索引的输入），
  而不是我们自己的文件：日文那份是 UMLS 派生的，
  另外五份机器派生的（de·es·fr·ko·ru）追溯不到它们声称的语言变体。
  两者都已删除，见 `LICENSE-3RD-PARTY`。
* **繁體中文 是两个问题，也就当两个问题处理。** 字形折叠是机械的（随包发布一张
  3,336 字的 zh-Hant → zh-Hans 对照表）；用词不是：台湾的习惯用词不一样，`血紅素`
  折叠过去会撞上 HbA1c 的码。这类词按繁体写法单独收录，而人工收录的那一行永远压
  过折叠的结果。
* **主诉和诊断落在 ICPC-3 上。** `resolve_symptom("头疼")` 得到 `NS01`，
  `resolve_condition("高血压")` 得到 `KD73`：ICPC-3 的 S、D 两个组件共 1,218 个码原样随包，
  英文、中文、日语、俄语说法是我们自己写的别名（繁體中文走同一套字形折叠，台湾用词如「氣喘」按繁体原文单独收录）。
  两套索引分开，主诉不会落到诊断上；拿不准就弃答并给原因，不猜。
  在 200 条真实问诊里的中英文主诉上测过，金标准由三个互相独立的模型标注（三种不同的模型，看不到解析器）：
  可计分的 142 条症状里没有一个错码，编上码的约五分之一，其余弃答。日语和俄语的说法是新加的，还没有做过同样的测评。
* **单位**按 328 个 UCUM 单位归一到 59 个 PROPERTY 家族，带量纲分析，
  有一座按 LOINC 码索引的摩尔质量桥，并且对 `%` 和 `10*9/L` 这种情况明确拒绝混算。
  316 项标准设备指标。
* **这里全是词法的，弃答是我们要守住的天花板。** 词表不认识的词返回 `unresolved`，
  不返回最近邻。1.4.x 在旁边挂过一层默认关闭的余弦召回，1.5.0 把它删了。它**不会
  弃答**：碰到从没见过的词，会用「答对了」的那种置信度把最近邻交给你——在 LOINC
  矩阵上实测，胡话能拿 0.78，而真实的指标名低到 0.56，两个区间重叠，没有阈值能把
  它们分开。它也从来没跑起来过：矩阵是 108,248 行 × 1024 维、只对某一对（供应商，
  模型）有效，而且从未发布，所以 `get_index()` 在 wheel 安装态和源码树里**都返回
  None**（两边都实测过）。一个没人能打开的开关，挡在一个我们本来也不敢信的答案前面。
  想要更高的召回，诚实的杠杆是在 `res/loinc/resolver_overrides.tsv` 里加一行人工词条。
* **我们量这个说法，而不是断言它。**
  [`test_engine_coverage.py`](https://github.com/thetahealth/mirobody/blob/c1aae297b4f3fad5cb87c5fc5bffc5163f7758c9/mirobody/tests/test_engine_coverage.py) 拿常规体检
  会出现的那些套餐去考离线解析器，词按报告上真正的印法写，覆盖英文、简体中文、
  繁體中文、日本語、俄文和爱沙尼亚文，外加平台 API 教给我们的那套穿戴设备词汇。
  **今天是 296/296；刚写出来那天是 32/94。**
  它判的是**临床**正确：把 `血红蛋白` 答成 HbA1c 的码算错，`血脂` 则必须解析不出结果。

```bash theme={null}
pytest mirobody/tests/test_engine_coverage.py -s   # 离线，大约一秒
```

<h3 id="两个语义索引以及哪一个是白送的">
  两个语义索引，以及哪一个是白送的
</h3>

上面那个矩阵属于**可下载语料**那一层：LOINC 的行被嵌入一次，由你用自己的 embedding
模型建。应用内问到某人自己的读数时并不用它：
`query_health_indicators` 对这个人自己的序列（报告上印的名字，
以及写入时存在每条读数旁的 LOINC 显示名和码）做词法排序，
找不到再退回离线解析器给出的码。这条路径不需要 embedding 供应商，
两个索引也都不改变 `resolve()` 的答案。

<h3 id="切的是哪个-loinc覆盖了什么没覆盖什么">
  切的是哪个 LOINC，覆盖了什么，没覆盖什么
</h3>

随包发布的词表切自 **LOINC 2.83**，而且这件事由包在运行时自己说，不是写在一句会
过期的注释里：

```python theme={null}
>>> import mirobody; mirobody.BUNDLE_VERSION
'loinc-2.83+2026.09.17-aacb2c715b56'
```

发行版本、切分日期，加一份对词表自身成员算出来的摘要，于是「构建期用的词表」和
「运行时 `pip` 钉住的那个」可以被断言为同一份语料，而光看包版本号你永远不知道这
一点。[LOINC 的许可](https://loinc.org/license/)要求每一份拷贝都带版本号；
`res/loinc/fhir_loinc_bundle.NOTICE` 带了，`scripts/stamp_bundle_version.py --check`
负责让这个戳保持诚实。

**这一刀切下了什么。** 2.83 的 99,737 个 ACTIVE 码里留 63,416 个，按规则切而不是
手挑（`translate_build/loinc_cut.py`）：CLASSTYPE 是实验室或临床；从来不承载读数的
CLASS 族丢掉（问卷、文档、放射、行政）；SCALE\_TYP 为 `Doc`/`Nar`/`-`/`Set`/`Multi`
的丢掉；体格检查类只在 `Qn` 或 `Ord` 时留；面板留化验那几个子类，外加生命体征与个人
健康记录那几个。**只删行，不改行**——这是[许可](https://loinc.org/license/)第 3 条的
要求；153 行带着别人版权声明的，删掉而不是原样复制。

1.4.x 停在 2.82 的理由已经不存在了：当时轴表和语料来自不同来源，靠折叠后的
`LONG_COMMON_NAME` 接在一起，于是 2.83 改掉 2,842 个名字就断了 3,486 条链接。现在
两者从同一个发行版一趟切出，根本没有那个接缝。随之而来的两件事以前做不到——轴表现在
带 `TIME_ASPCT`，没有它点尿蛋白和 24 小时尿蛋白会共用一个序列键；也带 `CLASS`，于是
一个 10,045 行、需要手工重新生成的门禁文件可以退休了。

**实测，7,354 条真实报告写法。** 在「金标准码在切法内」的 6,992 条上，
覆盖率 0.951，错答率 0.025。另外 362 条里，
330 条期望的是切法丢掉的码（叙述、文档或体检所见类），
24 条期望的是 2.83 自己退役的码，8 条本就不该有码：
解析器对其中 273 条弃答而不是作答，这正是这一刀的目的，不是回归。
1.4.x 还答得出的 DISCOURAGED / DEPRECATED 码，随 `STATUS` 门一起没了。

**LOINC 覆盖的穿戴设备世界比大多数人以为的多。** 它不只有化验套餐：`BDYWGT.*` 管
身体成分（`101685-6` 骨量、`73964-9` 肌肉量、`101684-9` 体水分百分比），
`HRTRATE.*` 把静息心率（`40443-4`）和随手一测区分开，还有步数（`41950-7`）、睡眠
分期（`93831-6` 深睡、`93830-8` 浅睡）、HRV SDNN（`112429-6`）、最大摄氧量峰值和
爬升高度的码。它停在哪里：厂商的复合指标。Garmin 的 Body Battery 和压力分数没有
码，这是对的，因为那是一家公司的公式，不是一项测量。

**一个词表覆盖了什么，和我们在它上面的召回率，是两回事**，而这个差距是我们的，不是
LOINC 的：`Body bone mass` 在这里能解析到 `101685-6`，但中文的 `骨量` 会解析到一个
牙科体积的码，因为没有别名把它路由过去。
[`res/loinc/resolver_overrides.tsv`](https://github.com/thetahealth/mirobody/blob/c1aae297b4f3fad5cb87c5fc5bffc5163f7758c9/mirobody/res/loinc/resolver_overrides.tsv) 就是干这个用
的：人写下的一行，永远压过索引里的一次表层匹配。

→ [loinc.org](https://loinc.org/) · [许可](https://loinc.org/license/) ·
[发布说明](https://loinc.org/kb/)。下载免费，但需要注册账号，这也是为什么这里发布
的是派生词表，而不是源发行版。

→ [标准化](/zh/api-reference/standardization) ·
[架构](/zh/concepts/architecture) ·
[数据流](/zh/concepts/data-flow)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.