fix: ScenarioCPlanner endpoint merging and game validation
Eliminate redundant 0-mile travel segments when start/end city matches the first/last game stop city, and fail early when no games exist at endpoint cities within the selected date range. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -162,6 +162,38 @@ final class ScenarioCPlanner: ScenarioPlanner {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────
|
||||||
|
// Step 2.5: Validate games exist at endpoint stadiums (explicit date range)
|
||||||
|
// ──────────────────────────────────────────────────────────────────
|
||||||
|
if let dateRange = request.dateRange {
|
||||||
|
let startStadiumIds = Set(startStadiums.map { $0.id })
|
||||||
|
let endStadiumIds = Set(endStadiums.map { $0.id })
|
||||||
|
|
||||||
|
let hasStartGames = request.allGames.contains {
|
||||||
|
startStadiumIds.contains($0.stadiumId) && dateRange.contains($0.startTime)
|
||||||
|
}
|
||||||
|
let hasEndGames = request.allGames.contains {
|
||||||
|
endStadiumIds.contains($0.stadiumId) && dateRange.contains($0.startTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasStartGames {
|
||||||
|
return .failure(PlanningFailure(
|
||||||
|
reason: .noGamesInRange,
|
||||||
|
violations: [ConstraintViolation(type: .general,
|
||||||
|
description: "No games found at \(startLocation.name) within the selected dates",
|
||||||
|
severity: .error)]
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if !hasEndGames {
|
||||||
|
return .failure(PlanningFailure(
|
||||||
|
reason: .noGamesInRange,
|
||||||
|
violations: [ConstraintViolation(type: .general,
|
||||||
|
description: "No games found at \(endLocation.name) within the selected dates",
|
||||||
|
severity: .error)]
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────
|
||||||
// Step 3: Generate date ranges
|
// Step 3: Generate date ranges
|
||||||
// ──────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────
|
||||||
@@ -544,6 +576,8 @@ final class ScenarioCPlanner: ScenarioPlanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Builds stops with start and end location endpoints.
|
/// Builds stops with start and end location endpoints.
|
||||||
|
/// Skips adding a separate endpoint stop when the first/last game stop
|
||||||
|
/// is already in the same city, avoiding redundant 0-mile travel segments.
|
||||||
private func buildStopsWithEndpoints(
|
private func buildStopsWithEndpoints(
|
||||||
start: LocationInput,
|
start: LocationInput,
|
||||||
end: LocationInput,
|
end: LocationInput,
|
||||||
@@ -551,43 +585,58 @@ final class ScenarioCPlanner: ScenarioPlanner {
|
|||||||
stadiums: [String: Stadium]
|
stadiums: [String: Stadium]
|
||||||
) -> [ItineraryStop] {
|
) -> [ItineraryStop] {
|
||||||
|
|
||||||
|
// Build game stops first so we can check for city overlap
|
||||||
|
let gameStops = buildStops(from: games, stadiums: stadiums)
|
||||||
|
|
||||||
var stops: [ItineraryStop] = []
|
var stops: [ItineraryStop] = []
|
||||||
|
|
||||||
// Start stop (no games)
|
// Only add start endpoint if start city differs from first game stop city
|
||||||
let startArrival = games.first?.gameDate.addingTimeInterval(-86400) ?? Date()
|
let firstGameCity = gameStops.first?.city
|
||||||
let startStop = ItineraryStop(
|
if firstGameCity == nil || !citiesMatch(start.name, firstGameCity!) {
|
||||||
city: start.name,
|
let startArrival = games.first?.gameDate.addingTimeInterval(-86400) ?? Date()
|
||||||
state: "",
|
let startStop = ItineraryStop(
|
||||||
coordinate: start.coordinate,
|
city: start.name,
|
||||||
games: [],
|
state: "",
|
||||||
arrivalDate: startArrival,
|
coordinate: start.coordinate,
|
||||||
departureDate: startArrival,
|
games: [],
|
||||||
location: start,
|
arrivalDate: startArrival,
|
||||||
firstGameStart: nil
|
departureDate: startArrival,
|
||||||
)
|
location: start,
|
||||||
stops.append(startStop)
|
firstGameStart: nil
|
||||||
|
)
|
||||||
|
stops.append(startStop)
|
||||||
|
}
|
||||||
|
|
||||||
// Game stops
|
|
||||||
let gameStops = buildStops(from: games, stadiums: stadiums)
|
|
||||||
stops.append(contentsOf: gameStops)
|
stops.append(contentsOf: gameStops)
|
||||||
|
|
||||||
// End stop (no games)
|
// Only add end endpoint if end city differs from last game stop city
|
||||||
let endArrival = games.last?.gameDate.addingTimeInterval(86400) ?? Date()
|
let lastGameCity = gameStops.last?.city
|
||||||
let endStop = ItineraryStop(
|
if lastGameCity == nil || !citiesMatch(end.name, lastGameCity!) {
|
||||||
city: end.name,
|
let endArrival = games.last?.gameDate.addingTimeInterval(86400) ?? Date()
|
||||||
state: "",
|
let endStop = ItineraryStop(
|
||||||
coordinate: end.coordinate,
|
city: end.name,
|
||||||
games: [],
|
state: "",
|
||||||
arrivalDate: endArrival,
|
coordinate: end.coordinate,
|
||||||
departureDate: endArrival,
|
games: [],
|
||||||
location: end,
|
arrivalDate: endArrival,
|
||||||
firstGameStart: nil
|
departureDate: endArrival,
|
||||||
)
|
location: end,
|
||||||
stops.append(endStop)
|
firstGameStart: nil
|
||||||
|
)
|
||||||
|
stops.append(endStop)
|
||||||
|
}
|
||||||
|
|
||||||
return stops
|
return stops
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if two city names refer to the same city using normalized comparison.
|
||||||
|
private func citiesMatch(_ cityA: String, _ cityB: String) -> Bool {
|
||||||
|
let a = normalizeCityName(cityA)
|
||||||
|
let b = normalizeCityName(cityB)
|
||||||
|
if a == b { return true }
|
||||||
|
return a.contains(b) || b.contains(a)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Monotonic Progress Validation
|
// MARK: - Monotonic Progress Validation
|
||||||
|
|
||||||
/// Validates that the route makes generally forward progress toward the end.
|
/// Validates that the route makes generally forward progress toward the end.
|
||||||
|
|||||||
@@ -453,6 +453,325 @@ struct ScenarioCPlannerTests {
|
|||||||
#expect(true, "Forward progress tolerance documented as 15%")
|
#expect(true, "Forward progress tolerance documented as 15%")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Regression Tests: Endpoint Merging
|
||||||
|
|
||||||
|
private let houstonCoord = CLLocationCoordinate2D(latitude: 29.7604, longitude: -95.3698)
|
||||||
|
private let denverCoord = CLLocationCoordinate2D(latitude: 39.7392, longitude: -104.9903)
|
||||||
|
|
||||||
|
@Test("plan: start city matches first game city — no redundant empty endpoint")
|
||||||
|
func plan_startCityMatchesFirstGameCity_noZeroMileTravel() {
|
||||||
|
let startDate = TestClock.now
|
||||||
|
let endDate = startDate.addingTimeInterval(86400 * 10)
|
||||||
|
|
||||||
|
let startLocation = LocationInput(name: "Houston, TX", coordinate: houstonCoord)
|
||||||
|
let endLocation = LocationInput(name: "New York", coordinate: nycCoord)
|
||||||
|
|
||||||
|
let houstonStadium = makeStadium(id: "houston", city: "Houston", coordinate: houstonCoord)
|
||||||
|
let clevelandStadium = makeStadium(id: "cleveland", city: "Cleveland", coordinate: clevelandCoord)
|
||||||
|
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
|
||||||
|
|
||||||
|
let houstonGame = makeGame(id: "hou-game", stadiumId: "houston", dateTime: startDate.addingTimeInterval(86400))
|
||||||
|
let clevelandGame = makeGame(id: "cle-game", stadiumId: "cleveland", dateTime: startDate.addingTimeInterval(86400 * 4))
|
||||||
|
let nycGame = makeGame(id: "nyc-game", stadiumId: "nyc", dateTime: startDate.addingTimeInterval(86400 * 7))
|
||||||
|
|
||||||
|
let prefs = TripPreferences(
|
||||||
|
planningMode: .locations,
|
||||||
|
startLocation: startLocation,
|
||||||
|
endLocation: endLocation,
|
||||||
|
sports: [.mlb],
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
leisureLevel: .moderate,
|
||||||
|
lodgingType: .hotel,
|
||||||
|
numberOfDrivers: 2
|
||||||
|
)
|
||||||
|
|
||||||
|
let request = PlanningRequest(
|
||||||
|
preferences: prefs,
|
||||||
|
availableGames: [houstonGame, clevelandGame, nycGame],
|
||||||
|
teams: [:],
|
||||||
|
stadiums: [
|
||||||
|
"houston": houstonStadium,
|
||||||
|
"cleveland": clevelandStadium,
|
||||||
|
"nyc": nycStadium
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
let result = planner.plan(request: request)
|
||||||
|
|
||||||
|
if case .success(let options) = result {
|
||||||
|
#expect(!options.isEmpty, "Should produce at least one itinerary")
|
||||||
|
for option in options {
|
||||||
|
// When the route includes a Houston game stop, there should NOT also be
|
||||||
|
// a separate empty Houston endpoint stop (the fix merges them)
|
||||||
|
let houstonStops = option.stops.filter { $0.city == "Houston" || $0.city == "Houston, TX" }
|
||||||
|
let emptyHoustonStops = houstonStops.filter { !$0.hasGames }
|
||||||
|
let gameHoustonStops = houstonStops.filter { $0.hasGames }
|
||||||
|
|
||||||
|
if !gameHoustonStops.isEmpty {
|
||||||
|
#expect(emptyHoustonStops.isEmpty,
|
||||||
|
"Should not have both a game stop and empty endpoint in Houston")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("plan: both endpoints match game cities — no redundant empty endpoints")
|
||||||
|
func plan_bothEndpointsMatchGameCities_noEmptyStops() {
|
||||||
|
let startDate = TestClock.now
|
||||||
|
let endDate = startDate.addingTimeInterval(86400 * 10)
|
||||||
|
|
||||||
|
let startLocation = LocationInput(name: "Chicago, IL", coordinate: chicagoCoord)
|
||||||
|
let endLocation = LocationInput(name: "New York, NY", coordinate: nycCoord)
|
||||||
|
|
||||||
|
let chicagoStadium = makeStadium(id: "chicago", city: "Chicago", coordinate: chicagoCoord)
|
||||||
|
let clevelandStadium = makeStadium(id: "cleveland", city: "Cleveland", coordinate: clevelandCoord)
|
||||||
|
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
|
||||||
|
|
||||||
|
let chicagoGame = makeGame(id: "chi-game", stadiumId: "chicago", dateTime: startDate.addingTimeInterval(86400))
|
||||||
|
let clevelandGame = makeGame(id: "cle-game", stadiumId: "cleveland", dateTime: startDate.addingTimeInterval(86400 * 4))
|
||||||
|
let nycGame = makeGame(id: "nyc-game", stadiumId: "nyc", dateTime: startDate.addingTimeInterval(86400 * 7))
|
||||||
|
|
||||||
|
let prefs = TripPreferences(
|
||||||
|
planningMode: .locations,
|
||||||
|
startLocation: startLocation,
|
||||||
|
endLocation: endLocation,
|
||||||
|
sports: [.mlb],
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
leisureLevel: .moderate,
|
||||||
|
lodgingType: .hotel,
|
||||||
|
numberOfDrivers: 2
|
||||||
|
)
|
||||||
|
|
||||||
|
let request = PlanningRequest(
|
||||||
|
preferences: prefs,
|
||||||
|
availableGames: [chicagoGame, clevelandGame, nycGame],
|
||||||
|
teams: [:],
|
||||||
|
stadiums: [
|
||||||
|
"chicago": chicagoStadium,
|
||||||
|
"cleveland": clevelandStadium,
|
||||||
|
"nyc": nycStadium
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
let result = planner.plan(request: request)
|
||||||
|
|
||||||
|
if case .success(let options) = result {
|
||||||
|
#expect(!options.isEmpty, "Should produce at least one itinerary")
|
||||||
|
for option in options {
|
||||||
|
// When a route includes a game in an endpoint city,
|
||||||
|
// there should NOT also be a separate empty endpoint stop for that city
|
||||||
|
let chicagoStops = option.stops.filter { $0.city == "Chicago" || $0.city == "Chicago, IL" }
|
||||||
|
if chicagoStops.contains(where: { $0.hasGames }) {
|
||||||
|
#expect(!chicagoStops.contains(where: { !$0.hasGames }),
|
||||||
|
"No redundant empty Chicago endpoint when game stop exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
let nycStops = option.stops.filter { $0.city == "New York" || $0.city == "New York, NY" }
|
||||||
|
if nycStops.contains(where: { $0.hasGames }) {
|
||||||
|
#expect(!nycStops.contains(where: { !$0.hasGames }),
|
||||||
|
"No redundant empty NYC endpoint when game stop exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("plan: start city differs from all game cities — adds empty endpoint stop")
|
||||||
|
func plan_endpointDiffersFromGameCity_stillAddsEndpointStop() {
|
||||||
|
let startDate = TestClock.now
|
||||||
|
let endDate = startDate.addingTimeInterval(86400 * 10)
|
||||||
|
|
||||||
|
// Start from a city that has a stadium but the route games are elsewhere
|
||||||
|
// Use Pittsburgh as an intermediate that differs from Chicago start
|
||||||
|
let pittsburghCoord = CLLocationCoordinate2D(latitude: 40.4406, longitude: -79.9959)
|
||||||
|
let startLocation = LocationInput(name: "Chicago", coordinate: chicagoCoord)
|
||||||
|
let endLocation = LocationInput(name: "New York", coordinate: nycCoord)
|
||||||
|
|
||||||
|
let chicagoStadium = makeStadium(id: "chicago", city: "Chicago", coordinate: chicagoCoord)
|
||||||
|
let pittsburghStadium = makeStadium(id: "pittsburgh", city: "Pittsburgh", coordinate: pittsburghCoord)
|
||||||
|
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
|
||||||
|
|
||||||
|
// Chicago game at start, Cleveland game at a non-endpoint city
|
||||||
|
let chicagoGame = makeGame(id: "chi-game", stadiumId: "chicago", dateTime: startDate.addingTimeInterval(86400))
|
||||||
|
let pittsburghGame = makeGame(id: "pit-game", stadiumId: "pittsburgh", dateTime: startDate.addingTimeInterval(86400 * 4))
|
||||||
|
let nycGame = makeGame(id: "nyc-game", stadiumId: "nyc", dateTime: startDate.addingTimeInterval(86400 * 7))
|
||||||
|
|
||||||
|
let prefs = TripPreferences(
|
||||||
|
planningMode: .locations,
|
||||||
|
startLocation: startLocation,
|
||||||
|
endLocation: endLocation,
|
||||||
|
sports: [.mlb],
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
leisureLevel: .moderate,
|
||||||
|
lodgingType: .hotel,
|
||||||
|
numberOfDrivers: 2
|
||||||
|
)
|
||||||
|
|
||||||
|
let request = PlanningRequest(
|
||||||
|
preferences: prefs,
|
||||||
|
availableGames: [chicagoGame, pittsburghGame, nycGame],
|
||||||
|
teams: [:],
|
||||||
|
stadiums: [
|
||||||
|
"chicago": chicagoStadium,
|
||||||
|
"pittsburgh": pittsburghStadium,
|
||||||
|
"nyc": nycStadium
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
let result = planner.plan(request: request)
|
||||||
|
|
||||||
|
if case .success(let options) = result {
|
||||||
|
#expect(!options.isEmpty)
|
||||||
|
// For routes that include the Chicago game, the start endpoint
|
||||||
|
// should be merged (no separate empty Chicago stop).
|
||||||
|
// For routes that don't include the Chicago game, an empty
|
||||||
|
// Chicago endpoint is correctly added.
|
||||||
|
for option in options {
|
||||||
|
let chicagoStops = option.stops.filter { $0.city == "Chicago" }
|
||||||
|
let hasGameInChicago = chicagoStops.contains { $0.hasGames }
|
||||||
|
let hasEmptyChicago = chicagoStops.contains { !$0.hasGames }
|
||||||
|
|
||||||
|
// Should never have BOTH an empty endpoint and a game stop for same city
|
||||||
|
#expect(!(hasGameInChicago && hasEmptyChicago),
|
||||||
|
"Should not have both game and empty stops for Chicago")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Regression Tests: Endpoint Game Validation
|
||||||
|
|
||||||
|
@Test("plan: explicit date range with no games at end city returns failure")
|
||||||
|
func plan_explicitDateRange_noGamesAtEndCity_returnsFailure() {
|
||||||
|
let startDate = TestClock.now
|
||||||
|
let endDate = startDate.addingTimeInterval(86400 * 7)
|
||||||
|
|
||||||
|
let startLocation = LocationInput(name: "Chicago", coordinate: chicagoCoord)
|
||||||
|
let endLocation = LocationInput(name: "Cleveland", coordinate: clevelandCoord)
|
||||||
|
|
||||||
|
let chicagoStadium = makeStadium(id: "chicago", city: "Chicago", coordinate: chicagoCoord)
|
||||||
|
let clevelandStadium = makeStadium(id: "cleveland", city: "Cleveland", coordinate: clevelandCoord)
|
||||||
|
|
||||||
|
// Game at start city, but NO game at end city within date range
|
||||||
|
let chicagoGame = makeGame(id: "chi-game", stadiumId: "chicago", dateTime: startDate.addingTimeInterval(86400))
|
||||||
|
|
||||||
|
let prefs = TripPreferences(
|
||||||
|
planningMode: .locations,
|
||||||
|
startLocation: startLocation,
|
||||||
|
endLocation: endLocation,
|
||||||
|
sports: [.mlb],
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
leisureLevel: .moderate,
|
||||||
|
lodgingType: .hotel,
|
||||||
|
numberOfDrivers: 2
|
||||||
|
)
|
||||||
|
|
||||||
|
let request = PlanningRequest(
|
||||||
|
preferences: prefs,
|
||||||
|
availableGames: [chicagoGame],
|
||||||
|
teams: [:],
|
||||||
|
stadiums: ["chicago": chicagoStadium, "cleveland": clevelandStadium]
|
||||||
|
)
|
||||||
|
|
||||||
|
let result = planner.plan(request: request)
|
||||||
|
|
||||||
|
guard case .failure(let failure) = result else {
|
||||||
|
Issue.record("Expected failure when no games at end city within date range")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
#expect(failure.reason == .noGamesInRange)
|
||||||
|
#expect(failure.violations.first?.description.contains("Cleveland") == true,
|
||||||
|
"Violation should mention end city")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("plan: explicit date range with no games at start city returns failure")
|
||||||
|
func plan_explicitDateRange_noGamesAtStartCity_returnsFailure() {
|
||||||
|
let startDate = TestClock.now
|
||||||
|
let endDate = startDate.addingTimeInterval(86400 * 7)
|
||||||
|
|
||||||
|
let startLocation = LocationInput(name: "Chicago", coordinate: chicagoCoord)
|
||||||
|
let endLocation = LocationInput(name: "Cleveland", coordinate: clevelandCoord)
|
||||||
|
|
||||||
|
let chicagoStadium = makeStadium(id: "chicago", city: "Chicago", coordinate: chicagoCoord)
|
||||||
|
let clevelandStadium = makeStadium(id: "cleveland", city: "Cleveland", coordinate: clevelandCoord)
|
||||||
|
|
||||||
|
// Game at end city, but NO game at start city within date range
|
||||||
|
let clevelandGame = makeGame(id: "cle-game", stadiumId: "cleveland", dateTime: startDate.addingTimeInterval(86400 * 5))
|
||||||
|
|
||||||
|
let prefs = TripPreferences(
|
||||||
|
planningMode: .locations,
|
||||||
|
startLocation: startLocation,
|
||||||
|
endLocation: endLocation,
|
||||||
|
sports: [.mlb],
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
leisureLevel: .moderate,
|
||||||
|
lodgingType: .hotel,
|
||||||
|
numberOfDrivers: 2
|
||||||
|
)
|
||||||
|
|
||||||
|
let request = PlanningRequest(
|
||||||
|
preferences: prefs,
|
||||||
|
availableGames: [clevelandGame],
|
||||||
|
teams: [:],
|
||||||
|
stadiums: ["chicago": chicagoStadium, "cleveland": clevelandStadium]
|
||||||
|
)
|
||||||
|
|
||||||
|
let result = planner.plan(request: request)
|
||||||
|
|
||||||
|
guard case .failure(let failure) = result else {
|
||||||
|
Issue.record("Expected failure when no games at start city within date range")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
#expect(failure.reason == .noGamesInRange)
|
||||||
|
#expect(failure.violations.first?.description.contains("Chicago") == true,
|
||||||
|
"Violation should mention start city")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("plan: explicit date range with games at both cities succeeds")
|
||||||
|
func plan_explicitDateRange_gamesAtBothCities_succeeds() {
|
||||||
|
let startDate = TestClock.now
|
||||||
|
let endDate = startDate.addingTimeInterval(86400 * 10)
|
||||||
|
|
||||||
|
let startLocation = LocationInput(name: "Chicago", coordinate: chicagoCoord)
|
||||||
|
let endLocation = LocationInput(name: "Cleveland", coordinate: clevelandCoord)
|
||||||
|
|
||||||
|
let chicagoStadium = makeStadium(id: "chicago", city: "Chicago", coordinate: chicagoCoord)
|
||||||
|
let clevelandStadium = makeStadium(id: "cleveland", city: "Cleveland", coordinate: clevelandCoord)
|
||||||
|
|
||||||
|
let chicagoGame = makeGame(id: "chi-game", stadiumId: "chicago", dateTime: startDate.addingTimeInterval(86400))
|
||||||
|
let clevelandGame = makeGame(id: "cle-game", stadiumId: "cleveland", dateTime: startDate.addingTimeInterval(86400 * 5))
|
||||||
|
|
||||||
|
let prefs = TripPreferences(
|
||||||
|
planningMode: .locations,
|
||||||
|
startLocation: startLocation,
|
||||||
|
endLocation: endLocation,
|
||||||
|
sports: [.mlb],
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
leisureLevel: .moderate,
|
||||||
|
lodgingType: .hotel,
|
||||||
|
numberOfDrivers: 2
|
||||||
|
)
|
||||||
|
|
||||||
|
let request = PlanningRequest(
|
||||||
|
preferences: prefs,
|
||||||
|
availableGames: [chicagoGame, clevelandGame],
|
||||||
|
teams: [:],
|
||||||
|
stadiums: ["chicago": chicagoStadium, "cleveland": clevelandStadium]
|
||||||
|
)
|
||||||
|
|
||||||
|
let result = planner.plan(request: request)
|
||||||
|
|
||||||
|
guard case .success(let options) = result else {
|
||||||
|
Issue.record("Expected success when games exist at both endpoint cities")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
#expect(!options.isEmpty, "Should produce at least one itinerary")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Helper Methods
|
// MARK: - Helper Methods
|
||||||
|
|
||||||
private func makeStadium(
|
private func makeStadium(
|
||||||
|
|||||||
Reference in New Issue
Block a user