[阶段6] 同步 Garmin 全量数据:31 项日指标 + 奖励 + 个人纪录
原来每天只存 7 个指标,而 get_user_summary 一次就返回 60+ 字段, 另有睡眠分期、训练准备度、耐力分等独立端点从未被调用。 db.py: - health_data 新增 31 列(距离/活动卡路里/基础代谢/爬楼/强度分钟/ 久坐时长/最高最低心率/最大压力/身体电量四项/血氧/呼吸/ 睡眠深浅REM清醒分期/睡眠血氧/睡眠呼吸/睡眠压力/训练准备度/ VO2max/耐力分) - 新增 badges 与 personal_records 两张表,均以 (user_id, garmin_id) 为主键,重复同步更新而非累积 - 新增增量迁移: CREATE TABLE IF NOT EXISTS 对已存在的表不生效, 新列必须显式 ALTER,否则生产库上永远不会出现。按列名比对后 逐个补齐,SQLite 与 MariaDB 都幂等 services/garmin.py: - _extract_daily 改为汇总 user_summary + sleep + hrv + training_readiness + training_status + endurance_score 五个端点 - 每个可选端点用 _safe 包裹:某项设备不记录时留 NULL,不影响当天其余数据 - 新增 sync_badges / sync_personal_records(账号级,每次同步取一次) fix(garmin): 个人纪录整批写入失败 - Garmin 在同一份数据里混用 ISO 字符串和 Unix 毫秒时间戳, prStartTimeGmt 是 1570961412000,写进 DATETIME 列被 MariaDB 以 1292 拒绝,导致 11 项个人纪录一条都没存进去 - 新增 _to_datetime 统一处理 ISO / 毫秒 / 秒三种形状,并优先取 Garmin 自己提供的 *Formatted 字段 services/ai.py: - 送给模型的 CSV 从 7 列扩到 23 列,纳入身体电量、血氧、呼吸、 训练准备度、耐力分和睡眠分期 接口: GET /api/health/badges、/api/health/personal-records tests (+13, 共 292): - 徽章/纪录的往返、重复同步不累积、按用户隔离 - 两个用户可持有同一个 Garmin 徽章 id 而不冲突 - 时间戳三种形状的归一化及无效值不抛异常 NAS 实测: 7 天数据每天 31 项指标、65 个奖励、11 项个人纪录 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -361,13 +361,28 @@ def resolve_chain(preferred=None):
|
||||
|
||||
|
||||
# --- prompt construction ----------------------------------------------------
|
||||
# Kept deliberately short: every extra column multiplies by the number of
|
||||
# days sent, and the column names double as the vocabulary the model cites
|
||||
# back in `basedOn`.
|
||||
_CSV_COLUMNS = [
|
||||
("date", "date"),
|
||||
("steps", "steps"),
|
||||
("distanceMeters", "dist_m"),
|
||||
("heartRate", "rest_hr"),
|
||||
("heartRateMax", "max_hr"),
|
||||
("heartRateVariability", "hrv"),
|
||||
("stress", "stress"),
|
||||
("stressMax", "stress_max"),
|
||||
("bodyBatteryHigh", "bb_high"),
|
||||
("bodyBatteryLow", "bb_low"),
|
||||
("spo2Avg", "spo2"),
|
||||
("respirationAvg", "resp"),
|
||||
("intensityMinutes", "intensity_min"),
|
||||
("caloriesBurned", "kcal"),
|
||||
("activeCalories", "active_kcal"),
|
||||
("floorsAscended", "floors"),
|
||||
("trainingReadiness", "readiness"),
|
||||
("enduranceScore", "endurance"),
|
||||
]
|
||||
|
||||
|
||||
@@ -379,7 +394,10 @@ def build_prompt(summary, activities=None, day_budget=None):
|
||||
"""
|
||||
day_budget = day_budget if day_budget is not None else default_day_budget()
|
||||
rows = summary[-day_budget:] if day_budget else summary
|
||||
header = ",".join(label for _, label in _CSV_COLUMNS) + ",sleep_h,sleep_q"
|
||||
header = (
|
||||
",".join(label for _, label in _CSV_COLUMNS)
|
||||
+ ",sleep_h,sleep_q,sleep_deep_s,sleep_rem_s,sleep_awake_s"
|
||||
)
|
||||
lines = [header]
|
||||
for r in rows:
|
||||
cells = []
|
||||
@@ -387,8 +405,9 @@ def build_prompt(summary, activities=None, day_budget=None):
|
||||
value = r.get(key)
|
||||
cells.append("" if value is None else str(value))
|
||||
sleep = r.get("sleep") or {}
|
||||
cells.append("" if sleep.get("duration") is None else str(sleep["duration"]))
|
||||
cells.append("" if sleep.get("quality") is None else str(sleep["quality"]))
|
||||
for key in ("duration", "quality", "deepSeconds", "remSeconds", "awakeSeconds"):
|
||||
value = sleep.get(key)
|
||||
cells.append("" if value is None else str(value))
|
||||
lines.append(",".join(cells))
|
||||
|
||||
sections = [
|
||||
|
||||
Reference in New Issue
Block a user