ericwyuan 617c8325a6 fix(analysis): refill_backlog 活动查询去掉 CAST(... AS TEXT)
MariaDB 不支持 CAST 到 TEXT(仅 SQLite 支持),导致该语句在 prepare 阶段
直接 1064,activity 分支每次 idle 循环都报错刷屏——且无论 backlog 是否有
数据都会失败(语法错误先于执行)。activities.id 本身是 VARCHAR(64),
ai_jobs/ai_insights.subject 是 VARCHAR(96),两侧同类型,CAST 本就不必要。
改为直接比较 ai.subject = a.id / aj.subject = a.id,双后端(SQLite/MariaDB)
语义一致。

已在生产 MariaDB 上只读验证两段 SQL 均正常执行(daily-backlog=0,
activity-backlog=0),本地 analysis/ai/coach 测试全绿。
2026-09-02 20:18:40 +08:00

佳明健康数据分析平台 (Garmin Health Lab)

一个完整的健康数据分析平台用于获取、分析和可视化你的佳明Garmin设备数据。

📊 主要功能

  • 数据同步: 通过 Garmin API 自动同步你的健康数据
  • 综合分析: 步数、心率、睡眠、运动、压力等多维度分析
  • 数据可视化: 交互式图表和仪表板展示健康数据趋势
  • 智能建议: 基于数据分析的个性化健康建议
  • 数据导出: 支持数据导出为 CSV/JSON 格式

🛠 技术栈

前端

  • React 18 + TypeScriptCRA / react-scripts 构建)
  • Framework7 9framework7-reactiOS 风格 UI
  • Recharts (数据可视化)
  • Axios (API 请求JWT 拦截器)

后端

  • Python 3.10+ + Flask应用工厂
  • Gunicorn生产运行NAS :8124
  • 可插拔数据层SQLite本地开发/ MariaDB生产NAS 10.11 库 garmin_health_lab,经 PyMySQL/socket
  • JWT 鉴权;登录走 auth-hub 统一 SSOOAuth2/OIDC本地密码登录已移除
  • garminconnectGarmin API 集成)

早期 Node.js/TypeScript 后端(server/)已整体重写为 backend/ 下的 Python/Flask 单体,目录已删除。

📁 项目结构

GarminHealthLab/
├── client/                 # 前端应用 (React 18 + TypeScript)
│   └── src/services/api.ts # API 客户端(含 JWT 拦截器)
├── backend/                # 后端应用 (Python + Flask) —— 当前可运行实现
│   ├── app.py              # 应用工厂 / 路由装配
│   ├── wsgi.py             # Gunicorn 入口
│   ├── config.py           # 配置(读 .env
│   ├── db.py               # 可插拔数据层 (SQLite / MariaDB)
│   ├── auth.py             # JWT 签发/校验 + require_auth
│   ├── services/           # 业务逻辑 (health / analysis / garmin)
│   ├── routes/             # 蓝图 (auth / garmin / health / analysis)
│   ├── tests/smoke.py      # 冒烟测试
│   ├── requirements.txt
│   └── .env.example
├── server/                 # (已删除)原 Node/TS 后端,已重写为 backend/
├── docs/                   # 文档
├── package.json           # 工作空间根配置
└── README.md

🚀 快速开始

前置要求

  • Node.js 18+
  • npm 或 yarn
  • Garmin Connect 账户

安装依赖

前端React

npm install

后端Python/Flask建议在虚拟环境中安装

cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt

配置环境变量

复制 backend/.env.examplebackend/.env 并填入真实值(.env 已被 .gitignore 忽略,不会进入版本库):

PORT=5000
DB_TYPE=sqlite                       # 本地开发用 sqlite生产切 mariadb
DATABASE_PATH=./data/health.db
JWT_SECRET=your_jwt_secret_here      # 生产务必更换
CORS_ORIGIN=http://localhost:3000,http://localhost:5173

早期 Node 后端的配置方式已弃用,一律使用 backend/.env

数据库SQLite / MariaDB 可插拔

数据层通过 DB_TYPE 环境变量切换后端,业务代码无需改动

  • SQLite默认本地开发:零配置,由 DATABASE_PATH 指定文件位置。
  • MariaDB生产:运行于 NAS192.168.50.64)本地 MariaDB 10.11root 经 socket /run/mysqld/mysqld10.sock(或 TCP 127.0.0.1:3306连接PyMySQL 独立库 garmin_health_lab。完整配置见 backend/.env.example
DB_TYPE=mariadb
MARIADB_SOCKET=/run/mysqld/mysqld10.sock
MARIADB_HOST=127.0.0.1
MARIADB_PORT=3306
MARIADB_USER=root
MARIADB_PASSWORD=your_production_mariadb_password
MARIADB_DATABASE=garmin_health_lab

复制 backend/.env.examplebackend/.env 并填入真实值;.env 已被 .gitignore 忽略,不会进入版本库。

启动开发服务器

后端Flask端口 5000

cd backend
source venv/bin/activate
python app.py
# 或生产方式gunicorn wsgi:app -b 0.0.0.0:5000

前端ReactCRA 开发服务器proxy 到后端 5000

npm install       # 在仓库根目录执行workspaces 装 client
npm run dev       # 同时起后端 5000 + 前端 3000或分别 dev:backend / dev:client

登录走 auth-hub SSO点登录会跳到统一认证页回跳后后端签 JWT。 数据库首次启动自动建表(db.init_db(),幂等)。

冒烟测试

cd backend
python tests/smoke.py

📚 API 文档

认证auth-hub 统一 SSO

  • POST /api/auth/auth-hub/start - 发起 SSO 登录(返回 auth-hub 授权 URL
  • GET /api/auth/callback?code=… - auth-hub 回调(浏览器跳转,签发 JWT
  • POST /api/auth/refresh - 刷新 Token
  • POST /api/auth/logout - 登出

本地邮箱/密码注册登录已移除;账号体系由 auth-hub 统一提供 (见 AUTH_HUB_INTEGRATION.md)。 Garmin 账号的绑定/解绑走 /api/garmin/*(见下),与 Web 登录相互独立。

Garmin 数据同步

  • POST /api/garmin/login - 发起 Garmin OAuth 授权(需验证码时 POST /api/garmin/mfa 提交)
  • GET /api/garmin/auth-status / GET /api/garmin/login-status - 授权状态
  • DELETE /api/garmin/login / POST /api/garmin/disconnect - 退出/解绑 Garmin 账号
  • POST /api/garmin/sync - 同步 Garmin 数据
  • POST /api/garmin/sync-latest - 仅同步最新数据
  • POST /api/garmin/sync-details - 补齐运动详情/全天曲线(后台队列)
  • GET /api/garmin/status / GET /api/garmin/auto-sync - 同步状态
  • GET /api/garmin/activities/<activity_id>/detail - 单次运动详情

健康数据

  • GET /api/health/summary - 获取健康摘要
  • GET /api/health/steps - 获取步数数据
  • GET /api/health/heart-rate - 获取心率数据
  • GET /api/health/sleep - 获取睡眠数据
  • GET /api/health/activities - 获取运动数据
  • GET /api/health/fitness-age - 身体年龄
  • GET /api/health/series - 全天曲线(心率/压力/身体电量/呼吸/血氧)
  • GET /api/health/body-composition / blood-pressure / race-predictions
  • GET /api/health/badges / personal-records / challenges / devices

分析与建议

  • GET /api/analysis/trends - 获取数据趋势
  • GET /api/analysis/recommendations - 规则引擎健康建议
  • GET /api/analysis/models - 可用大模型及其配置状态
  • GET /api/analysis/ai-recommendations - 大模型健康建议(带缓存)

AI 教练

  • GET /api/analysis/briefing - 晨间简报 + 今日运动处方,附计算出的特征上下文
    • 立即返回。若没有匹配当前数据的模型答案,先返回规则版并带上 meta.pending,模型版本在后台生成,再次请求即可取到
    • ?date= 指定日期(默认最新有数据的一天)、?refresh=1 忽略缓存、 ?wait=1 阻塞等待模型(一次生成 2~5 分钟)
  • GET /api/analysis/trend-insight?metric=&startDate=&endDate= - 对选定区间内 单个指标的变化做归因分析(阻塞,未知指标返回 400 并附 supported 列表)
  • POST /api/analysis/copilot - 健康 Copilot 问答SSE 流式返回
    • 请求体 {question, history?, date?, model?}
    • 事件序列 startdelta* → done,失败时为 error

AI 相关接口全部经由自建 ai-gatewayOpenAI 兼容,https://ai.zichuan.xyz/v1AI_GATEWAY_BASE_URL)。 该网关的主上游是大型推理模型,一次生成实测需 2~5 分钟,因此简报走后台生成 + 轮询,趋势归因与 Copilot 走显式触发;任一模型失败时降级为规则引擎, meta.source 会说明本次由谁作答。

🚀 部署(生产 = NAS

  • 位置NAS 192.168.50.64 /volume1/web/garmin-health-labroot 跑 gunicorn 0.0.0.0:81242 workers / 4 threads / --timeout 300开机自启 DSM 任务调度器执行 deploy/S99garmin.sh
  • 公网NAS frpc → http://129.146.26.249:8124
  • 一键部署:先 npm run build,再 ./deploy/push.sh(同步 backend + deploy + 清空重推 client/build + sudo 重启 + pid/health 双校验)。
  • 部署排障前:先读 PROGRESS.mddeploy/ 脚本确认事实。

🔐 安全说明

  • Garmin 账户密码不入库(令牌授权,约一年有效)
  • 所有 API 请求需要 JWT 认证auth-hub SSO 签发)
  • 敏感数据不在前端存储;backend/.env 含密钥,不提交 git

📝 开发指南

详见 DEVELOPMENT.mdARCHITECTURE.md

📄 许可证

MIT

Description
No description provided
Readme 9.5 MiB
Languages
JavaScript 61%
Python 22.2%
TypeScript 12.1%
CSS 4.1%
Shell 0.6%