mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
feat(roadtrip): leave a stop at its End and show the stay that makes
A visit's End could be edited and was shown in road trip mode, but the schedule never read it: the drive always left a stop after its stay, so a traveller who knew when they had to leave but not how long they would stand there had no way to say so. The End is now the time the drive leaves. RoadtripStop.leaveAt is filled from the visit's End (or its place's) on the client by useRoadtripRoutes and on the server by roadtrip-plan.service, so calculate_roadtrip and get_roadtrip_context agree with the planner, and computeSchedule takes it as the departure it already knew how to hold. With Start and End both set the stay is the time between them. With daily travel times on, the stay is the time until then, and an End before the window start starts the day early the way a Start does. Before the first fixed time the chain works backwards through an End as it does through a pinned Start. An End is read against the drive: the last passing of that clock time counts as missed when the drive went past it on the road or earlier the same day, anything else is the next one, so a drive across midnight neither waits a whole day nor pretends to be on time. A drive that gets there after the End leaves on arrival and files a missedLeave finding, shown beside the late badge in the rail and on the phone. The rail's STAY badge, the inspector's Stay tile, the phone stop sheet and the phone chain show the stay the End makes, with an "until 14:00" hint. It is read off the schedule and never written to duration_minutes: the stay belongs to the place, the End to one visit, and a stored copy would go stale as soon as the arrival moved. For such a stop the stay dialog, desktop and phone, shows that stay read only and offers to remove the End, through the visit's time route with an offline path (assignmentRepo.setTimes, setAssignmentTimes). While the road trip is on, the End field in both place forms says the drive leaves at that time. The dead checkoutAt in the plan service is gone. New strings in all 23 locales.
This commit is contained in:
@@ -12,7 +12,6 @@ import {
|
||||
splitIntoRuns,
|
||||
spillChains,
|
||||
dayWindow,
|
||||
parseClock,
|
||||
effectiveRangeKm,
|
||||
parseAvoid,
|
||||
formatDistance,
|
||||
@@ -34,6 +33,7 @@ interface StoredDay {
|
||||
}
|
||||
interface VisitRow {
|
||||
check_in: string | null;
|
||||
/** The drive does not read these two (#2357): they are the booking as get_roadtrip_context reports it. */
|
||||
check_out: string | null;
|
||||
checkout_day: number | null;
|
||||
id: number;
|
||||
@@ -43,6 +43,7 @@ interface VisitRow {
|
||||
lat: number | null;
|
||||
lng: number | null;
|
||||
time: string | null;
|
||||
end_time: string | null;
|
||||
duration_minutes: number | null;
|
||||
end_day: number;
|
||||
leg_transport_mode: string | null;
|
||||
@@ -72,7 +73,8 @@ export class RoadtripPlanService {
|
||||
);
|
||||
const visits = this.db.all<VisitRow>(
|
||||
`SELECT a.id, a.day_id, a.place_id, p.name, p.lat, p.lng,
|
||||
COALESCE(a.assignment_time, p.place_time) AS time, p.duration_minutes, a.end_day,
|
||||
COALESCE(a.assignment_time, p.place_time) AS time, COALESCE(a.assignment_end_time, p.end_time) AS end_time,
|
||||
p.duration_minutes, a.end_day,
|
||||
a.leg_transport_mode, a.incoming_leg_transport_mode, p.stop_type, p.fill_percent, stay.check_in, stay.check_out, checkout.day_number AS checkout_day
|
||||
FROM day_assignments a JOIN days d ON d.id = a.day_id JOIN places p ON p.id = a.place_id
|
||||
LEFT JOIN day_accommodations stay ON stay.id = (SELECT id FROM day_accommodations WHERE place_id = p.id AND start_day_id = d.id ORDER BY id LIMIT 1)
|
||||
@@ -117,8 +119,10 @@ export class RoadtripPlanService {
|
||||
lat: v.lat!,
|
||||
lng: v.lng!,
|
||||
time: v.time,
|
||||
// The visit's end time is when the drive leaves it, the same reading the
|
||||
// planner makes in the browser (useRoadtripRoutes).
|
||||
leaveAt: v.end_time,
|
||||
checkInTime: v.check_in,
|
||||
...(v.checkout_day != null && parseClock(v.check_out) !== null ? { checkoutAt: v.checkout_day * 1440 + parseClock(v.check_out)! } : {}),
|
||||
dwellMinutes: v.duration_minutes,
|
||||
endDay: v.end_day === 1,
|
||||
legMode: v.leg_transport_mode,
|
||||
|
||||
@@ -27,7 +27,7 @@ export class RoadtripPlanningMcp {
|
||||
@Tool({
|
||||
name: 'get_roadtrip_context',
|
||||
description:
|
||||
'Read the saved roadtrip days, visits, pinned times, stays, stop types, fill levels, travel modes, via points, followed tracks and manual day endings. Includes the shared driving preferences for this trip. No routing request and no browser needed. Coordinates missing from a visit prevent it from being routed. Use calculate_roadtrip to get the derived day layout. Edit visits with the existing place and assignment tools; change their saved order with reorder_day_assignments. Settings, visits and manual boundaries all belong to the shared trip.',
|
||||
'Read the saved roadtrip days, visits, pinned times, end times (when the drive leaves a visit), stays, stop types, fill levels, travel modes, via points, followed tracks and manual day endings. Includes the shared driving preferences for this trip. No routing request and no browser needed. Coordinates missing from a visit prevent it from being routed. Use calculate_roadtrip to get the derived day layout. Edit visits with the existing place and assignment tools; change their saved order with reorder_day_assignments. Settings, visits and manual boundaries all belong to the shared trip.',
|
||||
inputSchema: { tripId: z.number().int().positive() },
|
||||
annotations: TOOL_ANNOTATIONS_READONLY,
|
||||
access: { group: 'trips', mode: 'read' },
|
||||
@@ -40,7 +40,7 @@ export class RoadtripPlanningMcp {
|
||||
@Tool({
|
||||
name: 'calculate_roadtrip',
|
||||
description:
|
||||
'Calculate a saved trip without an open browser, using the same daily scheduling and vehicle-range rules as the planner. Returns derived days, arrivals, departures, driving limits, fuel warnings, automatic pauses and routing failures. Fixed visit times retain priority; no accommodation or place is created. Optional settings are a read-only preview, not saved. Distances are metres, durations seconds, dwell times minutes. A failed leg or conflicting window means the output is incomplete: never present its totals as a complete route. Up to 150 legs; requests are paced for public routing servers and can take time. Existing place/assignment tools edit stops, stays and ordering, update_roadtrip_settings saves preferences, set_assignment_end_day and set_day_boundary set exceptions. Recalculate after changes. Plugin profiles follow installed route-provider permissions.',
|
||||
'Calculate a saved trip without an open browser, using the same daily scheduling and vehicle-range rules as the planner. Returns derived days, arrivals, departures, driving limits, fuel warnings, automatic pauses and routing failures. Fixed visit times retain priority. The end time of a visit is when the drive leaves it, in place of its stay; reaching the visit after it is reported as a missedLeave warning. No accommodation or place is created. Optional settings are a read-only preview, not saved. Distances are metres, durations seconds, dwell times minutes. A failed leg or conflicting window means the output is incomplete: never present its totals as a complete route. Up to 150 legs; requests are paced for public routing servers and can take time. Existing place/assignment tools edit stops, stays and ordering, update_roadtrip_settings saves preferences, set_assignment_end_day and set_day_boundary set exceptions. Recalculate after changes. Plugin profiles follow installed route-provider permissions.',
|
||||
inputSchema: roadtripPlanRequestSchema.shape,
|
||||
annotations: TOOL_ANNOTATIONS_READONLY,
|
||||
access: { group: 'trips', mode: 'read' },
|
||||
|
||||
Reference in New Issue
Block a user