refactor: change domain model IDs from UUID to String canonical IDs

This refactor fixes the achievement system by using stable canonical string
IDs (e.g., "stadium_mlb_fenway_park") instead of random UUIDs. This ensures
stadium mappings for achievements are consistent across app launches and
CloudKit sync operations.

Changes:
- Stadium, Team, Game: id property changed from UUID to String
- Trip, TripStop, TripPreferences: updated to use String IDs for games/stadiums
- CKModels: removed UUID parsing, use canonical IDs directly
- AchievementEngine: now matches against canonical stadium IDs
- All test files updated to use String IDs instead of UUID()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-12 09:24:33 -06:00
parent 4b2cacaeba
commit 1703ca5b0f
53 changed files with 642 additions and 727 deletions

View File

@@ -163,7 +163,7 @@ final class CanonicalStadium {
func toDomain() -> Stadium {
Stadium(
id: uuid,
id: canonicalId,
name: name,
city: city,
state: state,
@@ -299,14 +299,14 @@ final class CanonicalTeam {
var sportEnum: Sport? { Sport(rawValue: sport) }
func toDomain(stadiumUUID: UUID) -> Team {
func toDomain() -> Team {
Team(
id: uuid,
id: canonicalId,
name: name,
abbreviation: abbreviation,
sport: sportEnum ?? .mlb,
city: city,
stadiumId: stadiumUUID,
stadiumId: stadiumCanonicalId,
logoURL: logoURL.flatMap { URL(string: $0) },
primaryColor: primaryColor,
secondaryColor: secondaryColor
@@ -466,12 +466,12 @@ final class CanonicalGame {
var sportEnum: Sport? { Sport(rawValue: sport) }
func toDomain(homeTeamUUID: UUID, awayTeamUUID: UUID, stadiumUUID: UUID) -> Game {
func toDomain() -> Game {
Game(
id: uuid,
homeTeamId: homeTeamUUID,
awayTeamId: awayTeamUUID,
stadiumId: stadiumUUID,
id: canonicalId,
homeTeamId: homeTeamCanonicalId,
awayTeamId: awayTeamCanonicalId,
stadiumId: stadiumCanonicalId,
dateTime: dateTime,
sport: sportEnum ?? .mlb,
season: season,

View File

@@ -16,7 +16,7 @@ final class SavedTrip {
var updatedAt: Date
var status: String
var tripData: Data // Encoded Trip struct
var gamesData: Data? // Encoded [UUID: RichGame] dictionary
var gamesData: Data? // Encoded [String: RichGame] dictionary
@Relationship(deleteRule: .cascade)
var votes: [TripVote]?
@@ -43,16 +43,16 @@ final class SavedTrip {
try? JSONDecoder().decode(Trip.self, from: tripData)
}
var games: [UUID: RichGame] {
var games: [String: RichGame] {
guard let data = gamesData else { return [:] }
return (try? JSONDecoder().decode([UUID: RichGame].self, from: data)) ?? [:]
return (try? JSONDecoder().decode([String: RichGame].self, from: data)) ?? [:]
}
var tripStatus: TripStatus {
TripStatus(rawValue: status) ?? .draft
}
static func from(_ trip: Trip, games: [UUID: RichGame] = [:], status: TripStatus = .planned) -> SavedTrip? {
static func from(_ trip: Trip, games: [String: RichGame] = [:], status: TripStatus = .planned) -> SavedTrip? {
guard let tripData = try? JSONEncoder().encode(trip) else { return nil }
let gamesData = try? JSONEncoder().encode(games)
return SavedTrip(
@@ -75,7 +75,7 @@ final class TripVote {
var tripId: UUID
var voterId: String
var voterName: String
var gameVotes: Data // [UUID: Bool] encoded
var gameVotes: Data // [String: Bool] encoded (game IDs to vote)
var routeVotes: Data // [String: Int] encoded
var leisurePreference: String
var createdAt: Date

View File

@@ -65,8 +65,7 @@ final class StadiumVisit {
@Attribute(.unique) var id: UUID
// Stadium identity (stable across renames)
var canonicalStadiumId: String // Links to CanonicalStadium.canonicalId
var stadiumUUID: UUID // Runtime UUID for display lookups
var stadiumId: String // Canonical ID: "stadium_mlb_fenway_park"
var stadiumNameAtVisit: String // Frozen at visit time
// Visit details
@@ -75,9 +74,9 @@ final class StadiumVisit {
var visitTypeRaw: String // VisitType.rawValue
// Game info (optional - nil for tours/other visits)
var gameId: UUID?
var homeTeamId: UUID?
var awayTeamId: UUID?
var gameId: String? // Canonical ID: "game_mlb_2026_bos_nyy_0401"
var homeTeamId: String? // Canonical ID: "team_mlb_bos"
var awayTeamId: String? // Canonical ID: "team_mlb_nyy"
var homeTeamName: String? // For display when team lookup fails
var awayTeamName: String?
var finalScore: String? // "5-3" format
@@ -109,15 +108,14 @@ final class StadiumVisit {
init(
id: UUID = UUID(),
canonicalStadiumId: String,
stadiumUUID: UUID,
stadiumId: String,
stadiumNameAtVisit: String,
visitDate: Date,
sport: Sport,
visitType: VisitType = .game,
gameId: UUID? = nil,
homeTeamId: UUID? = nil,
awayTeamId: UUID? = nil,
gameId: String? = nil,
homeTeamId: String? = nil,
awayTeamId: String? = nil,
homeTeamName: String? = nil,
awayTeamName: String? = nil,
finalScore: String? = nil,
@@ -133,8 +131,7 @@ final class StadiumVisit {
source: VisitSource = .manual
) {
self.id = id
self.canonicalStadiumId = canonicalStadiumId
self.stadiumUUID = stadiumUUID
self.stadiumId = stadiumId
self.stadiumNameAtVisit = stadiumNameAtVisit
self.visitDate = visitDate
self.sport = sport.rawValue