Files
GarminHealthLab/docs/REQUIREMENTS.md
ericwyuan f1319a6171 feat: 补齐 Garmin 未同步的数据,并各自配上界面
审计 57 个接口后,把账号里真有数据却从未入库的部分补上。全部走同步模块,
界面只读本地库。

新增数据
- 体重与身体成分(体脂率/肌肉量/体水分/骨量/内脏脂肪/代谢年龄)
- 血压(接口通,账号暂无记录)
- 跑步成绩预测(5 公里 / 10 公里 / 半马 / 全马)
- 爬坡分、饮水量、出汗量 → health_data 新增七列
- 全天曲线:心率 / 压力 / 身体电量 / 呼吸 / 血氧
- 挑战赛(徽章挑战与好友挑战,与一次性的徽章不同,有周期和进度)
- 已配对设备

新增界面
- /body/ 身体成分:体重大数字 + BMI 分级 + 体脂肌肉曲线 + 血压表格
- /race/ 成绩预测:四个距离的预测成绩与配速,以及预测随时间的变化
- /challenges/ 挑战赛:按类型筛选,有目标的显示进度条
- /devices/ 已配对设备
- 每日页新增「全天曲线」,这是存日内采样的主要目的
- 健康页新增「身体成分」分组与「更多」入口,运动页加挑战赛与成绩预测入口

同步开销
- 日内曲线每天五个请求,14 天以内的同步顺带拉,更长的历史交给后台
  「补齐详细数据」,否则一年的同步会多出约 1800 个请求
- 原来的「补齐运动详情」扩展为统一的补齐任务,分阶段上报进度

日内采样抽稀到每天 240 点:手机图表分辨不出更多,只会把行撑大。

全量 446 项测试通过。

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-24 04:16:33 +08:00

121 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Garmin Health Lab — 需求文档
记录所有已提出的需求、理解与进度。**每完成一条:更新本文件的状态 → commit → push。**
状态:`✅ 已完成` · `🚧 进行中` · `📋 待做` · `⏸ 已搁置`
最后更新2026-08-24
---
## 一、基础平台
| # | 需求 | 理解 | 状态 |
|---|---|---|---|
| 1.1 | 新建项目分析佳明海外账号健康数据 | Gitea 自建仓库Web 应用 | ✅ |
| 1.2 | 部署到 NAS (192.168.50.64),用 MariaDB | Flask + gunicorn + MariaDB(socket)SQLite 供开发 | ✅ |
| 1.3 | 公网可访问 | frp 隧道 NAS:8123 → Oracle:8123 | ✅ |
| 1.4 | 开发过程中写单元测试 | 324 项 pytest | ⏸ 用户 2026-08-24 要求暂停,优先做功能 |
| 1.5 | 一任务一 commit完成即推送 | 已成为固定流程 | ✅ |
## 二、数据同步
| # | 需求 | 理解 | 状态 |
|---|---|---|---|
| 2.1 | 同步佳明所有数据,不只是运动 | 31 项每日指标 + 运动 + 徽章 + 个人纪录 | ✅ |
| 2.2 | 验证码在前端输入(人不在电脑前) | Web MFA后台线程阻塞在 `prompt_mfa`,验证码经数据库跨 worker 交接 | ✅ |
| 2.3 | 同步改为用户手动触发,不自动跑 | 同步是界面动作 | ✅ |
| 2.4 | 每小时后台同步最新数据 | `job_locks` 抢占,多 worker 只跑一次 | ✅ |
| 2.5 | 「同步最新数据」按钮 | 同步页首要按钮,等待即返回结果 | ✅ |
| 2.6 | 同步频率可设置 | 设置页选择,调度器按各账号自己的频率判断是否该同步 | ✅ |
| 2.7 | 同步历史范围可设置(多久以前 / 全部) | 设置页选择,同步页「同步历史」按此范围拉取 | ✅ |
| 2.8 | 同步界面简化设计 | 两个按钮 + 三格状态,去掉原来四段说明文字 | ✅ |
## 三、界面框架
| # | 需求 | 理解 | 状态 |
|---|---|---|---|
| 3.1 | 前端推倒重做,要求好看 | Framework7 React (iOS 主题),桌面与手机都要好看 | ✅ |
| 3.2 | Tab今日 / 健康 / 趋势 / 运动 / 设置 | 每日是趋势的子页 | ✅ |
| 3.3 | 首页改三圆环:步数 / 睡眠 / HRV | 并列三环,满环=进入参考区间 | ✅ |
| 3.4 | 交互流畅、有动画 | 数字滚动、入场揭示、`prefers-reduced-motion` | ✅ |
| 3.5 | 删掉各页右上角的半透明椭圆 | 导航栏右侧 `Link`(带 tooltip渲染出来的已移除 | ✅ |
| 3.6 | 睡眠入口不放右上角,改为点健康页睡眠卡片进入 | 入口跟着内容走;每日入口同样移进趋势页内容 | ✅ |
| 3.7 | 主界面切到子界面加 loading 动画 | 顶部进度条,跑到 90% 等页面就位;有最短显示时长,避免快速跳转时闪一下 | ✅ |
## 四、数据展示
| # | 需求 | 理解 | 状态 |
|---|---|---|---|
| 4.1 | 每日详情模块,看某天所有数据 | 每日页 | ✅ |
| 4.2 | 趋势模块7 天 / 月 / 季 / 年 周期 | 按周期取日均,桶长不同也可比 | ✅ |
| 4.3 | 趋势覆盖全部 15 组指标,可自选显示/隐藏 | 指标选择器 | ✅ |
| 4.4 | 指标选择器太丑,改成选择弹窗 | 改为 F7 Popup页面上只留一行「显示指标 n/15」 | ✅ |
| 4.5 | 所有卡片可点击进入详情 | `/metric/:id/`:大数值 + 参考区间 + 趋势图 + 统计 + 依据;新增 `lib/metrics.ts` 统一指标注册表 | ✅ |
| 4.6 | 今日页左右箭头切换前一天/后一天 | 无记录的日期自动跳过;到边界置灰 | ✅ |
| 4.7 | 今日页顶部日期选择控件,可看历史任一天 | 点中间日期开 F7 日历,范围限定在已有数据内 | ✅ |
| 4.8 | 点击每个运动看本次运动详情,数据全展示 | 概览/数据/分段/图表四个 Tab含心率区间条与时间/距离横轴切换 | ✅ |
| 4.9 | 健康页增加身体年龄 | 健康页卡片 + `/body-age/` 展示每一步推算过程与出处 | ✅ |
## 四之二、补齐未同步的数据2026-08-24
审计garminconnect 0.2.8 共 57 个 `get_*`,原先只用了 16 个。只读探测后确认
账号里真有数据、却从未入库的部分如下,全部已加入同步模块并配了界面。
| # | 数据 | 接口 | 存放 | 界面 | 状态 |
|---|---|---|---|---|---|
| 4.10 | 体重与身体成分 | `get_body_composition` | `body_composition` 表 | `/body/` 身体成分 | ✅ |
| 4.11 | 血压 | `get_blood_pressure` | `blood_pressure` 表 | `/body/` 内表格 | ✅ 接口通,账号暂无数据 |
| 4.12 | 跑步成绩预测 | `get_race_predictions` | `race_predictions` 表 | `/race/` 成绩预测 | ✅ |
| 4.13 | 爬坡分 | `get_hill_score` | `health_data.hill_score` | 指标详情 | ✅ |
| 4.14 | 饮水与出汗 | `get_hydration_data` | `health_data` 三列 | 指标详情 | ✅ |
| 4.15 | 全天曲线(心率/压力/身体电量/呼吸/血氧) | 五个日内接口 | `daily_series` 表 | 每日页「全天曲线」 | ✅ |
| 4.16 | 挑战赛 | `get_badge_challenges` 等四个 | `challenges` 表 | `/challenges/` | ✅ |
| 4.17 | 设备 | `get_devices` | `devices` 表 | 设置 → 已配对设备 | ✅ |
**探测为空、未做界面**`get_max_metrics`VO₂max 已从训练状态取到)、
`get_goals``get_inprogress_virtual_challenges`
**判定为重复**`get_stats`/`get_steps_data`/`get_floors`/`get_stress_data`
日聚合接口,数据已在 `health_data``get_activities`(分页);
`get_device_settings`/`get_gear_defaults` 等配置类接口。
同步开销:日内曲线每天五个请求,因此 14 天以内的同步顺带拉取,更长的历史
交给「补齐详细数据」后台任务,否则一年的同步会多出约 1800 个请求。
## 五、设置
| # | 需求 | 理解 | 状态 |
|---|---|---|---|
| 5.1 | 设置项太少:身高/体重/年龄/性别 | 设置页个人资料分组,改动即存,另显示 BMI 与年龄 | ✅ |
| 5.2 | 单位设置 | 公制 / 英制,已存储;各页按此显示待接入 | ✅ 设置完成,🚧 全局套用 |
| 5.3 | 评分依据要在设置说明中显示 | 设置 → 评分依据 `/rating-basis/`11 项区间逐条列出处;指标详情页也各带一份 | ✅ |
## 六、AI
| # | 需求 | 理解 | 状态 |
|---|---|---|---|
| 6.1 | 多个大上下文文本模型,可切换 | gateway / gemini-flash / llama-70b / nemotron-49b / mistral-large | ✅ |
| 6.2 | 暂时隐藏 AI 模块 | `FEATURES.ai = false` | ✅ |
| 6.3 | AI 用来解释,不用来定阈值 | 阈值全部来自公开参考值AI 只负责解读 | ✅ 已确认为设计原则 |
---
## 已修复的缺陷(实机验证发现)
| 现象 | 原因 | 处理 |
|---|---|---|
| 部署后整站 Network Error | 构建时没带 `REACT_APP_API_URL`,打包进了开发默认值 localhost:5000 | 加 `client/.env.production`,写死 `/api`,不再依赖构建时手输 |
| 凌晨打不开当天数据 | 各页用 `toISOString()` 取日期,那是 UTC。UTC+8 每天前 8 小时都在查昨天 | 新增 `lib/day.ts`,全部改用本地日历日期 |
| 右上角/返回键的半透明椭圆 | Framework7 9 给 `.navbar .left/.right` 加了 frosted pill | 在 `f7theme.css` 覆盖掉 |
| 运动详情四个 Tab 竖着排 | F7 把每个 `<button>` 渲染成整宽块元素 | `.metric-tab` 显式 `width: auto` |
| 心率区间百分比偏高区间1 显示 90%,手表是 52% | 分母用了「落在区间内的总时长」,应为整次运动时长 | 改用运动时长低于区间1 的时间不再被挤掉 |
| 打开运动详情超时 60s | 每个请求都重新认证 Garmin`_connect` 单次约 11 秒 | 按进程缓存已认证会话15 分钟 TTL冷启 16s → 热 7s → 命中缓存 0.8s |
| 主要收益显示 UNKNOWN | Garmin 用 UNKNOWN 表示「没有结论」 | 映射为中文UNKNOWN 直接不显示该区块 |
## 设计原则(已达成一致)
1. **AI 不定阈值。** 让模型现编「正常范围」会得到一个不可复现、无法追溯、却带着医学口吻的数字。所有参考区间来自公开来源WHO、AASM、Garmin 官方分级、人群常模AI 只做解读。
2. **依据必须可见。** 任何把数字判为「偏低 / 正常 / 优秀」的区间都是在下判断,出处在 设置 → 评分依据 里逐条列出。
3. **身体年龄是估算。** garminconnect 0.2.8 没有 Fitness Age 接口Garmin 的模型也不公开。本地按 VO₂max 常模推算并展示每一步中间值,明确标注不是医学评估。
4. **缺数据就说缺数据。** 指标没有值时显示「—」并说明原因,不用 0 冒充。