Add VAM (climbing velocity) metric and per-duration curve

Extract pipeline now computes two VAM metrics per activity (cycling,
running, hiking, walking):
- climbing_vam_mh: VAM on ascending segments only, using 30 s forward
  lookahead to classify climbing vs. flat/descent (stored in detail JSON)
- vam_curve: [[duration_s, vam_mh], ...] best VAM per standard duration
  (60 s – 1 h), sliding window on 30 s smoothed elevation, only windows
  with ≥ 10 m net gain count (stored in summary + detail)

Athlete JSON aggregates vam_curve across all activities (all_time,
last_365d, last_90d), same structure as power_curve.

Frontend:
- ActivityDetail shows "Climbing VAM" stat (grouped with elevation)
- AthleteView adds a "VAM Curve" tab that appears only when the athlete
  has climbing data; renders VamChart (new component, mirrors MmpChart)

vam_curve stripped from combined global feed; kept in user year shards
for season-based on-the-fly aggregation in VamChart.

Requires bincio reextract to backfill existing activities.
This commit is contained in:
Davide Scaini
2026-05-16 21:34:06 +02:00
parent de602ff5d9
commit baf20b51ba
8 changed files with 369 additions and 6 deletions
+9
View File
@@ -36,10 +36,17 @@ export interface BestClimb {
title: string;
}
export interface AthleteVamCurve {
all_time: MmpCurve | null;
last_365d: MmpCurve | null;
last_90d: MmpCurve | null;
}
export interface AthleteJson {
bas_version: string;
generated_at: string;
power_curve: AthletePowerCurve;
vam_curve?: AthleteVamCurve | null;
records?: Record<string, Record<string, EffortRecord | ValueRecord>>;
best_climbs?: BestClimb[];
max_hr?: number;
@@ -66,6 +73,7 @@ export interface ActivitySummary {
avg_cadence_rpm: number | null;
avg_power_w: number | null;
mmp: MmpCurve | null;
vam_curve?: MmpCurve | null;
source: string | null;
privacy: Privacy;
detail_url: string | null;
@@ -122,6 +130,7 @@ export interface ActivityDetail extends Omit<ActivitySummary, 'detail_url' | 'tr
/** URL to fetch the timeseries — present for server-extracted activities. */
timeseries_url?: string | null;
mmp: MmpCurve | null;
climbing_vam_mh?: number | null;
strava_id: string | null;
duplicate_of: string | null;
source_file?: string | null;