Persist best efforts during import and add resumable backfill

This commit is contained in:
Peter Stockings
2026-10-02 14:05:37 +10:00
parent a82135bc89
commit 9145bf8e47
9 changed files with 317 additions and 16 deletions
+32 -4
View File
@@ -1,6 +1,6 @@
# Running personal records
The dashboard and owner's run page now calculate estimated best efforts for
Activity import calculates and persists estimated best efforts for
1 km, 2 km, 5 km, 10 km, half marathon (21,097.5 m), and marathon (42,195 m).
These are fastest stretches anywhere within a run, rather than lap times or
times projected from average pace. All existing running activities participate.
@@ -22,8 +22,36 @@ These are estimates from watch data, not verified race records or an exact clone
of Strava's proprietary calculation. GPS/distance spikes or sparse recording can
affect results. There is currently no manual effort exclusion or correction UI.
The initial implementation computes on page requests without a database migration.
For large histories, the next step is to persist per-run efforts at import time
with an algorithm version, backfill existing runs, and query those small results.
Imports (FIT upload, Garmin sync and local cache) persist per-run efforts in the
same transaction as the run. Refreshing recorded data recalculates the efforts.
`efforts_version` identifies the calculation version; version 0 means pending.
A current version with an empty list means calculated with no eligible distances.
Page requests only compare stored efforts; there is no sample-based fallback.
Raw samples and laps are deferred in list queries. Route availability is also
persisted, so the dashboard can choose its map without loading historical samples.
Chronological PR comparisons still happen on page requests, using at most six
stored efforts per run, preserving correct badges after backfills or removals.
After `flask --app app db upgrade`, run:
```sh
flask --app app backfill-efforts --batch-size 25
```
The command commits each bounded batch and skips current-version rows on reruns.
It includes non-running and incomplete activities, storing an empty result when
appropriate. `--owner-id ID` limits the operation to one athlete; `--force`
recalculates current results. It uses row locks on PostgreSQL to coordinate with
concurrent refreshes, and fixes an upper activity ID at the start so new imports
do not extend the backfill indefinitely. New imports calculate their own results.
No FIT files need to be downloaded again. On Dokku:
```sh
dokku run stride flask --app app backfill-efforts --batch-size 25
```
Until backfill finishes, pending activities do not contribute to records or
route selection. Keep backfills separate from schema migrations so a large
history does not delay every deployment.
Further additions could include second/third-best medals, annual records, manual
exclusion of bad recordings, and a selected-effort highlight on the route map.