Added handling for stale trains

- Renamed TrainLateness to LateTrain
This commit is contained in:
Tony Bark 2026-08-24 22:53:15 -04:00
parent e0077ec13b
commit f8eef909d2
5 changed files with 72 additions and 17 deletions

View file

@ -27,6 +27,10 @@ struct AmtrakClient {
func fetchAllTrains() async throws -> TrainResponse { func fetchAllTrains() async throws -> TrainResponse {
try await get(trainsBaseURL) try await get(trainsBaseURL)
} }
func fetchStaleStatus() async throws -> StaleData {
try await get(staleBaseURL)
}
func fetchAllTrainStatus(num trainNum: String) async throws -> [Train] { func fetchAllTrainStatus(num trainNum: String) async throws -> [Train] {
let reponse = try await fetchTrain(number: trainNum) let reponse = try await fetchTrain(number: trainNum)

View file

@ -49,7 +49,7 @@ struct AmtrakDateFormatting {
} }
func isOnTime(actual: String?, scheduled: String?) -> TrainLateness { func isOnTime(actual: String?, scheduled: String?) -> LateTrain {
guard guard
let actualDate = parsedDate(from: actual), let actualDate = parsedDate(from: actual),

View file

@ -14,6 +14,7 @@ struct ContentView: View {
let amtrakDate = AmtrakDateFormatting() let amtrakDate = AmtrakDateFormatting()
let unknown = "Unknown" let unknown = "Unknown"
let staleDataThreshold: TimeInterval = 60 * 60 * 24 // 24 hours
var body: some View { var body: some View {
VStack { VStack {
@ -146,15 +147,15 @@ struct ContentView: View {
where: { $0.code == code where: { $0.code == code
}, },
)?.platform )?.platform
await newRows let row = await trainStatus(
.append(
trainStatus(
for: train, for: train,
atStationCode: code, code: code,
timeZone: lookup.timeZone, zone: lookup.timeZone,
zoneCache: &zoneCache cache: &zoneCache
) )
) if let row {
newRows.append(row)
}
} }
} }
@ -171,11 +172,16 @@ struct ContentView: View {
func trainStatus( func trainStatus(
for train: Train, for train: Train,
atStationCode code: String, code stationCode: String,
timeZone: TimeZone?, zone timeZone: TimeZone?,
zoneCache: inout [String: TimeZone?] cache zoneCache: inout [String: TimeZone?]
) async -> TrainStatusRow { ) async -> TrainStatusRow? {
let leg = train.stations?.first { $0.code == code } let leg = train.stations?.first { $0.code == stationCode }
guard !isStale(leg, timeZone: timeZone) else {
return nil
}
let stationName = leg?.name ?? unknown let stationName = leg?.name ?? unknown
let track = leg?.platform ?? "" let track = leg?.platform ?? ""
@ -216,6 +222,45 @@ struct ContentView: View {
) )
} }
func isStale(_ leg: Station?, timeZone: TimeZone?) -> Bool {
guard leg?.status == .departed else { return false }
guard
let deparatureDate = amtrakDate.parsedDate(
from: leg?.dep
) ?? amtrakDate.parsedDate(from: leg?.schDep)
else {
return false
}
var calendar = Calendar.current
calendar.timeZone = timeZone ?? .current
return calendar
.compare(
deparatureDate,
to: Date(),
toGranularity: .day
) == .orderedAscending
}
func isDataStale(
_ trainId: String,
staleness: [String: TimeInterval]
) -> Bool {
guard let timeSince = staleness[trainId] else { return false }
return timeSince > staleDataThreshold
}
func staleTrainLookup() async -> [String: TimeInterval] {
guard let staleData = try? await client.fetchStaleStatus() else {
return [:]
}
return Dictionary(
staleData.lastUpdatedArr.map { ( $0.trainID, $0.timeSince) },
uniquingKeysWith: { first, _ in first }
)
}
func fetchTimeZone(forStationCode code: String, cache: inout [String: TimeZone?]) async -> TimeZone? { func fetchTimeZone(forStationCode code: String, cache: inout [String: TimeZone?]) async -> TimeZone? {
if let cached = cache[code] { if let cached = cache[code] {
return cached return cached

View file

@ -1,7 +1,7 @@
import Foundation import Foundation
import SwiftCrossUI import SwiftCrossUI
enum TrainLateness { enum LateTrain {
case onTime case onTime
case early(minutes: Int) case early(minutes: Int)
case late(minutes: Int) case late(minutes: Int)

View file

@ -81,9 +81,15 @@ struct StationMeta: Codable {
} }
struct StaleData: Codable { struct StaleData: Codable {
var avgLastUpdate: Double
var activeTrains: Int var activeTrains: Int
var stale: Bool var avgLastUpdate: Double
var lastUpdatedArr: [TrainFreshness]
var medianLastUpdate: Double
}
struct TrainFreshness: Codable, Sendable {
var trainID: String
var timeSince: Double
} }
typealias TrainResponse = [String: [Train]] typealias TrainResponse = [String: [Train]]
@ -99,6 +105,6 @@ struct TrainStatusRow: Identifiable {
var train: String var train: String
var to: String var to: String
var from: String var from: String
var status: TrainLateness var status: LateTrain
var track: String var track: String
} }