63 lines
3.5 KiB
Markdown
63 lines
3.5 KiB
Markdown
# Running personal records
|
|
|
|
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.
|
|
|
|
Following [Strava's elapsed-time convention](https://support.strava.com/en-us/articles/15401661-how-do-best-efforts-work-for-running-on-strava),
|
|
stops inside an effort count. The calculation uses full-resolution FIT cumulative
|
|
distance and timestamps, with linear interpolation at distance boundaries. Missing
|
|
distance/time, backwards distance, and non-increasing timestamps split the data.
|
|
Timestamp gaps count as elapsed time; there is no subtraction for auto-pause.
|
|
No distance or time is inferred from an activity summary when samples are absent.
|
|
|
|
The dashboard lists current records. Runs earn historical PR badges if they
|
|
established or strictly improved a record on their activity date; equal times
|
|
do not earn another badge. Import order does not affect the result. Records and
|
|
badges are recalculated from history, so older imports and removals are reflected.
|
|
Comparisons currently appear only in the owner's dashboard and activity page.
|
|
|
|
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.
|
|
|
|
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 env FLASK_APP=app flask 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.
|
|
|
|
Production verification on 2026-10-02: 19 existing activities backfilled, zero
|
|
pending rows, and an immediate rerun processed zero activities. The authenticated
|
|
dashboard returned HTTP 200 without selecting raw samples or laps; HTTPS health
|
|
returned HTTP 200 with `status: ok`.
|