mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(roadtrip): a stop with a start is driven where the day list shows it
The day list draws a day by its starts, the road trip drives the order it is stored in. Moving a stop with a start onto another day, or adding a place that carries one, stored it at the end of the day or where it was dropped, so the list showed it among the stops its hour falls between while the road trip went there last. It is now stored right behind the row the list draws it after, with a booked night read by its check-in as the server's time sort reads it. A stop without a start lands exactly where it did before, and wherever a moved stop lands in the middle of a day, the via points drawn on that day stay on the legs they were drawn for. Behind a later start the road trip reached such a stop late, and the night spill took its earlier clock for a midnight: the stop and every stop after it opened the next day's card, and a day left with one stop fell to the placeholders at the bottom. A stop the schedule marks late now stays on the day the schedule files it on, and the stops behind it are read from its hour, so a clock that goes back past that hour still starts the next day. The desktop rail, the phone's road trip tab and the MCP calculate_roadtrip tool share that spill.
This commit is contained in:
@@ -357,6 +357,132 @@ describe('spillChains', () => {
|
||||
expect(chains[1]!.spills).toEqual([]);
|
||||
});
|
||||
|
||||
// A pinned stop the drive reaches late reads EARLIER on the clock than the stop before
|
||||
// it, because the schedule keeps the traveller's hour and flags the lateness. That is
|
||||
// not a night: the schedule itself files the stop on day 0. Counted as one, the stop
|
||||
// and every stop behind it left for the top of the next day's card, which reads as the
|
||||
// road trip shuffling the day.
|
||||
it('FE-NIGHTSPILL-022: a pinned stop reached late behind a stop without an hour stays on its day', () => {
|
||||
// 09:00 + 1 h, a three hour drive to Celle (13:00), three more to a 12:00 in Hannover.
|
||||
const plan = [
|
||||
day(1, 1, [
|
||||
stop({ assignmentId: 1, name: 'Hamburg', time: '09:00', dwellMinutes: 60 }),
|
||||
stop({ assignmentId: 2, name: 'Celle' }),
|
||||
stop({ assignmentId: 3, name: 'Hannover', time: '12:00' }),
|
||||
]),
|
||||
day(2, 2, [stop({ assignmentId: 4, name: 'Goslar' })]),
|
||||
];
|
||||
|
||||
const chains = spillChains(plan, [], everyLeg(180));
|
||||
|
||||
expect(chains[0]!.stops.map((s) => s.name)).toEqual(['Hamburg', 'Celle', 'Hannover']);
|
||||
expect(chains[0]!.schedule.warnings).toContainEqual({ index: 2, code: 'late', minutes: 240 });
|
||||
expect(chains[1]!.stops.map((s) => s.name)).toEqual(['Goslar']);
|
||||
expect(chains[1]!.spills).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-NIGHTSPILL-023: a day stored against its hours keeps every stop on its own card', () => {
|
||||
// The order a stop dragged onto the day header used to be stored in: at the end,
|
||||
// behind stops with later hours. 10:00 after 14:00 is a stop reached late, not tomorrow.
|
||||
const plan = [
|
||||
day(1, 1, [
|
||||
stop({ assignmentId: 1, name: 'Lübeck', time: '14:00', dwellMinutes: 60 }),
|
||||
stop({ assignmentId: 2, name: 'Wismar', time: '10:00' }),
|
||||
stop({ assignmentId: 3, name: 'Rostock', time: '16:00' }),
|
||||
]),
|
||||
day(2, 2, [stop({ assignmentId: 4, name: 'Stralsund' })]),
|
||||
];
|
||||
|
||||
const chains = spillChains(plan, [], everyLeg(30));
|
||||
|
||||
expect(chains[0]!.stops.map((s) => s.name)).toEqual(['Lübeck', 'Wismar', 'Rostock']);
|
||||
expect(chains[0]!.schedule.warnings).toContainEqual({ index: 1, code: 'late', minutes: 330 });
|
||||
expect(chains[1]!.stops.map((s) => s.name)).toEqual(['Stralsund']);
|
||||
expect(chains[1]!.spills).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-NIGHTSPILL-024: the stop behind one reached late is timed on from its pinned hour', () => {
|
||||
// The schedule carries on from Wismar's 10:00, so Kühlungsborn comes out at 10:30:
|
||||
// earlier than Lübeck's 14:00, later than the hour the drive just went on from.
|
||||
const plan = [
|
||||
day(1, 1, [
|
||||
stop({ assignmentId: 1, name: 'Lübeck', time: '14:00', dwellMinutes: 60 }),
|
||||
stop({ assignmentId: 2, name: 'Wismar', time: '10:00' }),
|
||||
stop({ assignmentId: 3, name: 'Kühlungsborn' }),
|
||||
]),
|
||||
day(2, 2, [stop({ assignmentId: 4, name: 'Stralsund' })]),
|
||||
];
|
||||
|
||||
const chains = spillChains(plan, [], everyLeg(30));
|
||||
|
||||
expect(chains[0]!.stops.map((s) => s.name)).toEqual(['Lübeck', 'Wismar', 'Kühlungsborn']);
|
||||
expect(chains[0]!.schedule.entries[2]!.arrival).toBe('10:30');
|
||||
expect(chains[1]!.stops.map((s) => s.name)).toEqual(['Stralsund']);
|
||||
expect(chains[1]!.spills).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-NIGHTSPILL-025: a stop reached late after midnight still takes the stops behind it along', () => {
|
||||
// 20:00 + 1 h, four hours to a 00:30 in Berlin: late, and tomorrow. The leg out of
|
||||
// Berlin never routed, so Potsdam has no clock of its own and goes where Berlin goes.
|
||||
const plan = [
|
||||
day(1, 1, [
|
||||
stop({ assignmentId: 1, name: 'Hamburg', time: '20:00', dwellMinutes: 60 }),
|
||||
stop({ assignmentId: 2, name: 'Berlin', time: '00:30' }),
|
||||
stop({ assignmentId: 3, name: 'Potsdam' }),
|
||||
]),
|
||||
day(2, 2, [stop({ assignmentId: 4, name: 'Dresden', time: '12:00' })]),
|
||||
];
|
||||
const legFor = (from: RoadtripStop) => (from.name === 'Berlin' ? undefined : everyLeg(240)());
|
||||
|
||||
const chains = spillChains(plan, [], legFor);
|
||||
|
||||
expect(chains[0]!.stops.map((s) => s.name)).toEqual(['Hamburg']);
|
||||
expect(chains[1]!.stops.map((s) => s.name)).toEqual(['Berlin', 'Potsdam', 'Dresden']);
|
||||
expect(chains[1]!.schedule.warnings).toContainEqual({ index: 0, code: 'late', minutes: 30 });
|
||||
});
|
||||
|
||||
it('FE-NIGHTSPILL-026: the clock going back past a late hour is still a night', () => {
|
||||
// FE-NIGHTSPILL-006 with a night at Wittenberg: 23:00, reached late, eight hours on
|
||||
// and fifty minutes to Leipzig at 07:50. That is later than Cobbelsdorf's 07:00 but
|
||||
// earlier than the 23:00 it was timed on from, so it is tomorrow morning.
|
||||
const plan = [
|
||||
day(1, 1, [
|
||||
stop({ assignmentId: 1, name: 'Cobbelsdorf', time: '07:00', dwellMinutes: 45 }),
|
||||
stop({ assignmentId: 2, name: 'Wittenberg', time: '23:00', dwellMinutes: 480 }),
|
||||
stop({ assignmentId: 3, name: 'Leipzig' }),
|
||||
]),
|
||||
day(2, 2, [stop({ assignmentId: 4, name: 'Dresden', time: '12:00' })]),
|
||||
];
|
||||
|
||||
const chains = spillChains(plan, [], everyLeg(50));
|
||||
|
||||
expect(chains[0]!.stops.map((s) => s.name)).toEqual(['Cobbelsdorf', 'Wittenberg']);
|
||||
expect(chains[1]!.stops.map((s) => s.name)).toEqual(['Leipzig', 'Dresden']);
|
||||
expect(chains[1]!.schedule.entries[0]!.arrival).toBe('07:50');
|
||||
});
|
||||
|
||||
it('FE-NIGHTSPILL-027: a stop without a clock follows a night drive out of a late hour', () => {
|
||||
// 06:00 + 12 h, three hours to a 20:00 in Kassel: late. An hour there, ten hours
|
||||
// through the night to Munich at 07:00, and Garmisch behind it never routed.
|
||||
const plan = [
|
||||
day(1, 1, [
|
||||
stop({ assignmentId: 1, name: 'Hamburg', time: '06:00', dwellMinutes: 720 }),
|
||||
stop({ assignmentId: 2, name: 'Kassel', time: '20:00', dwellMinutes: 60 }),
|
||||
stop({ assignmentId: 3, name: 'München' }),
|
||||
stop({ assignmentId: 4, name: 'Garmisch' }),
|
||||
]),
|
||||
day(2, 2, [stop({ assignmentId: 5, name: 'Innsbruck' })]),
|
||||
];
|
||||
const legMinutes: Record<string, number> = { Hamburg: 180, Kassel: 600 };
|
||||
const legFor = (from: RoadtripStop) => (legMinutes[from.name] ? everyLeg(legMinutes[from.name]!)() : undefined);
|
||||
|
||||
const chains = spillChains(plan, [], legFor);
|
||||
|
||||
expect(chains[0]!.stops.map((s) => s.name)).toEqual(['Hamburg', 'Kassel']);
|
||||
expect(chains[0]!.schedule.warnings).toContainEqual({ index: 1, code: 'late', minutes: 60 });
|
||||
expect(chains[1]!.stops.map((s) => s.name)).toEqual(['München', 'Garmisch', 'Innsbruck']);
|
||||
});
|
||||
|
||||
describe('a stay that runs past midnight carries the clock into the next day', () => {
|
||||
// Standing somewhere for twenty-four hours is not over when the date changes. The
|
||||
// day after used to begin at nothing, so a trip whose second day had no pinned time
|
||||
|
||||
@@ -91,12 +91,12 @@ export function spillChains(plan: PlanDay[], quietDays: QuietDay[], legFor: LegL
|
||||
// Plus the drive from where the stay ends to where this day starts. The carried
|
||||
// minute is a DEPARTURE, and arriving at the same instant would mean the road
|
||||
// between them takes no time at all.
|
||||
const arriving = carriedInto?.dayNumber === d.dayNumber && d.stops[0]
|
||||
? legFor(carriedInto.from, d.stops[0])?.seg.duration
|
||||
: undefined;
|
||||
const notBefore = carriedInto?.dayNumber === d.dayNumber
|
||||
? carriedInto.minute + Math.round((arriving ?? 0) / 60)
|
||||
: null;
|
||||
const arriving =
|
||||
carriedInto?.dayNumber === d.dayNumber && d.stops[0]
|
||||
? legFor(carriedInto.from, d.stops[0])?.seg.duration
|
||||
: undefined;
|
||||
const notBefore =
|
||||
carriedInto?.dayNumber === d.dayNumber ? carriedInto.minute + Math.round((arriving ?? 0) / 60) : null;
|
||||
const routed = d.stops.slice(0, -1).map((s, i) => legFor(s, d.stops[i + 1]!));
|
||||
const legs = routed.map((l) => l?.seg);
|
||||
const schedule = computeSchedule(
|
||||
@@ -107,7 +107,11 @@ export function spillChains(plan: PlanDay[], quietDays: QuietDay[], legFor: LegL
|
||||
// Where this day's last clock lands, carried to whichever day that turns out to be.
|
||||
const ends = schedule.endsAt ?? null;
|
||||
if (ends !== null && ends > 1440) {
|
||||
carriedInto = { dayNumber: d.dayNumber + Math.floor(ends / 1440), minute: ends % 1440, from: d.stops[d.stops.length - 1]! };
|
||||
carriedInto = {
|
||||
dayNumber: d.dayNumber + Math.floor(ends / 1440),
|
||||
minute: ends % 1440,
|
||||
from: d.stops[d.stops.length - 1]!,
|
||||
};
|
||||
} else if (carriedInto && carriedInto.dayNumber <= d.dayNumber) {
|
||||
// Spent: this is the day it was pointing at.
|
||||
carriedInto = null;
|
||||
@@ -122,8 +126,18 @@ export function spillChains(plan: PlanDay[], quietDays: QuietDay[], legFor: LegL
|
||||
d.stops.forEach((stop, i) => {
|
||||
const entry = schedule.entries[i]!;
|
||||
const clock = parseClock(entry?.arrival);
|
||||
const marks = schedule.warnings.filter((w) => w.index === i);
|
||||
if (clock !== null) {
|
||||
if (previous !== null && clock < previous) day += 1;
|
||||
if (marks.some((w) => w.code === 'late')) {
|
||||
// A pinned stop the drive reaches late shows the hour the traveller set, not
|
||||
// the minute they get there, so it can read earlier than the stop before it
|
||||
// without a night in between. Counted as one, it took the stop and every stop
|
||||
// behind it to the top of the next day's card. The schedule has already filed
|
||||
// it on its day, so that is the day it counts for.
|
||||
day = Math.max(day, entry.dayOffset - baseOffset);
|
||||
} else if (previous !== null && clock < previous) day += 1;
|
||||
// Always the reading just made, the late hour included: the stops after it are
|
||||
// timed on from that hour, so a clock that goes back past it is a real night.
|
||||
previous = clock;
|
||||
}
|
||||
// How many days on from where this one's clock STARTS, not the schedule's absolute
|
||||
@@ -136,7 +150,6 @@ export function spillChains(plan: PlanDay[], quietDays: QuietDay[], legFor: LegL
|
||||
// ran through the night: twenty-four hours on puts you in tomorrow at the same
|
||||
// reading on the clock, never an earlier one.
|
||||
const offset = entry?.arrival != null ? Math.max(entry.dayOffset - baseOffset, day) : day;
|
||||
const marks = schedule.warnings.filter((w) => w.index === i);
|
||||
|
||||
const reachable = offset > 0 && numbers.has(d.dayNumber + offset);
|
||||
const target = reachable ? d.dayNumber + offset : d.dayNumber;
|
||||
|
||||
Reference in New Issue
Block a user