- 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>
148 lines
5.0 KiB
Markdown
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*
|