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

@@ -174,12 +174,14 @@ final class PhotoImportViewModel {
// Create the visit
let visit = StadiumVisit(
canonicalStadiumId: match.stadium.id.uuidString,
stadiumUUID: match.stadium.id,
stadiumId: match.stadium.id,
stadiumNameAtVisit: match.stadium.name,
visitDate: match.game.dateTime,
sport: match.game.sport,
visitType: .game,
gameId: match.game.id,
homeTeamId: match.homeTeam.id,
awayTeamId: match.awayTeam.id,
homeTeamName: match.homeTeam.fullName,
awayTeamName: match.awayTeam.fullName,
finalScore: match.formattedFinalScore,

View File

@@ -41,10 +41,10 @@ final class ProgressViewModel {
let visitedStadiumIds = Set(
visits
.filter { $0.sportEnum == selectedSport }
.compactMap { visit -> UUID? in
.compactMap { visit -> String? in
// Match visit's canonical stadium ID to a stadium
stadiums.first { stadium in
stadium.id == visit.stadiumUUID
stadium.id == visit.stadiumId
}?.id
}
)
@@ -62,11 +62,11 @@ final class ProgressViewModel {
}
/// Stadium visit status indexed by stadium ID
var stadiumVisitStatus: [UUID: StadiumVisitStatus] {
var statusMap: [UUID: StadiumVisitStatus] = [:]
var stadiumVisitStatus: [String: StadiumVisitStatus] {
var statusMap: [String: StadiumVisitStatus] = [:]
// Group visits by stadium
let visitsByStadium = Dictionary(grouping: visits.filter { $0.sportEnum == selectedSport }) { $0.stadiumUUID }
let visitsByStadium = Dictionary(grouping: visits.filter { $0.sportEnum == selectedSport }) { $0.stadiumId }
for stadium in stadiums {
if let stadiumVisits = visitsByStadium[stadium.id], !stadiumVisits.isEmpty {
@@ -114,7 +114,7 @@ final class ProgressViewModel {
.sorted { $0.visitDate > $1.visitDate }
.prefix(10)
.compactMap { visit -> VisitSummary? in
guard let stadium = stadiums.first(where: { $0.id == visit.stadiumUUID }),
guard let stadium = stadiums.first(where: { $0.id == visit.stadiumId }),
let sport = visit.sportEnum else {
return nil
}

View File

@@ -12,7 +12,7 @@ import MapKit
struct ProgressMapView: View {
let stadiums: [Stadium]
let visitStatus: [UUID: StadiumVisitStatus]
let visitStatus: [String: StadiumVisitStatus]
@Binding var selectedStadium: Stadium?
// Fixed region for continental US - map is locked to this view

View File

@@ -389,8 +389,7 @@ struct StadiumVisitSheet: View {
// Create the visit
let visit = StadiumVisit(
canonicalStadiumId: stadium.id.uuidString, // Simplified - in production use StadiumIdentityService
stadiumUUID: stadium.id,
stadiumId: stadium.id,
stadiumNameAtVisit: stadium.name,
visitDate: visitDate,
sport: selectedSport,

View File

@@ -523,7 +523,7 @@ extension VisitSource {
#Preview {
let stadium = Stadium(
id: UUID(),
id: "stadium_preview_oracle_park",
name: "Oracle Park",
city: "San Francisco",
state: "CA",