Files
Sportstime/docs/STADIUM_IDENTITY_SYSTEM.md
Trey t a8b0491571 wip
2026-01-19 22:12:53 -06:00

5.8 KiB

Stadium Identity System

How SportsTime handles stadium renames, new stadiums, and team relocations while preserving user data.

Architecture Overview

The system uses immutable canonical IDs for all references and mutable display data for names/locations. User data only stores canonical IDs, so it never needs migration when real-world changes occur.

User data → canonical IDs (stable) → current display names (via StadiumIdentityService)

Data Models

Canonical Data (Synced from CloudKit)

// Core identity - canonicalId NEVER changes
CanonicalStadium:
  canonicalId: "stadium_nba_los_angeles_lakers"  // Immutable
  name: "Crypto.com Arena"                        // Can change
  city: "Los Angeles"                             // Can change
  deprecatedAt: nil                               // Set when demolished

CanonicalTeam:
  canonicalId: "team_mlb_athletics"              // Immutable
  stadiumCanonicalId: "stadium_mlb_las_vegas"   // Can change (relocation)
  city: "Las Vegas"                              // Can change
  deprecatedAt: nil                              // Set if team ceases

// Historical name tracking
StadiumAlias:
  aliasName: "staples center"                    // Lowercase for matching
  stadiumCanonicalId: "stadium_nba_los_angeles_lakers"
  validFrom: 2021-01-01
  validUntil: 2022-12-25

TeamAlias:
  aliasValue: "Oakland"
  aliasType: .city
  teamCanonicalId: "team_mlb_athletics"
  validUntil: 2024-12-31

User Data (Local Only)

StadiumVisit:
  stadiumId: String           // Canonical ID - stable reference
  stadiumNameAtVisit: String  // Frozen at visit time for history
  homeTeamId: String?         // Canonical ID
  awayTeamId: String?         // Canonical ID
  homeTeamName: String?       // Frozen at visit time
  awayTeamName: String?       // Frozen at visit time

Achievement:
  achievementTypeId: String   // League structure ID (e.g., "mlb_al_west")
  sport: String?
  visitIdsSnapshot: Data      // UUIDs of StadiumVisits - immutable

Scenario Handling

Stadium Renames

Example: Staples Center → Crypto.com Arena (December 2021)

What happens:

  1. CloudKit updates CanonicalStadium.name to "Crypto.com Arena"
  2. CloudKit adds StadiumAlias for "Staples Center" with validity dates
  3. Next sync updates local SwiftData
  4. canonicalId remains "stadium_nba_los_angeles_lakers"

User impact: None

  • Existing StadiumVisit.stadiumId still resolves correctly
  • StadiumVisit.stadiumNameAtVisit preserves "Staples Center" for historical display
  • Searching "Staples Center" still finds the stadium via alias lookup

New Stadium Built

Example: New Las Vegas A's stadium opens in 2028

What happens:

  1. CloudKit adds new CanonicalStadium record with new canonical ID
  2. Next sync creates record in SwiftData
  3. Deterministic UUID generated: SHA256(canonicalId) → UUID
  4. Stadium appears in app automatically

User impact: None

  • New stadium available for visits and achievements
  • No migration needed

Team Relocates

Example: Oakland A's → Las Vegas A's (2024-2028)

What happens:

  1. CloudKit updates CanonicalTeam:
    • canonicalId stays "team_mlb_athletics" (never changes)
    • stadiumCanonicalId updated to Las Vegas stadium
    • city updated to "Las Vegas"
  2. CloudKit adds TeamAlias for "Oakland" with end date
  3. Old Oakland Coliseum gets deprecatedAt timestamp (soft delete)

User impact: None

  • Old visits preserved: StadiumVisit.stadiumId = Oakland Coliseum (still valid)
  • Old visits show historical context: "Oakland A's at Oakland Coliseum"
  • Achievements adapt: "Complete AL West" now requires Las Vegas stadium

Stadium Demolished / Team Ceases

What happens:

  • Record gets deprecatedAt timestamp (soft delete, never hard delete)
  • Filtered from active queries: predicate { $0.deprecatedAt == nil }
  • Historical data fully preserved

User impact: None

  • Visits remain in history, just not in active stadium lists
  • Achievements not revoked - you earned it, you keep it

Identity Resolution

StadiumIdentityService handles all lookups:

// Find canonical ID from any name (current or historical)
StadiumIdentityService.shared.canonicalId(forName: "Staples Center")
// → "stadium_nba_los_angeles_lakers"

// Get current display name from canonical ID
StadiumIdentityService.shared.currentName(forCanonicalId: "stadium_nba_los_angeles_lakers")
// → "Crypto.com Arena"

// Get all historical names
StadiumIdentityService.shared.allNames(forCanonicalId: "stadium_nba_los_angeles_lakers")
// → ["Crypto.com Arena", "Staples Center", "Great Western Forum"]

Sync Safety

During CanonicalSyncService.mergeStadium():

if let existing = try context.fetch(descriptor).first {
    // PRESERVE user customizations
    let savedNickname = existing.userNickname
    let savedFavorite = existing.isFavorite

    // UPDATE system fields only
    existing.name = remote.name
    existing.city = remote.city
    // canonicalId is NOT updated - it's immutable

    // RESTORE user customizations
    existing.userNickname = savedNickname
    existing.isFavorite = savedFavorite
}

Impact Summary

Scenario User Visits Achievements Historical Display
Stadium rename Preserved Preserved Shows name at visit time
New stadium N/A Available to earn N/A
Team relocates Preserved Logic adapts Shows team + old stadium
Stadium demolished Preserved Not revoked Marked deprecated, visible in history

Key Principle

Immutable references, mutable display. User data stores only canonical IDs. Display names are resolved at read time via StadiumIdentityService. This means we can update the real world without ever migrating user data.