> ## 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.

# 数据映射

> vendor 数据如何按 DataDomain 分类、归一化到 UCUM 单位，并表示为 FHIR R4。

## 概览

在 Mirobody 中，"数据映射"是三个各自独立的关注点：

1. **域映射** —— vendor 把它的端点映射到归一化的 `DataDomain` 枚举，让调用方按类别请求数据。
2. **单位归一化** —— 自由文本的单位被折叠为规范的 **UCUM**（已上线）。
3. **FHIR 表示** —— 一切最终汇聚到的规范资源形态是 **FHIR R4**（测量值用 `Observation`）。

<Info>
  `fetch` 返回**该 vendor 自己的 JSON**（FHIR 原生源则返回 FHIR JSON）；规范存储是内嵌的 FHIR R4 端点。
</Info>

## 1 —— 域映射

每次 `fetch` 都接收一个 `DataDomain`，每个 vendor 把它映射到它实际中转的端点：

```cpp theme={null}
enum class DataDomain {
    Activity, Sleep, HeartRate, Glucose,
    Nutrition, BodyMetrics, Labs, Clinical
};
```

一个 REST vendor 在域上 switch 以选择路径（Fitbit）：

```cpp theme={null}
switch (d) {
    case DataDomain::Activity:    return "/1/user/" + u + "/activities/steps/date/" + s + "/" + e + ".json";
    case DataDomain::HeartRate:   return "/1/user/" + u + "/activities/heart/date/" + s + "/" + e + ".json";
    case DataDomain::Sleep:       return "/1.2/user/" + u + "/sleep/date/" + s + "/" + e + ".json";
    case DataDomain::BodyMetrics: return "/1/user/" + u + "/body/log/weight/date/" + s + "/" + e + ".json";
    default: throw VendorError("fitbit: unsupported domain (activity, heart_rate, sleep, body_metrics)");
}
```

一个 FHIR vendor 则把域映射到 FHIR `Observation` 的 `category` token（`ehr` SMART-on-FHIR 客户端）：

```cpp theme={null}
case DataDomain::HeartRate:   return "vital-signs";
case DataDomain::BodyMetrics: return "vital-signs";
case DataDomain::Glucose:     return "laboratory";
case DataDomain::Labs:        return "laboratory";
case DataDomain::Activity:    return "activity";
case DataDomain::Clinical:    return nullptr;   // unfiltered: the full observation set
```

vendor 会**对它不中转的域抛错**，而非返回空 —— 让调用方了解该源的范围。

## 2 —— 单位归一化（UCUM）

[`src/fhir/units/`](https://github.com/thetahealth/mirobody/tree/main/src/fhir/units) 中的术语引擎把一段自由文本的"数值 + 单位"字符串变成一个规范的 **UCUM** 单位，外加用于消歧的 LOINC PROPERTY 族。纯本地计算 —— 无数据库、无 embedding API。

```cpp theme={null}
#include "fhir/units/normalize.hpp"
#include "fhir/units/families.hpp"
using namespace mirobody::fhir::units;

normalize_unit("MG/DL");        // "mg/dL"
normalize_unit(u8"毫摩尔每升");  // "mmol/L"
normalize_unit("mmHg");         // "mm[Hg]"

ParsedQuantity q = parse_value_unit("<5.6 mg/dL");
// q.comparator == "<", q.value == 5.6, q.unit == "mg/dL"

unit_family("mmol/L");          // "SCnc"
unit_family("mg/dL");           // "MCnc"
```

流水线：NFKC-lite（全角形态、上标）→ 符号折叠 → 别名查找 → 注释剥离 → 贪婪的语素分词-组合。大小写被保留（UCUM 区分大小写）。支持多语言输入：**en、zh、ja、ko、ru、de、fr、es**。由 `tests/fhir/units_test.cpp` 覆盖。

## 3 —— FHIR R4 表示

规范存储是 FHIR R4（`src/fhir/`）。FHIR 原生的 vendor —— 通用 `ehr` 客户端，以及 Vitalera / HealthConnect / Redox / Particle Health / Metriport-Medical —— 已直接返回 FHIR 资源；设备本地存储 POST `Observation`。一个测量值是一个 `Observation`，带一个归一化（UCUM）的 `valueQuantity`：

```json theme={null}
{
  "resourceType": "Observation",
  "status": "final",
  "category": [{ "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "vital-signs" }] }],
  "code": { "text": "Heart rate" },
  "valueQuantity": { "value": 72, "unit": "/min", "system": "http://unitsofmeasure.org", "code": "/min" }
}
```

资源按用户以通用的、经校验的 JSON 存储，并通过 `/fhir` 提供（参见[数据流](/zh/concepts/data-flow)）。

<Note>
  把一个指标映射到**带编码**的 `Observation.code`（SNOMED CT / LOINC / RxNorm）—— 即术语解析以及文档 → 带编码 FHIR 的写回 —— 尚在**进行中**。UCUM 归一化与 RESTful FHIR 存储已上线；完整的术语映射尚在计划中（参见 `src/fhir/README.md` 的第 3–4 阶段）。在此之前，`Observation.code` 可能携带一个 `text` 而非解析出的编码。*(verify)*
</Note>

## 映射一个新数据源

当你添加一个 `vendor::Vendor`（[Provider 集成](/zh/development/provider-integration)）时：

1. 确定该源中转哪些 `DataDomain`；在 `VendorInfo.domains` 中列出它们。
2. 在 `fetch` 中，把每个域映射到该源的端点（REST 路径）或 FHIR `category`（FHIR 源）；对不支持的域抛错。
3. 返回该源的 JSON（或 FHIR JSON）。不要预先归一化 —— 归一化 / FHIR 转换是下游的、共享的关注点。
4. 若你暴露单位，把它们经 `parse_value_unit` / `normalize_unit` 处理，使其落地为 UCUM。

## 下一步

<CardGroup cols={2}>
  <Card title="Provider 集成" icon="code" href="/zh/development/provider-integration">
    实现 `fetch` 与域映射
  </Card>

  <Card title="数据流" icon="diagram-project" href="/zh/concepts/data-flow">
    从 vendor JSON 到 FHIR 存储
  </Card>

  <Card title="文件处理" icon="file" href="/zh/concepts/file-processing">
    文档数值的 UCUM 归一化
  </Card>

  <Card title="Provider 测试" icon="flask" href="/zh/development/provider-testing">
    验证你的 fetch 输出
  </Card>
</CardGroup>
