fix: resolve specificStadium achievement ID mismatch

The Green Monster (Fenway) and Ivy League (Wrigley) achievements
weren't working because:
1. Symbolic IDs use lowercase sport (stadium_mlb_bos)
2. Sport enum uses uppercase raw values (MLB)
3. Visits store stadium UUIDs, not symbolic IDs

Added resolveSymbolicStadiumId() helper that:
- Uppercases the sport string before Sport(rawValue:)
- Looks up team by abbreviation and sport
- Returns the team's stadiumId as UUID string

Also fixed:
- getStadiumIdsForLeague returns UUID strings (not symbolic IDs)
- AchievementProgress.isEarned computed from progress OR stored record
- getStadiumIdsForDivision queries CanonicalTeam properly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-11 22:22:29 -06:00
parent dcd5edb229
commit 5c13650742
20 changed files with 1619 additions and 141 deletions

View File

@@ -33,6 +33,8 @@ struct StadiumVisitSheet: View {
// UI state
@State private var showStadiumPicker = false
@State private var isSaving = false
@State private var isLookingUpGame = false
@State private var scoreFromScraper = false // Track if score was auto-filled
@State private var errorMessage: String?
@State private var showAwayTeamSuggestions = false
@State private var showHomeTeamSuggestions = false
@@ -220,10 +222,36 @@ struct StadiumVisitSheet: View {
.frame(width: 50)
.multilineTextAlignment(.center)
}
// Look Up Game Button
if selectedStadium != nil {
Button {
Task {
await lookUpGame()
}
} label: {
HStack {
if isLookingUpGame {
ProgressView()
.scaleEffect(0.8)
} else {
Image(systemName: "magnifyingglass")
}
Text("Look Up Game")
}
.frame(maxWidth: .infinity)
.foregroundStyle(Theme.warmOrange)
}
.disabled(isLookingUpGame)
}
} header: {
Text("Game Info")
} footer: {
Text("Leave blank if you don't remember the score")
if selectedStadium != nil {
Text("Tap 'Look Up Game' to auto-fill teams and score from historical data")
} else {
Text("Select a stadium to enable game lookup")
}
}
.listRowBackground(Theme.cardBackground(colorScheme))
}
@@ -320,6 +348,36 @@ struct StadiumVisitSheet: View {
// MARK: - Actions
private func lookUpGame() async {
guard let stadium = selectedStadium else { return }
isLookingUpGame = true
errorMessage = nil
// Use the historical game scraper
if let scrapedGame = await HistoricalGameScraper.shared.scrapeGame(
stadium: stadium,
date: visitDate
) {
// Fill in the form with scraped data
awayTeamName = scrapedGame.awayTeam
homeTeamName = scrapedGame.homeTeam
if let away = scrapedGame.awayScore {
awayScore = String(away)
scoreFromScraper = true
}
if let home = scrapedGame.homeScore {
homeScore = String(home)
scoreFromScraper = true
}
} else {
errorMessage = "No game found for \(stadium.name) on this date"
}
isLookingUpGame = false
}
private func saveVisit() {
guard let stadium = selectedStadium else {
errorMessage = "Please select a stadium"
@@ -340,8 +398,8 @@ struct StadiumVisitSheet: View {
homeTeamName: homeTeamName.isEmpty ? nil : homeTeamName,
awayTeamName: awayTeamName.isEmpty ? nil : awayTeamName,
finalScore: finalScoreString,
scoreSource: finalScoreString != nil ? .user : nil,
dataSource: .fullyManual,
scoreSource: finalScoreString != nil ? (scoreFromScraper ? .scraped : .user) : nil,
dataSource: scoreFromScraper ? .automatic : .fullyManual,
seatLocation: seatLocation.isEmpty ? nil : seatLocation,
notes: notes.isEmpty ? nil : notes,
source: .manual