Files
Sportstime/.planning/codebase/ARCHITECTURE.md
Trey t 60b450d869 docs: add Phase 1 plans and codebase documentation
- 01-01-PLAN.md: core.py + mlb.py (executed)
- 01-02-PLAN.md: nba.py + nhl.py
- 01-03-PLAN.md: nfl.py + orchestrator refactor
- Codebase documentation for planning context

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 00:00:45 -06:00

148 lines
5.0 KiB
Markdown

# Architecture
**Analysis Date:** 2026-01-09
## Pattern Overview
**Overall:** Clean MVVM with Feature-Based Modules + Offline-First Data Architecture
**Key Characteristics:**
- Three-layer architecture (Presentation, Domain, Data)
- Single source of truth (`AppDataProvider.shared`)
- Offline-first with background CloudKit sync
- Scenario-based trip planning (A/B/C discriminated scenarios)
- Explicit failure handling (no silent errors)
## Layers
**Presentation Layer (`Features/`):**
- Purpose: SwiftUI Views + @Observable ViewModels
- Contains: Feature modules (Home, Trip, Schedule, Settings, Progress)
- Depends on: Domain layer for planning, Data layer for models
- Used by: App entry point
**Domain Layer (`Planning/`):**
- Purpose: Trip planning business logic
- Contains: TripPlanningEngine, Scenario Planners, GameDAGRouter, TravelEstimator
- Depends on: Domain models only (Game, Stadium, Team)
- Used by: Presentation layer ViewModels
**Data Layer (`Core/`):**
- Purpose: Models, services, persistence
- Contains: Domain models, SwiftData models, CloudKit sync, location services
- Depends on: Foundation, SwiftData, CloudKit, MapKit
- Used by: All layers
**Export Layer (`Export/`):**
- Purpose: PDF generation with asset prefetching
- Contains: PDFGenerator, map snapshots, remote images, POI search
- Depends on: Domain models, UIKit/PDFKit
- Used by: TripDetailView
## Data Flow
**App Startup Sequence:**
1. `SportsTimeApp.swift` (@main) → WindowGroup
2. `BootstrappedContentView` initializes data:
- `BootstrapService.bootstrapIfNeeded()` - JSON → SwiftData (first launch)
- `AppDataProvider.configure(with: context)` - Sets ModelContext
- `AppDataProvider.loadInitialData()` - SwiftData → memory
3. App immediately usable with local data
4. Background: `CanonicalSyncService.syncAll()` - CloudKit → SwiftData (non-blocking)
**Trip Planning Request:**
1. User Input → `TripCreationView`
2. `TripCreationViewModel` builds `PlanningRequest`
3. `TripPlanningEngine.planItineraries(request:)`
- Detects scenario (A: date range, B: selected games, C: locations)
- Delegates to appropriate `ScenarioPlanner`
4. `GameDAGRouter` finds routes with diversity
5. Returns `ItineraryResult` (success with ranked options or explicit failure)
6. User selects option → `SavedTrip` persisted
**State Management:**
- ViewModels use `@Observable` (not ObservableObject)
- SwiftData `@Query` for saved trips
- In-memory cache in `AppDataProvider` for canonical data
## Key Abstractions
**AppDataProvider:**
- Purpose: Single source of truth for all canonical data reads
- Location: `Core/Services/DataProvider.swift`
- Pattern: Singleton with in-memory cache
- Usage: `AppDataProvider.shared.stadiums`, `.teams`, `.fetchGames()`
**ScenarioPlanner Protocol:**
- Purpose: Encapsulates planning algorithm for each scenario type
- Examples: `ScenarioAPlanner`, `ScenarioBPlanner`, `ScenarioCPlanner`
- Pattern: Strategy pattern with factory (`ScenarioPlannerFactory`)
**GameDAGRouter:**
- Purpose: DAG-based route finding with multi-dimensional diversity
- Location: `Planning/Engine/GameDAGRouter.swift`
- Pattern: Beam search with diversity pruning
**TripPlanningEngine:**
- Purpose: Thin orchestrator delegating to scenario planners
- Location: `Planning/Engine/TripPlanningEngine.swift`
- Pattern: Facade wrapping scenario detection and filter application
## Entry Points
**App Entry:**
- Location: `SportsTime/SportsTimeApp.swift`
- Triggers: App launch
- Responsibilities: SwiftData schema, ModelContainer, BootstrappedContentView
**Main UI Entry:**
- Location: `Features/Home/Views/HomeView.swift`
- Triggers: After bootstrap completes
- Responsibilities: TabView with 4 tabs (Home, Schedule, Trips, Progress)
**Trip Creation:**
- Location: `Features/Trip/Views/TripCreationView.swift`
- Triggers: User taps "New Trip"
- Responsibilities: Form input, calls TripCreationViewModel
**Trip Display:**
- Location: `Features/Trip/Views/TripDetailView.swift`
- Triggers: User selects trip
- Responsibilities: Itinerary display, conflict detection, PDF export
## Error Handling
**Strategy:** Explicit failure types with reasons, no silent errors
**Patterns:**
- `ItineraryResult` enum: `.success([ItineraryOption])` or `.failure(PlanningFailure)`
- `PlanningFailure` contains `FailureReason` and user-friendly message
- ViewModels expose `.error` state for UI display
- Services use `throws` for recoverable errors
## Cross-Cutting Concerns
**Logging:**
- `print()` with emoji prefixes for debugging
- No production logging framework
**Validation:**
- Input validation in ViewModels before planning
- `ConstraintViolation` type for planning constraints
**Thread Safety:**
- `@MainActor` for UI-bound services
- `actor` types for planning engine components
- Explicit isolation annotations throughout
**Data Consistency:**
- Canonical IDs (string) + UUIDs for stable identity
- SwiftData ↔ Domain model conversion via `.toDomain()` methods
---
*Architecture analysis: 2026-01-09*
*Update when major patterns change*