diff --git a/src/lib/activity-rules.ts b/src/lib/activity-rules.ts new file mode 100644 index 0000000..b5d4288 --- /dev/null +++ b/src/lib/activity-rules.ts @@ -0,0 +1,8 @@ +// Lift-assisted downhill types: their recorded elevation gain comes from +// lifts, so it doesn't count toward ascent totals. Touring variants +// ("ski touring", "backcountry ski") earn their metres and do count. +export const LIFT_ASSISTED_TYPES = ['ski', 'alpine ski', 'downhill ski', 'snowboard']; + +export function countsTowardAscent(type: string): boolean { + return !LIFT_ASSISTED_TYPES.includes(type.toLowerCase().trim()); +} diff --git a/src/lib/server/db.ts b/src/lib/server/db.ts index 9559e9e..cf2becb 100644 --- a/src/lib/server/db.ts +++ b/src/lib/server/db.ts @@ -3,6 +3,12 @@ import fs from 'node:fs'; import path from 'node:path'; import { PEAKS } from './peaks-seed'; import { assignMinzoom, scorePeak } from './peak-score'; +import { LIFT_ASSISTED_TYPES } from '$lib/activity-rules'; + +// SQL fragment: elevation gain that counts toward ascent totals +// (lift-assisted downhill types contribute 0) +const LIFT_LIST = LIFT_ASSISTED_TYPES.map((t) => `'${t}'`).join(', '); +export const COUNTED_GAIN = `CASE WHEN lower(trim(type)) IN (${LIFT_LIST}) THEN 0 ELSE elev_gain_m END`; const DATA_DIR = process.env.STREBA_DATA_DIR ?? 'data'; fs.mkdirSync(DATA_DIR, { recursive: true }); @@ -234,7 +240,7 @@ export function userStats(userId: number): { .prepare( `SELECT count(*) activities, coalesce(sum(distance_m), 0) distance_m, - coalesce(sum(elev_gain_m), 0) elev_gain_m, + coalesce(sum(${COUNTED_GAIN}), 0) elev_gain_m, coalesce(sum(coalesce(moving_s, duration_s, 0)), 0) moving_s FROM activities WHERE user_id = ?` ) @@ -252,7 +258,7 @@ export function communityTotals(): { `SELECT (SELECT count(*) FROM users) users, count(*) activities, coalesce(sum(distance_m), 0) distance_m, - coalesce(sum(elev_gain_m), 0) elev_gain_m + coalesce(sum(${COUNTED_GAIN}), 0) elev_gain_m FROM activities` ) .get() as { users: number; activities: number; distance_m: number; elev_gain_m: number }; diff --git a/src/routes/activities/+page.server.ts b/src/routes/activities/+page.server.ts index 9f0bffb..f4f471d 100644 --- a/src/routes/activities/+page.server.ts +++ b/src/routes/activities/+page.server.ts @@ -1,4 +1,5 @@ -import { db, listActivities } from '$lib/server/db'; +import { COUNTED_GAIN, db, listActivities } from '$lib/server/db'; +import { countsTowardAscent } from '$lib/activity-rules'; import type { PageServerLoad } from './$types'; export const load: PageServerLoad = ({ locals }) => { @@ -9,7 +10,7 @@ export const load: PageServerLoad = ({ locals }) => { `SELECT substr(coalesce(date, substr(created_at, 1, 10)), 1, 4) year, count(*) count, sum(distance_m) distance_m, - sum(elev_gain_m) elev_gain_m, + sum(${COUNTED_GAIN}) elev_gain_m, sum(coalesce(moving_s, duration_s, 0)) moving_s FROM activities WHERE user_id = ? GROUP BY year ORDER BY year DESC` @@ -28,7 +29,10 @@ export const load: PageServerLoad = ({ locals }) => { totals: { count: activities.length, distance_m: activities.reduce((sum, a) => sum + a.distance_m, 0), - elev_gain_m: activities.reduce((sum, a) => sum + a.elev_gain_m, 0), + elev_gain_m: activities.reduce( + (sum, a) => sum + (countsTowardAscent(a.type) ? a.elev_gain_m : 0), + 0 + ), moving_s: activities.reduce((sum, a) => sum + (a.moving_s ?? a.duration_s ?? 0), 0) } }; diff --git a/src/routes/activities/+page.svelte b/src/routes/activities/+page.svelte index 5c05d1d..2d06be1 100644 --- a/src/routes/activities/+page.svelte +++ b/src/routes/activities/+page.svelte @@ -1,6 +1,7 @@