fix(planner): drop the hotel bookend leg when the hotel is the edge stop

The day's accommodation is drawn as a leg to the first and from the last
waypoint. Now that a booked night puts its hotel on the check-in day as a stop
of its own, that last waypoint IS the hotel on most days, and the leg drawn to
it is a round trip of zero kilometres: in the map line, in the sidebar's leg
list and in every exported directions link, which carried the hotel twice in a
row. The defect already existed for a night booked in the road trip; the change
above would have made it the normal case.

Both bookend rules now decline a leg whose edge stop sits at the hotel's own
coordinates, which covers the map, the sidebar, the phone route and both export
links from one place. Compared by coordinates rather than ids, because the
bookend comes off the stay row and the waypoint off the assignment.
This commit is contained in:
Maurice
2026-09-13 01:13:41 +02:00
parent 5e0245046d
commit 462a208fdf
2 changed files with 45 additions and 0 deletions
+27
View File
@@ -308,6 +308,33 @@ describe('shouldDrawMorningLeg', () => {
})
})
describe('the hotel is the edge stop itself', () => {
// Booking a night now puts its hotel on the check-in day as a stop of its own, so
// the day's last waypoint IS the hotel on most days. Without this the drawn line,
// the sidebar leg list and every exported directions link carry a hotel-to-hotel
// leg of zero kilometres.
const atHotel = { isPlace: true, time: null, lat: 48.1, lng: 11.5 }
it('draws no evening leg when the last stop is the hotel you sleep in tonight', () => {
const bookends = { evening: hotel({ end_day_id: 30 }), eveningIsOvernight: true }
expect(shouldDrawEveningLeg(bookends, days[0], atHotel)).toBe(false)
// A different stop at the same distance keeps its leg.
expect(shouldDrawEveningLeg(bookends, days[0], { ...atHotel, lat: 48.2 })).toBe(true)
})
it('draws no morning leg when the first stop is the hotel you woke up in', () => {
const bookends = { morning: hotel({}), morningIsSleptHere: true }
expect(shouldDrawMorningLeg(bookends, days[1], atHotel)).toBe(false)
expect(shouldDrawMorningLeg(bookends, days[1], { ...atHotel, lng: 11.6 })).toBe(true)
})
it('leaves a stop without coordinates alone', () => {
// A transport endpoint carries no lat/lng; the carrier rules below decide it.
const bookends = { evening: hotel({}), eveningIsOvernight: true }
expect(shouldDrawEveningLeg(bookends, days[0], { isPlace: false, time: null })).toBe(true)
})
})
describe('shouldDrawEveningLeg', () => {
const checkOutDay = days[2] // id 30
const out = (over: Partial<Accommodation> = {}) =>
+18
View File
@@ -32,6 +32,22 @@ const noTimeLoopHolds = (hotel: Accommodation, stop?: EdgeStop, dayHasCarrier?:
return withinDayTripRange({ lat: hotel.place_lat, lng: hotel.place_lng }, { lat: stop.lat, lng: stop.lng })
}
/**
* The hotel and the day's edge waypoint are one and the same spot.
*
* Booking a night also puts its hotel on the check-in day as a stop, so that day's
* last waypoint IS the hotel far more often than not, and the bookend leg drawn to
* it would be a zero-kilometre round trip: in the drawn line, in the sidebar's leg
* list and in every exported directions link, which would carry the hotel twice in
* a row. Compared by coordinates, because the bookend comes off the stay row and
* the waypoint off the assignment, and the same tie holds for two places pinned at
* the same spot.
*/
const hotelIsTheStop = (hotel: Accommodation | undefined, stop?: EdgeStop): boolean =>
!!hotel && hotel.place_lat != null && hotel.place_lng != null
&& stop?.lat != null && stop.lng != null
&& hotel.place_lat === stop.lat && hotel.place_lng === stop.lng
// The two hotels that bookend a day: the one you woke up in (morning) and the one you sleep in
// tonight (evening). On a transfer day these differ; on any other day both are the single hotel.
// The morning hotel is keyed off "checked in on an earlier day and still in range" (i.e. you slept
@@ -119,6 +135,7 @@ export const shouldDrawMorningLeg = (
firstStop?: EdgeStop,
dayHasCarrier?: boolean,
): boolean => {
if (hotelIsTheStop(bookends.morning, firstStop)) return false
// You landed here. Whatever hotel the day belongs to, nobody drove out of it to the
// airport they arrived at — so there is no morning leg, not even on a night you
// provably slept in that hotel (#2133).
@@ -150,6 +167,7 @@ export const shouldDrawEveningLeg = (
lastStop?: EdgeStop,
dayHasCarrier?: boolean,
): boolean => {
if (hotelIsTheStop(bookends.evening, lastStop)) return false
// Mirror: you took off from here, so no drive leads from it back to tonight's hotel —
// the reported "flight starting airport connected to the accommodation" (#2133).
if (lastStop?.carrierEdge === 'departure') return false