mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
c0eb1c6e38
MapsSearchDto validates a locationBias and MapsService passes it through, but no caller ever sent one. That gap is the whole difference between the numbers in this PR and what the software does. Measured against a live instance over the 127 places of a real trip, searching each by name: without a hint 50.0% -> 54.8% top-5 with a hint 50.0% -> 73.0% top-5 (this PR reports 72.7%) mess/eval_merge.mjs sends lat/lng on every index call and a viewbox to Nominatim; the client sent neither. Without coordinates the index refuses common queries as too expensive and contributes to 41% of searches instead of 100%, so the merge degenerates to Nominatim alone. The hint has to be NARROW, which is the part that is easy to get wrong. Same corpus, same instance, varying only how the box is derived: no hint 54.8% top-5, 38.1% at rank 1 box around the whole trip 57.1% top-5, 17.5% at rank 1 box around the nearest few places 70.6% top-5, 34.9% at rank 1 A box spanning a round trip has its centre in open country between the cities. It points at nothing and costs more than half the rank-1 hits. useLocationBias therefore prefers the day being planned, falls back to the trip only while it still fits inside one region, and drops the hint past a 60km radius rather than sending a misleading one. This also replaces the two hand-rolled copies of the bounding box in the planner dialog and the phone sheet, which shared a defect: Number(null) is 0, so a place without coordinates entered the box as the Gulf of Guinea and stretched it across the planet.
84 lines
3.6 KiB
TypeScript
84 lines
3.6 KiB
TypeScript
// FE-HOOK-LOCBIAS-001 to FE-HOOK-LOCBIAS-008
|
|
import { renderHook } from '@testing-library/react';
|
|
import { useTripStore } from '../store/tripStore';
|
|
import { boxFromCoords, pointFromBox, radiusKm, useLocationBias } from './useLocationBias';
|
|
|
|
// Coordinates of real places, because the whole point of the hint is distance
|
|
// and a made-up grid would not show when a box stops being useful.
|
|
const KYOTO_A = { id: 1, lat: 35.0116, lng: 135.7681 }; // Kyoto Bahnhof
|
|
const KYOTO_B = { id: 2, lat: 34.9949, lng: 135.7850 }; // Kiyomizu-dera, ~2 km
|
|
const TOKIO = { id: 3, lat: 35.6762, lng: 139.6503 }; // ~370 km entfernt
|
|
|
|
function seed(state: Record<string, unknown>) {
|
|
useTripStore.setState({ places: [], assignments: {}, selectedDayId: null, ...state } as never);
|
|
}
|
|
|
|
afterEach(() => seed({}));
|
|
|
|
describe('boxFromCoords', () => {
|
|
it('ignoriert Eintraege ohne brauchbare Koordinaten', () => {
|
|
const box = boxFromCoords([KYOTO_A, { lat: null, lng: null }, { lat: 'x', lng: 'y' }]);
|
|
expect(box).toEqual({ low: { lat: KYOTO_A.lat, lng: KYOTO_A.lng }, high: { lat: KYOTO_A.lat, lng: KYOTO_A.lng } });
|
|
});
|
|
|
|
it('gibt nichts zurueck, wenn keine Koordinate brauchbar ist', () => {
|
|
expect(boxFromCoords([{ lat: null, lng: null }])).toBeUndefined();
|
|
expect(boxFromCoords([])).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('pointFromBox', () => {
|
|
it('legt den Punkt in die Mitte und deckt die Box mit dem Radius ab', () => {
|
|
const punkt = pointFromBox(boxFromCoords([KYOTO_A, KYOTO_B]));
|
|
expect(punkt!.lat).toBeCloseTo((KYOTO_A.lat + KYOTO_B.lat) / 2, 6);
|
|
expect(punkt!.lng).toBeCloseTo((KYOTO_A.lng + KYOTO_B.lng) / 2, 6);
|
|
// gut 2 km Diagonale, also rund 1 km Radius — angehoben auf die Untergrenze
|
|
expect(punkt!.radius).toBe(5000);
|
|
});
|
|
|
|
it('haelt eine Untergrenze ein, damit ein einzelner Ort die Nachbarschaft meint', () => {
|
|
// Ein Punkt ohne Ausdehnung wuerde sonst mit Radius 0 auf die Tuerschwelle zeigen.
|
|
expect(pointFromBox(boxFromCoords([KYOTO_A]))!.radius).toBe(5000);
|
|
});
|
|
});
|
|
|
|
describe('useLocationBias', () => {
|
|
it('gibt ohne Orte keinen Hinweis', () => {
|
|
seed({});
|
|
expect(renderHook(() => useLocationBias()).result.current).toEqual({});
|
|
});
|
|
|
|
it('nimmt die Orte des geoeffneten Tages, nicht die der ganzen Reise', () => {
|
|
// Die Reise reicht von Kyoto bis Tokio; der offene Tag spielt in Kyoto.
|
|
seed({
|
|
places: [KYOTO_A, KYOTO_B, TOKIO],
|
|
assignments: { '7': [{ place_id: 1 }, { place_id: 2 }] },
|
|
selectedDayId: 7,
|
|
});
|
|
const { box } = renderHook(() => useLocationBias()).result.current;
|
|
expect(box).toBeDefined();
|
|
// Tokio darf nicht in der Box liegen, sonst zeigt der Hinweis ins Leere.
|
|
expect(box!.high.lng).toBeLessThan(TOKIO.lng);
|
|
expect(radiusKm(box!)).toBeLessThan(5);
|
|
});
|
|
|
|
it('faellt auf die Reise zurueck, solange sie in eine Region passt', () => {
|
|
seed({ places: [KYOTO_A, KYOTO_B] });
|
|
const { box, point } = renderHook(() => useLocationBias()).result.current;
|
|
expect(box).toBeDefined();
|
|
expect(point!.lat).toBeCloseTo((KYOTO_A.lat + KYOTO_B.lat) / 2, 6);
|
|
});
|
|
|
|
it('verwirft einen Hinweis, der ueber eine ganze Rundreise spannt', () => {
|
|
// Gemessen: eine Box ueber Kyoto UND Tokio kostet mehr Treffer auf Platz 1
|
|
// als sie einbringt. Kein Hinweis ist dann die bessere Antwort.
|
|
seed({ places: [KYOTO_A, TOKIO] });
|
|
expect(renderHook(() => useLocationBias()).result.current).toEqual({});
|
|
});
|
|
|
|
it('verwirft die zu weite Reise-Box auch dann, wenn ein Tag gewaehlt aber leer ist', () => {
|
|
seed({ places: [KYOTO_A, TOKIO], assignments: { '7': [] }, selectedDayId: 7 });
|
|
expect(renderHook(() => useLocationBias()).result.current).toEqual({});
|
|
});
|
|
});
|