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 直接不显示该区块 |
设计原则(已达成一致)
- AI 不定阈值。 让模型现编「正常范围」会得到一个不可复现、无法追溯、却带着医学口吻的数字。所有参考区间来自公开来源(WHO、AASM、Garmin 官方分级、人群常模),AI 只做解读。
- 依据必须可见。 任何把数字判为「偏低 / 正常 / 优秀」的区间都是在下判断,出处在 设置 → 评分依据 里逐条列出。
- 身体年龄是估算。 garminconnect 0.2.8 没有 Fitness Age 接口,Garmin 的模型也不公开。本地按 VO₂max 常模推算并展示每一步中间值,明确标注不是医学评估。
- 缺数据就说缺数据。 指标没有值时显示「—」并说明原因,不用 0 冒充。