Files
GarminHealthLab/docs/REQUIREMENTS.md
ericwyuan 4331a07462 docs: docs/ 目录同步甲骨文部署事实(上一提交漏加)
上一个提交 git add 漏了 docs/,架构/开发/需求/auth-hub 集成文档里的 NAS
IP、8124 端口还是旧的,补上。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 23:53:26 +08:00

11 KiB
Raw Permalink Blame History

Garmin Health Lab — 需求文档

记录所有已提出的需求、理解与进度。每完成一条:更新本文件的状态 → commit → push。

状态:✅ 已完成 · 🚧 进行中 · 📋 待做 · ⏸ 已搁置

最后更新2026-09-02


一、基础平台

# 需求 理解 状态
1.1 新建项目分析佳明海外账号健康数据 Gitea 自建仓库Web 应用
1.2 部署到甲骨文云主机,用 MariaDB Flask + gunicorn(ubuntu, systemd, :5500) + 本机 MariaDB 10.3.39(库 garmin_health_labSQLite 供开发2026-09-12 从 NAS 迁移
1.3 公网可访问 Caddy 反代 https://garmin.zichuan.xyz(自动 TLS与本机 ai-gateway/auth-hub 同一套约定
1.4 单元测试 / 界面自测 / 性能测试 446 项 pytest界面用浏览器实测接口逐个计时
1.5 一任务一 commit完成即推送 已成为固定流程
1.6 auth-hub 统一登录SSO OAuth2/OIDC回调注册 LAN + 公网两地址;本地邮箱密码登录已移除
1.7 一键部署脚本 deploy/push.shtar-over-ssh 同步 + 清空重推前端 + sudo 重启 + pid/health 双校验

二、数据同步

# 需求 理解 状态
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_metricsVO₂max 已从训练状态取到)、 get_goalsget_inprogress_virtual_challenges判定为重复get_stats/get_steps_data/get_floors/get_stress_data 等 日聚合接口,数据已在 health_dataget_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 只负责解读 已确认为设计原则
6.4 AI 教练:晨报 / 趋势归因 / Copilot2026-09-01 services/insights 特征工程 → coach.py 三套提示词 + 规则兜底 → ai-gateway晨报后台生成 + 轮询Copilot SSE
6.5 AI 生成走后台队列,界面不阻塞 services/jobs.py 生产者/消费者DB 互斥);每日/运动详情持续排队不限量(refill_backlog
6.6 队列状态可见、可重试 设置 → AI 生成队列页 + /analysis/insight/queue/retry
6.7 AI 模型不可用时降级不报错 规则引擎兜底,meta.source 标注谁作答;流式失败自动退回非流式

已修复的缺陷(实机验证发现)

现象 原因 处理
部署后整站 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 直接不显示该区块
Copilot 浮窗布局错乱(✕ 错位、输入框缩成 21px Framework7 全局 button { width: 100% } 压过自定义类 .copilot-* 按钮显式 width: auto2026-09-02 已修并部署 NAS :8124
Garmin 429 限流反复触发(同步死循环) 退避太短 + 守卫被多 worker 内存卡死 + RetryError 里的 429 未识别 退避 24h + rate_limited_until 以 DB 为准 + 沿异常 __cause__ 链识别 429
文档与实现脱节导致误判部署位置 文档停在 Node.js / 甲骨文 8123 时代 docs/* 与 CLAUDE.md/README 已按 NAS :8124 + Flask 现状重写2026-09-02

测试与性能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——这个交易在手机上不划算。

基线往返约 440msNAS → Oracle → 客户端),所以请求个数比单个大小更值得省。

设计原则(已达成一致)

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