Commit Graph

17 Commits

Author SHA1 Message Date
owain 4a4cbdcc92 Fix pace sentinel, route map thumbnails, tiled segments, health/dashboard layout
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 8s
- Pace: FIT 0xFFFF sentinel (65.535 m/s) was stored as avg_speed_ms on every
  activity and lap; add _sanitize_speed() to parser falling back to dist/dur,
  plus a startup SQL migration that fixed 120 activities and 688 laps in-place
- Records: remove swimming from Distance PRs; Route Records rows are clickable
  (navigate to activity), View button removed, small SVG route map per row;
  Segment Records uses same tiled route-card layout as Segments page
- Segments: replace route dropdown with responsive tile grid showing SVG map
  thumbnails; selecting a tile reveals the segment management panel below
- RouteMiniMap: new pure-SVG component (no Leaflet) for route thumbnails,
  decodes polyline and normalises coords into a fixed viewBox
- Health: rename "Avg Heart Rate (day)" → "Heart Rate"; weight chart now
  filters to non-null rows and enables connectNulls + dots for sparse data
- Dashboard: 4-col layout at lg breakpoint so Body Battery sits between weekly
  chart and Health Today; Body Battery card gains a 24-hr sparkline from the
  values[] already present in the health summary response

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 16:36:54 +01:00
owain da9c1e04cb Body
Build and push images / validate (push) Successful in 19s
Build and push images / build-backend (push) Successful in 1m15s
Build and push images / build-worker (push) Successful in 1m13s
Build and push images / build-frontend (push) Successful in 51s
2026-06-07 15:26:54 +01:00
owain 568dc31e97 Round 2: body battery redesign, profile cleanup, segment integration, route/segment records
Build and push images / validate (push) Successful in 18s
Build and push images / build-backend (push) Successful in 31s
Build and push images / build-worker (push) Successful in 32s
Build and push images / build-frontend (push) Successful in 34s
- Body battery: replace circular ring with compact full-height colored bar chart,
  level as line overlay, legend shows only types present in data
- Dashboard: add mini body battery summary card above health today panel
- Profile: remove editable resting HR and manual weight log; show 7-day avg
  resting HR and latest Garmin weight as read-only
- Backend: add GET /routes/{id}/segment-bests bulk endpoint (fetches all matched
  activity data points in one query, computes best segment time per segment)
- Backend: add GET /records/routes for fastest activity per named route
- Routes page: add Segments panel to route detail (grouped as 1km splits vs
  hills/turns, best times, delete, theoretical best)
- Activity detail page: show segment times computed client-side from data points,
  🏆 badge if new best
- Records page: add Route Records and Segment Records tabs alongside Distance PRs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 13:14:00 +01:00
owain 02eccad578 Add segments, YTD stats, route matching fixes, body battery layout, pace fix
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 9s
- Segments page: new /segments route with auto-generate (1km splits, turn
  detection, hill detection), manual segment creation, per-segment performance
  times across matched activities; fixed auth on existing segment endpoints
- YTD distance: new /activities/stats/ytd endpoint; Dashboard replaces
  'Total distance' with 'Running this year' + 'Cycling this year'; Activities
  page shows YTD stats row
- Weekly chart click: clicking a Dashboard bar navigates to Activities filtered
  to that week; Activities reads from/to query params with dismissable chip
- Route matching: add ±2.5% distance gate + 3% relative DTW threshold
  (was flat 80m); tighten candidate pre-filter from 80/120% to 95/105%
- Body battery layout: HR chart and body battery now side-by-side at same
  height on large screens instead of stacked full-width
- Pace display fix: MetricTimeline clamps GPS speed outliers before computing
  Y-axis domain; tick formatter guards against v<=0 or v>25 m/s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 12:01:25 +01:00
owain 616099402b Add body battery: sync, storage, and health UI chart
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 10s
Parses Garmin Connect get_body_battery() per day, storing charged/drained/
start+end levels and the fine-grained [[ts_ms, level, type, stress]] values
array in a new body_battery JSONB column on health_metrics.

Frontend adds:
- BatteryRing SVG gauge (color-scaled 0–100)
- BodyBatteryChart: ComposedChart with type-colored bars (REST/ACTIVE/SLEEP/
  STRESS) and battery level overlay line, matching Garmin's layout
- Body battery trend chart in the Trends section (end_level per day)

Also adds avg_hr_day and weight data which now correctly sync with the
intraday_hr JSON serialization fix from the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 11:13:38 +01:00
owain f927e32853 Fix missing avg_hr_day/weight data; add 24hr HR chart to daily snapshot
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 5s
Build and push images / build-frontend (push) Successful in 10s
Backend:
- main.py: add ADD COLUMN IF NOT EXISTS migrations for avg_hr_day, max_hr_day,
  and intraday_hr (JSONB) on health_metrics — these columns were in the model
  but missing from existing DB instances, silently dropping all avg/max HR data.
- models/user.py: add intraday_hr JSON column to HealthMetric.
- garmin_connect_sync.py: fetch body composition (weight, BMI, body fat, muscle
  mass) via get_body_composition() per day, with stats.bodyWeight as fallback.
  Fetch intraday heart rate via get_heart_rates() and store non-null
  [epoch_ms, bpm] pairs in intraday_hr.
- health.py: add GET /health-metrics/intraday?date=YYYY-MM-DD endpoint that
  returns the stored intraday_hr array for a specific day.

Frontend (HealthPage):
- Add IntradayHrChart component: AreaChart rendering the 24-hour HR trace
  with time-of-day x-axis.
- DailySnapshot: show 24-hour HR chart (when intraday data present) above
  the activity strip; add weight + body fat % to the Heart & HRV card;
  show max HR alongside avg HR.
- HealthPage: query /intraday for the selected day and pass data down.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 10:47:53 +01:00
owain 22b41109f5 Fix Garmin stats sync, add route merge/map/links, fix PR constraint
Build and push images / validate (push) Successful in 3s
Build and push images / build-backend (push) Successful in 7s
Build and push images / build-worker (push) Successful in 7s
Build and push images / build-frontend (push) Successful in 10s
Garmin health: fix display_name=None when using stored OAuth tokens.
authenticate_garmin() now calls login(tokenstore=...) instead of
garth.loads() directly, so display_name is populated and get_user_summary
works. Also add avg_hr_day / max_hr_day from stats response.

Routes: add merge endpoint (POST /{id}/merge/{source}), delete endpoint.
Routes page: polyline SVG mini-map on each route card, merge UI with
confirmation, activity rows are now Links to the activity detail page.

Personal records: replace all-columns unique constraint with a partial
index (unique on current records only) to stop UniqueViolation crashes
when parallel workers deactivate the same PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 01:25:01 +01:00
owain a3c039b3ea Fix Garmin Connect save — make password optional for settings updates
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 7s
Build and push images / build-worker (push) Successful in 49s
Build and push images / build-frontend (push) Successful in 27s
password was required in GarminConfigIn, causing a 422 when users updated
toggles or sync_lookback_days without re-entering their credentials.

Now only re-authenticates when a new password is supplied; settings-only
updates (sync_enabled, sync_activities, sync_wellness, sync_lookback_days)
go through without touching the stored credentials.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:58:00 +01:00
owain f8c126fbda Add configurable sync_lookback_days for Garmin Connect
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 7s
Build and push images / build-worker (push) Successful in 6s
Build and push images / build-frontend (push) Successful in 10s
Users can now set how many days back the first sync fetches. -1 syncs all
history back to 2010; any positive value sets a rolling window. Values
over 365 show a rate-limit warning in the UI. The default remains 30 days.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:40:55 +01:00
owain 6d224d51c5 Add Garmin Connect auto-sync via python-garminconnect
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 7s
Build and push images / build-worker (push) Successful in 6s
Build and push images / build-frontend (push) Successful in 8s
- GarminConnectConfig model stores encrypted credentials and OAuth token
- garmin_connect_sync service: token-based auth with password fallback,
  activity FIT download + queue, daily wellness from JSON API
- Celery beat schedule: sync_all_garmin_connect fires every hour
- New API router /api/garmin-sync: config CRUD, manual trigger
- Beat container added to docker-compose.yml and docker-compose.deploy.yml
- ProfilePage: Garmin Connect section with connect/update/disconnect and Sync now

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:08:12 +01:00
owain 95f704cb54 Fix upload auto-refresh, health data refresh, and HR zone recalculation
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 6s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 10s
- UploadPage now polls task status every 2s and invalidates activity,
  health-summary, and health-metrics queries on completion so new
  activities and health data appear without a hard refresh
- Garmin and Strava export endpoints now return a task_id for polling
- Updating max HR in Profile triggers a background Celery task to
  recalculate hr_zones for all existing activities; profile page shows
  a confirmation note when this is queued
- Add CLAUDE.md with repo architecture and dev commands

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 23:13:44 +01:00
owain df6c993709 Fix PocketID OIDC endpoints for auth.jarrett.eu
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 5s
2026-06-06 19:51:08 +01:00
owain 34284f3d9d Fix PocketID callback URL to use full base URL
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 6s
Build and push images / build-worker (push) Successful in 4s
Build and push images / build-frontend (push) Successful in 5s
2026-06-06 19:43:26 +01:00
owain e9811d8d83 Fix duplicate detection, add wellness suffixes, add reprocess endpoint
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 5s
2026-06-06 19:02:42 +01:00
owain 93b8f00f94 Fix map route not rendering; fix health date filter timezone mismatch
Build and push images / validate (push) Successful in 3s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 9s
2026-06-06 18:52:29 +01:00
owain ec5a01d12a All tweaks added
Build and push images / build-backend (push) Successful in 33s
Build and push images / build-worker (push) Successful in 32s
Build and push images / build-frontend (push) Failing after 6s
2026-06-06 18:10:35 +01:00
owain 1a0d45dd67 Initial Commit 2026-06-06 13:23:33 +01:00