garmin sync: fix same-day second activity skipped by date-only dedup
Build and push images / validate (push) Successful in 3s
Build and push images / build-backend (push) Successful in 6s
Build and push images / build-worker (push) Successful in 6s
Build and push images / build-frontend (push) Successful in 5s

This commit is contained in:
2026-06-18 11:38:10 +01:00
parent 3a07655e2e
commit 66076f2585
2 changed files with 22 additions and 10 deletions
+11 -5
View File
@@ -133,18 +133,24 @@ def sync_activities(garmin, user_id: int, since: Optional[datetime],
continue
# Slow-path dedup: activity imported via bulk export (no garmin_activity_id).
# Check by start_time; stamp the ID so future syncs skip it in the fast path.
act_start_str = act.get("startTimeLocal") or act.get("startTimeGMT") or ""
# Match on the actual start instant (not just the date — two activities on
# the same day are distinct), comparing GMT-to-GMT since FIT start_times are
# stored in UTC. A small window absorbs sub-second/rounding differences.
act_start_str = act.get("startTimeGMT") or ""
if act_start_str:
try:
from datetime import datetime as _dt
from datetime import datetime as _dt, timezone as _tz
act_start = _dt.fromisoformat(act_start_str.replace("Z", "+00:00"))
if act_start.tzinfo is None:
act_start = act_start.replace(tzinfo=_tz.utc) # startTimeGMT is UTC
window = timedelta(minutes=5)
time_match = db.execute(
select(Activity).where(
Activity.user_id == user_id,
func.date(Activity.start_time) == act_start.date(),
Activity.start_time >= act_start - window,
Activity.start_time <= act_start + window,
)
).scalar_one_or_none()
).scalars().first()
if time_match:
if not time_match.garmin_activity_id:
time_match.garmin_activity_id = garmin_id
@@ -127,18 +127,24 @@ def sync_activities(garmin, user_id: int, since: Optional[datetime],
continue
# Slow-path dedup: activity imported via bulk export (no garmin_activity_id).
# Check by start_time; stamp the ID so future syncs skip it in the fast path.
act_start_str = act.get("startTimeLocal") or act.get("startTimeGMT") or ""
# Match on the actual start instant (not just the date — two activities on
# the same day are distinct), comparing GMT-to-GMT since FIT start_times are
# stored in UTC. A small window absorbs sub-second/rounding differences.
act_start_str = act.get("startTimeGMT") or ""
if act_start_str:
try:
from datetime import datetime as _dt
from datetime import datetime as _dt, timezone as _tz
act_start = _dt.fromisoformat(act_start_str.replace("Z", "+00:00"))
if act_start.tzinfo is None:
act_start = act_start.replace(tzinfo=_tz.utc) # startTimeGMT is UTC
window = timedelta(minutes=5)
time_match = db.execute(
select(Activity).where(
Activity.user_id == user_id,
func.date(Activity.start_time) == act_start.date(),
Activity.start_time >= act_start - window,
Activity.start_time <= act_start + window,
)
).scalar_one_or_none()
).scalars().first()
if time_match:
if not time_match.garmin_activity_id:
time_match.garmin_activity_id = garmin_id