- 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>
5.0 KiB
5.0 KiB
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:
SportsTimeApp.swift(@main) → WindowGroupBootstrappedContentViewinitializes data:BootstrapService.bootstrapIfNeeded()- JSON → SwiftData (first launch)AppDataProvider.configure(with: context)- Sets ModelContextAppDataProvider.loadInitialData()- SwiftData → memory
- App immediately usable with local data
- Background:
CanonicalSyncService.syncAll()- CloudKit → SwiftData (non-blocking)
Trip Planning Request:
- User Input →
TripCreationView TripCreationViewModelbuildsPlanningRequestTripPlanningEngine.planItineraries(request:)- Detects scenario (A: date range, B: selected games, C: locations)
- Delegates to appropriate
ScenarioPlanner
GameDAGRouterfinds routes with diversity- Returns
ItineraryResult(success with ranked options or explicit failure) - User selects option →
SavedTrippersisted
State Management:
- ViewModels use
@Observable(not ObservableObject) - SwiftData
@Queryfor saved trips - In-memory cache in
AppDataProviderfor 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:
ItineraryResultenum:.success([ItineraryOption])or.failure(PlanningFailure)PlanningFailurecontainsFailureReasonand user-friendly message- ViewModels expose
.errorstate for UI display - Services use
throwsfor recoverable errors
Cross-Cutting Concerns
Logging:
print()with emoji prefixes for debugging- No production logging framework
Validation:
- Input validation in ViewModels before planning
ConstraintViolationtype for planning constraints
Thread Safety:
@MainActorfor UI-bound servicesactortypes 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