fix: 12 planning engine bugs + App Store preview export at 886x1920

Planning engine fixes (from adversarial code review):
- Bug #1: sortByLeisure tie-breaking uses totalDrivingHours
- Bug #2: allDates/calculateRestDays guard-let-break prevents infinite loop
- Bug #3: same-day trip no longer rejected (>= in dateRange guard)
- Bug #4: ScenarioD rationale shows game count not stop count
- Bug #5: ScenarioD departureDate advanced to next day after last game
- Bug #6: ScenarioC date range boundary uses <= instead of <
- Bug #7: DrivingConstraints clamps maxHoursPerDriverPerDay via max(1.0,...)
- Bug #8: effectiveTripDuration uses inclusive day counting (+1)
- Bug #9: TripWizardViewModel validates endDate >= startDate
- Bug #10: allDates() uses min/max instead of first/last for robustness
- Bug #12: arrivalBeforeGameStart accounts for game end time at departure
- Bug #15: ScenarioBPlanner replaces force unwraps with safe unwrapping

Tests: 16 regression test suites + updated existing test expectations
Marketing: Remotion canvas set to 886x1920 for App Store preview spec

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-15 17:08:50 -06:00
parent b320a773aa
commit 787a0f795e
14 changed files with 820 additions and 27 deletions

View File

@@ -70,7 +70,8 @@ final class ScenarioDPlanner: ScenarioPlanner {
//
// Step 1: Validate team selection
//
guard let teamId = request.preferences.followTeamId else {
guard let teamId = request.preferences.followTeamId
?? request.preferences.selectedTeamIds.first else {
return .failure(
PlanningFailure(
reason: .missingTeamSelection,
@@ -261,7 +262,7 @@ final class ScenarioDPlanner: ScenarioPlanner {
travelSegments: itinerary.travelSegments,
totalDrivingHours: itinerary.totalDrivingHours,
totalDistanceMiles: itinerary.totalDistanceMiles,
geographicRationale: "Follow Team: \(stops.count) games - \(cities)"
geographicRationale: "Follow Team: \(itinerary.stops.reduce(0) { $0 + $1.games.count }) games - \(cities)"
)
itineraryOptions.append(option)
}
@@ -396,6 +397,7 @@ final class ScenarioDPlanner: ScenarioPlanner {
)
let lastGameDate = sortedGames.last?.gameDate ?? Date()
let calendar = Calendar.current
return ItineraryStop(
city: city,
@@ -403,7 +405,7 @@ final class ScenarioDPlanner: ScenarioPlanner {
coordinate: coordinate,
games: sortedGames.map { $0.id },
arrivalDate: sortedGames.first?.gameDate ?? Date(),
departureDate: lastGameDate,
departureDate: calendar.date(byAdding: .day, value: 1, to: lastGameDate) ?? lastGameDate.addingTimeInterval(86400),
location: location,
firstGameStart: sortedGames.first?.startTime
)