Skip to content

v0.9.23 — Stop non-paddle water sports syncing to eFB

Latest

Choose a tag to compare

@sauroter sauroter released this 27 Jul 15:25
810fd7e

Bug-fix release. Behaviour changes for 6 users on deploy: sailing, windsurfing, surfing and motorboating stop syncing to Kanu-EFB unless explicitly enabled.

Reported by user #115: sailing trips logged on a Fenix as activity "Sail" were landing in eFB. He was right that it wasn't intended.

Root cause

scripts/garmin_fetch.py:is_water_sport admits anything with parentTypeId == 228, described in the code as "all water sport subtypes (kayaking_v2, paddling_v2, rowing_v2)". But 228 is Garmin's entire Water Sports category. The per-user filter couldn't help either — it knew only six paddle prefixes, and the engine deliberately kept unrecognised typeKeys. Sailing was both synced and unexcludable.

What changed

  • Inverted the activity-type filter (migration 0014). users.selected_activity_types records what the user wants rather than what they excluded, superseding the excluded_activity_types model from v0.9.18. Defaults: paddle sports + an other_water catch-all on; sailing, windsurf, surfing, motorboat off unless ticked. Every prior exclusion is preserved by the backfill.
  • Established Garmin's parent-228 membership empirically, not by guessing: sync_runs.type_keys_seen is pre-filter and activities_found post-filter, so sweeping 216 users' histories reveals which typeKeys pass. Inside 228: sailing_v2, windsurfing_v2, boating_v2, wakeboarding_v2, surfing_v2. Outside it: open_water_swimming (0/137), lap_swimming (0/197), apnea_diving (0/26), single_gas_diving (0/10) — Garmin files swimming and diving under their own parents, so they never reach us and deliberately get no categories.
  • other_water stays on by design. We can't tell whether an unrecognised water sport is paddling, and losing a real trip is worse than an unwanted one. It makes a future new Garmin type tickable, not blocked — it is not a guarantee against another leak.
  • New dashboard banner. The "no matching activities" hint fired for exactly the users this change affects and told them to re-tag correctly-tagged Garmin activities. Runs with excluded_count > 0 now get a distinct banner pointing at /settings instead.
  • match_by_name activities now obey the filter. They arrive as typeKey other under parent 17, so unticking a category previously did nothing for the Venu-3 users that fallback exists for. Classified by name now, mirroring garmin_fetch.py (widened to catch German "Paddeln").
  • Fail-open hardening. An empty selection means "sync nothing", so an unreadable column would have silently killed a user's imports: decodeSelectedCategories and the column DEFAULT now fall back to the defaults, while a deliberate [] is still honoured. The 0014 backfill also guards json_type = 'array' and IS NOT NULLjson_valid alone let null/[null] through, where json_each yields SQL NULL and NOT IN backfills an empty selection.

Verification

make test, make lint, make test-python, go vet green; CI green on master. Migration dry-run against a copy of the production DB (272 users): clean boot, 267 users → paddle set + other_water, the 5 with prior exclusions each preserved exactly, zero empty or invalid selections, idempotent on second boot.

Follow-up (not in this release)

The 6 affected users (63, 80, 115, 187, 219, 248) keep their already-uploaded sailing/windsurf/surfing/motorboat Fahrten — the connector never deletes from EFB, so those need removing by hand.