Files
Sportstime/Scripts/import_to_cloudkit.swift
Trey t 9088b46563 Initial commit: SportsTime trip planning app
- Three-scenario planning engine (A: date range, B: selected games, C: directional routes)
- GeographicRouteExplorer with anchor game support for route exploration
- Shared ItineraryBuilder for travel segment calculation
- TravelEstimator for driving time/distance estimation
- SwiftUI views for trip creation and detail display
- CloudKit integration for schedule data
- Python scraping scripts for sports schedules

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 00:46:40 -06:00

276 lines
8.4 KiB
Swift

#!/usr/bin/env swift
//
// import_to_cloudkit.swift
// SportsTime
//
// Imports scraped JSON data into CloudKit public database.
// Run from command line: swift import_to_cloudkit.swift --games data/games.json --stadiums data/stadiums.json
//
import Foundation
import CloudKit
// MARK: - Data Models (matching scraper output)
struct ScrapedGame: Codable {
let id: String
let sport: String
let season: String
let date: String
let time: String?
let home_team: String
let away_team: String
let home_team_abbrev: String
let away_team_abbrev: String
let venue: String
let source: String
let is_playoff: Bool?
let broadcast: String?
}
struct ScrapedStadium: Codable {
let id: String
let name: String
let city: String
let state: String
let latitude: Double
let longitude: Double
let capacity: Int
let sport: String
let team_abbrevs: [String]
let source: String
let year_opened: Int?
}
// MARK: - CloudKit Importer
class CloudKitImporter {
let container: CKContainer
let database: CKDatabase
init(containerIdentifier: String = "iCloud.com.sportstime.app") {
self.container = CKContainer(identifier: containerIdentifier)
self.database = container.publicCloudDatabase
}
// MARK: - Import Stadiums
func importStadiums(from stadiums: [ScrapedStadium]) async throws -> Int {
var imported = 0
for stadium in stadiums {
let record = CKRecord(recordType: "Stadium")
record["stadiumId"] = stadium.id
record["name"] = stadium.name
record["city"] = stadium.city
record["state"] = stadium.state
record["location"] = CLLocation(latitude: stadium.latitude, longitude: stadium.longitude)
record["capacity"] = stadium.capacity
record["sport"] = stadium.sport
record["teamAbbrevs"] = stadium.team_abbrevs
record["source"] = stadium.source
if let yearOpened = stadium.year_opened {
record["yearOpened"] = yearOpened
}
do {
_ = try await database.save(record)
imported += 1
print(" Imported stadium: \(stadium.name)")
} catch {
print(" Error importing \(stadium.name): \(error)")
}
}
return imported
}
// MARK: - Import Teams
func importTeams(from stadiums: [ScrapedStadium], teamMappings: [String: TeamInfo]) async throws -> [String: CKRecord.ID] {
var teamRecordIDs: [String: CKRecord.ID] = [:]
for (abbrev, info) in teamMappings {
let record = CKRecord(recordType: "Team")
record["teamId"] = UUID().uuidString
record["name"] = info.name
record["abbreviation"] = abbrev
record["sport"] = info.sport
record["city"] = info.city
do {
let saved = try await database.save(record)
teamRecordIDs[abbrev] = saved.recordID
print(" Imported team: \(info.name)")
} catch {
print(" Error importing team \(info.name): \(error)")
}
}
return teamRecordIDs
}
// MARK: - Import Games
func importGames(
from games: [ScrapedGame],
teamRecordIDs: [String: CKRecord.ID],
stadiumRecordIDs: [String: CKRecord.ID]
) async throws -> Int {
var imported = 0
// Batch imports for efficiency
let batchSize = 100
var batch: [CKRecord] = []
for game in games {
let record = CKRecord(recordType: "Game")
record["gameId"] = game.id
record["sport"] = game.sport
record["season"] = game.season
// Parse date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
if let date = dateFormatter.date(from: game.date) {
if let timeStr = game.time {
// Combine date and time
let timeFormatter = DateFormatter()
timeFormatter.dateFormat = "HH:mm"
if let time = timeFormatter.date(from: timeStr) {
let calendar = Calendar.current
let timeComponents = calendar.dateComponents([.hour, .minute], from: time)
if let combined = calendar.date(bySettingHour: timeComponents.hour ?? 19,
minute: timeComponents.minute ?? 0,
second: 0, of: date) {
record["dateTime"] = combined
}
}
} else {
// Default to 7 PM if no time
let calendar = Calendar.current
if let defaultTime = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: date) {
record["dateTime"] = defaultTime
}
}
}
// Team references
if let homeTeamID = teamRecordIDs[game.home_team_abbrev] {
record["homeTeamRef"] = CKRecord.Reference(recordID: homeTeamID, action: .none)
}
if let awayTeamID = teamRecordIDs[game.away_team_abbrev] {
record["awayTeamRef"] = CKRecord.Reference(recordID: awayTeamID, action: .none)
}
record["isPlayoff"] = (game.is_playoff ?? false) ? 1 : 0
record["broadcastInfo"] = game.broadcast
record["source"] = game.source
batch.append(record)
// Save batch
if batch.count >= batchSize {
do {
let operation = CKModifyRecordsOperation(recordsToSave: batch, recordIDsToDelete: nil)
operation.savePolicy = .changedKeys
try await database.modifyRecords(saving: batch, deleting: [])
imported += batch.count
print(" Imported batch of \(batch.count) games (total: \(imported))")
batch.removeAll()
} catch {
print(" Error importing batch: \(error)")
}
}
}
// Save remaining
if !batch.isEmpty {
do {
try await database.modifyRecords(saving: batch, deleting: [])
imported += batch.count
} catch {
print(" Error importing final batch: \(error)")
}
}
return imported
}
}
// MARK: - Team Info
struct TeamInfo {
let name: String
let city: String
let sport: String
}
// MARK: - Main
func loadJSON<T: Codable>(from path: String) throws -> T {
let url = URL(fileURLWithPath: path)
let data = try Data(contentsOf: url)
return try JSONDecoder().decode(T.self, from: data)
}
func main() async {
let args = CommandLine.arguments
guard args.count >= 3 else {
print("Usage: swift import_to_cloudkit.swift --games <path> --stadiums <path>")
return
}
var gamesPath: String?
var stadiumsPath: String?
for i in 1..<args.count {
if args[i] == "--games" && i + 1 < args.count {
gamesPath = args[i + 1]
}
if args[i] == "--stadiums" && i + 1 < args.count {
stadiumsPath = args[i + 1]
}
}
let importer = CloudKitImporter()
// Import stadiums
if let path = stadiumsPath {
print("\n=== Importing Stadiums ===")
do {
let stadiums: [ScrapedStadium] = try loadJSON(from: path)
let count = try await importer.importStadiums(from: stadiums)
print("Imported \(count) stadiums")
} catch {
print("Error loading stadiums: \(error)")
}
}
// Import games
if let path = gamesPath {
print("\n=== Importing Games ===")
do {
let games: [ScrapedGame] = try loadJSON(from: path)
// Note: Would need to first import teams and get their record IDs
// This is a simplified version
print("Loaded \(games.count) games for import")
} catch {
print("Error loading games: \(error)")
}
}
print("\n=== Import Complete ===")
}
// Run
Task {
await main()
}
// Keep the process running for async operations
RunLoop.main.run()