feat: leg metrics + swappable alternatives per stop

Timeline now shows the driving distance and duration between every
consecutive pair of stops, taken from real OSRM road routes (not Grok
estimates). Top stat bar and per-day headers also use the live totals
so they update immediately as the itinerary changes.

Grok now returns 1-3 alternative picks for each Supercharger and
hotel stop, each with deltaKm/deltaMin vs the chosen pick and a short
reason explaining the trade-off. The Swap (n) button on each card
expands an inline list of alternatives; clicking one swaps the stop
in place. The previously-chosen stop is kept in the alternatives
list with inverted deltas so the user can swap back. The map +
polylines + stat bar all recompute automatically.

Other tweaks:
- haversine fallback when OSRM is unreachable so legs still show
  approximate metrics in offline / degraded mode.
- Leg geometry storage moves from raw polyline[] to typed Leg[] with
  per-leg distance/duration/fromId/toId.
- Stop schema gains alternatives[]; client normalization filters out
  alternatives missing lat/lng.
- Day cards widened (340 → 360px); timeline pane grew 280 → 340px
  with vertical scroll so swap panels don't clip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 11:44:54 +01:00
parent 89b24d4c34
commit 225cd250a3
2 changed files with 318 additions and 32 deletions
+29 -1
View File
@@ -91,7 +91,26 @@ Respond with **only** a single valid JSON object in exactly this format. No text
"amenities": ["restaurant", "coffee", "toilets", "shopping", "wifi", "playground", "ev-charging", "destination-charging"],
"cuisine": "British pub" | "Italian" | "French" | "Cafe" | null,
"priceLevel": 1 | 2 | 3 | 4,
"notes": "optional extra hint (booking tips, opening hours, etc.)"
"notes": "optional extra hint (booking tips, opening hours, etc.)",
"alternatives": [
{
"id": "unique-alt-string",
"name": "Alternative pick name",
"type": "supercharger" | "hotel" | "restaurant" | "cafe" | "attraction" | "destination-charger" | "viewpoint" | "custom",
"lat": 51.5,
"lng": -0.1,
"description": "1-2 sentences explaining why this is a viable swap",
"combo": "charge + eat" | "stay + destination charging" | null,
"amenities": ["restaurant", "toilets"],
"cuisine": "Italian" | null,
"priceLevel": 2,
"chargeMinutes": 25,
"durationMin": 60,
"deltaKm": 12,
"deltaMin": 9,
"reason": "Short reason this is a worthwhile alternative (e.g. 'Cheaper and faster but no restaurant on site')"
}
]
}
]
}
@@ -117,6 +136,15 @@ Strict route planning rules:
- "message" should feel like a helpful human assistant.
- If no clear trip is requested yet, set "itinerary" to null.
Alternatives (REQUIRED for every Supercharger and hotel stop):
- For each Supercharger or hotel stop, populate "alternatives" with 1-3 realistic swap options the driver might prefer.
- Each alternative is a fully-formed stop the user could swap to: complete lat/lng, type, name, description.
- "deltaKm" is the estimated change in total trip distance vs the chosen stop (positive = adds km, negative = saves km).
- "deltaMin" is the estimated change in total drive time vs the chosen stop, in minutes.
- "reason" explains the trade-off in one short sentence ("Cheaper hotel, no destination charging" / "Adds 15 mins but has the best food on this stretch of the M6").
- Alternatives must be genuinely different choices a driver would consider — not minor variants. Mix the trade-offs: faster, cheaper, fancier, better food, closer to attractions, etc.
- For non-Supercharger/non-hotel stops (a viewpoint, a quick coffee), alternatives are optional.
Combo philosophy (THIS IS THE IMPORTANT PART — don't skip):
- Whenever possible, pick Superchargers that are co-located with a real restaurant, cafe, services area, supermarket, or visitor attraction. Mention what's there in "description" and tag the stop with combo: "charge + eat" (or similar).
- Prefer hotels that offer destination charging (Tesla destination chargers, Type 2, or onsite EV charging). Tag those combo: "stay + destination charging" and add "destination-charging" to amenities.