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:
@@ -5,7 +5,7 @@
|
||||
|
||||
export let activities: ActivitySummary[] = [];
|
||||
|
||||
type Metric = 'distance' | 'elevation' | 'time';
|
||||
type Metric = 'distance' | 'elevation' | 'time' | 'vam';
|
||||
type Granularity = 'week' | 'month';
|
||||
|
||||
let metric: Metric = 'distance';
|
||||
@@ -15,11 +15,13 @@
|
||||
distance: 'Distance (km)',
|
||||
elevation: 'Elevation gain (m)',
|
||||
time: 'Moving time (h)',
|
||||
vam: 'Avg climbing VAM (m/h)',
|
||||
};
|
||||
const METRIC_FMT: Record<Metric, (v: number) => string> = {
|
||||
distance: v => `${Math.round(v)} km`,
|
||||
elevation: v => `${Math.round(v)} m`,
|
||||
time: v => `${v.toFixed(1)} h`,
|
||||
vam: v => `${Math.round(v)} m/h`,
|
||||
};
|
||||
|
||||
// Cool→warm ramp for past years; current year is always blue-400
|
||||
@@ -57,18 +59,34 @@
|
||||
function buildData(acts: ActivitySummary[], m: Metric, g: Granularity) {
|
||||
const curPeriod = g === 'week' ? weekOfYear(_now) : _now.getMonth() + 1;
|
||||
const byYear = new Map<number, Map<number, number>>();
|
||||
const byYearCnt = new Map<number, Map<number, number>>(); // for VAM averaging
|
||||
|
||||
for (const act of acts) {
|
||||
if (!act.started_at) continue;
|
||||
if (m === 'vam' && act.climbing_vam_mh == null) continue;
|
||||
const d = new Date(act.started_at);
|
||||
const yr = d.getFullYear();
|
||||
const per = g === 'week' ? weekOfYear(d) : d.getMonth() + 1;
|
||||
const val = m === 'distance' ? (act.distance_m ?? 0) / 1000
|
||||
: m === 'elevation' ? (act.elevation_gain_m ?? 0)
|
||||
: m === 'vam' ? (act.climbing_vam_mh ?? 0)
|
||||
: (act.moving_time_s ?? 0) / 3600;
|
||||
if (!byYear.has(yr)) byYear.set(yr, new Map());
|
||||
const ym = byYear.get(yr)!;
|
||||
ym.set(per, (ym.get(per) ?? 0) + val);
|
||||
if (!byYear.has(yr)) byYear.set(yr, new Map());
|
||||
if (!byYearCnt.has(yr)) byYearCnt.set(yr, new Map());
|
||||
const ym = byYear.get(yr)!;
|
||||
const ymc = byYearCnt.get(yr)!;
|
||||
ym.set(per, (ym.get(per) ?? 0) + val);
|
||||
ymc.set(per, (ymc.get(per) ?? 0) + 1);
|
||||
}
|
||||
|
||||
// VAM: convert sums to averages
|
||||
if (m === 'vam') {
|
||||
for (const [yr, ym] of byYear) {
|
||||
const ymc = byYearCnt.get(yr)!;
|
||||
for (const [per, sum] of ym) {
|
||||
ym.set(per, sum / (ymc.get(per) ?? 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const years = [...byYear.keys()].sort();
|
||||
@@ -241,6 +259,7 @@
|
||||
<button class="pill" class:active={metric === 'distance'} on:click={() => metric = 'distance'}>Distance</button>
|
||||
<button class="pill" class:active={metric === 'elevation'} on:click={() => metric = 'elevation'}>Elevation</button>
|
||||
<button class="pill" class:active={metric === 'time'} on:click={() => metric = 'time'}>Time</button>
|
||||
<button class="pill" class:active={metric === 'vam'} on:click={() => metric = 'vam'}>Climbing VAM</button>
|
||||
</div>
|
||||
<div class="pill-group">
|
||||
<button class="pill" class:active={granularity === 'week'} on:click={() => granularity = 'week'}>Weekly</button>
|
||||
@@ -250,8 +269,10 @@
|
||||
|
||||
<div bind:this={chartEl} class="w-full min-h-[320px]"></div>
|
||||
|
||||
<p class="section-label">Cumulative</p>
|
||||
<div bind:this={chartCumEl} class="w-full min-h-[320px]"></div>
|
||||
{#if metric !== 'vam'}
|
||||
<p class="section-label">Cumulative</p>
|
||||
<div bind:this={chartCumEl} class="w-full min-h-[320px]"></div>
|
||||
{/if}
|
||||
|
||||
{#if !rows.length}
|
||||
<p class="text-zinc-500 text-sm mt-4">No activity data to display.</p>
|
||||
|
||||
Reference in New Issue
Block a user