Commit Graph

58 Commits

Author SHA1 Message Date
ericwyuan
c83340742c [阶段4.1] AI 健康建议 - 多模型可切换 + 大上下文 + 失败兜底
services/ai.py:
- 模型目录(catalog)按短 id 索引,业务代码不感知厂商
  gemini-flash (Google, 1M 上下文)
  llama-70b / qwen-72b / deepseek-r1 (NVIDIA NIM, 128k)
  仅注册纯文本模型,不含视觉模型
- 两个 provider: GeminiProvider、OpenAICompatProvider
  (后者兼容 NVIDIA NIM / Ollama / vLLM)
- 大上下文: 每日指标序列化为 CSV 而非 JSON,同样的数据 token 数约为
  1/4,一整年历史仍远小于最小的 128k 窗口;按 AI_DAY_BUDGET 截断
- 兜底链: 首选模型超时/报错/返回无法解析的文本时自动降级到下一个,
  meta.fallbackFrom 记录降级路径
- 响应解析容忍 markdown 代码块包裹和 JSON 前的多余句子

services/analysis.py:
- get_ai_recommendations(): 所有模型都失败时回落到规则引擎,
  端点始终 200,meta.source 区分 ai / rules

routes/analysis.py:
- GET /api/analysis/models 列出模型及各自是否已配置密钥
- GET /api/analysis/ai-recommendations?model=&days=

tests/test_ai.py (59 通过, 全程 mock 不联网):
- prompt: 大预算截断保留最新的天、缺失指标不写成 "None"、
  一年数据估算 token 数上界
- 解析: 代码块包裹/前置句子/单对象/非法 priority/空建议 等 7 种畸形输入
- provider: 超时、HTTP 4xx/5xx、响应结构异常均转为 AIError;
  未配置密钥时不发出任何请求
- 兜底: gemini 超时后 llama 接管、首个成功则不再调用第二个
- 端点: /models 不泄漏 API key;无密钥时仍返回 200 + 规则建议

密钥一律从环境变量读取,.env.example 只留空占位符。

全量: 161 passed, 1 skipped

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 12:38:19 +08:00
ericwyuan
8e37e5a551 [阶段3.1] pytest 测试基建 + auth/health/analysis 单元测试 - 102 用例全绿
测试基建:
- pytest.ini / requirements-dev.txt
- tests/conftest.py: 每个用例独立 SQLite 库,提供
  db / app / client / user / auth / seed_health 六个 fixture

tests/test_auth.py (38 通过, 1 跳过):
- 密码哈希: 加盐唯一性、格式自描述、明文不入库、畸形哈希不抛异常
- 回归用例: 无 scrypt 的解释器上也能哈希(覆盖上一个 commit 的 bug)
- JWT: 过期/换密钥/篡改签名均拒绝
- 登录错误不区分"邮箱不存在"与"密码错误"(防用户枚举)
- require_auth: 缺失/畸形/过期 header 一律 401 而非 500

tests/test_health.py (30 通过):
- 日期范围上下界均为闭区间
- 数据按 user_id 隔离,查不到他人数据
- 重复 upsert 同一天不产生重复行
- 各指标端点正确剔除 NULL 行

tests/test_analysis.py (34 通过):
- 5 条建议规则的阈值边界逐条固化(8000 步 / 7 小时 / 50 压力 /
  65 静息心率 / 40 HRV)
- 均值按窗口计算而非逐日;只取最近 14 天
- 无睡眠记录的日子不被当作 0 拉低均值
- metric 名走白名单,SQL 注入串不生效
- 建议按 high/medium/low 排序

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 12:34:18 +08:00
ericwyuan
a71c5438ce fix(auth): hashlib.scrypt 在 LibreSSL 构建下缺失导致注册返回 500
macOS 自带 Python 3.9 链接 LibreSSL,hashlib 不提供 scrypt,
register 调用 hash_password 时抛 AttributeError,被 Flask 兜成
500 internal error(登录随之全部失败)。

改用 PBKDF2-HMAC-SHA256(每个 CPython 构建都有),并采用自描述的
存储格式 pbkdf2_sha256$iterations$salt$hash,便于日后迁移算法。
verify_password 仍兼容旧的 salt:hash scrypt 格式(当解释器支持时)。

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 12:34:18 +08:00
ericwyuan
0177758e1f [阶段2.3-2.4] 实现前端数据同步和仪表板页面
前端实现:
- 创建 DataSync 页面
  - 显示同步状态(就绪/正在同步/失败)
  - 手动触发同步按钮
  - 显示同步日志和错误信息
  - 显示同步帮助信息

- 创建 Dashboard 页面
  - 今日概览(步数、心率、睡眠、卡路里)
  - 30 日统计(平均值、总计)
  - 交互式图表展示(步数、心率、睡眠、卡路里)

- 创建 Chart 组件
  - 支持折线图和柱状图
  - 使用 Recharts 库
  - 响应式布局

前端样式:
- DataSync.css
- Dashboard.css
- 完整的响应式设计

验收标准已满足:
- 数据同步页面功能完整
- 仪表板显示健康数据
- 图表正确展示数据趋势
- 移动端响应式

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 12:27:43 +08:00
ericwyuan
637347082a [阶段2.1-2.2] 实现 Garmin 数据同步和健康数据查询路由
后端实现:
- 实现 garmin 路由
  - POST /api/garmin/sync 触发 Garmin 数据同步
  - GET /api/garmin/status 获取同步状态
- 实现 health 路由(所有端点都需要认证)
  - GET /api/health/summary 获取健康数据摘要
  - GET /api/health/steps 获取步数数据
  - GET /api/health/heart-rate 获取心率数据
  - GET /api/health/sleep 获取睡眠数据
  - GET /api/health/activities 获取运动数据

依赖:
- 安装 garmin-connect 用于 Garmin API 集成
- 所有健康数据查询都通过认证中间件保护

验收标准已满足:
- 所有路由返回正确的格式
- 认证检查已应用
- 日期范围过滤支持

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 12:26:27 +08:00
ericwyuan
3b2d0697f0 [阶段1.1-1.7] 实现完整的认证系统
后端实现:
- 创建 AuthService 包含密码加密、JWT 生成和验证
- 创建 authMiddleware 用于 API 路由保护
- 实现 auth 路由 (register, login, logout, /me)

前端实现:
- 创建 Login 页面 (登录/注册标签页)
- 创建 ProtectedRoute 组件用于路由保护
- 更新 App.tsx 集成路由保护
- 前端 API 客户端已包含认证方法和拦截器

验收标准已满足:
- 用户可以注册和登录
- JWT Token 正确生成和验证
- 受保护的路由需要有效 Token
- 未认证用户重定向到登录页面

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 12:25:33 +08:00
ericwyuan
6b05d04773 数据层可插拔(SQLite/MariaDB) + 后端 services 骨架
- database.ts 改为按 DB_TYPE 切换 SQLite / MariaDB(mysql2),sqlite3 按需动态导入
- index.ts 改为初始化数据库后再监听端口
- 新增 AuthService / HealthService / AnalysisService / GarminService
- 新增 .gitignore、server/.env(忽略) 与 .env.example
- README 增加数据库配置说明;package.json 增加 mysql2/@types
2026-08-23 11:58:07 +08:00
ericwyuan
d73405decb Initial commit: Set up Garmin Health Lab project structure
- Initialize monorepo with root workspace configuration
- Set up Express.js backend with TypeScript
- Set up React 18 frontend with TypeScript
- Create database schema with SQLite
- Implement project architecture and documentation
- Add development and deployment guidelines

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 11:11:29 +08:00