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>
This commit is contained in:
Trey t
2026-01-07 00:46:40 -06:00
commit 9088b46563
84 changed files with 180371 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
//
// CKModels.swift
// SportsTime
//
// CloudKit Record Type Definitions for Public Database
//
import Foundation
import CloudKit
// MARK: - Record Type Constants
enum CKRecordType {
static let team = "Team"
static let stadium = "Stadium"
static let game = "Game"
static let sport = "Sport"
}
// MARK: - CKTeam
struct CKTeam {
static let idKey = "teamId"
static let nameKey = "name"
static let abbreviationKey = "abbreviation"
static let sportKey = "sport"
static let cityKey = "city"
static let stadiumRefKey = "stadiumRef"
static let logoURLKey = "logoURL"
static let primaryColorKey = "primaryColor"
static let secondaryColorKey = "secondaryColor"
let record: CKRecord
init(record: CKRecord) {
self.record = record
}
init(team: Team, stadiumRecordID: CKRecord.ID) {
let record = CKRecord(recordType: CKRecordType.team)
record[CKTeam.idKey] = team.id.uuidString
record[CKTeam.nameKey] = team.name
record[CKTeam.abbreviationKey] = team.abbreviation
record[CKTeam.sportKey] = team.sport.rawValue
record[CKTeam.cityKey] = team.city
record[CKTeam.stadiumRefKey] = CKRecord.Reference(recordID: stadiumRecordID, action: .none)
record[CKTeam.logoURLKey] = team.logoURL?.absoluteString
record[CKTeam.primaryColorKey] = team.primaryColor
record[CKTeam.secondaryColorKey] = team.secondaryColor
self.record = record
}
var team: Team? {
guard let idString = record[CKTeam.idKey] as? String,
let id = UUID(uuidString: idString),
let name = record[CKTeam.nameKey] as? String,
let abbreviation = record[CKTeam.abbreviationKey] as? String,
let sportRaw = record[CKTeam.sportKey] as? String,
let sport = Sport(rawValue: sportRaw),
let city = record[CKTeam.cityKey] as? String,
let stadiumRef = record[CKTeam.stadiumRefKey] as? CKRecord.Reference,
let stadiumIdString = stadiumRef.recordID.recordName.split(separator: ":").last,
let stadiumId = UUID(uuidString: String(stadiumIdString))
else { return nil }
let logoURL = (record[CKTeam.logoURLKey] as? String).flatMap { URL(string: $0) }
return Team(
id: id,
name: name,
abbreviation: abbreviation,
sport: sport,
city: city,
stadiumId: stadiumId,
logoURL: logoURL,
primaryColor: record[CKTeam.primaryColorKey] as? String,
secondaryColor: record[CKTeam.secondaryColorKey] as? String
)
}
}
// MARK: - CKStadium
struct CKStadium {
static let idKey = "stadiumId"
static let nameKey = "name"
static let cityKey = "city"
static let stateKey = "state"
static let locationKey = "location"
static let capacityKey = "capacity"
static let yearOpenedKey = "yearOpened"
static let imageURLKey = "imageURL"
let record: CKRecord
init(record: CKRecord) {
self.record = record
}
init(stadium: Stadium) {
let record = CKRecord(recordType: CKRecordType.stadium)
record[CKStadium.idKey] = stadium.id.uuidString
record[CKStadium.nameKey] = stadium.name
record[CKStadium.cityKey] = stadium.city
record[CKStadium.stateKey] = stadium.state
record[CKStadium.locationKey] = CLLocation(latitude: stadium.latitude, longitude: stadium.longitude)
record[CKStadium.capacityKey] = stadium.capacity
record[CKStadium.yearOpenedKey] = stadium.yearOpened
record[CKStadium.imageURLKey] = stadium.imageURL?.absoluteString
self.record = record
}
var stadium: Stadium? {
guard let idString = record[CKStadium.idKey] as? String,
let id = UUID(uuidString: idString),
let name = record[CKStadium.nameKey] as? String,
let city = record[CKStadium.cityKey] as? String,
let state = record[CKStadium.stateKey] as? String,
let location = record[CKStadium.locationKey] as? CLLocation,
let capacity = record[CKStadium.capacityKey] as? Int
else { return nil }
let imageURL = (record[CKStadium.imageURLKey] as? String).flatMap { URL(string: $0) }
return Stadium(
id: id,
name: name,
city: city,
state: state,
latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude,
capacity: capacity,
yearOpened: record[CKStadium.yearOpenedKey] as? Int,
imageURL: imageURL
)
}
}
// MARK: - CKGame
struct CKGame {
static let idKey = "gameId"
static let homeTeamRefKey = "homeTeamRef"
static let awayTeamRefKey = "awayTeamRef"
static let stadiumRefKey = "stadiumRef"
static let dateTimeKey = "dateTime"
static let sportKey = "sport"
static let seasonKey = "season"
static let isPlayoffKey = "isPlayoff"
static let broadcastInfoKey = "broadcastInfo"
let record: CKRecord
init(record: CKRecord) {
self.record = record
}
init(game: Game, homeTeamRecordID: CKRecord.ID, awayTeamRecordID: CKRecord.ID, stadiumRecordID: CKRecord.ID) {
let record = CKRecord(recordType: CKRecordType.game)
record[CKGame.idKey] = game.id.uuidString
record[CKGame.homeTeamRefKey] = CKRecord.Reference(recordID: homeTeamRecordID, action: .none)
record[CKGame.awayTeamRefKey] = CKRecord.Reference(recordID: awayTeamRecordID, action: .none)
record[CKGame.stadiumRefKey] = CKRecord.Reference(recordID: stadiumRecordID, action: .none)
record[CKGame.dateTimeKey] = game.dateTime
record[CKGame.sportKey] = game.sport.rawValue
record[CKGame.seasonKey] = game.season
record[CKGame.isPlayoffKey] = game.isPlayoff ? 1 : 0
record[CKGame.broadcastInfoKey] = game.broadcastInfo
self.record = record
}
func game(homeTeamId: UUID, awayTeamId: UUID, stadiumId: UUID) -> Game? {
guard let idString = record[CKGame.idKey] as? String,
let id = UUID(uuidString: idString),
let dateTime = record[CKGame.dateTimeKey] as? Date,
let sportRaw = record[CKGame.sportKey] as? String,
let sport = Sport(rawValue: sportRaw),
let season = record[CKGame.seasonKey] as? String
else { return nil }
return Game(
id: id,
homeTeamId: homeTeamId,
awayTeamId: awayTeamId,
stadiumId: stadiumId,
dateTime: dateTime,
sport: sport,
season: season,
isPlayoff: (record[CKGame.isPlayoffKey] as? Int) == 1,
broadcastInfo: record[CKGame.broadcastInfoKey] as? String
)
}
}