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

# 抽取

> POST /v1/extract —— 化验报告进、标准化指标出；默认 dry-run。

```http theme={null}
POST /v1/extract
Authorization: Bearer mb_live_*
```

**文档进，标准化指标出 —— 同步完成。** `/v1/extract` 对化验报告 / 健康文档运行完整的[标准化管线](/zh/api-reference/standardization)，返回其中找到的每条读数：原始文本 → schema 约束的 LLM 抽取 → 确定性 LOINC 解析 → UCUM 单位归一。

默认是 **dry-run**（`store=false`）：你拿到标准化结果，**什么都不持久化** —— 零副作用。设 `store=true`（并给出 `retention`）则同时把读数经由与 [`POST /v1/data`](/zh/api-reference/data) 相同的管线写入 Subject 的存储。

## 请求

两种输入形态：

* **multipart/form-data** —— 一个 `file`（PDF / 图片 / Excel / 纯文本；图片与 PDF 会 OCR），下表字段作为表单字段。
* **application/json** —— `{"text": "..."}`（原始报告文本）**或** `{"file_key": "..."}`（已通过 [`/v1/files`](/zh/api-reference/files) 上传的文件）。

| 字段                           | 类型     | 说明                                                                                                                             |
| ---------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `file` / `text` / `file_key` | —      | 恰好一个来源。`file_key` 未知或无可抽取文本 → `404`。                                                                                           |
| `user`                       | string | 抽取所针对的 Subject。                                                                                                                |
| `store`                      | bool   | 默认 **`false`**（dry-run）。`true` 时同时写入读数。                                                                                        |
| `retention`                  | string | **`store=true` 时必填** —— 与 [`POST /v1/data`](/zh/api-reference/data) 同一枚举（`permanent` / `1h` / `2h` / `6h` / `1d` / `session`）。 |
| `session_id`                 | string | `retention=session` 时必填。                                                                                                       |

<Note>
  **`user` 字段是租户隔离键。** 后端把 `(你的账户, user)` 映射为内部的**数据主体(Subject)**；你传入的每个 `user` 彼此完全隔离。为每个终端用户传入其稳定 id，数据就绝不会串。省略时回落到你账户的默认 Subject。Subject 对 Mirobody 消费端 App 和其他开发者均不可见。
</Note>

### Dry-run（默认）

```bash theme={null}
curl https://mirobody-api.thetahealth.ai/v1/extract \
  -H "Authorization: Bearer $MIROBODY_API_KEY" \
  -F "user=alice" \
  -F "file=@lab_report.pdf"
```

### 抽取并存储

```bash theme={null}
curl https://mirobody-api.thetahealth.ai/v1/extract \
  -H "Authorization: Bearer $MIROBODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "user": "alice",
        "text": "Fasting glucose 5.6 mmol/L (2026-07-01); LDL cholesterol 3.1 mmol/L",
        "store": true,
        "retention": "permanent"
      }'
```

## 响应

```json theme={null}
{
  "object": "extraction",
  "data": [
    {
      "indicator_raw": "Fasting glucose",
      "canonical_name": "Fasting glucose [Mass/volume] in Serum or Plasma",
      "loinc_code": "1558-6",
      "value_raw": "5.6",
      "parsed_value": "5.6",
      "unit_raw": "mmol/L",
      "unit_ucum": "mmol/L",
      "confidence": 0.82,
      "measured_at": "2026-07-01"
    },
    {
      "indicator_raw": "LDL cholesterol",
      "canonical_name": "Cholesterol in LDL [Mass/volume] in Serum or Plasma",
      "loinc_code": "2089-1",
      "value_raw": "3.1",
      "parsed_value": "3.1",
      "unit_raw": "mmol/L",
      "unit_ucum": "mmol/L",
      "confidence": 0.79,
      "measured_at": null
    }
  ],
  "stored": false,
  "stored_count": 0,
  "subject": "alice"
}
```

| 字段                                         | 说明                                          |
| ------------------------------------------ | ------------------------------------------- |
| `data[]`                                   | 每条抽取到的读数一行。                                 |
| `indicator_raw` / `value_raw` / `unit_raw` | 文档原文怎么写的。                                   |
| `canonical_name` / `loinc_code`            | 确定性 LOINC 解析（`null` = 无高置信编码 —— **绝不给猜的**）。 |
| `parsed_value` / `unit_ucum`               | 解析后的数值(以字符串返回)+ UCUM 归一化单位。                 |
| `confidence`                               | LOINC 匹配的相似度分数（0–1）。                        |
| `measured_at`                              | 文档中找到的时间戳（如有）。                              |
| `stored`                                   | 本次调用是否写入了存储（回显 `store`）。                    |
| `stored_count`                             | 实际写入的读数条数（dry-run 时为 `0`）。                  |
| `note`                                     | **仅当 `data` 为空时出现** —— 说明未找到可量化读数（见下）。      |

## 未找到可量化读数 —— 叙事文本

`/v1/extract` 抽取的是**可量化读数**。纯叙事文本 —— *「整个下午头晕头疼」* —— 是明确声明的边界，不是错误：调用成功（`200`），返回空的 `data` 数组加一个 `note`：

```json theme={null}
{
  "object": "extraction",
  "data": [],
  "stored": false,
  "stored_count": 0,
  "subject": "alice",
  "note": "no quantifiable readings found in the text; subjective/narrative entries (journal) are not supported yet"
}
```

混合文本按预期工作 —— *「头疼了一天，体温 38.2 °C」* 会抽出体温读数、丢弃叙事部分。面向主观/叙事记录的专用 journal 端点在规划中。

## 错误

| HTTP  | 何时                                                                                                  |
| ----- | --------------------------------------------------------------------------------------------------- |
| `400` | 没有 `file` / `text` / `file_key`；`store=true` 但 `retention` 缺失或非法；`retention=session` 缺 `session_id` |
| `404` | `file_key` 未知或无可抽取文本                                                                                |
| `413` | 文件超出上传大小上限                                                                                          |
| `422` | 无法从文件抽取文本                                                                                           |
| `502` | 抽取模型不可用 —— 瞬态；退避重试                                                                                  |

所有失败都是显式的 —— 不存在静默的部分成功。
