Best Value: baseline on the closest eligible price and gate on freshness

- Add FuelPriceFreshness: selected-fuel-aware eligibility plus a shared
  comparison confidence, so a saving is presented as verified only when
  both the candidate and the baseline pass the gate for the fuel on screen.
- Rank against the closest station with an eligible selected-fuel price
  instead of the physically closest station, so a single stale neighbour
  can no longer invalidate every comparison in the list. Ordinary Closest
  sorting still includes every priced station.
- Keep ineligible prices visible, fall back to pump-price order, and label
  Best Value unavailable when no comparison qualifies.
- Widget: same baseline and confidence path as the app.
- Tests: stale-closest baseline, alert baseline, eligibility policy,
  boundary and missing-timestamp regressions.
This commit is contained in:
FuelBoard Contributor
2026-10-15 21:37:24 +01:00
parent 39a97f4905
commit 7c2eeb2604
11 changed files with 895 additions and 48 deletions
+14 -2
View File
@@ -203,6 +203,8 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
// 3) Load + order stations per widget mode.
let ordered: [FuelStation]
var bestValueResult: FuelSavingsResult?
var bestValuePriceConfidence: FuelPriceFreshness.ComparisonConfidence = .ineligible
var bestValuePriceWarning: String?
if isFavourites {
let favourites = FuelStore.loadFavourites()
.filter { $0.station.prices[$0.fuel] != nil }
@@ -306,7 +308,8 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
break
}
let amount = settings.preferredAmount
let baseline = FuelSavingsEstimator.baselineStation(filtered, fuel: fuel, journeyMiles: journey)
let baseline = FuelSavingsEstimator.bestValueBaselineStation(filtered, fuel: fuel,
journeyMiles: journey)
let ranked = FuelSavingsEstimator.ranked(
filtered,
fuel: fuel,
@@ -317,6 +320,13 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
)
ordered = ranked
if let first = ranked.first, let baseline {
bestValuePriceConfidence = FuelPriceFreshness.comparisonConfidence(
station: first, fuel: fuel, baseline: baseline)
bestValuePriceWarning = FuelPriceFreshness.comparisonWarning(
station: first, fuel: fuel, baseline: baseline)
}
if let first = ranked.first, let baseline,
bestValuePriceConfidence != .ineligible {
let baselineJourney = journey(baseline)
let result = FuelSavingsEstimator.availability(
station: first,
@@ -348,7 +358,9 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
date: Date(), stations: Array(ordered.prefix(8)),
fuel: fuel, sort: sort, isFavourites: isFavourites,
location: origin, locationSource: source,
unit: unit, bestValueResult: bestValueResult
unit: unit, bestValueResult: bestValueResult,
bestValuePriceConfidence: bestValuePriceConfidence,
bestValuePriceWarning: bestValuePriceWarning
)
), originSource)
}