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:
@@ -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
|
||||
|
||||
@@ -542,8 +542,10 @@ enum GameDAGRouter {
|
||||
let availableHours = deadline.timeIntervalSince(departureTime) / 3600.0
|
||||
|
||||
// Calculate driving hours available
|
||||
// For same-day games: enforce both time availability AND daily driving limit
|
||||
// For multi-day trips: use total available driving hours across days
|
||||
let maxDrivingHoursAvailable = daysBetween == 0
|
||||
? max(0, availableHours)
|
||||
? min(max(0, availableHours), constraints.maxDailyDrivingHours)
|
||||
: Double(daysBetween) * constraints.maxDailyDrivingHours
|
||||
|
||||
let feasible = drivingHours <= maxDrivingHoursAvailable && drivingHours <= availableHours
|
||||
|
||||
Reference in New Issue
Block a user