- DESIGN.md: full design incl. driving-trip requirements (R1-R4), stays model, focus mode, mobile, provenance rules - SURVEY.md: open-source landscape - mock/: interaction mock (Florence itinerary, focus mode, stays, region stops, mobile layout) - router/: Go module (stdlib-only) with Router interface (Valhalla + OSRM backends), stop_cost, optimize_stops, corridor, routectl CLI, bench (5 real NE-corridor tasks, 26 checks passing), integration tests, and setup-osrm.sh for the self-hosted router - osm/: NH+MA+CT+NY PBFs (gitignored) + setup artifacts
265 lines
18 KiB
JavaScript
265 lines
18 KiB
JavaScript
// Fake data layer — same shapes the real API will return.
|
||
// Each stop: day, slot (mutual-exclusion group), state: planned | maybe | alt.
|
||
// Only one `planned` stop per slot per day; `alt`/`maybe` don't count against
|
||
// the day's time budget.
|
||
window.MOCK = {
|
||
trip: {
|
||
id: 'trip-2026-09',
|
||
version: 1,
|
||
title: 'Florence 12–15 Sep',
|
||
// stays, not a single hotel: {hotel, place, checkIn, checkOut, state}.
|
||
// Days resolve their anchor per date; >1 candidate stay covering the
|
||
// same nights → worst-case anchoring for those nights.
|
||
// Multi-city trips = more stays, each anchoring its own dates.
|
||
stays: [
|
||
{ id: 'ST1', name: 'Hotel Palagio', at: [43.7684, 11.2566], place: 'Florence',
|
||
checkIn: '2026-09-12', checkOut: '2026-09-15', state: 'booked', price: 180 },
|
||
],
|
||
places: [
|
||
{ id: 'PL1', name: 'Florence', bbox: [43.73, 11.21, 43.80, 11.29],
|
||
center: [43.769, 11.255], vibe: 'art + food, relaxed pace',
|
||
dates: ['2026-09-12', '2026-09-15'] }
|
||
],
|
||
bookings: [
|
||
{ type: 'hotel', name: 'Hotel Palagio', ref: 'H-77821',
|
||
dates: '12–15 Sep', where: [43.7684, 11.2566],
|
||
status: 'booked', source: 'user_booking' },
|
||
{ type: 'train', name: 'Frecciarossa 9512', ref: 'F-55812',
|
||
from: 'Firenze SMN', to: 'Pisa Centrale', date: '2026-09-15',
|
||
depart: '09:12', arrive: '09:58', status: 'booked', source: 'user_booking',
|
||
stations: { from: [43.7726, 11.2597], to: [43.7157, 10.3922] } }
|
||
]
|
||
},
|
||
|
||
days: [
|
||
{ id: 'D1', date: '2026-09-12', label: 'Day 1 · Fri 12',
|
||
startMin: 8 * 60, wakingHours: 11,
|
||
base: { name: 'Hotel Palagio', at: [43.7684, 11.2566] },
|
||
legs: [],
|
||
stops: [
|
||
{ id: 'S1', name: 'Breakfast at the hotel', kind: 'meal', mealKind: 'breakfast',
|
||
slot: 'breakfast', state: 'planned',
|
||
at: [43.7684, 11.2566], dur: 30, durConf: 3,
|
||
suggested: { value: 30, conf: 2, src: 'triptadvisor.com' } },
|
||
{ id: 'S2', name: 'Palazzo Pitti', kind: 'visit', slot: 'visit', state: 'planned',
|
||
url: 'https://www.palazzopittiturin.it',
|
||
at: [43.7639, 11.2518], dur: 60, durConf: 5, // user override (sticky)
|
||
suggested: { value: 150, conf: 2, src: 'wikivoyage.org' },
|
||
price: { amount: 14, cur: '€', conf: 2, src: 'uffizi.it' },
|
||
note: 'guides suggest 2–3 h', img: ['🏛️', 'Palazzo Pitti', 'linear-gradient(135deg,#b5533c,#7a3a2c)',
|
||
'https://thumb.wikimedia.org/wikipedia/commons/thumb/7/72/Palacio_Pitti%2C_Florencia%2C_Italia%2C_2022-09-18%2C_DD_197.jpg/1280px-Palacio_Pitti%2C_Florencia%2C_Italia%2C_2022-09-18%2C_DD_197.jpg'],
|
||
detail: {
|
||
summary: 'The Medici family’s riverside palazzo: the Galleria Palatina (Portraiture, the 500 Room) and the modern-art Galleria d’Arte Moderna. Rooms are small and crowded in peak season — the apartments alone run 1.5–2 h if you linger, ~1 h for the highlights. September afternoons are the sweet spot: light is good for photos and lines thin after 16:00.',
|
||
links: [
|
||
{ label: 'Official site', href: 'https://www.palazzopittiturin.it' },
|
||
{ label: 'Tickets', href: 'https://www.uffizi.it/en/visit/tickets' },
|
||
{ label: 'TripAdvisor', href: 'https://www.tripadvisor.com/Attraction_Review-g187791-d183726-Reviews-Palazzo_Pitti-Florence_Tuscany.html' },
|
||
{ label: 'Reddit r/Florence', href: 'https://www.reddit.com/r/Florence/search/?q=palazzo+pitti&restrict_sr=1' }
|
||
] } },
|
||
{ id: 'S4', name: 'Lunch — Trattoria Mario', kind: 'meal', mealKind: 'lunch',
|
||
slot: 'lunch', state: 'planned', url: 'https://www.trattoriamario.it',
|
||
at: [43.7667, 11.2544], dur: 45, durConf: 3,
|
||
suggested: { value: 90, conf: 2, src: 'trattoriamario.it' },
|
||
price: { amount: 25, cur: '€', conf: 2, src: 'trattoriamario.it' },
|
||
img: ['🍝', 'Trattoria Mario', 'linear-gradient(135deg,#d08b3c,#9c5f1e)',
|
||
'https://upload.wikimedia.org/wikipedia/commons/1/12/Bistecca_alla_fiorentina-01.jpg'],
|
||
detail: {
|
||
summary: 'A no-frills Florentine kitchen on Via dei Neri, open since 1954. Counter seating, short menu (bistecca, lampredotto, tripe soup), and the queue is real — arriving as lunch opens (12:00) is the difference between 10 minutes and 40. Budget ~1 h including eating.',
|
||
links: [
|
||
{ label: 'Official site', href: 'https://www.trattoriamario.it' },
|
||
{ label: 'Menu', href: 'https://www.trattoriamario.it/menu' },
|
||
{ label: 'TripAdvisor', href: 'https://www.tripadvisor.com/Restaurant_Review-g187791-d310951-Reviews-Trattoria_Mario-Florence_Tuscany.html' },
|
||
{ label: 'Reddit r/food', href: 'https://www.reddit.com/r/food/search/?q=trattoria+mario+florence&restrict_sr=1' }
|
||
] } },
|
||
{ id: 'S5', name: 'Boboli Gardens', kind: 'visit', slot: 'visit', state: 'planned',
|
||
url: 'https://www.boboli.it',
|
||
at: [43.7579, 11.2463], dur: 120, durConf: 3,
|
||
suggested: { value: 120, conf: 2, src: 'boboli.it' },
|
||
price: { amount: 10, cur: '€', conf: 2, src: 'uffizi.it' },
|
||
img: ['🌿', 'Boboli Gardens', 'linear-gradient(135deg,#4f9e63,#2c6b40)',
|
||
'https://thumb.wikimedia.org/wikipedia/commons/thumb/d/d2/Obelisco%2C_Boboli_Gardens%2C_Florence_%2826408174180%29.jpg/1280px-Obelisco%2C_Boboli_Gardens%2C_Florence_%2826408174180%29.jpg'],
|
||
detail: {
|
||
summary: 'The Medici’s formal garden behind Pitti — statues, the Apollo fountain, a hilltop view over the city. The full loop with the Accademia annex runs 2 h; the upper terraces alone are a fine 45-minute pace-break. Pairs naturally with Pitti: the entrance is the courtyard behind it.',
|
||
links: [
|
||
{ label: 'Official site', href: 'https://www.boboli.it' },
|
||
{ label: 'Tickets', href: 'https://www.uffizi.it/en/visit/tickets' },
|
||
{ label: 'TripAdvisor', href: 'https://www.tripadvisor.com/Attraction_Review-g187791-d183872-Reviews-Giardini_Di_Boboli-Florence_Tuscany.html' },
|
||
{ label: 'Reddit r/Florence', href: 'https://www.reddit.com/r/Florence/search/?q=boboli&restrict_sr=1' }
|
||
] } },
|
||
{ id: 'S6', name: 'Gelato — Vivoli', kind: 'meal', mealKind: 'snack',
|
||
slot: 'snack', state: 'planned',
|
||
at: [43.7680, 11.2537], dur: 20, durConf: 3,
|
||
suggested: { value: 20, conf: 1, src: null },
|
||
price: null,
|
||
img: ['🍦', 'Vivoli gelato', 'linear-gradient(135deg,#e0a5c0,#a56a92)'] },
|
||
{ id: 'S7', name: 'Lunch — Ristorante Santini', kind: 'meal', mealKind: 'lunch',
|
||
slot: 'lunch', state: 'alt', url: 'https://www.santini.it',
|
||
at: [43.7646, 11.2528], dur: 75, durConf: 3,
|
||
suggested: { value: 90, conf: 2, src: 'santini.it' },
|
||
price: { amount: 45, cur: '€', conf: 2, src: 'santini.it' },
|
||
img: ['🍷', 'Ristorante Santini', 'linear-gradient(135deg,#7a4a5c,#4c2b3a)',
|
||
'https://thumb.wikimedia.org/wikipedia/commons/thumb/c/c0/View_from_Giotto%27s_Bell_Tower_-_Florence.jpg/1280px-View_from_Giotto%27s_Bell_Tower_-_Florence.jpg'],
|
||
detail: {
|
||
summary: 'A classic Florentine restaurant in a 16th-century building on the Arno — the terrace looks across to Pitti. Set tables, longer menus, lunch from 12:30. The step-up option: worth holding if Mario’s queue looks long, or if the day goes well and you want to mark it.',
|
||
links: [
|
||
{ label: 'Official site', href: 'https://www.santini.it' },
|
||
{ label: 'Menu', href: 'https://www.santini.it/en/menu' },
|
||
{ label: 'TripAdvisor', href: 'https://www.tripadvisor.com/Restaurant_Review-g187791-d1442007-Reviews-Ristorante_Santini-Florence_Tuscany.html' },
|
||
{ label: 'Reddit r/food', href: 'https://www.reddit.com/r/food/search/?q=santini+florence&restrict_sr=1' }
|
||
] } }
|
||
]
|
||
},
|
||
{ id: 'D2', date: '2026-09-13', label: 'Day 2 · Sat 13',
|
||
startMin: 8 * 60 + 30, wakingHours: 11,
|
||
base: { name: 'Hotel Palagio', at: [43.7684, 11.2566] },
|
||
legs: [],
|
||
stops: [
|
||
{ id: 'S8', name: 'Breakfast at the hotel', kind: 'meal', mealKind: 'breakfast',
|
||
slot: 'breakfast', state: 'planned',
|
||
at: [43.7684, 11.2566], dur: 30, durConf: 3,
|
||
suggested: { value: 30, conf: 2, src: 'tripadvisor.com' } },
|
||
{ id: 'S9', name: 'Galleria dell’Accademia (David)', kind: 'visit', slot: 'visit', state: 'planned',
|
||
url: 'https://www.galleriaaccademia.firenze.it',
|
||
at: [43.7734, 11.2628], dur: 90, durConf: 3,
|
||
suggested: { value: 120, conf: 2, src: 'wikivoyage.org' },
|
||
price: { amount: 16, cur: '€', conf: 2, src: 'galleriaaccademia.firenze.it' },
|
||
img: ['🗿', 'Accademia', 'linear-gradient(135deg,#6b7280,#3f4650)'],
|
||
detail: {
|
||
summary: 'Home of the original David plus Donatello’s Prigione. Timed-entry tickets, and the David room moves in batches — a 09:30 slot is noticeably calmer than midday. Allow ~90 min; 2 h if you do the whole collection.',
|
||
links: [
|
||
{ label: 'Official site', href: 'https://www.galleriaaccademia.firenze.it' },
|
||
{ label: 'Tickets', href: 'https://www.galleriaaccademia.firenze.it/en/tickets' },
|
||
{ label: 'TripAdvisor', href: 'https://www.tripadvisor.com/Attraction_Review-g187791-d183692-Reviews-Galleria_dell_Accademia-Florence_Tuscany.html' },
|
||
{ label: 'Reddit r/Florence', href: 'https://www.reddit.com/r/Florence/search/?q=accademia+david&restrict_sr=1' }
|
||
] } },
|
||
{ id: 'S10', name: 'Lunch — Trattoria Sostanza', kind: 'meal', mealKind: 'lunch',
|
||
slot: 'lunch', state: 'planned', url: 'https://www.sostanza.it',
|
||
at: [43.7609, 11.2469], dur: 75, durConf: 3,
|
||
suggested: { value: 90, conf: 2, src: 'tripadvisor.com' },
|
||
price: { amount: 35, cur: '€', conf: 2, src: 'tripadvisor.com' },
|
||
img: ['🍕', 'Trattoria Sostanza', 'linear-gradient(135deg,#b0563c,#7a3a2c)'],
|
||
detail: {
|
||
summary: 'Oltrarno kitchen a short walk across the Arno from the Accademia: Florentine classics, a proper wine list, and a roomy back — the kind of lunch that runs long. Reservations recommended on Saturdays.',
|
||
links: [
|
||
{ label: 'Official site', href: 'https://www.sostanza.it' },
|
||
{ label: 'Menu', href: 'https://www.sostanza.it/menu' },
|
||
{ label: 'TripAdvisor', href: 'https://www.tripadvisor.com/Restaurant_Review-g187791-d559983-Reviews-Trattoria_Sostanza-Florence_Tuscany.html' },
|
||
{ label: 'Reddit r/food', href: 'https://www.reddit.com/r/food/search/?q=sostanza+florence&restrict_sr=1' }
|
||
] } },
|
||
{ id: 'S11', name: 'Oltrarno stroll · San Niccolò', kind: 'visit', slot: 'visit', state: 'planned',
|
||
at: [43.7613, 11.2497], dur: 60, durConf: 3,
|
||
suggested: { value: 60, conf: 1, src: null },
|
||
price: null,
|
||
img: ['⛪', 'Oltrarno', 'linear-gradient(135deg,#8f7a4f,#5c4d2e)'] }
|
||
]
|
||
},
|
||
{ id: 'D3', date: '2026-09-14', label: 'Day 3 · Sun 14',
|
||
startMin: 9 * 60, wakingHours: 11,
|
||
base: { name: 'Hotel Palagio', at: [43.7684, 11.2566] },
|
||
legs: [],
|
||
stops: [
|
||
{ id: 'S12', name: 'Breakfast at the hotel', kind: 'meal', mealKind: 'breakfast',
|
||
slot: 'breakfast', state: 'planned',
|
||
at: [43.7684, 11.2566], dur: 30, durConf: 3,
|
||
suggested: { value: 30, conf: 2, src: 'tripadvisor.com' } },
|
||
{ id: 'S13', name: 'San Marco & cloister', kind: 'visit', slot: 'visit', state: 'planned',
|
||
at: [43.7723, 11.2597], dur: 45, durConf: 3,
|
||
suggested: { value: 45, conf: 1, src: null },
|
||
price: { amount: 8, cur: '€', conf: 1, src: null },
|
||
img: ['🖼️', 'San Marco', 'linear-gradient(135deg,#7a6a4f,#4d422e)'] },
|
||
{ id: 'S14', name: 'Aperitivo — Il Latte', kind: 'meal', mealKind: 'dinner',
|
||
slot: 'dinner', state: 'maybe',
|
||
url: 'https://www.illatte.it',
|
||
at: [43.7700, 11.2560], dur: 60, durConf: 3,
|
||
suggested: { value: 60, conf: 1, src: null },
|
||
price: { amount: 12, cur: '€', conf: 2, src: 'tripadvisor.com' },
|
||
img: ['🥂', 'Il Latte', 'linear-gradient(135deg,#c9a13c,#8f6f1e)'] }
|
||
]
|
||
},
|
||
{ id: 'D4', date: '2026-09-15', label: 'Day 4 · Mon 15', isDeparture: true,
|
||
startMin: 8 * 60, wakingHours: 11,
|
||
base: { name: 'Hotel Palagio', at: [43.7684, 11.2566] },
|
||
legs: [],
|
||
stops: [
|
||
{ id: 'S15', name: 'Breakfast & check-out', kind: 'meal', mealKind: 'breakfast',
|
||
slot: 'breakfast', state: 'planned',
|
||
at: [43.7684, 11.2566], dur: 30, durConf: 3,
|
||
suggested: { value: 30, conf: 2, src: 'tripadvisor.com' } },
|
||
{ id: 'S16', name: 'Train → Pisa Centrale', kind: 'transit', slot: 'transit', state: 'planned',
|
||
at: [43.7726, 11.2597], dur: 30, durConf: 4,
|
||
transit: { name: 'Frecciarossa 9512', ref: 'F-55812',
|
||
from: 'Firenze SMN', to: 'Pisa Centrale',
|
||
depart: '09:12', arrive: '09:58', arriveBy: '08:42',
|
||
buffer: '30 min before departure' },
|
||
suggested: null,
|
||
price: { amount: 45, cur: '€', conf: 4, src: 'booking' },
|
||
img: ['🚄', 'Frecciarossa 9512', 'linear-gradient(135deg,#274b8f,#16294d)'] }
|
||
]
|
||
}
|
||
],
|
||
|
||
// focus-mode candidate pools (what a slot search returns)
|
||
candidates: {
|
||
hotel: [
|
||
{ id: 'H1', name: 'Hotel Palagio', at: [43.7684, 11.2566], price: 180, dur: null,
|
||
tags: ['central', 'quiet', 'refundable'], pitch: 'Where you are now — refundable until the 8th.', url: null },
|
||
{ id: 'H2', name: 'Hotel Savoy', at: [43.7726, 11.2565], price: 210, dur: null,
|
||
tags: ['near SMN', 'grand', 'rooftop'], pitch: 'Grand hotel by the station — handy for the 15th, 10 min from the centre.', url: null },
|
||
{ id: 'H3', name: 'Residenza Oltrarno', at: [43.7604, 11.2510], price: 140, dur: null,
|
||
tags: ['oltrarno', 'small', 'value'], pitch: 'Six rooms above a bookshop — cheaper, and puts you on the south side.', url: null },
|
||
],
|
||
lunch: [
|
||
{ id: 'C1', name: 'Trattoria Mario', at: [43.7667, 11.2544], price: 25, dur: 90,
|
||
tags: ['counter', 'local', 'queue'], pitch: 'No-frills counter since 1954 — the queue is the point.',
|
||
url: 'https://www.trattoriamario.it', img: ['🍝', 'Trattoria Mario', 'linear-gradient(135deg,#d08b3c,#9c5f1e)', 'https://upload.wikimedia.org/wikipedia/commons/1/12/Bistecca_alla_fiorentina-01.jpg'] },
|
||
{ id: 'C2', name: 'Ristorante Santini', at: [43.7646, 11.2528], price: 45, dur: 90,
|
||
tags: ['views', 'terrace', 'splurge'], pitch: '16th-century set tables on the Arno, terrace across from Pitti.',
|
||
url: 'https://www.santini.it', img: ['🍷', 'Santini', 'linear-gradient(135deg,#7a4a5c,#4c2b3a)'] },
|
||
{ id: 'C3', name: 'Trattoria Sostanza', at: [43.7609, 11.2469], price: 35, dur: 90,
|
||
tags: ['oltrarno', 'roomy', 'wine'], pitch: 'Oltrarno kitchen, proper wine list, a roomy back — runs long.',
|
||
url: 'https://www.sostanza.it', img: ['🍕', 'Sostanza', 'linear-gradient(135deg,#b0563c,#7a3a2c)'] },
|
||
{ id: 'C4', name: 'Vendemmia', at: [43.7735, 11.2585], price: 30, dur: 75,
|
||
tags: ['vegetarian', 'garden', 'near SMN'], pitch: 'Garden terrace by SMN, strong vegetarian menu, calmer pace.',
|
||
url: 'https://www.vendemmia.it', img: ['🥗', 'Vendemmia', 'linear-gradient(135deg,#5f9e6b,#33613c)'] },
|
||
{ id: 'C5', name: 'La Cantina di San Niccolò', at: [43.7615, 11.2490], price: 22, dur: 75,
|
||
tags: ['cellar', 'cozy', 'oltrarno'], pitch: 'Stone cellar, candlelight, short honest menu — book ahead.',
|
||
url: 'https://www.lacantina.it', img: ['🕯️', 'La Cantina', 'linear-gradient(135deg,#8f7a4f,#5c4d2e)'] },
|
||
{ id: 'C6', name: 'Buonami Pizza', at: [43.7680, 11.2550], price: 15, dur: 45,
|
||
tags: ['fast', 'casual', 'near hotel'], pitch: 'Two-stop, fast, good — the reset-button lunch.',
|
||
url: 'https://www.buonami.it', img: ['🍕', 'Buonami', 'linear-gradient(135deg,#c98a2c,#8a5f1a)'] }
|
||
],
|
||
visit: [
|
||
{ id: 'V1', name: 'Piazzale Michelangelo', at: [43.7669, 11.2455], price: 0, dur: 60,
|
||
tags: ['views', 'sunset', 'free'], pitch: 'The postcard view — best from 17:00 as the light warms.',
|
||
url: 'https://en.wikipedia.org/wiki/Piazzale_Michelangelo', img: ['🌇', 'Piazzale Michelangelo', 'linear-gradient(135deg,#c97a3c,#8a4f1a)'] },
|
||
{ id: 'V2', name: 'San Miniato al Monte', at: [43.7620, 11.2521], price: 4, dur: 60,
|
||
tags: ['views', 'romanesque', 'calm'], pitch: 'Romanesque basilica on the hill — quiet, cool in the afternoon.',
|
||
url: 'https://en.wikipedia.org/wiki/San_Miniato_al_Monte', img: ['⛪', 'San Miniato', 'linear-gradient(135deg,#8f9a7a,#5c644d)'] },
|
||
{ id: 'V3', name: 'Santo Spirito square', at: [43.7603, 11.2496], price: 0, dur: 45,
|
||
tags: ['square', 'lazier', 'market'], pitch: 'Oltrarno’s lazy square; Saturday’s market is the event.',
|
||
url: 'https://en.wikipedia.org/wiki/Santo_Spirito,_Florence', img: ['☕', 'Santo Spirito', 'linear-gradient(135deg,#a58a6a,#6d5840)'] },
|
||
{ id: 'V4', name: 'Terme di Caracalla (ruins)', at: [43.7589, 11.2707], price: 10, dur: 75,
|
||
tags: ['ruins', 'open', 'afternoon'], pitch: 'Third-century baths in open fields — cool, empty, odd.',
|
||
url: 'https://en.wikipedia.org/wiki/Baths_of_Caracalla', img: ['🏛️', 'Terme di Caracalla', 'linear-gradient(135deg,#b0a58a,#756a4f)'] }
|
||
]
|
||
},
|
||
|
||
vibes: [
|
||
{ id: 'v1', label: 'art & food · relaxed pace' },
|
||
{ id: 'v2', label: 'gardens & walking' },
|
||
{ id: 'v3', label: 'day trip · Fiesole' },
|
||
{ id: 'v4', label: 'views & aperitivo' }
|
||
],
|
||
|
||
suggestions: [
|
||
{ stop: 'S2', pitch: 'The Palazzo Medici’s private apartments — less crowded than the Uffizi, opens right at 9:30.',
|
||
enrich: 'open 09:30–19:30 · skip-the-line available' },
|
||
{ stop: 'S4', pitch: 'No-frills Florentine kitchen the locals queue for. Expect a wait without a reservation.',
|
||
enrich: 'lunch 12:00–14:30 · dinner from 19:30 · ~1 h typically' },
|
||
{ stop: 'S5', pitch: 'Renaissance gardens behind Pitti — a good pace-break in the heat; ~2 h is the sweet spot.',
|
||
enrich: 'open 08:15–19:30 · last entry 18:15' },
|
||
{ stop: 'S7', pitch: 'The step-up lunch: set tables on the Arno, terrace across from Pitti. Worth holding if Mario’s queue looks long.',
|
||
enrich: 'lunch from 12:30 · book ahead in season' }
|
||
]
|
||
};
|