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
+61 -2
View File
@@ -2,8 +2,11 @@
from bisect import bisect_left, bisect_right
import click
from .analytics import numeric
EFFORTS_VERSION = 1
DISTANCES = (
("1 km", 1000),
("2 km", 2000),
@@ -75,15 +78,26 @@ def best_efforts(activity):
return [best[label] for label, _ in DISTANCES if label in best]
def persist_efforts(activity):
"""Store derived data in the same transaction as the imported run."""
activity.best_efforts = best_efforts(activity)
activity.efforts_version = EFFORTS_VERSION
activity.has_route = any(sample.get("point") for sample in activity.samples)
def record_book(activities):
"""Recompute in activity chronology so backfills and removals stay correct.
"""Compare persisted efforts in chronology; never read samples here.
First efforts establish a record. Strict improvements earn historical PR
badges, which remain on the run after a later activity beats that record.
"""
records, runs = {}, {}
for activity in sorted(activities, key=lambda row: (row.started_at, row.id)):
efforts = best_efforts(activity)
efforts = (
[dict(effort) for effort in activity.best_efforts]
if activity.efforts_version == EFFORTS_VERSION and activity.sport == "running"
else []
)
for effort in efforts:
previous = records.get(effort["label"])
effort["pr"] = previous is None or effort["seconds"] < previous["seconds"] - 1e-6
@@ -91,3 +105,48 @@ def record_book(activities):
records[effort["label"]] = dict(effort, activity=activity)
runs[activity.id] = efforts
return {"records": [records[label] for label, _ in DISTANCES if label in records], "runs": runs}
def register_cli(app):
@app.cli.command("backfill-efforts")
@click.option("--batch-size", default=25, type=click.IntRange(1, 1000), show_default=True)
@click.option("--owner-id", type=click.IntRange(1), help="Limit to one athlete.")
@click.option("--force", is_flag=True, help="Recalculate even current-version efforts.")
def backfill(batch_size, owner_id, force):
"""Backfill persisted best efforts in bounded, resumable transactions."""
from sqlalchemy.orm import undefer
from .extensions import db
from .models import Activity
last_id, count = 0, 0
# A fixed upper bound prevents concurrent imports extending this run forever.
maximum = db.session.scalar(db.select(db.func.max(Activity.id))) or 0
while last_id < maximum:
query = db.select(Activity).where(Activity.id > last_id, Activity.id <= maximum)
if owner_id:
query = query.where(Activity.owner_id == owner_id)
if not force:
query = query.where(Activity.efforts_version != EFFORTS_VERSION)
rows = db.session.scalars(
query.options(undefer(Activity.samples))
.order_by(Activity.id)
.limit(batch_size)
.with_for_update()
).all()
if not rows:
break
try:
for row in rows:
persist_efforts(row)
last_id = rows[-1].id
db.session.commit()
except Exception as error:
db.session.rollback()
raise click.ClickException(
"Backfill failed; completed batches are saved. Rerun to resume."
) from error
count += len(rows)
db.session.expunge_all()
click.echo(f"Backfilled {count} activities (through ID {last_id}).")
click.echo(f"Complete: {count} activities backfilled; effort version {EFFORTS_VERSION}.")