- backend/.env.example: AI 网关地址 129.146.203.203 → 129.146.26.249; MariaDB 注释段更新为生产实际(garmin 专用账号 @ 127.0.0.1:3306,socket 已不用) - README: 生产数据层/数据库说明改为 Oracle 新机本地 MariaDB 10.3 - docs/REQUIREMENTS: 部署目标 NAS → Oracle 新机;公网方式 frp 隧道 → gunicorn 直绑 8123 - docs/ARCHITECTURE: 部署架构改为真实生产栈(Flask+gunicorn+MariaDB+SPA)
144 lines
9.5 KiB
Markdown
144 lines
9.5 KiB
Markdown
# Garmin Health Lab — 需求文档
|
||
|
||
记录所有已提出的需求、理解与进度。**每完成一条:更新本文件的状态 → commit → push。**
|
||
|
||
状态:`✅ 已完成` · `🚧 进行中` · `📋 待做` · `⏸ 已搁置`
|
||
|
||
最后更新:2026-08-25
|
||
|
||
---
|
||
|
||
## 一、基础平台
|
||
|
||
| # | 需求 | 理解 | 状态 |
|
||
|---|---|---|---|
|
||
| 1.1 | 新建项目分析佳明海外账号健康数据 | Gitea 自建仓库,Web 应用 | ✅ |
|
||
| 1.2 | 部署到 Oracle 云服务器 (129.146.26.249),用 MariaDB | Flask + gunicorn + MariaDB(127.0.0.1:3306),SQLite 供开发 | ✅ |
|
||
| 1.3 | 公网可访问 | gunicorn 直绑 Oracle:8123(frp 隧道已移除) | ✅ |
|
||
| 1.4 | 单元测试 / 界面自测 / 性能测试 | 446 项 pytest;界面用浏览器实测;接口逐个计时 | ✅ |
|
||
| 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 直接不显示该区块 |
|
||
|
||
## 测试与性能(2026-08-24)
|
||
|
||
### 单元测试:446 项
|
||
新增 122 项覆盖这一轮的新代码——设置校验与吸附、身体年龄的方向性与边界、
|
||
运动详情的列存解析与抽稀、同步入库与「只读本地」的保证。
|
||
|
||
### 性能:接口逐个计时(经 frp 公网)
|
||
|
||
| 接口 | 优化前 | 优化后 |
|
||
|---|---|---|
|
||
| 今日页首屏 | 394 KB / 2.03s | **51.5 KB / 0.82s** |
|
||
| 趋势 365 天 | 394 KB / 2.03s | 320 KB / 2.77s(仅在用户主动选 1 年时) |
|
||
| 运动列表 | 38 KB / 1.05s | 38 KB / 0.82s |
|
||
| 打开运动详情 | 超时 60s | **0.8s**(本地读) |
|
||
|
||
两处改动:
|
||
1. **摘要不再下发空值。** 一天 40 项指标里大部分是这块表没有的传感器,
|
||
全按 null 发出去占了整整两成体积。
|
||
2. **今日页首屏只取 60 天**,往前翻越界时再按需加载 180 天。原先一次取一年
|
||
是为了「翻页不发请求」,代价是首屏 394 KB——这个交易在手机上不划算。
|
||
|
||
基线往返约 440ms(NAS → Oracle → 客户端),所以请求**个数**比单个大小更值得省。
|
||
|
||
## 设计原则(已达成一致)
|
||
|
||
1. **AI 不定阈值。** 让模型现编「正常范围」会得到一个不可复现、无法追溯、却带着医学口吻的数字。所有参考区间来自公开来源(WHO、AASM、Garmin 官方分级、人群常模),AI 只做解读。
|
||
2. **依据必须可见。** 任何把数字判为「偏低 / 正常 / 优秀」的区间都是在下判断,出处在 设置 → 评分依据 里逐条列出。
|
||
3. **身体年龄是估算。** garminconnect 0.2.8 没有 Fitness Age 接口,Garmin 的模型也不公开。本地按 VO₂max 常模推算并展示每一步中间值,明确标注不是医学评估。
|
||
4. **缺数据就说缺数据。** 指标没有值时显示「—」并说明原因,不用 0 冒充。
|