Stop Approx. distances beside routed values; add Best Value alert mode
Distances (the "Approx. 1.5 mi" report): - Refuse junk CoreLocation fixes (invalid accuracy, or a coarse >300 m fix within 3 km of the position already held; a coarse fix in another city is real travel and is still accepted). - Resolve ONE display basis per render through FuelStore.distanceDisplayOrigin, used by rows, radius filter, sorting, savings figures and the Live Activity, so a stray fix can no longer re-base the list outside the route cache's 600 m ring while the routed values sit unused in the cache. - Treat an incomplete route set as a state: retry it after 30 s rather than the full 10-minute throttle, and repair a stale incomplete flag once every in-radius station has a route (nothing left to route, flag stuck down). - Widget extension resolves the same origin for fetch, radius, ordering and labels, and keeps its own fix in fuelboard.widgetLocation instead of overwriting the app's GPS fix. Alerts: - Best Value as a distinct alert mode on the existing geofence/notification pipeline, with a persisted threshold; reverts to cheapest when the feature is unconfigured and is not selectable while disabled. Diagnostics (DEBUG, gated): FB_DIST_FOCUS now prints FB-DIST-ORIGIN (candidate, accuracy, anchor, offset, resolved branch, served origin), FB_FAKE_FIX feeds a synthetic fix through the real guard, FB_STALE_INCOMPLETE forces the stale-flag state, and the route-fill status is surfaced in Settings behind debug mode.
This commit is contained in:
@@ -253,6 +253,11 @@ struct FuelSavingsRankedStation: Equatable {
|
||||
let pricePencePerLitre: Double
|
||||
}
|
||||
|
||||
struct BestValueAlertCandidate: Equatable {
|
||||
let station: FuelStation
|
||||
let result: FuelSavingsResult
|
||||
}
|
||||
|
||||
enum FuelSavingsError: Error, Equatable {
|
||||
case invalidMPG
|
||||
case invalidAmount
|
||||
@@ -417,6 +422,64 @@ enum FuelSavingsCalculator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func bestValueAlertCandidate(
|
||||
stations: [FuelStation],
|
||||
fuel: FuelType,
|
||||
settings: FuelSavingsSettings,
|
||||
amount: FuelSavingsFillAmount,
|
||||
minimumNetSavingGBP: Double,
|
||||
journeyMiles: (FuelStation) -> FuelSavingsJourney?
|
||||
) -> BestValueAlertCandidate? {
|
||||
guard settings.isConfigured, minimumNetSavingGBP.isFinite else { return nil }
|
||||
let sellers = stations.filter { $0.prices[fuel] != nil }
|
||||
guard !sellers.isEmpty else { return nil }
|
||||
let baseline = FuelSavingsEstimator.baselineStation(sellers, fuel: fuel, journeyMiles: journeyMiles)
|
||||
var best: BestValueAlertCandidate?
|
||||
var bestDistance = Double.greatestFiniteMagnitude
|
||||
var bestPrice = Double.infinity
|
||||
var baselineJourneyCache: FuelSavingsJourney?
|
||||
var baselineJourneyLoaded = false
|
||||
for station in sellers {
|
||||
let journey = journeyMiles(station)
|
||||
let baselineJourney: FuelSavingsJourney? = {
|
||||
guard let baseline else { return nil }
|
||||
if baseline.id == station.id { return journey }
|
||||
if !baselineJourneyLoaded {
|
||||
baselineJourneyCache = journeyMiles(baseline)
|
||||
baselineJourneyLoaded = true
|
||||
}
|
||||
return baselineJourneyCache
|
||||
}()
|
||||
guard case .available(let result) = FuelSavingsEstimator.availability(
|
||||
station: station,
|
||||
fuel: fuel,
|
||||
baseline: baseline,
|
||||
settings: settings,
|
||||
amount: amount,
|
||||
journey: journey,
|
||||
baselineJourney: baselineJourney
|
||||
), result.status == .worthTheDrive,
|
||||
result.netSavingGBP >= minimumNetSavingGBP else { continue }
|
||||
let distance = journey?.miles ?? .greatestFiniteMagnitude
|
||||
let price = station.prices[fuel] ?? .infinity
|
||||
guard let current = best else {
|
||||
best = BestValueAlertCandidate(station: station, result: result)
|
||||
bestDistance = distance
|
||||
bestPrice = price
|
||||
continue
|
||||
}
|
||||
if result.netSavingGBP > current.result.netSavingGBP ||
|
||||
(result.netSavingGBP == current.result.netSavingGBP && distance < bestDistance) ||
|
||||
(result.netSavingGBP == current.result.netSavingGBP && distance == bestDistance && price < bestPrice) ||
|
||||
(result.netSavingGBP == current.result.netSavingGBP && distance == bestDistance && price == bestPrice && station.id < current.station.id) {
|
||||
best = BestValueAlertCandidate(station: station, result: result)
|
||||
bestDistance = distance
|
||||
bestPrice = price
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns stations into savings estimates.
|
||||
|
||||
Reference in New Issue
Block a user