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:
127
SportsTime/Planning/Validators/DateRangeValidator.swift
Normal file
127
SportsTime/Planning/Validators/DateRangeValidator.swift
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user