Files
GarminHealthLab/CLAUDE.md
ericwyuan 2d9a2be185 docs: 文档/配置/测试同步 NAS :8124 生产现状,清除 Node.js 与甲骨文残留
背景:文档停在 Node.js 时代或甲骨文 8123 部署,与生产(NAS :8124 + Flask +
auth-hub + ai-gateway)严重脱节,曾导致凭旧记忆误判'无线上环境'。

- CLAUDE.md 重写:技术栈/结构/命令/部署事实/关键坑(F7 button、UTC 日期、
  429 退避以 DB 为准、迁移幂等、AI 生成耗时)
- docs/ARCHITECTURE.md 重写为 Flask 蓝图+services+可插拔数据层 + NAS 部署
- docs/DEVELOPMENT.md 重写为 Flask/CRA 开发指南 + push.sh 部署流程
- docs/REQUIREMENTS.md:部署条目改 NAS 8124;补 auth-hub/AI 教练/新修复
- docs/AUTH_HUB_INTEGRATION.md 新增(补 .env.example 悬空引用)
- README.md:技术栈/DB/auth-hub/API 清单/部署节修正
- backend/config.py 与 .env.example:AUTH_HUB_REDIRECT_URI 默认 8123→8124,
  MariaDB 注释 Oracle→NAS
- tests:GatewayCourtesy 并发测试对齐 MAX_CONCURRENT(AI_JOB_CONCURRENCY=2);
  conftest 禁用 create_app 后台队列线程,修整库测试 flaky(585 passed)
2026-09-02 19:34:32 +08:00

139 lines
8.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 — 项目指南
佳明Garmin健康数据分析平台同步 Garmin Connect 数据,做多维度健康分析、
可视化与 AI 解读(晨报 / 趋势归因 / Copilot
> **阅读顺序**:先读本文件(协作约定与现状速览)→ [PROGRESS.md](PROGRESS.md)
> (功能与部署进度,**排查前必读**)→ [README.md](README.md)API 与使用)→
> [docs/](docs/)(架构 / 开发 / 需求)。判断"部署在哪里、跑没跑、什么版本"时
> 一律以 `PROGRESS.md` 和 `deploy/` 脚本为准,**不要凭记忆断言**。
## 技术栈现状2026-09
- **后端**Python 3.10+ / Flask应用工厂Gunicorn 生产运行。原 Node/Express
后端早已重写为 Python仓库中**不存在 `server/` 目录**。
- **前端**React 18 + TypeScript用 **CRAreact-scripts**构建UI 组件库为
**Framework7 9**`framework7-react`iOS 主题)+ Recharts 图表。根目录
`package.json` 是 npm workspaces`client`)。
- **数据库**:可插拔数据层(`backend/db.py``DB_TYPE` 切换——
本地开发 SQLite`backend/data/health.db`,默认);生产 NAS MariaDB
`garmin_health_lab` 库,连接配置见 NAS 上 `backend/.env`)。
- **认证****auth-hub SSO**OAuth2/OIDC`services/auth_hub_client.py`
本地邮箱密码注册/登录已移除;应用内 JWT 由后端签发。
- **AI**:自建 ai-gatewayOpenAI 兼容,`https://ai.zichuan.xyz/v1`)为唯一上游
NVIDIA 推理模型,单次生成可达 2~5 分钟);生产 `AI_MODEL_CHAIN=gateway`
## 目录结构
```
GarminHealthLab/
├── backend/ # Flask 后端(当前唯一后端)
│ ├── app.py # 应用工厂:装配蓝图 + 静态 UI + scheduler/jobs
│ ├── wsgi.py # Gunicorn 入口
│ ├── config.py # 集中配置(读 backend/.env
│ ├── auth.py # JWT 签发/校验 + require_auth
│ ├── db.py # 可插拔数据层SQLite ↔ MariaDB+ 幂等迁移
│ ├── routes/ # API 蓝图blueprint
│ │ ├── auth.py # /api/auth auth-hub SSO: callback / logout / refresh / auth-hub/start
│ │ ├── garmin.py # /api/garmin 绑定/同步/MFA/退出/活动详情/同步详情
│ │ ├── health.py # /api/health 摘要/睡眠/心率/运动/身体成分/血氧/设备/挑战等
│ │ ├── analysis.py # /api/analysis 趋势/建议/briefing/trend-insight/copilot/insight
│ │ └── settings.py # /api/settings 个人资料/单位/同步频率/评分依据
│ ├── services/ # 业务逻辑(纯函数/无 Flask 依赖,可单测)
│ │ ├── garmin.py # Garmin API 同步核心(限流退避 24h 写 DB
│ │ ├── garmin_auth.py # Garmin OAuth 登录 + MFA 会话
│ │ ├── garmin_extras.py # 身体成分/血压/血氧/设备/徽章等扩展数据
│ │ ├── scheduler.py # 后台自动同步DB 锁,多 worker 只跑一次)
│ │ ├── ai.py / coach.py / insights.py / jobs.py # AI 教练 + 生产者/消费者队列
│ │ ├── analysis.py / health.py / fitness_age.py / settings.py
│ │ └── auth_hub_client.py / scopes.py
│ ├── data/ # SQLite本地开发git 忽略)
│ ├── static/ # 前端构建产物(生产由 Flask 同端口提供)
│ ├── tests/ # pytest 测试446+ 项)
│ └── .env.example # 环境变量样例(真实 .env 不提交)
├── client/ # React 前端CRA + Framework7
│ ├── src/pages/ # 今日/健康/趋势/运动/设置/每日/睡眠/指标详情等页面
│ ├── src/components/ # AI 晨报 / Copilot 浮窗 / 趋势归因 / Screen 骨架
│ ├── src/lib/ # day.ts本地日期/ metrics.ts指标注册表
│ ├── src/features.ts # 功能开关FEATURES.ai
│ └── .env.production # REACT_APP_API_URL=/api同源防 Network Error
├── deploy/ # NAS 部署脚本族S99garmin/start/stop/push/deploy
├── docs/ # 架构 / 开发 / 需求文档
├── PROGRESS.md # 进度与部署事实(排查必读)
└── README.md # 项目说明 + API 文档
```
## 常用命令
```bash
# 后端依赖(首次):在 backend/ 下建 .venv 并装 requirements-dev.txt
npm run setup:backend
# 同时起前后端开发backend:5000 由 BACKEND_PORT 固定client CRA 代理到 5000
npm run dev
# 单独起后端 / 前端 / 测试 / 类型检查 / 构建
npm run dev:backend
npm run dev:client
npm run test # backend/.venv/bin/python -m pytest446+ 项)
npm run typecheck
npm run build # client/react-scripts build → client/build/
# 冒烟测试(后端单测入口)
cd backend && .venv/bin/python tests/smoke.py
```
## 部署(生产 = NAS端口 8124
- **位置**NAS `192.168.50.64` `/volume1/web/garmin-health-lab`root 用户跑
gunicorn `0.0.0.0:8124`2 workers / 4 threads / --timeout 300开机自启走
DSM 任务调度器执行 `deploy/S99garmin.sh`
- **一键部署**:本地 `./deploy/push.sh`tar 经 ssh 同步 backend + deploy +
清空重推 client/build + sudo 重启,**校验 gunicorn pid 变化 + health 200**)。
前置:先 `npm run build`
- **公网**NAS frpc → 甲骨文 `http://129.146.26.249:8124`frp 重连需几秒)。
- **DB**NAS MariaDB 10.11root 经 socket `/run/mysqld/mysqld10.sock`(或
TCP 127.0.0.1:3306`garmin_health_lab`。10.11 dump 含 `/*M!999999`
注释、旧客户端会报错,且须 `--default-character-set=utf8mb4` 防中文丢失。
- **auth-hub**client `996aLPw4T5gl-rYZ`;回调注册了 LAN
`http://192.168.50.64:8124/auth/callback` 与公网
`http://129.146.26.249:8124/auth/callback` 两个地址。
- **部署/排障前**:先读 `PROGRESS.md``deploy/` 脚本确认事实(曾经凭旧记忆
断言"无线上环境"而误判)。服务以 root 运行:重启用 sudo日志
`logs/error.log``logs/access.log` 是 root 所有。
## 关键约定与坑(写代码/改样式前看)
1. **日期一律本地日历日期**,别用 `toISOString()`UTCUTC+8 每天前 8 小时
查的是昨天)。前端统一走 `client/src/lib/day.ts`
2. **阈值全部来自公开参考值AI 只解读不定阈值**(设计原则,勿破)。
3. **Framework7 全局 `button { width: 100% }`** 会把任何 `<button>` 拉满父容器
——自定义组件里的按钮要显式 `width: auto`Copilot 浮窗 ✕/发送按钮曾因此
错位,见 `Copilot.css` 注释。F7 `.navbar .left/.right` 的 frosted pill 也
需在 `f7theme.css` 覆盖。
4. **同步限流**Garmin 账号 429 退避 24h`rate_limited_until`**DB 为准**
(多 worker 内存不一致会卡死守卫);`_is_rate_limited` 沿异常 `__cause__`
链识别 RetryError 里的 429。
5. **AI 生成慢**:网关上游推理模型单次 2~5 分钟,晨报走后台生成 + 轮询,
接口超时/流式不可靠见 `services/ai.py` 的非流式回退;生产队列并发
`AI_JOB_CONCURRENCY=2`(共享网关,太高会把别家打成 502
6. **迁移幂等**`db.py``init_db()` 会被多 worker 并发调用,所有
`ALTER/CREATE` 必须幂等(`CREATE TABLE IF NOT EXISTS` + 先查列再加列)。
7. **不要提交**`.env`、构建产物 `client/build`、SQLite 数据、`.workbuddy/`
`.claude/`(已在 .gitignore。改代码后同步 `backend/static/` 用 deploy 流程。
8. **一任务一 commit**,完成即推送 NAS Giteaorigin 就是 Gitea
## 测试
- pytest446+ 项)覆盖:设置吸附/校验、身体年龄、运动详情列存解析、Garmin
同步入库与只读本地保证、AI 缓存/队列、调度器等。改后端先跑
`npm run test`(或 `cd backend && .venv/bin/python -m pytest`)。
- 前端无单测框架,用 Chrome headless + CDP 截图做界面回归(见每日日志)。
## 下一步 / 已知问题
-`PROGRESS.md` 的待办区与 `docs/REQUIREMENTS.md`
- 已知独立 bugNAS `logs/stdout.log` 里 `refill_backlog ... SQL syntax near
'TEXT)) AND NOT EXISTS'` 反复刷——某条 AI 回填 SQL 与 NAS MariaDB 语法对
不上,尚未修复。