feat: add Follow Team Mode (Scenario D) for road trip planning
Adds a new planning mode that lets users follow a team's schedule (home + away games) and builds multi-city routes accordingly. Key changes: - New ScenarioDPlanner with team filtering and route generation - Team picker UI with sport grouping and search - Fix TravelEstimator 5-day limit (was 2-day) for cross-country routes - Fix DateInterval end boundary to include games on last day - Comprehensive test suite covering edge cases: - Multi-city routes with adequate/insufficient time - Optimal game selection per city for feasibility - 5-day driving segment limits - Multiple driver scenarios Enables trips like Houston → Chicago → Anaheim following the Astros. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ enum PlanningScenario: Equatable {
|
||||
case scenarioA // Date range only
|
||||
case scenarioB // Selected games + date range
|
||||
case scenarioC // Start + end locations
|
||||
case scenarioD // Follow team schedule
|
||||
}
|
||||
|
||||
// MARK: - Planning Failure
|
||||
@@ -29,6 +30,7 @@ struct PlanningFailure: Error {
|
||||
case noValidRoutes
|
||||
case missingDateRange
|
||||
case missingLocations
|
||||
case missingTeamSelection
|
||||
case dateRangeViolation(games: [Game])
|
||||
case drivingExceedsLimit
|
||||
case cannotArriveInTime
|
||||
@@ -43,6 +45,7 @@ struct PlanningFailure: Error {
|
||||
(.noValidRoutes, .noValidRoutes),
|
||||
(.missingDateRange, .missingDateRange),
|
||||
(.missingLocations, .missingLocations),
|
||||
(.missingTeamSelection, .missingTeamSelection),
|
||||
(.drivingExceedsLimit, .drivingExceedsLimit),
|
||||
(.cannotArriveInTime, .cannotArriveInTime),
|
||||
(.travelSegmentMissing, .travelSegmentMissing),
|
||||
@@ -70,6 +73,7 @@ struct PlanningFailure: Error {
|
||||
case .noValidRoutes: return "No valid routes could be constructed"
|
||||
case .missingDateRange: return "Date range is required"
|
||||
case .missingLocations: return "Start and end locations are required"
|
||||
case .missingTeamSelection: return "Select a team to follow"
|
||||
case .dateRangeViolation(let games):
|
||||
return "\(games.count) selected game(s) fall outside the date range"
|
||||
case .drivingExceedsLimit: return "Driving time exceeds daily limit"
|
||||
@@ -512,9 +516,15 @@ struct PlanningRequest {
|
||||
}
|
||||
|
||||
/// Date range as DateInterval
|
||||
/// Note: End date is extended to end-of-day to include all games on the last day,
|
||||
/// since DateInterval.contains() uses exclusive end boundary.
|
||||
var dateRange: DateInterval? {
|
||||
guard preferences.endDate > preferences.startDate else { return nil }
|
||||
return DateInterval(start: preferences.startDate, end: preferences.endDate)
|
||||
// Extend end date to end of day (23:59:59) to include games on the last day
|
||||
let calendar = Calendar.current
|
||||
let endOfDay = calendar.date(bySettingHour: 23, minute: 59, second: 59, of: preferences.endDate)
|
||||
?? preferences.endDate
|
||||
return DateInterval(start: preferences.startDate, end: endOfDay)
|
||||
}
|
||||
|
||||
/// First must-stop location (if any)
|
||||
|
||||
Reference in New Issue
Block a user