Make station price age informational only

The relay's price_updated is the maximum price_last_updated across the grades it maps, so it cannot vouch for the selected grade: stop using it to score, gate or warn about Best Value.

- FuelPriceFreshness is now station-level and display-only: one age per station, fresh/outdated/unknown/invalid, "May be outdated" past 30 days, with future-tolerance and non-finite guards.
- Best Value restores the closest-station-selling-the-selected-fuel baseline and its savings ranking; eligibility gating, eligible-first sorting and the unavailable banner are gone.
- Price age appears in the More info sheet only - no listing-row warnings, no extra banners.
- Copy reverted from "closest eligible station" to "closest station"; stale price-confidence strings removed, "May be outdated" added.
- Dormant per-grade timestamp plumbing removed, and regression tests added for every fuel type, missing/invalid/future stamps and the bundled offline dump.
This commit is contained in:
FuelBoard Contributor
2026-10-16 09:48:01 +01:00
parent 5900a81473
commit e960ef85c6
19 changed files with 530 additions and 732 deletions
+11 -31
View File
@@ -401,9 +401,9 @@ struct ContentView: View {
/// (computing a journey for each) and `savingsLookup` immediately scanned them
/// all AGAIN to fill the per-station map. Both now share one result.
///
/// The baseline is chosen with the same journey and selected-fuel freshness
/// guards as `FuelSavingsEstimator.bestValueBaselineStation` when configured;
/// with the feature off, the ordinary Closest station still names that row.
/// The baseline is chosen with the same journey guards as
/// `FuelSavingsEstimator.baselineStation` when configured; with the feature
/// off, the ordinary Closest station still names that row.
private func savingsScaffold() -> (baseline: FuelStation?, journeys: [String: FuelSavingsJourney]) {
guard let location else { return (nil, [:]) }
guard fuelSavingsSettings.isConfigured else {
@@ -418,7 +418,7 @@ struct ContentView: View {
journeys[station.id] = journey
}
}
let baseline = FuelSavingsEstimator.bestValueBaselineStation(stations, fuel: selectedFuel) {
let baseline = FuelSavingsEstimator.baselineStation(stations, fuel: selectedFuel) {
journeys[$0.id]
}
return (baseline, journeys)
@@ -454,15 +454,15 @@ struct ContentView: View {
}
}
/// The Closest station with an eligible selected-fuel price — the Best Value
/// comparison baseline. Ordinary Closest sorting still includes stale stations.
/// It uses the same journey values as the savings calculation, so a cached
/// road route can change the baseline and keep the "extra return travel"
/// labels synchronized with the cost calculation. Without a cached route,
/// the journey falls back to straight-line and the result is an estimate.
/// The nearest station that sells the selected fuel — the Best Value
/// comparison baseline. It is chosen with the SAME journey values used by
/// the savings calculation, so a cached road route can change the baseline
/// and keep the "extra return travel" labels synchronized with the
/// cost calculation. When no road route is cached, the journey falls back to
/// straight-line out-and-back, and the result is marked as an estimate.
private var nearestSavingsBaseline: FuelStation? {
guard let location else { return nil }
return FuelSavingsEstimator.bestValueBaselineStation(
return FuelSavingsEstimator.baselineStation(
stations,
fuel: selectedFuel,
journeyMiles: { [self] station in savingsJourneyMiles(for: station, location: location) }
@@ -802,10 +802,6 @@ struct ContentView: View {
fuelSavings: savingsLookup(baseline: scaffold.baseline,
journeys: scaffold.journeys)(station),
fuelSavingsAmount: sessionFuelSavingsAmount,
savingsPriceConfidence: FuelPriceFreshness.ComparisonConfidence.assess(
station: station, fuel: selectedFuel, baseline: scaffold.baseline),
savingsPriceWarning: FuelPriceFreshness.comparisonWarning(
station: station, fuel: selectedFuel, baseline: scaffold.baseline),
savingsBaselineStationName: scaffold.baseline?.name,
journeyDiagnostics: journeyDiagnostics(for: station, baseline: scaffold.baseline),
isFavourite: favouriteIDs.contains(station.id),
@@ -1532,12 +1528,6 @@ struct ContentView: View {
fuelSavingsAmount: $sessionFuelSavingsAmount,
savingsForStation: savingsLookup(baseline: baselineStation, journeys: scaffold.journeys),
journeyDiagnosticsForStation: { station in journeyDiagnostics(for: station, baseline: baselineStation) },
savingsPriceConfidenceForStation: { station in
FuelPriceFreshness.ComparisonConfidence.assess(station: station, fuel: selectedFuel, baseline: baselineStation)
},
savingsPriceWarningForStation: { station in
FuelPriceFreshness.comparisonWarning(station: station, fuel: selectedFuel, baseline: baselineStation)
},
onToggleFavourite: toggleFavourite,
onRefresh: { await refresh(force: true) }
)
@@ -1559,12 +1549,6 @@ struct ContentView: View {
fuelSavingsAmount: $sessionFuelSavingsAmount,
savingsForStation: savingsLookup(baseline: baselineStation, journeys: scaffold.journeys),
journeyDiagnosticsForStation: { station in journeyDiagnostics(for: station, baseline: baselineStation) },
savingsPriceConfidenceForStation: { station in
FuelPriceFreshness.ComparisonConfidence.assess(station: station, fuel: selectedFuel, baseline: baselineStation)
},
savingsPriceWarningForStation: { station in
FuelPriceFreshness.comparisonWarning(station: station, fuel: selectedFuel, baseline: baselineStation)
},
onToggleFavourite: toggleFavourite,
onReorder: reorderFavourites,
onHistoryUnavailable: { if dataStatus == .live { dataStatus = .connectionProblem } },
@@ -1802,8 +1786,6 @@ struct StationRow: View {
/// compact banner does not print it today, but a row that carried a hard
/// zero would print "for £0" the moment it did — pass the real figure.
var fuelSavingsAmount: FuelSavingsFillAmount = FuelSavingsSettings.defaultPreferredAmount
var savingsPriceConfidence: FuelPriceFreshness.ComparisonConfidence = .ineligible
var savingsPriceWarning: String? = nil
var onToggleFavourite: () -> Void = {}
var onShowDetails: (FuelStation) -> Void = { _ in }
@@ -1964,8 +1946,6 @@ struct StationRow: View {
FuelSavingsSummaryView(
availability: fuelSavings,
amount: fuelSavingsAmount,
priceConfidence: savingsPriceConfidence,
priceWarning: savingsPriceWarning,
compact: true,
distanceUnit: distanceUnit
)