- 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>
128 lines
4.3 KiB
Swift
128 lines
4.3 KiB
Swift
//
|
|
// DateRangeValidator.swift
|
|
// SportsTime
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Validates that all games fall within the specified date range.
|
|
/// Priority 1 in the rule hierarchy - checked first before any other constraints.
|
|
struct DateRangeValidator {
|
|
|
|
// MARK: - Validation Result
|
|
|
|
struct ValidationResult {
|
|
let isValid: Bool
|
|
let violations: [ConstraintViolation]
|
|
let gamesOutsideRange: [UUID]
|
|
|
|
static let valid = ValidationResult(isValid: true, violations: [], gamesOutsideRange: [])
|
|
|
|
static func invalid(games: [UUID]) -> ValidationResult {
|
|
let violations = games.map { gameId in
|
|
ConstraintViolation(
|
|
type: .dateRange,
|
|
description: "Game \(gameId.uuidString.prefix(8)) falls outside the specified date range",
|
|
severity: .error
|
|
)
|
|
}
|
|
return ValidationResult(isValid: false, violations: violations, gamesOutsideRange: games)
|
|
}
|
|
}
|
|
|
|
// MARK: - Validation
|
|
|
|
/// Validates that ALL selected games (must-see games) fall within the date range.
|
|
/// This is a HARD constraint - if any selected game is outside the range, planning fails.
|
|
///
|
|
/// - Parameters:
|
|
/// - mustSeeGameIds: Set of game IDs that MUST be included in the trip
|
|
/// - allGames: All available games to check against
|
|
/// - startDate: Start of the valid date range (inclusive)
|
|
/// - endDate: End of the valid date range (inclusive)
|
|
/// - Returns: ValidationResult indicating success or failure with specific violations
|
|
func validate(
|
|
mustSeeGameIds: Set<UUID>,
|
|
allGames: [Game],
|
|
startDate: Date,
|
|
endDate: Date
|
|
) -> ValidationResult {
|
|
// If no must-see games, validation passes
|
|
guard !mustSeeGameIds.isEmpty else {
|
|
return .valid
|
|
}
|
|
|
|
// Find all must-see games that fall outside the range
|
|
let gamesOutsideRange = allGames
|
|
.filter { mustSeeGameIds.contains($0.id) }
|
|
.filter { game in
|
|
game.dateTime < startDate || game.dateTime > endDate
|
|
}
|
|
.map { $0.id }
|
|
|
|
if gamesOutsideRange.isEmpty {
|
|
return .valid
|
|
} else {
|
|
return .invalid(games: gamesOutsideRange)
|
|
}
|
|
}
|
|
|
|
/// Validates games for Scenario B (Selected Games mode).
|
|
/// ALL selected games MUST be within the date range - no exceptions.
|
|
///
|
|
/// - Parameters:
|
|
/// - request: The planning request containing preferences and games
|
|
/// - Returns: ValidationResult with explicit failure if any selected game is out of range
|
|
func validateForScenarioB(_ request: PlanningRequest) -> ValidationResult {
|
|
return validate(
|
|
mustSeeGameIds: request.preferences.mustSeeGameIds,
|
|
allGames: request.availableGames,
|
|
startDate: request.preferences.startDate,
|
|
endDate: request.preferences.endDate
|
|
)
|
|
}
|
|
|
|
/// Checks if there are any games available within the date range.
|
|
/// Used to determine if planning can proceed at all.
|
|
///
|
|
/// - Parameters:
|
|
/// - games: All available games
|
|
/// - startDate: Start of the valid date range
|
|
/// - endDate: End of the valid date range
|
|
/// - sports: Sports to filter by
|
|
/// - Returns: True if at least one game exists in the range
|
|
func hasGamesInRange(
|
|
games: [Game],
|
|
startDate: Date,
|
|
endDate: Date,
|
|
sports: Set<Sport>
|
|
) -> Bool {
|
|
games.contains { game in
|
|
game.dateTime >= startDate &&
|
|
game.dateTime <= endDate &&
|
|
sports.contains(game.sport)
|
|
}
|
|
}
|
|
|
|
/// Returns all games that fall within the specified date range.
|
|
///
|
|
/// - Parameters:
|
|
/// - games: All available games
|
|
/// - startDate: Start of the valid date range
|
|
/// - endDate: End of the valid date range
|
|
/// - sports: Sports to filter by
|
|
/// - Returns: Array of games within the range
|
|
func gamesInRange(
|
|
games: [Game],
|
|
startDate: Date,
|
|
endDate: Date,
|
|
sports: Set<Sport>
|
|
) -> [Game] {
|
|
games.filter { game in
|
|
game.dateTime >= startDate &&
|
|
game.dateTime <= endDate &&
|
|
sports.contains(game.sport)
|
|
}
|
|
}
|
|
}
|