generate name and type for date-only activity names

This commit is contained in:
Vincent van der Wal
2026-07-22 12:24:49 +02:00
parent 5937cedcc5
commit cc9037c922
2 changed files with 59 additions and 7 deletions
+3
View File
@@ -12,6 +12,8 @@ export interface ParsedGpx {
name: string | null; name: string | null;
type: string | null; type: string | null;
date: string | null; date: string | null;
/** ISO timestamp of the first trackpoint, when the GPX has times */
start: string | null;
points: TrackPoint[]; points: TrackPoint[];
distance_m: number; distance_m: number;
duration_s: number | null; duration_s: number | null;
@@ -149,6 +151,7 @@ export function parseGpx(xml: string): ParsedGpx {
name: trk0?.name ? String(trk0.name) : meta?.name ? String(meta.name) : null, name: trk0?.name ? String(trk0.name) : meta?.name ? String(meta.name) : null,
type: trk0?.type ? String(trk0.type).toLowerCase() : null, type: trk0?.type ? String(trk0.type).toLowerCase() : null,
date, date,
start: firstTime !== null ? new Date(firstTime).toISOString() : null,
points: simplify(points, 2500), points: simplify(points, 2500),
distance_m: distance, distance_m: distance,
duration_s: duration, duration_s: duration,
+56 -7
View File
@@ -3,6 +3,47 @@ import { db, peaksNearBounds, recordAscent } from '$lib/server/db';
import { matchPeaks, parseGpx } from '$lib/server/gpx'; import { matchPeaks, parseGpx } from '$lib/server/gpx';
import type { Actions } from './$types'; import type { Actions } from './$types';
/** A "name" that is really just a date or timestamp, e.g. "2023-10-03 17:39:35". */
const DATE_ONLY_NAME =
/^\d{4}[-/.]\d{1,2}[-/.]\d{1,2}([ T](\d{1,2}[:.]\d{2}([:.]\d{2})?|\d{4,6}))?Z?$/;
/** Infer an activity type from average moving speed when the GPX doesn't say. */
function inferType(gpx: { distance_m: number; moving_s: number | null }): string | null {
if (!gpx.moving_s || gpx.moving_s < 60) return null;
const kmh = (gpx.distance_m / gpx.moving_s) * 3.6;
if (kmh >= 13) return 'ride';
if (kmh >= 7) return 'run';
return 'hike';
}
/** "Morning Hike", "Evening Run", or "Hike on 3 Oct 2023" when there's no start time. */
function generateName(type: string, start: string | null, date: string | null): string {
const capitalized = type.charAt(0).toUpperCase() + type.slice(1);
if (start) {
const hour = new Date(start).getUTCHours();
const period =
hour >= 4 && hour < 11
? 'Morning'
: hour >= 11 && hour < 14
? 'Lunch'
: hour >= 14 && hour < 18
? 'Afternoon'
: hour >= 18 && hour < 22
? 'Evening'
: 'Night';
return `${period} ${capitalized}`;
}
if (date) {
const nice = new Date(date + 'T12:00:00').toLocaleDateString('en-GB', {
day: 'numeric',
month: 'short',
year: 'numeric'
});
return `${capitalized} on ${nice}`;
}
return capitalized;
}
/** /**
* Parse export-style filenames like * Parse export-style filenames like
* "2018-01-11 141322 - Run - Afternoon Run.gpx" (Date - Type - Name). * "2018-01-11 141322 - Run - Afternoon Run.gpx" (Date - Type - Name).
@@ -45,10 +86,22 @@ export const actions: Actions = {
try { try {
const gpx = parseGpx(await file.text()); const gpx = parseGpx(await file.text());
const parsed = parseFilename(file.name); const parsed = parseFilename(file.name);
// a missing, "outdoor", or numeric (old Strava code) type gets inferred from pace
let type = parsed?.type ?? gpx.type ?? '';
if (!type || type === 'outdoor' || /^\d+$/.test(type)) {
type = inferType(gpx) ?? 'outdoor';
}
// a name that is just a date/timestamp gets a friendly generated one
let name = parsed?.name ?? gpx.name ?? file.name.replace(/\.gpx$/i, '');
if (!name.trim() || DATE_ONLY_NAME.test(name.trim())) {
name = generateName(type, gpx.start, gpx.date ?? parsed?.date ?? null);
}
const result = insert.run({ const result = insert.run({
user_id: userId, user_id: userId,
name: parsed?.name ?? gpx.name ?? file.name.replace(/\.gpx$/i, ''), name,
type: parsed?.type ?? gpx.type ?? 'outdoor', type,
date: gpx.date ?? parsed?.date ?? null, date: gpx.date ?? parsed?.date ?? null,
distance_m: gpx.distance_m, distance_m: gpx.distance_m,
duration_s: gpx.duration_s, duration_s: gpx.duration_s,
@@ -69,11 +122,7 @@ export const actions: Actions = {
if (recordAscent(userId, peak.id, activityId, gpx.date)) newPeaks.push(peak.name); if (recordAscent(userId, peak.id, activityId, gpx.date)) newPeaks.push(peak.name);
} }
uploaded.push({ uploaded.push({ id: activityId, name, newPeaks });
id: activityId,
name: parsed?.name ?? gpx.name ?? file.name,
newPeaks
});
} catch (err) { } catch (err) {
errors.push(`${file.name}: ${err instanceof Error ? err.message : 'could not parse'}`); errors.push(`${file.name}: ${err instanceof Error ? err.message : 'could not parse'}`);
} }