VAM: drop duration curve, show avg climbing VAM in Nerd Corner
Remove the per-duration VAM curve everywhere (metrics, summaries, detail JSON, athlete.json, VamChart.svelte, AthleteView VAM tab). Keep only climbing_vam_mh per activity. Add it to activity summaries so NerdCorner can plot average climbing VAM per week/month year-over-year alongside distance/elevation/time. Add --backfill-vam-summary flag to copy the field from existing detail JSONs into index.json without re-extracting.
This commit is contained in:
@@ -102,7 +102,6 @@ def write_activity(
|
||||
"best_efforts": metrics.best_efforts,
|
||||
"best_climb_m": metrics.best_climb_m,
|
||||
"climbing_vam_mh": metrics.climbing_vam_mh,
|
||||
"vam_curve": metrics.vam_curve,
|
||||
"laps": [_serialise_lap(lap) for lap in activity.laps],
|
||||
"timeseries_url": f"activities/{activity_id}.timeseries.json" if timeseries else None,
|
||||
"source": source,
|
||||
@@ -259,7 +258,7 @@ def build_summary(
|
||||
"mmp": metrics.mmp,
|
||||
"best_efforts": metrics.best_efforts,
|
||||
"best_climb_m": metrics.best_climb_m,
|
||||
"vam_curve": metrics.vam_curve,
|
||||
"climbing_vam_mh": metrics.climbing_vam_mh,
|
||||
"source": _infer_source(activity),
|
||||
"privacy": privacy,
|
||||
"detail_url": f"activities/{activity_id}.json",
|
||||
@@ -303,25 +302,6 @@ def write_athlete_json(summaries: list[dict], output_dir: Path, athlete_config:
|
||||
mmps_365 = [s["mmp"] for s in summaries if s.get("mmp") and _is_outdoor(s) and s["started_at"] >= cutoff_365]
|
||||
mmps_90 = [s["mmp"] for s in summaries if s.get("mmp") and _is_outdoor(s) and s["started_at"] >= cutoff_90]
|
||||
|
||||
# ── VAM curve aggregation ─────────────────────────────────────────────────
|
||||
|
||||
def _merge_vam_curves(vam_lists: list[list[list[int]]]) -> list[list[int]]:
|
||||
best: dict[int, int] = {}
|
||||
for vc in vam_lists:
|
||||
for d, v in vc:
|
||||
if d not in best or v > best[d]:
|
||||
best[d] = v
|
||||
return [[d, v] for d, v in sorted(best.items())]
|
||||
|
||||
_VAM_SPORTS = {"cycling", "running", "hiking", "walking"}
|
||||
|
||||
def _has_vam(s: dict) -> bool:
|
||||
return bool(s.get("vam_curve")) and s.get("sport") in _VAM_SPORTS and _is_outdoor(s)
|
||||
|
||||
all_vams = [s["vam_curve"] for s in summaries if _has_vam(s)]
|
||||
vams_365 = [s["vam_curve"] for s in summaries if _has_vam(s) and s["started_at"] >= cutoff_365]
|
||||
vams_90 = [s["vam_curve"] for s in summaries if _has_vam(s) and s["started_at"] >= cutoff_90]
|
||||
|
||||
# ── Personal records aggregation ──────────────────────────────────────────
|
||||
# records[sport][distance_km] = {time_s, activity_id, started_at, title}
|
||||
# best_climb[activity_id] = {climb_m, started_at, title}
|
||||
@@ -390,11 +370,6 @@ def write_athlete_json(summaries: list[dict], output_dir: Path, athlete_config:
|
||||
"last_365d": _merge_mmps(mmps_365) if mmps_365 else None,
|
||||
"last_90d": _merge_mmps(mmps_90) if mmps_90 else None,
|
||||
},
|
||||
"vam_curve": {
|
||||
"all_time": _merge_vam_curves(all_vams) if all_vams else None,
|
||||
"last_365d": _merge_vam_curves(vams_365) if vams_365 else None,
|
||||
"last_90d": _merge_vam_curves(vams_90) if vams_90 else None,
|
||||
},
|
||||
"records": {
|
||||
sport: _serialise_sport_records(records[sport])
|
||||
for sport in SPORTS
|
||||
|
||||
Reference in New Issue
Block a user