# 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*
