Worth the Drive: calculation audit + listing/detail polish
Calculation audit (brief config: 30 MPG imperial, £60 fill-up, baseline
Rontec Shelf 171.9p at 0.4 mi):
- One mile<->kilometre constant pair (kilometresPerMile 1.609344,
metresPerMile 1609.344) replaces the mixed 1609.34 / 0.621371 / 1.60934
used in the savings path. Effect ~2 ppm (invisible), but the conversions
are now auditable in one place.
- New FuelSavingsCalculationAuditTests (20 deterministic tests, no live
MKDirections) pins the brief's three stations - Tesco 170.9p/2.0 mi,
MFG Hare & Hounds 172.9p/2.8 mi, PGG Napoleon 171.9p/2.8 mi - plus
imperial-vs-US gallon, MPG and budget scaling, the break-even tolerance
boundary, the nearer-candidate clamp, route-unavailable, mixed-basis
estimates and display-rounding boundaries.
- Formula unchanged, and verified correct: fixed quantity (budget / baseline
price), pump saving, round-trip extra distance clamped at zero, travel fuel
priced at the candidate station, +/-5p break even, baseline labelled
"Closest station", sorting on the unrounded net saving. The brief's "£0.18"
for Tesco is the rounded 1 dp extra-distance caption (2.02-2.05 mi reads as
"2.0 mi"), not a rounding bug in the money.
UI (uncommitted since d7a0ecb):
- Done bar drawn in the bottom safe area from keyboard notifications:
.toolbar(placement: .keyboard) is device-dependent (present on the 15 Pro,
absent on the SE on the same iOS build).
- Favourite star in the More-info heading, wired to the same fuel-scoped
toggle as the listing rows at all three call sites.
- CHEAPEST/TOP badge moved above the price so it reads as part of it.
- Listing rows carry the real session fill-up amount instead of a hard zero
(the compact banner does not print it today; a future row layout would).
153 tests, 0 failures. Simulator and device builds succeed.
This commit is contained in:
@@ -100,6 +100,25 @@ enum FuelSavingsFormat {
|
||||
static func money(_ value: Double) -> String {
|
||||
String(format: "£%.2f", value)
|
||||
}
|
||||
|
||||
/// A distance stored in MILES, formatted in the display unit. The figures
|
||||
/// behind an estimate are stored in miles whatever the user's unit, so this
|
||||
/// is the one place that conversion lives.
|
||||
static func distance(_ miles: Double, unit: DistanceUnit) -> String {
|
||||
// Convert miles→km FIRST, then let the unit format it. `unit.toKM` would
|
||||
// convert by the CURRENT unit instead, leaving a km user's figure
|
||||
// unlabelled and 1.6× too large.
|
||||
unit.format(miles * FuelSavingsCalculator.kilometresPerMile)
|
||||
}
|
||||
|
||||
/// Row label for the extra driving a saving has to beat. The stored figure is
|
||||
/// always MILES while the row's distance line is one-way, so the wording says
|
||||
/// "more driving": the extra is an out-and-back figure and can legitimately
|
||||
/// exceed the one-way distance, which read as a contradiction before.
|
||||
static func extraTravelLabel(extraDistanceMiles: Double, unit: DistanceUnit) -> String? {
|
||||
guard extraDistanceMiles > 0.05 else { return nil }
|
||||
return "\(distance(extraDistanceMiles, unit: unit)) more driving than closest"
|
||||
}
|
||||
}
|
||||
|
||||
extension FuelSavingsResult {
|
||||
@@ -140,6 +159,15 @@ extension FuelSavingsResult {
|
||||
return "\(FuelSavingsFormat.money(abs(netSavingGBP))) worse off after travel\(estimate)"
|
||||
}
|
||||
}
|
||||
|
||||
/// Row capsule copy: the headline, plus what the baseline row IS. Without the
|
||||
/// suffix the baseline capsule just repeated the status word above it
|
||||
/// ("Closest station" twice) and said nothing about the saving being measured
|
||||
/// against it.
|
||||
func capsuleHeadline() -> String {
|
||||
let headline = headline(compact: true)
|
||||
return status == .baseline ? "\(headline) · your baseline" : headline
|
||||
}
|
||||
}
|
||||
|
||||
/// One station's journey distance, plus how it was obtained.
|
||||
@@ -173,7 +201,18 @@ enum FuelSavingsError: Error, Equatable {
|
||||
}
|
||||
|
||||
enum FuelSavingsCalculator {
|
||||
/// 1 UK (imperial) gallon. Must never be the US gallon (3.785411784 L):
|
||||
/// UK pumps are priced per litre and drivers quote UK MPG, so a US gallon
|
||||
/// would understate every travel cost by 17%.
|
||||
static let imperialGallonLitres = 4.54609
|
||||
/// Exact mile↔kilometre factors (1 mile = 1.609344 km). Every figure the
|
||||
/// savings maths keeps is in MILES, and every displayed or fallback
|
||||
/// distance converts through these, so one quantity is never converted with
|
||||
/// two different constants. The audit found 1.60934, 1609.34 and 0.621371
|
||||
/// mixed in the same path — a ~2 parts-per-million difference, immaterial to
|
||||
/// the money, but three constants where there should be one.
|
||||
static let kilometresPerMile = 1.609344
|
||||
static let metresPerMile = 1609.344
|
||||
static let breakEvenToleranceGBP = 0.05
|
||||
static let minimumMPG: Double = 5
|
||||
static let maximumMPG: Double = 200
|
||||
|
||||
Reference in New Issue
Block a user