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
+2 -20
View File
@@ -35,11 +35,6 @@ struct FuelPriceWidgetEntryData {
let locationSource: String // "live" | "cached" | "none"
let unit: DistanceUnit // user's display unit for distances
let bestValueResult: FuelSavingsResult? // only populated for the small Best Value face
/// Both selected-fuel prices are assessed by the shared freshness policy.
var bestValuePriceConfidence: FuelPriceFreshness.ComparisonConfidence = .ineligible
/// Candidate-aware warning copy (names the fuel, and the baseline when the
/// baseline is the weak link). Falls back to the confidence's own text.
var bestValuePriceWarning: String? = nil
}
/// The widget face, parameterised by family so the app can preview it.
@@ -97,16 +92,10 @@ struct FuelPriceWidgetContent: View {
Text(result.headline(compact: true))
.font(.title3.weight(.bold))
.lineLimit(1)
Text(result.status == .baseline ? "your baseline" : "vs closest eligible station")
Text(result.status == .baseline ? "your baseline" : "vs closest station")
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
if let warning = entry.bestValuePriceWarning ?? entry.bestValuePriceConfidence.warningText {
Text(warning)
.font(.caption2)
.foregroundStyle(.orange)
.lineLimit(2)
}
} else if entry.sort == .worthTheDrive {
if let price = station.prices[entry.fuel] {
FuelStore.priceTextAttributed(price, size: 26, weight: .bold, color: .green)
@@ -116,11 +105,6 @@ struct FuelPriceWidgetContent: View {
.font(.caption2)
.foregroundStyle(.orange)
.lineLimit(2)
} else {
Text("Best Value unavailable · price confidence insufficient")
.font(.caption2)
.foregroundStyle(.orange)
.lineLimit(2)
}
} else if let price = station.prices[entry.fuel] {
FuelStore.priceTextAttributed(price, size: 26, weight: .bold, color: .green)
@@ -163,9 +147,7 @@ struct FuelPriceWidgetContent: View {
.font(.caption2)
.foregroundStyle(.secondary)
Spacer()
Text(entry.sort == .worthTheDrive && entry.bestValuePriceConfidence == .ineligible
? "price order · Best Value unavailable"
: entry.isFavourites ? "your order"
Text(entry.isFavourites ? "your order"
: (entry.locationSource == "none" ? "by price" : "near you"))
.font(.caption2)
.foregroundStyle(.secondary)