Files
GarminHealthLab/PROGRESS.md
ericwyuan c57c930949 feat(ai): AI 教练 —— 晨间简报、运动处方、趋势归因与 Copilot
数值全部在服务端算好再交给模型,模型只做解读。让模型从 CSV 里自己推
z 分数,它算错的次数足以让简报引用图表反驳它的数字。

- services/insights.py:z 分数(28 天个人基线,且**排除当天**——用一个
  值参与算出来的均值去衡量它自己,会把真实离群点摊平)、13 个月趋势斜率
  (按序数日期最小二乘,手表放充电器上一周不会压缩 x 轴)、近 7 天活动量
  对比。
- services/coach.py:三套提示词 + 回复解析,每套都配一个规则引擎版本。
  网关一次生成要几分钟,上游被限流时给一个朴素的答案,好过给一张空卡片。
- services/ai.py:多轮 chat()、SSE stream()、complete()/stream_chat(),
  以及 extract_json()——上游是推理模型,可见输出以思维链开头,所以从末尾
  倒着找最后一个配平的 JSON(字符串感知,扛得住引号里的 } 和转义引号)。
- 接口 briefing / trend-insight / copilot(SSE),缓存表 ai_insights。
- 前端:今日页晨报卡(后台生成 + 轮询升级)、全局 Copilot 浮窗、指标详情
  页归因面板。features.ai 打开。

实测(对着自建 ai-gateway):晨报一次 273 秒,缓存命中 18 毫秒——所以简报
绝不能同步阻塞首屏。网关的流式通道比阻塞通道更不可靠:同一条提示词流式
139 秒后返回「所有模型均不可用」,阻塞则成功,因此 stream_chat() 在流式零
输出时对同一模型退回非流式重试。Copilot 实测 TTFB 9ms、全程 40 秒。

顺带修两处:refresh 原来只跳过缓存读、不删行,导致「重新生成」后的轮询读
到旧行、看到 cached 就停了,用户一直盯着他刚要求替换掉的那段字;基线零方差
时原来返回 z=0.0,把「和每一条观测都不同」标成「完全正常」,改为 z=null。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 13:57:35 +08:00

91 lines
4.5 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 — 项目进度
## 已完成
### 部署
- [x] 后端从 Node.js 重构为 Python/Flask
- [x] 数据层可插拔:开发用 SQLite生产用 MariaDB
- [x] Gunicorn 生产服务器配置2 workers / 4 threads
- [x] 部署到本地 NAS`/volume1/web/garmin-health-lab`),端口 8124
- [x] frp 隧道配置,通过甲骨文公网 IP 外网访问(`http://129.146.26.249:8124`
- [x] 开机自启脚本(`deploy/S99garmin.sh`
- [x] 甲骨文 iptables 放行 8124 端口
### 认证
- [x] auth-hub 统一登录接入OAuth2 / OIDC
- [x] 新建 NAS 专用 client`client_id: 996aLPw4T5gl-rYZ`
- [x] 注册 NAS 回调地址 `http://192.168.50.64:8124/auth/callback` 和公网地址 `http://129.146.26.249:8124/auth/callback`
- [x] JWT 令牌签发与验证
- [x] Garmin OAuth 令牌授权(密码不入库,令牌约一年有效)
### 后端功能
- [x] Garmin 数据同步核心逻辑(`services/garmin.py`
- [x] 后台自动同步调度器(`services/scheduler.py`
- [x] 用户设置:身高/体重/出生日期/性别/单位/同步频率/历史范围(`services/settings.py`
- [x] 健康数据分析服务(`services/analysis.py`
- [x] 身体年龄计算(`services/fitness_age.py`
- [x] 运动详情与全天曲线同步(`services/extras.py`
- [x] 可插拔数据层(`db.py`,支持 SQLite ↔ MariaDB 切换)
- [x] 集中配置管理(`config.py`,从 `.env` 读取)
- [x] 自动同步调度器读取用户 `history_days` 设置(修复前固定 2 天)
### 前端功能
- [x] 仪表板:健康数据概览
- [x] 趋势分析数据可视化与趋势图Recharts
- [x] 数据同步页:同步最新数据 / 同步历史 / 补齐详细数据
- [x] 同步进度轮询与状态展示
- [x] 设置页:个人资料、单位、自动同步、历史范围
- [x] 评分依据:每个评级分段的公开参考值来源
- [x] 数据绑定页Garmin 邮箱+密码输入
- [x] 历史范围选项:自上次同步 / 全部历史 / N 天 / N 年
### 同步逻辑修复
- [x] 自动同步调度器认用户设置的 `history_days`,不再固定 2 天
- [x] 前端 `0`(全部历史)不再被 `||` 吞掉,改为 `??` 处理
- [x] 后端路由和 `sync_data``0` 不再被当成 falsy 回退默认值
- [x] `sync_data``0` → 730 天(全部历史=最大范围)
- [x] 调度器 `0` → 730 天转换
- [x] 「已同步天数」显示数据库实际总天数(`totalDays`),而非上次同步记录数
### 数据库
- [x] MariaDB 数据库创建(`garmin_health_lab`
- [x] 完整表结构:`health_data` / `daily_series` / `activities` / `activity_details` / `users` / `user_settings` / `garmin_tokens` / `sync_status`
- [x] 257 天健康数据已同步2025-12-19 ~ 2026-09-01
### AI 教练2026-09-01
- [x] 特征工程层 `services/insights.py`z 分数28 天个人基线、13 个月趋势
斜率、近 7 天活动量对比,全部服务端算好再交给模型
- [x] 提示词与解析层 `services/coach.py`:晨报 / 趋势归因 / Copilot 三套提示词,
每套都有对应的规则引擎兜底版本
- [x] `services/ai.py` 扩展:多轮 `chat()`、SSE `stream()``extract_json()`
(从推理模型的思维链里取最后一个 JSON
- [x] 接口:`GET /analysis/briefing``GET /analysis/trend-insight`
`POST /analysis/copilot`SSE
- [x] 缓存表 `ai_insights`(按 user + kind + subject数据指纹失效
- [x] 前端:今日页 AI 晨报卡片(后台生成 + 轮询升级)、全局 Copilot 浮窗、
指标详情页 AI 归因面板;`features.ts``ai` 开关已打开
- [x] 接入自建 ai-gateway`https://oracle.zichuan.xyz/ai/v1`),实测走通
> **实测数据2026-09-01**:网关一次晨报生成 **273 秒**(上游 nvidia
> 缓存命中 18 毫秒。网关的**流式**通道比阻塞通道更不可靠——同一条提示词
> 流式 139 秒后返回「所有模型均不可用」,阻塞则成功,因此 `stream_chat()`
> 在流式无输出时会对同一模型退回非流式重试。
## 待办
### 功能完善
- [ ] 仪表板数据可视化组件完善
- [x] 健康建议 / AI 解读功能AI 教练,见下)
- [ ] 数据分析报告生成
- [ ] 多用户支持完善
### 运维
- [ ] 监控与告警
- [ ] 日志轮转与清理
- [ ] 数据库备份策略
- [ ] HTTPS 证书配置Let's Encrypt
### 文档
- [x] ARCHITECTURE.md 需更新(当前仍为 Node.js 架构描述)
- [x] DEVELOPMENT.md 需更新(当前仍为 Node.js 开发指南)
- [x] REQUIREMENTS.md 需更新