Add canonical ID pipeline and fix UUID consistency for CloudKit sync
- Add local canonicalization pipeline (stadiums, teams, games) that generates deterministic canonical IDs before CloudKit upload - Fix CanonicalSyncService to use deterministic UUIDs from canonical IDs instead of random UUIDs from CloudKit records - Add SyncStadium/SyncTeam/SyncGame types to CloudKitService that preserve canonical ID relationships during sync - Add canonical ID field keys to CKModels for reading from CloudKit records - Bundle canonical JSON files (stadiums_canonical, teams_canonical, games_canonical, stadium_aliases) for consistent bootstrap data - Update BootstrapService to prefer canonical format files over legacy format This ensures all entities use consistent deterministic UUIDs derived from their canonical IDs, preventing duplicate records when syncing CloudKit data with bootstrapped local data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,11 +24,13 @@ enum CKRecordType {
|
||||
|
||||
struct CKTeam {
|
||||
static let idKey = "teamId"
|
||||
static let canonicalIdKey = "canonicalId"
|
||||
static let nameKey = "name"
|
||||
static let abbreviationKey = "abbreviation"
|
||||
static let sportKey = "sport"
|
||||
static let cityKey = "city"
|
||||
static let stadiumRefKey = "stadiumRef"
|
||||
static let stadiumCanonicalIdKey = "stadiumCanonicalId"
|
||||
static let logoURLKey = "logoURL"
|
||||
static let primaryColorKey = "primaryColor"
|
||||
static let secondaryColorKey = "secondaryColor"
|
||||
@@ -53,6 +55,16 @@ struct CKTeam {
|
||||
self.record = record
|
||||
}
|
||||
|
||||
/// The canonical ID string from CloudKit (e.g., "team_nba_atl")
|
||||
var canonicalId: String? {
|
||||
record[CKTeam.canonicalIdKey] as? String
|
||||
}
|
||||
|
||||
/// The stadium canonical ID string from CloudKit (e.g., "stadium_nba_state_farm_arena")
|
||||
var stadiumCanonicalId: String? {
|
||||
record[CKTeam.stadiumCanonicalIdKey] as? String
|
||||
}
|
||||
|
||||
var team: Team? {
|
||||
// Use teamId field, or fall back to record name
|
||||
let idString = (record[CKTeam.idKey] as? String) ?? record.recordID.recordName
|
||||
@@ -96,6 +108,7 @@ struct CKTeam {
|
||||
|
||||
struct CKStadium {
|
||||
static let idKey = "stadiumId"
|
||||
static let canonicalIdKey = "canonicalId"
|
||||
static let nameKey = "name"
|
||||
static let cityKey = "city"
|
||||
static let stateKey = "state"
|
||||
@@ -125,6 +138,11 @@ struct CKStadium {
|
||||
self.record = record
|
||||
}
|
||||
|
||||
/// The canonical ID string from CloudKit (e.g., "stadium_nba_state_farm_arena")
|
||||
var canonicalId: String? {
|
||||
record[CKStadium.canonicalIdKey] as? String
|
||||
}
|
||||
|
||||
var stadium: Stadium? {
|
||||
// Use stadiumId field, or fall back to record name
|
||||
let idString = (record[CKStadium.idKey] as? String) ?? record.recordID.recordName
|
||||
@@ -160,9 +178,13 @@ struct CKStadium {
|
||||
|
||||
struct CKGame {
|
||||
static let idKey = "gameId"
|
||||
static let canonicalIdKey = "canonicalId"
|
||||
static let homeTeamRefKey = "homeTeamRef"
|
||||
static let awayTeamRefKey = "awayTeamRef"
|
||||
static let stadiumRefKey = "stadiumRef"
|
||||
static let homeTeamCanonicalIdKey = "homeTeamCanonicalId"
|
||||
static let awayTeamCanonicalIdKey = "awayTeamCanonicalId"
|
||||
static let stadiumCanonicalIdKey = "stadiumCanonicalId"
|
||||
static let dateTimeKey = "dateTime"
|
||||
static let sportKey = "sport"
|
||||
static let seasonKey = "season"
|
||||
@@ -189,6 +211,26 @@ struct CKGame {
|
||||
self.record = record
|
||||
}
|
||||
|
||||
/// The canonical ID string from CloudKit (e.g., "game_nba_202526_20251021_hou_okc")
|
||||
var canonicalId: String? {
|
||||
record[CKGame.canonicalIdKey] as? String
|
||||
}
|
||||
|
||||
/// The home team canonical ID string from CloudKit (e.g., "team_nba_okc")
|
||||
var homeTeamCanonicalId: String? {
|
||||
record[CKGame.homeTeamCanonicalIdKey] as? String
|
||||
}
|
||||
|
||||
/// The away team canonical ID string from CloudKit (e.g., "team_nba_hou")
|
||||
var awayTeamCanonicalId: String? {
|
||||
record[CKGame.awayTeamCanonicalIdKey] as? String
|
||||
}
|
||||
|
||||
/// The stadium canonical ID string from CloudKit (e.g., "stadium_nba_paycom_center")
|
||||
var stadiumCanonicalId: String? {
|
||||
record[CKGame.stadiumCanonicalIdKey] as? String
|
||||
}
|
||||
|
||||
func game(homeTeamId: UUID, awayTeamId: UUID, stadiumId: UUID) -> Game? {
|
||||
guard let idString = record[CKGame.idKey] as? String,
|
||||
let id = UUID(uuidString: idString),
|
||||
|
||||
@@ -17,7 +17,7 @@ struct Game: Identifiable, Codable, Hashable {
|
||||
let broadcastInfo: String?
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
id: UUID ,
|
||||
homeTeamId: UUID,
|
||||
awayTeamId: UUID,
|
||||
stadiumId: UUID,
|
||||
|
||||
@@ -19,7 +19,7 @@ struct Stadium: Identifiable, Codable, Hashable {
|
||||
let imageURL: URL?
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
id: UUID,
|
||||
name: String,
|
||||
city: String,
|
||||
state: String,
|
||||
|
||||
@@ -17,7 +17,7 @@ struct Team: Identifiable, Codable, Hashable {
|
||||
let secondaryColor: String?
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
id: UUID,
|
||||
name: String,
|
||||
abbreviation: String,
|
||||
sport: Sport,
|
||||
|
||||
@@ -81,7 +81,7 @@ struct LodgingSuggestion: Identifiable, Codable, Hashable {
|
||||
let rating: Double?
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
id: UUID,
|
||||
name: String,
|
||||
type: LodgingType,
|
||||
address: String? = nil,
|
||||
|
||||
Reference in New Issue
Block a user