feat: 补齐 Garmin 未同步的数据,并各自配上界面

审计 57 个接口后,把账号里真有数据却从未入库的部分补上。全部走同步模块,
界面只读本地库。

新增数据
- 体重与身体成分(体脂率/肌肉量/体水分/骨量/内脏脂肪/代谢年龄)
- 血压(接口通,账号暂无记录)
- 跑步成绩预测(5 公里 / 10 公里 / 半马 / 全马)
- 爬坡分、饮水量、出汗量 → health_data 新增七列
- 全天曲线:心率 / 压力 / 身体电量 / 呼吸 / 血氧
- 挑战赛(徽章挑战与好友挑战,与一次性的徽章不同,有周期和进度)
- 已配对设备

新增界面
- /body/ 身体成分:体重大数字 + BMI 分级 + 体脂肌肉曲线 + 血压表格
- /race/ 成绩预测:四个距离的预测成绩与配速,以及预测随时间的变化
- /challenges/ 挑战赛:按类型筛选,有目标的显示进度条
- /devices/ 已配对设备
- 每日页新增「全天曲线」,这是存日内采样的主要目的
- 健康页新增「身体成分」分组与「更多」入口,运动页加挑战赛与成绩预测入口

同步开销
- 日内曲线每天五个请求,14 天以内的同步顺带拉,更长的历史交给后台
  「补齐详细数据」,否则一年的同步会多出约 1800 个请求
- 原来的「补齐运动详情」扩展为统一的补齐任务,分阶段上报进度

日内采样抽稀到每天 240 点:手机图表分辨不出更多,只会把行撑大。

全量 446 项测试通过。

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
ericwyuan
2026-08-24 04:16:33 +08:00
parent 34940cc387
commit f1319a6171
19 changed files with 1520 additions and 29 deletions

View File

@@ -7,6 +7,7 @@ from auth import require_auth
from services import health as health_svc
from services import settings as settings_svc
from services import fitness_age
from services import garmin_extras as extras
bp = Blueprint("health", __name__)
@@ -88,3 +89,47 @@ def badges():
@require_auth
def personal_records():
return jsonify(health_svc.get_personal_records(g.user_id))
@bp.route("/body-composition", methods=["GET"])
@require_auth
def body_composition():
"""Weight and everything a connected scale reports with it."""
s, e = _range()
return jsonify(extras.get_body_composition(g.user_id, s, e))
@bp.route("/blood-pressure", methods=["GET"])
@require_auth
def blood_pressure():
return jsonify(extras.get_blood_pressure(g.user_id))
@bp.route("/race-predictions", methods=["GET"])
@require_auth
def race_predictions():
"""Garmin's predicted 5K / 10K / half / marathon times, in seconds."""
return jsonify(extras.get_race_predictions(g.user_id))
@bp.route("/series", methods=["GET"])
@require_auth
def daily_series():
"""Within-day curves for one date: heart rate, stress, body battery,
respiration, SpO2."""
date = request.args.get("date")
if not date:
return jsonify({"error": "缺少 date 参数"}), 400
return jsonify(extras.get_daily_series(g.user_id, date))
@bp.route("/challenges", methods=["GET"])
@require_auth
def challenges():
return jsonify(extras.get_challenges(g.user_id))
@bp.route("/devices", methods=["GET"])
@require_auth
def devices():
return jsonify(extras.get_devices(g.user_id))