mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
fix(tours): the other ways of a leg follow the points of the call that asks for them
The search behind propose_hike_leg_way is kept for a while under the spots of the hike, but it kept the finished points of the first call. A later call over the same spots in another form, such as a round trip given with its way back as its last point, or with other names or stage ends, got the first call's points back, and its create_with no longer came back to the start. The search now keeps only the pins that hold each way, and each call sets them on its own points.
This commit is contained in:
@@ -31,7 +31,6 @@ import {
|
||||
type HikeLegWayRequest,
|
||||
type HikeRoundProposal,
|
||||
type HikeRoundProposeRequest,
|
||||
type HikeStop,
|
||||
type HikeWayMiss,
|
||||
type HikeWayMissing,
|
||||
type HikeWayPoints,
|
||||
@@ -55,9 +54,13 @@ interface RoundSearch {
|
||||
why: HikeWayMiss | null;
|
||||
}
|
||||
|
||||
/** What the ways of one leg came to: the hike's points with each way pinned on, or why there are none. */
|
||||
/**
|
||||
* What the ways of one leg came to: the pins that hold each way the router
|
||||
* keeps to, or why there are none. Only what the router was asked is kept;
|
||||
* each call sets the pins on its own points.
|
||||
*/
|
||||
interface LegWays {
|
||||
ways: HikeStop[][];
|
||||
pins: WayPin[][];
|
||||
why: HikeWayMiss | null;
|
||||
}
|
||||
|
||||
@@ -153,7 +156,12 @@ export class HikeWaysService {
|
||||
}
|
||||
const key = legWaysKey(userId, tripId, request);
|
||||
const search = await this.recall(key, () => this.searchLegWays(userId, tripId, request));
|
||||
const { ways, why } = search as LegWays;
|
||||
const { pins, why } = search as LegWays;
|
||||
// Spots that read as one key may come with other names, stage ends or the way back given as a point.
|
||||
const ways = pins.flatMap((held) => {
|
||||
const pinned = pinnedPointsOf(request.waypoints, request.leg, held);
|
||||
return pinned ? [pinned] : [];
|
||||
});
|
||||
const index = wayIndexOf(request.way);
|
||||
const waypoints = ways[index];
|
||||
if (!waypoints) return wayMissing(request.way, ways.length, why);
|
||||
@@ -254,9 +262,9 @@ export class HikeWaysService {
|
||||
/**
|
||||
* The other ways of one leg: the router's ways between its two ends under
|
||||
* the hike's costing, those that are no way already known, each held by
|
||||
* the fewest pins the router keeps to (as the planner holds a way chosen),
|
||||
* and the hike's points with those pins set on the leg. A way the router
|
||||
* does not keep to, or that would take the hike past its points, is left out.
|
||||
* the fewest pins the router keeps to (as the planner holds a way chosen).
|
||||
* A way the router does not keep to is left out; one whose pins would take
|
||||
* the hike past its points is left out where they are set on them.
|
||||
*/
|
||||
private async searchLegWays(userId: number, tripId: number, request: HikeLegWayRequest): Promise<LegWays> {
|
||||
const ends = legEndsOf(request.waypoints, request.round_trip, request.leg);
|
||||
@@ -268,10 +276,10 @@ export class HikeWaysService {
|
||||
offers = await this.router.alternatesOnFoot(userId, ends.from, ends.to, costingOptions, HIKE_WAY_KEYS.length);
|
||||
} catch (err: unknown) {
|
||||
if (!(err instanceof RouteRefusal)) logWarn(`[Hike] no other ways for trip ${tripId}: ${reasonOf(err)}`);
|
||||
return { ways: [], why: missOf(err) };
|
||||
return { pins: [], why: missOf(err) };
|
||||
}
|
||||
const [own] = offers;
|
||||
if (!own) return { ways: [], why: 'no_way' };
|
||||
if (!own) return { pins: [], why: 'no_way' };
|
||||
const current = roadOf(own);
|
||||
const route = async (pins: WayPin[]): Promise<RoadLine & { fellBack?: boolean }> => {
|
||||
try {
|
||||
@@ -290,12 +298,11 @@ export class HikeWaysService {
|
||||
HIKE_WAY_KEYS.length,
|
||||
);
|
||||
// One way after another: each check asks the router's one paced queue.
|
||||
const ways = await others.reduce<Promise<HikeStop[][]>>(async (before, offer) => {
|
||||
const kept = await others.reduce<Promise<WayPin[][]>>(async (before, offer) => {
|
||||
const held = await before;
|
||||
const proof = await pinWay({ offer: roadOf(offer.coordinates), current, route });
|
||||
const pinned = proof?.held ? pinnedPointsOf(request.waypoints, request.leg, proof.pins) : null;
|
||||
return pinned ? [...held, pinned] : held;
|
||||
return proof?.held ? [...held, proof.pins] : held;
|
||||
}, Promise.resolve([]));
|
||||
return { ways, why: null };
|
||||
return { pins: kept, why: null };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import type { HikeProposeRequest } from '@trek/shared';
|
||||
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// HIKE-WAYS-MCP-001 to HIKE-WAYS-MCP-008
|
||||
// HIKE-WAYS-MCP-001 to HIKE-WAYS-MCP-009
|
||||
|
||||
type Spot = { lat: number; lng: number };
|
||||
|
||||
@@ -230,4 +230,24 @@ describe('propose_hike_leg_way', () => {
|
||||
);
|
||||
expect(answer.leg_way.tour_id).toBe(place.id);
|
||||
});
|
||||
|
||||
it('HIKE-WAYS-MCP-009: a later call over the same spots asks the router nothing new, and its way follows its own points', async () => {
|
||||
const { userId, tripId } = await tripOf();
|
||||
// The way back of a round trip, B to A.
|
||||
alternatesOnFoot.mockResolvedValue([[...OWN].reverse(), [...OTHER].reverse()]);
|
||||
const pin = { lat: 47.51, lng: 11.12 };
|
||||
const round = read(
|
||||
await mcp.proposeHikeLegWay({ tripId, name: 'H', waypoints: [A, B], round_trip: true, leg: 2 }, ctx(userId)),
|
||||
);
|
||||
expect(round.create_with).toEqual({ waypoints: [A, B, pin], round_trip: true });
|
||||
const asked = route.mock.calls.length;
|
||||
// The same hike as get_tour_plan reads it: the way back given as its last point, and a night at the hut.
|
||||
const hut = { ...B, stage_end: true };
|
||||
const saved = read(await mcp.proposeHikeLegWay({ tripId, name: 'H', waypoints: [A, hut, A], leg: 2 }, ctx(userId)));
|
||||
expect(alternatesOnFoot).toHaveBeenCalledTimes(1);
|
||||
expect(route.mock.calls.length).toBe(asked);
|
||||
expect(saved.leg_way).toMatchObject({ key: 'a', leg: 2, ways_found: 1 });
|
||||
expect(saved.create_with).toEqual({ waypoints: [A, hut, pin, A], round_trip: false });
|
||||
expect(propose.mock.calls.at(-1)?.[2]).toMatchObject({ waypoints: [A, hut, pin, A], round_trip: false });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user