Add implementation code for all 4 improvement plan phases

Production changes:
- TravelEstimator: remove 300mi fallback, return nil on missing coords
- TripPlanningEngine: add warnings array, empty sports warning, inverted
  date range rejection, must-stop filter, segment validation gate
- GameDAGRouter: add routePreference parameter with preference-aware
  bucket ordering and sorting in selectDiverseRoutes()
- ScenarioA-E: pass routePreference through to GameDAGRouter
- ScenarioA: track games with missing stadium data
- ScenarioE: add region filtering for home games
- TravelSegment: add requiresOvernightStop and travelDays() helpers

Test changes:
- GameDAGRouterTests: +252 lines for route preference verification
- TripPlanningEngineTests: +153 lines for segment validation, date range,
  empty sports
- ScenarioEPlannerTests: +119 lines for region filter tests
- TravelEstimatorTests: remove obsolete fallback distance tests
- ItineraryBuilderTests: update nil-coords test expectation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-03-21 09:40:32 -05:00
parent db6ab2f923
commit 6cbcef47ae
14 changed files with 807 additions and 88 deletions

View File

@@ -90,9 +90,16 @@ final class ScenarioEPlanner: ScenarioPlanner {
// the user wants to visit each team's home stadium.
var homeGamesByTeam: [String: [Game]] = [:]
var allHomeGames: [Game] = []
let selectedRegions = request.preferences.selectedRegions
for game in request.allGames {
if selectedTeamIds.contains(game.homeTeamId) {
// Apply region filter if regions are specified
if !selectedRegions.isEmpty {
guard let stadium = request.stadiums[game.stadiumId] else { continue }
let gameRegion = Region.classify(longitude: stadium.coordinate.longitude)
guard selectedRegions.contains(gameRegion) else { continue }
}
homeGamesByTeam[game.homeTeamId, default: []].append(game)
allHomeGames.append(game)
}
@@ -212,6 +219,7 @@ final class ScenarioEPlanner: ScenarioPlanner {
stadiums: request.stadiums,
anchorGameIds: earliestAnchorIds,
allowRepeatCities: request.preferences.allowRepeatCities,
routePreference: request.preferences.routePreference,
stopBuilder: buildStops
)
var validRoutes = candidateRoutes.filter { route in
@@ -230,6 +238,7 @@ final class ScenarioEPlanner: ScenarioPlanner {
stadiums: request.stadiums,
anchorGameIds: latestAnchorIds,
allowRepeatCities: request.preferences.allowRepeatCities,
routePreference: request.preferences.routePreference,
stopBuilder: buildStops
)
candidateRoutes.append(contentsOf: latestAnchorRoutes)
@@ -239,6 +248,7 @@ final class ScenarioEPlanner: ScenarioPlanner {
from: uniqueGames,
stadiums: request.stadiums,
allowRepeatCities: request.preferences.allowRepeatCities,
routePreference: request.preferences.routePreference,
stopBuilder: buildStops
)
candidateRoutes.append(contentsOf: noAnchorRoutes)