From 37ffd4c9e028e6836378ee822f2306c575f60b80 Mon Sep 17 00:00:00 2001 From: owain Date: Sun, 7 Jun 2026 11:00:22 +0100 Subject: [PATCH] Fix wellness sync crash: serialize intraday_hr as JSON string for psycopg2 psycopg2 treats Python lists as PostgreSQL arrays (bigint[]) rather than JSON, causing a DatatypeMismatch error on the json/jsonb column. Serializing with json.dumps() before the raw SQL INSERT fixes the type error. Also wrap per-day INSERT in try/except+rollback so one bad day doesn't abort the entire session, and add db.rollback() in tasks.py after sync_wellness failure so the final status-update commit can always succeed. Co-Authored-By: Claude Sonnet 4.6 --- backend/app/services/garmin_connect_sync.py | 24 +++++++++++++++------ backend/app/workers/tasks.py | 1 + 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/backend/app/services/garmin_connect_sync.py b/backend/app/services/garmin_connect_sync.py index cd460b3..5bb9239 100644 --- a/backend/app/services/garmin_connect_sync.py +++ b/backend/app/services/garmin_connect_sync.py @@ -253,6 +253,12 @@ def sync_wellness(garmin, user_id: int, since: Optional[datetime], db, if not row: continue + # psycopg2 treats Python lists as PostgreSQL arrays; serialize JSON columns + # explicitly so they arrive as a JSON string that the json/jsonb column accepts. + import json as _json + if "intraday_hr" in row and isinstance(row["intraday_hr"], list): + row["intraday_hr"] = _json.dumps(row["intraday_hr"]) + cols = list(row.keys()) col_sql = ", ".join(cols) val_sql = ", ".join(f":{c}" for c in cols) @@ -267,13 +273,17 @@ def sync_wellness(garmin, user_id: int, since: Optional[datetime], db, params = {"user_id": user_id, "day": day.isoformat()} params.update(row) - db.execute(text(f""" - INSERT INTO health_metrics (user_id, date, {col_sql}) - VALUES (:user_id, :day, {val_sql}) - ON CONFLICT (user_id, date) DO UPDATE SET {upd_sql} - """), params) - db.commit() - processed += 1 + try: + db.execute(text(f""" + INSERT INTO health_metrics (user_id, date, {col_sql}) + VALUES (:user_id, :day, {val_sql}) + ON CONFLICT (user_id, date) DO UPDATE SET {upd_sql} + """), params) + db.commit() + processed += 1 + except Exception as exc: + logger.warning("Failed to upsert health_metrics for %s: %s", day_str, exc) + db.rollback() return processed diff --git a/backend/app/workers/tasks.py b/backend/app/workers/tasks.py index c23b471..50b79f1 100644 --- a/backend/app/workers/tasks.py +++ b/backend/app/workers/tasks.py @@ -530,6 +530,7 @@ def sync_garmin_connect_user(user_id: int): ) except Exception as exc: errors.append(f"wellness: {exc}") + db.rollback() # recover session so the final status commit can succeed cfg.last_sync_at = datetime.now(timezone.utc) cfg.last_sync_status = (