---
url: /docs/api-docs/v1/frontend/home-api-reference.md
---
# 首页 API 接口参考

> 首页本屏 + 全部跳转详情页 —— **17 个端点** / 4 个域
> 契约基准：**后端源码**（`QM-ServerV2`）· 前端基线 `qm-frontend-api` @ `d9a357c`
> 本文只写接口，不写背景与结论

## 通用约定

### 请求

| 项 | 值 |
|---|---|
| 方法 | `POST` —— **17 个端点全部是 POST** |
| 路径 | `/api/v1/<域>`（域：`stats` / `device` / `sport` / `health-archive`） |
| `Content-Type` | `application/json` |
| `Authorization` | `Bearer <access_token>` —— **必填**，缺失即 `401` |
| `X-Env` | `Dev` 或 `Prd` —— **必填**，且与 token 签发时一致（不一致直接失效） |

### 请求体

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `action` | string | ✔ | action 名（各端点见下文） |
| `payload` | object | ✖ | 入参；**无入参的 action 可省略该键** |

> ⚠️ 入参一律在 `payload` 内，**不是** body 顶层。

### 周期模式（period 契约 · 2026-09-28 上线）

全部健康详情 action 支持可选的 **period 模式**（对 `DETAIL_API_PARAMS.md` 目标契约的落地）。

**入参**（与 `date` 并存；三字段成对出现）：

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `period` | string | ✖ | `day` | `week` | `month` | `year`（各 action 支持面见下表） |
| `startDate` | string | ✖ | 区间起（含），`YYYY-MM-DD` 真实公历 |
| `endDate` | string | ✖ | 区间止（含） |

规则：

* **不传 `period`** → legacy 模式：响应含**全部**周期块（现状，逐字兼容）
* **传 `period`** → 必须**成对**传 `startDate`/`endDate`（缺一 → `40403001`）。区间与 `period` 一致性校验：`week` 必须周一至周日（连续 7 天）；`month` 必须 1 日至月末（实际天数）；`year` 必须 01-01 至 12-31（闰年自然支持）；`day` 起止同日
* `date` 缺省取 `startDate`；给了必须落在 `[startDate, endDate]` 内（否则 `40403001`）
* **出参裁剪**：`data` 只含**所请求周期**的块 + 公共键 `period`/`date`/`startDate`/`endDate` + 周期无关元数据（`vendor` 等）；其余周期键**整体省略**（不用 `null`/`{}` 占位）
* 周期专属块仅在对应请求出现：`heartRateDetail.todaySeries` / `oxygenDetail.overview`·`card` / `stressDetail.card` 仅 `day`；`sleepDetail` 的 `dayMetrics·daySegments·bedWake·score·stages` 仅 `day`、`weekRanges` 仅 `week`、`monthRanges` 仅 `month`

**各 action 支持面**（不支持 → `40403001` 明确报错，不静默降级）：

| action | day | week | month | year |
|---|---|---|---|---|
| `stepsDetail` / `caloriesDetail` / `strengthDetail` | ✔ | ✔ | ✔ | ✖（无 year 块） |
| `sleepDetail` | ✔ | ✔ | ✔ | ✖ |
| `activitySummary` | ✔ | ✔ | ✔（含 `monthDays`） | ✖ |
| `heartRateDetail` / `stressDetail` / `weightDetail` | ✔ | ✔ | ✔ | ✔ |
| `oxygenDetail` | ✔ | ✔ | ✔ | ✖ |

示例（周视图）：

```json
{
  "action": "stepsDetail",
  "payload": {
    "period": "week",
    "date": "2026-09-10",
    "startDate": "2026-09-07",
    "endDate": "2026-09-13"
  }
}
```

→ `data` 仅含 `week` 块 + `period/date/startDate/endDate`（+ `vendor` 条件出现）。

### 响应信封

```json
{ "code": "0", "msg": "ok", "data": {} }
```

| 字段 | 类型 | 说明 |
|---|---|---|
| `code` | string | `"0"` = 成功。**非 `"0"` 时 HTTP 仍可能是 200** —— 请判 `code`，不要只判 status |
| `msg` | string | `"ok"` 或错误描述 |
| `data` | object | 各端点返回体 |

### 记号

| 记号 | 含义 |
|---|---|
| 类型后 `\| null` | 该字段**可能为 `null`** |
| **恒 null** | 后端无数据源，永远返回 `null`（前端须按无数据渲染） |
| **条件出现** | 仅在特定条件下才存在的键（不出现 ≠ 为 null） |

***

## 端点一览

| # | 域 | action | 页面 | 入参 |
|---|---|---|---|---|
| 1 | `stats` | `getTodayOverview` | 首页 · 热量 / 连续天数卡 | — |
| 2 | `device` | `myTodayHealth` | 首页 · 健康卡 | — |
| 3 | `device` | `oxygenDetail` | 首页 · 血氧卡 → 血氧详情页 | `date?` |
| 4 | `device` | `stressDetail` | 首页 · 压力卡 → 压力详情页 | `date?` |
| 5 | `device` | `weightDetail` | 首页 · 体重卡 → 体重详情页 | `date?` |
| 6 | `device` | `strengthDetail` | 首页 · 力量卡 → 力量详情页 | `date?` |
| 7 | `sport` | `myRunRecords` | 首页 · 运动卡 → 运动数据页 | `period?` `limit?` |
| 8 | `device` | `sleepDetail` | 睡眠详情页 | `date?` |
| 9 | `device` | `heartRateDetail` | 心率详情页 | `date?` |
| 10 | `device` | `activitySummary` | 健康汇总页 | `date?` `month?` |
| 11 | `device` | `caloriesDetail` | 卡路里详情页 | `date?` |
| 12 | `device` | `stepsDetail` | 步数详情页 | `date?` |
| 13 | `device` | `recordDates` | 日期选择页（6 处入口） | `days?` |
| 14 | `sport` | `runSessionDetail` | 运动详情页 | `sessionId` |
| 15 | `sport` | `addWeightRecord` | 添加体重页 | `date` `weight` … |
| 16 | `sport` | `reportRunCoordinate` | 运动数据页 · 轨迹上报 | `sessionId` `latitude` … |
| 17 | `health-archive` | `archive` | 健康档案页 | — |

> 首页本屏并发发出 **#1–#7 共 7 个**；其余在对应详情页触发。

***

## 一、首页本屏（7 个）

### 1. `getTodayOverview`

`stats` · 首页热量 / 连续天数卡 · **无入参**

**入参 `payload`**

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| — | | | 无入参，`payload` 可省 |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `date` | string | CN 今日 `YYYY-MM-DD` |
| `calorie` | object | 热量块 ↓ |
| `calorie.budgetKcal` | number | null | 热量预算（int）。**画像不全 = `null`（不编数）** |
| `calorie.intakeKcal` | number | 已摄入（float）。无记录 = `0` |
| `calorie.burnKcal` | number | 已消耗 = 跑步 + 力量（float）。无记录 = `0` |
| `calorie.remainingKcal` | number | null | 剩余额度 = `budget + burn − intake`，**向零截断**（int）。画像不全 = `null` |
| `calorie.profileComplete` | boolean | `false` = 画像缺失 → 上两项为 `null` |
| `checkin` | object | 打卡块 ↓ |
| `checkin.count` | number | 当日打卡数（int） |
| `checkin.distanceKm` | number | 当日打卡总距离（float）。无打卡 = `0` |

> ⚠️ **没有 `basis` 字段** —— `basis` 属于 `stats.calorieBalance` action（另一个端点）。
> ⚠️ `calorie` 是**嵌套的**：读 `res.data.calorie.budgetKcal`，不是 `res.data.budgetKcal`。
> ⚠️ `budgetKcal` / `remainingKcal` 为 `null` 时**请渲染 `--`，不要兜默认值**。

```bash
curl -s "$BASE_URL/api/v1/stats" -H 'Content-Type: application/json' \
  -H "X-Env: $ENV" -H "Authorization: Bearer $ACCESS" \
  -d '{"action":"getTodayOverview","payload":{}}'
```

***

### 2. `myTodayHealth`

`device` · 首页健康卡 · **无入参** · **块状结构（非扁平字段）**

**入参 `payload`**

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| — | | | 无入参 |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `date` | string | CN 今日 `YYYY-MM-DD` |
| `steps` | `{value, date}` | null | 步数块（`value` int / `date` string）。无数据 = `null` |
| `todayActivity` | `{totalCalories, totalDistanceKm}` | null | 当日活动块（`number` / `number`，距离保留 2 位）。**无当日行 = `null`** |
| `hr` | `{value, date}` | null | 心率块 |
| `spo2` | `{value, date}` | null | 血氧块（**v2.0.64 解禁**：当日有 SPO2 读数则给）。无读数 = `null` |
| `stress` | `{value, date}` | null | 压力块（**v2.0.64 新增**）。无读数 = `null` |
| `weight` | `{value, date}` | null | 体重块（**v2.0.64 新增**，最新一条体成分，date 为 CN 日）。无数据 = `null` |
| `sleep` | object | null | 睡眠块 ↓ |
| `sleep.durationHours` | number | 总时长（小时） |
| `sleep.deepHours` | number | null | 深睡（小时，1 位小数）。分段为空 = `null` |
| `sleep.lightHours` | number | null | 浅睡（小时，1 位小数） |
| `sleep.remHours` | number | null | REM（小时，1 位小数） |
| `sleep.score` | **恒 null** | 睡眠评分 —— V2 sync 无该来源 |
| `sleep.date` | string | 睡眠所属日 |
| `sources` | object | 数据来源映射 `{steps?, hr?, spo2?, stress?, sleep?, weight?}` —— 各键值 = 厂商 id。**无数据时为 `{}`**，**键按有无数据出现** |

> ⚠️ 顶层只有这 **9 个键**：`date` / `steps` / `todayActivity` / `hr` / `spo2` / `stress` / `weight` / `sleep` / `sources`（v2.0.64 起 stress/weight 加入）。
> **顶层没有 `distance`** —— 距离在 `todayActivity.totalDistanceKm`。
> `steps` / `hr` 是 `{value, date}` **对象**，不是数字。
>
> ⭐ **新用户初始化语义（v2.0.64）**：7 天窗内无任何设备行/读数的新用户，**累积量（steps / todayActivity）返回 0**（今天确实没走没记 —— 真实状态）；**测量量（hr / spo2 / stress / sleep）保持 null**（心率 0 荒谬）。老用户缺当日行 → 同样 null（`--`）。

***

### 3. `oxygenDetail`

`device` · 血氧 · 入参同 [端点 8](#_8-sleepdetail) 的通用形态

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `date` | string | ✖ | CN 今日 | 锚点日 `YYYY-MM-DD`。**格式非法 → 400** |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `date` | string | 锚点日 |
| `summaryMap` | object | **按窗口映射**：键 `day` / `week` / `month` / `year`，值 `{date, average}`（`average` int） |
| `pointSets` | object | 按窗口的散点序列 |
| `overview` | `{max, min, average}` | null | **当日**概览（均为 int）。当日无值 = `null` |
| `card` | object | null | 首页卡视图 `{oxygen, measureTime, series24}`。`measureTime` / `series24` **恒 null**（无逐样本源） |
| `pointSetsEcharts` | object | ⭐ **echarts 5.6 dataset-ready**（v2.0.63 新增，平行字段）—— `{dimensions, source, labels, dates}`，见[共享结构 · echarts 数据包装](#共享结构·echarts-数据包装) |
| `vendor` | string | **条件出现** —— 仅在有数据来源时出现在顶层 |

***

### 4. `stressDetail`

`device` · 压力 · 入参同 [端点 8](#_8-sleepdetail)

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `date` | string | ✖ | CN 今日 | 锚点日 `YYYY-MM-DD` |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `date` | string | 锚点日 |
| `summaryMap` | object | 按窗口映射：`{date, average}` |
| `distributionMap` | object | 按窗口的**等级分布**（放松 / 中等 / 偏高 三带） |
| `pointSets` | object | 按窗口的散点序列 |
| `card` | object | null | 首页卡 `{stress, level, date, measureTime, series24}`。`level` ∈ `放松` / `中等` / `偏高`；`series24` 为 24 项逐时序列（v2.0.64 起：raw.samples 逐时组装，无采样 → `null`）；`measureTime` 恒 `null` |
| `pointSetsEcharts` | object | ⭐ **echarts 5.6 dataset-ready**（v2.0.63 新增，平行字段）—— `{dimensions, source, labels, dates}`，见[共享结构 · echarts 数据包装](#共享结构·echarts-数据包装) |
| `vendor` | string | **条件出现** |

> 等级带阈值：`≤33` 放松 · `≤66` 中等 · `else` 偏高。

***

### 5. `weightDetail`

`device` · 体重 · 入参同 [端点 8](#_8-sleepdetail)

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `date` | string | ✖ | CN 今日 | 锚点日 `YYYY-MM-DD` |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `date` | string | 锚点日 |
| `current` | object | 当前值块 ↓ |
| `current.weight` | string | null | 体重文本（带单位） |
| `current.weightValue` | number | null | 体重数值（2 位小数） |
| `current.date` | string | null | 测量日 |
| `current.bmi` | number | null | BMI（1 位小数） |
| `current.status` | string | null | BMI 状态文案 |
| `current.initialWeight` | string | null | 初始体重文本 |
| `current.targetWeight` | **恒 null** | 目标体重 —— 目标域未落表 |
| `current.lostWeight` | string | null | 已减重文本 |
| `current.progressPercent` | **恒 null** | 无目标 → 无进度 |
| `current.progressText` | **恒 null** | 同上 |
| `calories` | object | `{intake, suggested, burned, remaining}` —— **四项恒 null**（热量属 stats 域） |
| `dataMap` | object | 按窗口的体重序列（2 位小数，含统计量） |
| `fatMap` | object | 按窗口的体脂序列（1 位小数） |
| `muscleMap` | object | 按窗口的肌肉量序列（1 位小数） |
| `vendor` | string | **条件出现** |

***

### 6. `strengthDetail`

`device` · 中高强度 · 入参同 [端点 8](#_8-sleepdetail)

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `date` | string | ✖ | CN 今日 | 锚点日 `YYYY-MM-DD` |

**出参 `data`** —— 三个**同构活动块**（见 [共享结构 · 活动块](#共享结构·活动块)）

| 字段 | 类型 | 说明 |
|---|---|---|
| `day` | object | 日块（锚点日） |
| `week` | object | 周块 |
| `month` | object | 月块 |
| `vendor` | string | **条件出现** |

> 块的 `strength` 键 = 该窗口合计分钟数。

***

### 7. `myRunRecords`

`sport` · 首页运动卡 / 运动数据页

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `period` | string | ✖ | `all` | 枚举：`week` / `month` / `year` / `all` |
| `limit` | number | ✖ | `200` | 记录条数上限，范围 **1–500** |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `period` | string | 回显入参 |
| `since` | string | null | 统计起点（`all` 时为 `null`） |
| `summary` | object | **全量**累计摘要，见 [共享结构 · 累计摘要](#共享结构·累计摘要) |
| `periodSummary` | object | **本周期**累计摘要（同结构） |
| `records` | array | 记录行数组，见 [共享结构 · 运动记录行](#共享结构·运动记录行) |
| `groups` | array | 按日期分组的展示结构 |

***

## 二、详情页（10 个）

### 8. `sleepDetail`

`device` · 睡眠详情页 —— **本端点是与 #3/#4/#5/#6/#9/#11/#12 同款的「通用形态」**

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `date` | string | ✖ | CN 今日 | 锚点日 `YYYY-MM-DD`，正则 `^\d{4}-\d{2}-\d{2}$`。**非法 → `40403001` 400** |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `date` | string | 锚点日 |
| `summaries` | object | 按窗口的睡眠摘要 |
| `dayMetrics` | object | 按窗口的日指标 |
| `score` | number | null | 睡眠评分 |
| `stages` | object | null | 分期构成 |
| `bedWake` | object | null | 入睡 / 起床时刻 |
| `daySegments` | object | 日内分段 |
| `weekRanges` | object | 周区间 |
| `monthRanges` | object | 月区间 |
| `periodMetrics` | object | 各周期指标 |
| `rangesEcharts` | object | ⭐ **echarts 5.6 dataset-ready**（v2.0.63 新增）—— `{week: 包装, month: 包装}`，见[共享结构 · echarts 数据包装](#共享结构·echarts-数据包装) |
| `vendor` | string | **条件出现** |

***

### 9. `heartRateDetail`

`device` · 心率详情页

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 描述 |
|---|---|---|---|---|
| `date` | string | ✖ | CN 今天 | **要查哪一天**的心率（历史日也可传），格式 `YYYY-MM-DD`，非法 → `40403001` 400 |

**出参 `data`**

| 字段 | 类型 | 描述 |
|---|---|---|
| `date` | string | 锚点日（= 入参 `date` 或今天） |
| `summaryMap` | object | **按档位**摘要：键 `day/week/month/year`，值 `{label, value, date}`；`hour` 档**恒 null**（无逐时源） |
| `metricsMap` | object | 当日指标卡：范围 / 静息心率 / **步行平均心率（恒 null —— 厂商无源）** |
| `rangeSets` | object | 按档位范围序列；`hour` 档**恒 null** |
| `restingSource` | string | 静息心率取值来源：`'REST_HR'`（专列）或 `'HR'`（回退） |
| `todaySeries` | array | null | ⭐ **当日 24 小时逐时心率**（详见〔todaySeries 详解〕） |
| `rangeSetsEcharts` | object | ⭐ **echarts 5.6 dataset-ready** 平行字段（dimensions/source/labels/dates） |
| `vendor` | string | **条件出现**：数据来源厂商（`simulated` = 示例数据） |

#### 〔todaySeries 详解〕—— 心率详情页日档折线图的数据源

* **形态**：长度 **24** 的数组，下标 = **CN 小时 0–23**，值 = 该小时心率（float）；该小时无采样 → `null`
* **数据来源**：当日 `health_metric` 行的 **`raw.samples`**（honor hourly job 每小时拉的采样集明细，样本含 `startTime` + `dynamicHeartRate`）
* **何时有值**：绑定 honor 设备 **且** hourly job 运行 **且** 当日有采样 —— 三者缺一即 `null`
* **渲染建议**：折线/柱状图；`null` 处**断点**（不连线、不补 0 —— 与「诚实空态」口径一致）

**实测样例响应**（锚点 2026-09-20 · 生产真数据 · 节选）：

```json
{
  "code": "0",
  "data": {
    "date": "2026-09-20",
    "summaryMap": {
      "day":  {"label": "范围", "value": "69", "date": "2026年9月20日"},
      "week": {"label": "范围", "value": "63-70", "date": "2026年9月14日–20日"}
    },
    "rangeSets": {
      "week": [[0, 63, 63], [1, 65, 65], [2, 65, 65], [3, 67, 67], [4, 66, 66], [5, 65, 65], [6, 69, 69]]
    },
    "rangeSetsEcharts": {
      "week": {
        "dimensions": [{"name":"index","type":"int"},{"name":"low","type":"float"},{"name":"high","type":"float"}],
        "source": [[0, 63, 63], [1, 65, 65], [2, 65, 65], [3, 67, 67], [4, 66, 66], [5, 65, 65], [6, 69, 69]],
        "labels": ["一", "二", "三", "四", "五", "六", "日"],
        "dates":  ["2026-09-14", "2026-09-15", "2026-09-16", "2026-09-17", "2026-09-18", "2026-09-19", "2026-09-20"]
      }
    },
    "restingSource": "REST_HR",
    "todaySeries": null
  }
}
```

**更多说明**：[API 空值字段与样例说明](/internal-wiki/operations/2026-09-23-api-null-fields-samples)（恒 null 全量清单 + 逐时序列抢救路径与解禁记录）。

***

### 10. `activitySummary`

`device` · 健康汇总页 · **条件键最多**

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `date` | string | ✖ | CN 今日 | 锚点日 `^\d{4}-\d{2}-\d{2}$` |
| `month` | string | ✖ | — | `YYYY-MM`。**传了才返回 `month` / `monthDays`**；非法月份 → `40403001` 400 |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `date` | string | 锚点日 |
| `anchor` | string | 同 `date` |
| `goals` | object | 目标值 `{steps: 8000, calories: 400, strength: 30}` |
| `day` | object | 单日活动视图，见 [共享结构 · 单日活动视图](#共享结构·单日活动视图) |
| `week` | array | 7 天数组：`[{weekday, ...精简视图}]` |
| `month` | string | **条件出现** —— 仅传 `payload.month` 时。回显 `YYYY-MM` |
| `monthDays` | array | **条件出现** —— 仅传 `payload.month` 时。`[{day, ...精简视图}]` |
| `vendor` | string | **条件出现** |

***

### 11. `caloriesDetail`

`device` · 卡路里详情页 · 入参同 [端点 8](#_8-sleepdetail)

**出参 `data`** —— 三个**同构活动块**（见 [共享结构 · 活动块](#共享结构·活动块)）

| 字段 | 类型 | 说明 |
|---|---|---|
| `day` / `week` / `month` | object | 块的 `calories` 键 = 该窗口合计千卡 |
| `vendor` | string | **条件出现** |

***

### 12. `stepsDetail`

`device` · 步数详情页 · 入参同 [端点 8](#_8-sleepdetail)

**出参 `data`** —— 三个**同构活动块**

| 字段 | 类型 | 说明 |
|---|---|---|
| `day` / `week` / `month` | object | 块的 `steps` 键 = 该窗口合计步数；**含距离指标**（块内 `metrics` 多两项） |
| `vendor` | string | **条件出现** |

***

### 13. `recordDates`

`device` · 日期选择页（6 处入口共用）

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `days` | number | ✖ | `180` | 回溯天数，范围 **1–366** |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `days` | number | 回显入参 |
| `dates` | array | 有记录的日期数组（`YYYY-MM-DD`） |

***

### 14. `runSessionDetail`

`sport` · 运动详情页

**入参 `payload`**

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `sessionId` | string | ✔ | 会话 id **或**打卡 id —— 两者同字段，长度 1–64 |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `kind` | string | `'session'`（跑步会话）或 `'checkin'`（打卡） |
| `id` | string | 会话 / 打卡 id |
| `summary` | object | 摘要，见 [共享结构 · 会话摘要](#共享结构·会话摘要) |
| `routePoints` | array | 轨迹点 |
| `paceSeries` | array | 配速序列 |
| `paceLaps` | array | 配速分段 |
| `heartZones` | array | null | 心率区间分布 |
| `heartSeries` | array | 心率序列 |
| `cadenceSeries` | array | 步频序列 |
| `strideSeries` | array | 步幅序列 |
| `elevationSeries` | array | 海拔序列 |

***

### 15. `addWeightRecord`

`sport` · 添加体重页 · **写操作**

**入参 `payload`**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `date` | string | ✔ | — | `YYYY-MM-DD`（东八区本地） |
| `time` | string | ✖ | `00:00` | `HH:mm`（东八区本地） |
| `weight` | number | ✔ | — | 体重 |
| `bodyFat` | number | ✖ | — | 体脂率 |

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `ok` | boolean | 是否成功 |
| `id` | string | 新记录 id |
| `date` | string | 回显 |
| `time` | string | 回显（缺省补 `00:00`） |
| `weight` | number | 回显 |
| `bodyFat` | number | null | 回显 |

***

### 16. `reportRunCoordinate`

`sport` · 运动数据页 · **轨迹上报（约 10s / 点）** · 写操作

**入参 `payload`**

| 字段 | 类型 | 必填 | 单位 / 说明 |
|---|---|---|---|
| `sessionId` | string | ✔ | 长度 1–64 |
| `activityType` | string | ✔ | 活动类型枚举 |
| `latitude` | number | ✔ | 纬度 |
| `longitude` | number | ✔ | 经度 |
| `locationTimestamp` | number | ✔ | **epoch 毫秒**（数值型，兼容 JS 浮点毫秒） |
| `accuracy` | number | ✖ | 定位精度 |
| `speed` | number | ✖ | m·s⁻¹ |
| `distance` | number | ✖ | **累计米** |
| `duration` | number | ✖ | **累计毫秒** |
| `reportTimestamp` | number | ✖ | **epoch 毫秒**（存证） |

> ⚠️ 单位与前端实参一致：**距离米 / 时长毫秒 / 速度 m·s⁻¹ / 时间戳 epoch 毫秒**。

**出参 `data`**

| 字段 | 类型 | 说明 |
|---|---|---|
| `ok` | boolean | 是否接收成功 |
| `receivedAt` | string | 服务端接收时刻 |

***

### 17. `archive`

`health-archive` · 健康档案页 · **无入参**

**出参 `data`** —— **7 个组件**

| 字段 | 类型 | 说明 |
|---|---|---|
| `basicInfo` | object | `{age, gender, height, weight, bmi}` |
| `healthIncident` | object | `{symptom, injuries, updatedAt}` —— `updatedAt` 为 ISO 串或 `null` |
| `physiological` | object | `{hrv, sleepHours, heartRateRecovery, activeMetabolism}`；**`heartRateRecovery` 恒 null**（厂商云无该指标） |
| `cardiopulmonary` | object | `{maxHeartRate, restingHeartRate, lactateThresholdHeartRate, vo2max}` |
| `heartRateZones` | object | `{method, methodLabel, thresholds, zones, derived}` ↓ |
| `heartRateZones.thresholds` | `number[]` | null | 恰 4 个，60–240，严格递增 |
| `heartRateZones.zones` | `[{name, value, color}]` | null | **可为 null** —— 直接用 `.length` 会抛 TypeError |
| `runningAbility` | object | `{vdot, records}`；`records = [{key, label, value}]`，**无成绩时 `value` 为 `null`** |
| `compatibilityRanges` | object | `{zones}`；`zones = [{name, value, desc, color}]`，**可为 null**（无打卡成绩时推导不出） |
| `dataSource` | string | **条件出现** —— 仅含模板示例数据时 |
| `defaultDims` | array | **条件出现** —— 同上，哪几项来自模板 |

> **写路径**（`saveHealthIncident` / `saveHeartRateZones`）不在本文范围 —— 健康档案页当前不调用。

***

## 共享结构

### 共享结构·echarts 数据包装

`rangeSetsEcharts` / `pointSetsEcharts` / `rangesEcharts`（v2.0.63 起）—— 后端按 **echarts 5.6 dataset 规范**产出的图表数据，前端可零转换消费：

```js
chartOption = {
  dataset: { dimensions: ec.dimensions, source: ec.source },
  xAxis: { type: 'category', data: ec.labels },   // 全桶标签（含空桶）—— 后端权威给出
  series: [{ type: 'custom', encode: { x: 'index', y: ['low', 'high'] }, renderItem }],
}
```

| 字段 | 类型 | 说明 |
|---|---|---|
| `dimensions` | array | `{name, type}`，type ∈ `int` / `float`（TypedArray 性能优化） |
| `source` | array | 与旧 `points` **同源同值**（`[[index, low, high], …]`，稀疏） |
| `labels` | string\[] | **全桶**标签（含无数据桶）—— 周 `'一'…'日'` · 月 `'1'…'31'` · 年 `'1'…'12'` · 日 `日号` |
| `dates` | string\[] | 全桶真实日期（ISO）—— tooltip / 校验用 |

> 旧字段（`rangeSets` / `pointSets` / `weekRanges` / `monthRanges`）**原样保留**，本字段为平行新增。

### 共享结构·活动块

`stepsDetail` / `caloriesDetail` / `strengthDetail` 三者的 `day` / `week` / `month` 同构：

| 字段 | 类型 | 说明 |
|---|---|---|
| `date` | string | 窗口锚点日 |
| `subtitle` | string | null | **日块恒 null**（无逐样本源）；周 / 月块 = `date` |
| `steps` | `calories` | `strength` | number | null | **键名随端点变**。该窗口合计（int） |
| `overviewTitle` | string | 概览标题文案 |
| `metrics` | array | `[{label, value, unit?}]` —— `value` 为 **string**，无数据为 `null` |
| `labels` | array | 横轴标签。日块 = 24 个 `HH:00`；周块 = 周一…周日；月块 = 日号 |
| `values` | array | null | 纵轴值。**日块恒 null**（无逐小时源） |

> 日块的 `metrics` 含：`今日步数/消耗/中高强度` +（`stepsDetail` 另有）`今日距离`。
> 周 / 月块含：`总X` / `日均X` +（`stepsDetail` 另有）`总距离` / `日均距离`。

### 共享结构·单日活动视图

`activitySummary.day`：

| 字段 | 类型 | 说明 |
|---|---|---|
| `date` | string | 日期 |
| `steps` / `calories` / `strength` | number | null | 当日三值（int） |
| `distance` | number | null | 公里（2 位小数） |
| `stepsPercent` / `caloriesPercent` / `strengthPercent` | number | null | 相对目标百分比 |
| `stepData` / `calorieData` / `strengthData` | **恒 null** | 24 点逐小时序列 —— 无源 |

`activitySummary.week[]` / `monthDays[]` 用**精简视图** = 上述 7 项（去掉三个 `*Data`）+ `weekday`（仅 `week`）/ `day`（仅 `monthDays`）。

### 共享结构·运动记录行

`myRunRecords.records[]` —— `kind` 区分两种来源，字段名相同：

| 字段 | 类型 | `kind='session'` | `kind='checkin'` |
|---|---|---|---|
| `id` | string | 会话 id | 打卡 id |
| `kind` | string | `'session'` | `'checkin'` |
| `activityType` | string | 实际类型 | 恒 `running` |
| `label` | string | 类型中文名 | 恒「跑步」 |
| `date` | string | 东八区历日 | CN 日期串 |
| `time` | string | `HH:mm` | `HH:mm` |
| `startedAt` | string | null | ISO | **恒 null** |
| `distanceKm` | number | 公里 | 公里（2 位小数） |
| `durationSec` | number | 秒 | 秒（缺省 `0`） |
| `pace` | string | null | 平均配速 | 平均配速（缺则计算） |
| `heartRate` | number | null | 平均心率 | 平均心率 |
| `calories` | number | null | 热量 | **恒 null**（打卡契约无热量字段，不编造） |
| `detail` | string | 展示文案 | 展示文案（含步频时追加） |

### 共享结构·累计摘要

`myRunRecords.summary` / `periodSummary`：

| 字段 | 类型 | 说明 |
|---|---|---|
| `count` | number | 记录数 |
| `distanceKm` | number | 总距离（2 位小数） |
| `durationSec` | number | 总时长（秒） |
| `durationMin` | number | 总时长（分钟） |
| `calories` | number | null | 总热量 |
| `avgHeartRate` | number | null | 平均心率 |
| `avgPace` | string | null | 平均配速 |

### 共享结构·会话摘要

`runSessionDetail.summary`：

| 字段 | 类型 | 说明 |
|---|---|---|
| `sessionId` | string | 会话 id |
| `activityType` | string | 活动类型 |
| `label` | string | 类型中文名 |
| `status` | string | 会话状态 |
| `startedAt` | string | 开始时刻（ISO） |
| `endedAt` | string | null | 结束时刻 |
| `distanceKm` | number | null | 距离 |
| `durationSec` | number | null | 时长（秒） |
| `avgPace` | string | null | 平均配速 |
| `calories` | number | null | 热量 |
| `avgHr` | number | null | 平均心率 |
| `steps` | number | null | 步数 |

***

## 错误码

| 域 | 段 | 常用码 |
|---|---|---|
| `device` | **`40403xxx`** | `40403001` 参数非法 → 400 · `40403002` 未登录 → 401 |
| `stats` | **`40422xxx`** | `40422001` 参数非法 → 400 · `40422002` 未登录 → 401 |
| `sport` | **`40418xxx`** | `40418001` 参数非法 → 400 · `40418002` 未登录 → 401 |
| `health-archive` | **`40427xxx`** | `40427001` 参数非法 → 400 · `40427002` 未登录 → 401 |

> ⚠️ 业务码优先：**失败时 HTTP 状态与 `code` 都要看**。多数参数 / 鉴权错误返对应的 4xx，
> 但部分业务失败（如样本不足 `40422005`）**返 HTTP 200 + 非 0 `code`**。

***

*字段与类型均取自后端源码（Pydantic 入参模型 + 用例返回构造）。发现与实现不符处，请附原始输出提出复核。*
