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

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:

  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