# Team-First Planning Mode Implementation Plan ## Overview **Feature:** Select teams (not dates), find optimal trip windows across the entire season. **Use Case:** User has 3 stadiums left on bucket list. Select those 3 teams, app finds all windows throughout the season where all 3 teams play at home within a reasonable trip duration. ## Requirements | Constraint | Value | |------------|-------| | Sport scope | Single sport per search | | Start city | Flexible (algorithm picks optimal) | | Max duration | `teams × 2` days (3 teams = 6 days) | | Drive limit | Existing settings value | | Optimize for | Shortest duration + minimal backtracking | | Results | Top 10 options with reasoning | | Season | Current season only | --- ## Architecture ### Current Flow (Date-First) ``` User picks dates → Fetch games in range → Find routes → Rank → Display ``` ### New Flow (Team-First) ``` User picks teams → Fetch ALL home games for those teams (full season) → Generate sliding windows (N-day chunks) → Filter to windows where all teams have ≥1 home game → For each valid window: find optimal routes → Rank by duration + miles → Return top 10 ``` ### Reuse Ratio: ~90% | Component | Status | Notes | |-----------|--------|-------| | `allGames(for sports:)` | ✅ Exists | Full season fetch | | Sliding window generator | ✅ Exists | In ScenarioBPlanner | | GameDAGRouter | ✅ Exists | Supports anchor constraints | | ItineraryBuilder | ✅ Exists | No changes needed | | Max driving time | ✅ Exists | `UserPreferences.maxDrivingHoursPerDriver` | | Multi-team selection UI | ❌ New | Currently single-team only | | Team-first planner | ❌ New | ~300 lines adapting Scenario B | | Window optimization | ⚠️ Adapt | Season = 180+ days → need sampling | --- ## Parallel Track Structure ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ PHASE 1 (Parallel) │ ├──────────────────────┬──────────────────────┬──────────────────────────────┤ │ Track A: Models │ Track B: Planner │ Track C: UI Components │ │ (Agent 1) │ (Agent 2) │ (Agent 3) │ ├──────────────────────┼──────────────────────┼──────────────────────────────┤ │ A1. Add teamFirst │ B1. Create │ C1. Create TeamPickerView │ │ to PlanningMode │ ScenarioEPlanner │ (multi-select grid) │ │ │ skeleton │ │ │ A2. Add │ B2. Implement │ C2. Create │ │ selectedTeamIds │ window generator │ TeamFirstWizardStep │ │ to TripPrefs │ for teams │ │ │ │ │ │ │ A3. Update │ B3. Implement route │ C3. Update WizardViewModel │ │ PlanningRequest │ finding with │ for teamFirst mode │ │ computed props │ team anchors │ │ └──────────────────────┴──────────────────────┴──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ PHASE 2 (Parallel) │ ├─────────────────────────────────┬───────────────────────────────────────────┤ │ Track D: Integration │ Track E: Tests │ │ (Agent 4) │ (Agent 5) │ ├─────────────────────────────────┼───────────────────────────────────────────┤ │ D1. Update ScenarioPlannerFactory│ E1. Unit tests for window generator │ │ to detect teamFirst mode │ │ │ │ E2. Unit tests for ScenarioEPlanner │ │ D2. Wire TeamPickerView into │ │ │ existing wizard flow │ E3. Integration test: 3 teams → routes │ │ │ │ │ D3. Add teamFirst option to │ E4. Edge case tests (no windows, etc.) │ │ planning mode selector │ │ └─────────────────────────────────┴───────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ PHASE 3 (Sequential) │ ├─────────────────────────────────────────────────────────────────────────────┤ │ F1. End-to-end testing & bug fixes │ │ F2. Performance optimization (if needed) │ └─────────────────────────────────────────────────────────────────────────────┘ ``` --- ## Detailed Task Specifications ### Track A: Data Models (Agent 1) **A1. Add `teamFirst` to PlanningMode** ``` File: SportsTime/Core/Models/Domain/TripPreferences.swift Action: Add case to PlanningMode enum ``` **A2. Add `selectedTeamIds` to TripPreferences** ``` File: SportsTime/Core/Models/Domain/TripPreferences.swift Action: Add `var selectedTeamIds: Set = []` Action: Add `var teamFirstMaxDays: Int` computed as `selectedTeamIds.count * 2` ``` **A3. Update PlanningRequest computed properties** ``` File: SportsTime/Planning/Models/PlanningModels.swift Action: Add computed property to extract teams for selected IDs Action: Add helper to get all home games for selected teams ``` --- ### Track B: Scenario Planner (Agent 2) **B1. Create ScenarioEPlanner skeleton** ``` File: SportsTime/Planning/Scenarios/ScenarioEPlanner.swift (NEW) Action: Create class conforming to ScenarioPlanner protocol Action: Implement required methods with TODOs ``` **B2. Implement window generator for teams** ``` File: SportsTime/Planning/Scenarios/ScenarioEPlanner.swift Action: Generate all N-day windows across season Action: Filter to windows where ALL selected teams have ≥1 home game Action: Cap at 50 windows (sample if more) ``` **B3. Implement route finding with team anchors** ``` File: SportsTime/Planning/Scenarios/ScenarioEPlanner.swift Action: For each valid window, collect all home games for selected teams Action: Pass to GameDAGRouter with those games as anchors Action: Build itineraries, rank by duration + miles Action: Return top 10 ``` --- ### Track C: UI Components (Agent 3) **C1. Create TeamPickerView** ``` File: SportsTime/Features/Trip/Views/TeamPickerView.swift (NEW) Action: Grid of team logos/names for selected sport Action: Multi-select with checkmarks Action: Show count badge "3 selected" Action: Binding to Set for team IDs ``` **C2. Create TeamFirstWizardStep** ``` File: SportsTime/Features/Trip/Views/Wizard/TeamFirstWizardStep.swift (NEW) Action: Wrapper that uses TeamPickerView Action: Validation: require ≥2 teams selected Action: "Find trips for these teams" CTA ``` **C3. Update WizardViewModel for teamFirst mode** ``` File: SportsTime/Features/Trip/ViewModels/TripWizardViewModel.swift Action: Add teamFirst step configuration Action: Handle selectedTeamIds state Action: Skip date selection step when in teamFirst mode ``` --- ### Track D: Integration (Agent 4) **D1. Update ScenarioPlannerFactory** ``` File: SportsTime/Planning/Scenarios/ScenarioPlannerFactory.swift Action: Add detection for teamFirst mode Action: Return ScenarioEPlanner when selectedTeamIds.count >= 2 ``` **D2. Wire TeamPickerView into wizard** ``` File: SportsTime/Features/Trip/Views/Wizard/TripWizardView.swift Action: Add case for teamFirst step Action: Navigation flow: Sport → Teams → (skip dates) → Results ``` **D3. Add teamFirst to mode selector** ``` File: SportsTime/Features/Trip/Views/Wizard/PlanningModeSelector.swift Action: Add "Teams First" option with icon Action: Description: "Pick teams, we'll find the best windows" ``` --- ### Track E: Tests (Agent 5) **E1. Unit tests for window generator** ``` File: SportsTimeTests/Planning/ScenarioEPlannerTests.swift (NEW) Tests: - 3 teams, 6-day window → finds valid windows - Window with only 2 of 3 teams → excluded - Empty season → returns empty - Sampling works when >50 windows ``` **E2. Unit tests for ScenarioEPlanner** ``` File: SportsTimeTests/Planning/ScenarioEPlannerTests.swift Tests: - Returns PlanningResult with routes - All routes include all selected teams - Routes sorted by duration ascending - Respects max driving time constraint ``` **E3. Integration test** ``` File: SportsTimeTests/Planning/TeamFirstIntegrationTests.swift (NEW) Tests: - Full flow: 3 MLB teams → top 10 routes - Each route visits all 3 stadiums - Total duration ≤ 6 days ``` **E4. Edge case tests** ``` File: SportsTimeTests/Planning/ScenarioEPlannerTests.swift Tests: - Teams with no overlapping games → graceful error - Single team selected → validation error - Teams in same city → treated as separate stops ``` --- ## Risks & Mitigations | Risk | Mitigation | |------|------------| | Season = 180+ days = too many windows | Sample every 2nd day, or limit to weekends | | No valid windows (teams never align) | Early validation + user feedback | | Slow computation (10+ windows × routing) | Cap at 50 windows, parallelize | | Teams in same city (e.g., 2 NYC teams) | Treat as separate stops (different stadiums) | --- ## Test Strategy 1. **Unit:** Window generator finds correct multi-team windows 2. **Unit:** Anchor constraints include all teams' home games 3. **Integration:** 3 teams → returns routes visiting all 3 4. **Edge:** Teams with non-overlapping seasons → graceful "no trips found" 5. **Perf:** Full MLB season (2,430 games) completes in <5s --- ## Estimated Effort | Track | New Code | Modified Code | Total | |-------|----------|---------------|-------| | A: Models | 50 lines | 20 lines | 70 lines | | B: Planner | 300 lines | 0 lines | 300 lines | | C: UI | 250 lines | 50 lines | 300 lines | | D: Integration | 30 lines | 80 lines | 110 lines | | E: Tests | 400 lines | 0 lines | 400 lines | | **Total** | **1,030 lines** | **150 lines** | **1,180 lines** |