fix(planning): enforce daily driving limit for same-day games and group itinerary by city

Two fixes for route planning and display:

1. GameDAGRouter: Same-day game transitions now respect maxDailyDrivingHours
   constraint. Previously, a 12:05 AM game in Arlington could connect to an
   11:40 PM game in Milwaukee (19+ hour drive) because the code only checked
   available time, not the 8-hour daily limit.

2. TripDetailView: Itinerary sections now group by (day, city) not just day.
   Games in different cities on the same calendar day are shown as separate
   sections with travel segments between them.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-15 09:42:08 -06:00
parent 57eab22746
commit 00a5e4ef0e
2 changed files with 29 additions and 16 deletions

View File

@@ -348,36 +348,47 @@ struct TripDetailView: View {
}
}
/// Build itinerary sections: days with travel between different cities
/// Build itinerary sections: group by day AND city, with travel between different cities
private var itinerarySections: [ItinerarySection] {
var sections: [ItinerarySection] = []
// Build day sections for days with games
var daySections: [(dayNumber: Int, date: Date, city: String, games: [RichGame])] = []
// Build day+city sections for days with games
var dayCitySections: [(dayNumber: Int, date: Date, city: String, games: [RichGame])] = []
let days = tripDays
for (index, dayDate) in days.enumerated() {
let dayNum = index + 1
let gamesOnDay = gamesOn(date: dayDate)
// Get city from games (preferred) or from stops as fallback
let cityForDay = gamesOnDay.first?.stadium.city ?? cityOn(date: dayDate) ?? ""
guard !gamesOnDay.isEmpty else { continue }
// Include days with games
// Skip empty days at the end (departure day after last game)
if !gamesOnDay.isEmpty {
daySections.append((dayNum, dayDate, cityForDay, gamesOnDay))
// Group games by city, maintaining chronological order
var gamesByCity: [(city: String, games: [RichGame])] = []
for game in gamesOnDay {
let city = game.stadium.city
if let lastIndex = gamesByCity.indices.last, gamesByCity[lastIndex].city == city {
// Same city as previous game - add to existing group
gamesByCity[lastIndex].games.append(game)
} else {
// Different city - start new group
gamesByCity.append((city, [game]))
}
}
// Add each city group as a separate section
for cityGroup in gamesByCity {
dayCitySections.append((dayNum, dayDate, cityGroup.city, cityGroup.games))
}
}
// Build sections: insert travel BEFORE each day when coming from different city
for (index, daySection) in daySections.enumerated() {
// Build sections: insert travel BEFORE each section when coming from different city
for (index, section) in dayCitySections.enumerated() {
// Check if we need travel BEFORE this day (coming from different city)
// Check if we need travel BEFORE this section (coming from different city)
if index > 0 {
let prevSection = daySections[index - 1]
let prevSection = dayCitySections[index - 1]
let prevCity = prevSection.city
let currentCity = daySection.city
let currentCity = section.city
// If cities differ, find travel segment from prev -> current
if !prevCity.isEmpty && !currentCity.isEmpty && prevCity != currentCity {
@@ -388,7 +399,7 @@ struct TripDetailView: View {
}
// Add the day section
sections.append(.day(dayNumber: daySection.dayNumber, date: daySection.date, games: daySection.games))
sections.append(.day(dayNumber: section.dayNumber, date: section.date, games: section.games))
}
return sections