From 5e0f216b0c4556f810ce855b325a413820148ebe Mon Sep 17 00:00:00 2001 From: Maurice Date: Tue, 20 Oct 2026 16:52:08 +0200 Subject: [PATCH] fix(tours): name an actual track's metrics and a tour's stages as derived in get_tour_plan What a recorded track measures is read off its line on every read, and a multi-day tour's stage count and stops are counted from its stage ends, yet derived did not list them, so an assistant would take them for saved facts. --- server/src/nest/tours/tour-plan-read.ts | 6 +++++- server/tests/unit/nest/tour-plan-read.test.ts | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/server/src/nest/tours/tour-plan-read.ts b/server/src/nest/tours/tour-plan-read.ts index fd765f1e7..17480fe2f 100644 --- a/server/src/nest/tours/tour-plan-read.ts +++ b/server/src/nest/tours/tour-plan-read.ts @@ -151,7 +151,9 @@ interface PlanExtras { * The fields of this answer worked out when it was read rather than stored * with the tour. The distance, heights and walking time of a tour are stored * when it is saved, and the days are the trip's own assignments, so neither - * is listed here. + * is listed here. What a recorded track measures (ACT-01) is read off its + * line on every read, and a multi-day tour's stages (MDT-01) are counted from + * its stage ends, so both are. */ export function derivedFields(detail: TourDetailResponse, { controlsFromTrack, line, run }: PlanExtras): string[] { const type = detail.tour.tour_type; @@ -166,5 +168,7 @@ export function derivedFields(detail: TourDetailResponse, { controlsFromTrack, l ...(type === 'walk' ? ['tour.station_count', 'tour.station_stay_minutes', 'tour.station_stays_unknown'] : []), ...(run ? ['run.open'] : []), ...(type === 'kayak' ? ['tour.paddle_check', 'obstacle_check'] : []), + ...((detail.activities?.length ?? 0) > 0 ? ['activities[].metrics'] : []), + ...(detail.tour.stage_count != null ? ['tour.stage_count', 'tour.stage_stops'] : []), ]; } diff --git a/server/tests/unit/nest/tour-plan-read.test.ts b/server/tests/unit/nest/tour-plan-read.test.ts index 2f6fa07a6..da8e45cd8 100644 --- a/server/tests/unit/nest/tour-plan-read.test.ts +++ b/server/tests/unit/nest/tour-plan-read.test.ts @@ -4,7 +4,7 @@ import type { TourDetailResponse, TourWaypoint } from '@trek/shared'; import { describe, expect, it } from 'vitest'; -// TOUR-PLAN-READ-001 to TOUR-PLAN-READ-006 +// TOUR-PLAN-READ-001 to TOUR-PLAN-READ-007 /** The detail of a tour of this type, only as far as the plan reads it. */ function detailOf(tourType: TourDetailResponse['tour']['tour_type']): TourDetailResponse { @@ -107,4 +107,19 @@ describe('tour plan read (MCP-T01)', () => { }); expect(planLegs([free, point(1)], { recorded: true })).toEqual([expect.objectContaining({ kind: 'free' })]); }); + + it('TOUR-PLAN-READ-007: an actual track names its metrics as worked out, a multi-day tour its stage count and stops', () => { + const none = { controlsFromTrack: false, line: null, run: null }; + const plain = derivedFields(detailOf('hike'), none); + expect(plain).not.toContain('activities[].metrics'); + expect(plain).not.toContain('tour.stage_count'); + const tracked = { ...detailOf('hike'), activities: [{ id: 1 }] } as unknown as TourDetailResponse; + expect(derivedFields(tracked, none)).toContain('activities[].metrics'); + expect(derivedFields({ ...detailOf('hike'), activities: [] }, none)).not.toContain('activities[].metrics'); + const staged = { + ...detailOf('hike'), + tour: { tour_type: 'hike', stage_count: 2 }, + } as unknown as TourDetailResponse; + expect(derivedFields(staged, none)).toEqual(expect.arrayContaining(['tour.stage_count', 'tour.stage_stops'])); + }); });