Clarify Best Value route confidence
This commit is contained in:
@@ -688,6 +688,41 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
XCTAssertEqual(km, moved.distanceKM(to: 51.6, lng2: -0.1), accuracy: 0.0001,
|
||||
"road value routed to the old pin leaked onto a different coordinate")
|
||||
}
|
||||
|
||||
func testPartialRoadCacheCanMixRoadAndStraightLineDistances() {
|
||||
let routed = station("routed", 51.5074, -0.1278)
|
||||
let estimated = station("estimated", 51.5100, -0.1300)
|
||||
FuelStore.saveRoadDistances(sourceLat: 51.6, sourceLng: -0.1,
|
||||
entries: ["routed": .init(meters: 3200, lat: 51.5074, lng: -0.1278, returnMeters: 3600)])
|
||||
|
||||
XCTAssertEqual(FuelStore.roadDistanceMeters(for: routed, userLat: 51.6, userLng: -0.1), 3200)
|
||||
XCTAssertNil(FuelStore.roadDistanceMeters(for: estimated, userLat: 51.6, userLng: -0.1))
|
||||
XCTAssertEqual(FuelStore.displayDistanceKM(station: routed, userLat: 51.6, userLng: -0.1), 3.2, accuracy: 0.0001)
|
||||
XCTAssertEqual(FuelStore.displayDistanceKM(station: estimated, userLat: 51.6, userLng: -0.1),
|
||||
estimated.distanceKM(to: 51.6, lng2: -0.1),
|
||||
accuracy: 0.0001)
|
||||
XCTAssertEqual(FuelStore.roadJourneyMeters(for: routed, userLat: 51.6, userLng: -0.1), 6800,
|
||||
"outward and return road routes are combined once, not doubled")
|
||||
XCTAssertTrue(FuelStore.hasMeasuredReturnRoadDistance(for: routed, userLat: 51.6, userLng: -0.1))
|
||||
XCTAssertFalse(FuelStore.hasMeasuredReturnRoadDistance(for: estimated, userLat: 51.6, userLng: -0.1))
|
||||
}
|
||||
|
||||
func testLegacyOneWayRoadCacheIsUsableButNotConfirmed() {
|
||||
let legacy = station("legacy", 51.5074, -0.1278)
|
||||
FuelStore.saveRoadDistances(sourceLat: 51.6, sourceLng: -0.1,
|
||||
entries: ["legacy": .init(meters: 3200, lat: 51.5074, lng: -0.1278)])
|
||||
|
||||
XCTAssertEqual(FuelStore.roadJourneyMeters(for: legacy, userLat: 51.6, userLng: -0.1), 6400)
|
||||
XCTAssertFalse(FuelStore.hasMeasuredReturnRoadDistance(for: legacy, userLat: 51.6, userLng: -0.1),
|
||||
"legacy one-way cache entries infer the return leg and must remain estimated")
|
||||
let diagnostics = FuelStore.roadDistanceDiagnostics(for: legacy, userLat: 51.6, userLng: -0.1)
|
||||
XCTAssertEqual(diagnostics.displaySource, .roadRoute)
|
||||
XCTAssertEqual(diagnostics.outwardMeters, 3200)
|
||||
XCTAssertNil(diagnostics.returnMeters)
|
||||
XCTAssertEqual(diagnostics.totalJourneyMeters, 6400)
|
||||
XCTAssertFalse(diagnostics.hasMeasuredReturn)
|
||||
XCTAssertTrue(diagnostics.hasValidCacheEntry)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Install identity
|
||||
|
||||
@@ -392,8 +392,8 @@ final class FuelSavingsCalculationAuditTests: XCTestCase {
|
||||
)
|
||||
XCTAssertEqual(mixed?.result?.distanceBasis, .straightLine,
|
||||
"one unmeasured leg makes the whole estimate an estimate")
|
||||
let headline = mixed?.result?.headline(compact: true) ?? ""
|
||||
XCTAssertTrue(headline.hasSuffix(" (est.)"), "an estimate must be marked as one, got \(headline)")
|
||||
XCTAssertEqual(mixed?.result?.journeyConfidence, .estimated)
|
||||
XCTAssertEqual(mixed?.result?.headline(compact: true), "~£0.17 worse off after travel")
|
||||
}
|
||||
|
||||
func testBaselineStationIsLabelledClosestStationNotBreakEven() {
|
||||
|
||||
@@ -159,6 +159,60 @@ final class FuelSavingsEstimateTests: XCTestCase {
|
||||
XCTAssertEqual(result?.distanceBasis, .straightLine)
|
||||
}
|
||||
|
||||
func testBaselineStationUsesTheSameJourneyValuesAsSavings() {
|
||||
let geographicallyNearest = station("geographically-nearest", price: 145.9)
|
||||
let roadNearest = station("road-nearest", price: 146.9)
|
||||
let noPrice = station("no-price", price: nil)
|
||||
|
||||
let baseline = FuelSavingsEstimator.baselineStation(
|
||||
[geographicallyNearest, roadNearest, noPrice],
|
||||
fuel: .e10,
|
||||
journeyMiles: { station in
|
||||
switch station.id {
|
||||
case "geographically-nearest": return FuelSavingsJourney(miles: 8.0, basis: .roadRoute)
|
||||
case "road-nearest": return FuelSavingsJourney(miles: 4.0, basis: .roadRoute)
|
||||
case "no-price": return FuelSavingsJourney(miles: 1.0, basis: .roadRoute)
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(baseline?.id, "road-nearest",
|
||||
"the Best Value baseline must be the shortest usable journey, not a separate straight-line/display-distance choice")
|
||||
}
|
||||
|
||||
func testBaselineStationFallsBackThenChangesWhenRoadRoutesArrive() {
|
||||
let stationA = station("a", price: 145.9)
|
||||
let stationB = station("b", price: 146.9)
|
||||
|
||||
let noRoadRoutes = FuelSavingsEstimator.baselineStation(
|
||||
[stationA, stationB],
|
||||
fuel: .e10,
|
||||
journeyMiles: { station in
|
||||
switch station.id {
|
||||
case "a": return FuelSavingsJourney(miles: 2.0, basis: .straightLine)
|
||||
case "b": return FuelSavingsJourney(miles: 3.0, basis: .straightLine)
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
)
|
||||
XCTAssertEqual(noRoadRoutes?.id, "a")
|
||||
|
||||
let partialRoadRoutes = FuelSavingsEstimator.baselineStation(
|
||||
[stationA, stationB],
|
||||
fuel: .e10,
|
||||
journeyMiles: { station in
|
||||
switch station.id {
|
||||
case "a": return FuelSavingsJourney(miles: 5.0, basis: .roadRoute)
|
||||
case "b": return FuelSavingsJourney(miles: 3.0, basis: .straightLine)
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
)
|
||||
XCTAssertEqual(partialRoadRoutes?.id, "b",
|
||||
"baseline must be recalculated from the latest journey values when routes arrive")
|
||||
}
|
||||
|
||||
func testStationWithoutAPriceIsUnavailable() {
|
||||
let baseline = station("baseline", price: 145.9)
|
||||
let noPrice = station("no-price", price: nil)
|
||||
@@ -237,7 +291,8 @@ final class FuelSavingsEstimateTests: XCTestCase {
|
||||
XCTAssertEqual(result.extraDrivingCostGBP, 1.730, accuracy: 0.005)
|
||||
XCTAssertEqual(result.netSavingGBP, -0.334, accuracy: 0.005)
|
||||
XCTAssertEqual(result.status, .notWorthTheDrive)
|
||||
XCTAssertEqual(result.headline(compact: true), "£0.33 worse off after travel (est.)")
|
||||
XCTAssertEqual(result.journeyConfidence, .estimated)
|
||||
XCTAssertEqual(result.headline(compact: true), "~£0.33 worse off after travel")
|
||||
}
|
||||
|
||||
func testMeasuredRoadRoutesDropTheEstimateMarker() {
|
||||
@@ -255,6 +310,7 @@ final class FuelSavingsEstimateTests: XCTestCase {
|
||||
)
|
||||
|
||||
XCTAssertEqual(availability?.result?.distanceBasis, .roadRoute)
|
||||
XCTAssertEqual(availability?.result?.journeyConfidence, .confirmed)
|
||||
XCTAssertEqual(availability?.result?.headline(compact: true), "£0.33 worse off after travel")
|
||||
XCTAssertEqual(availability?.result?.headline(), "£0.33 worse off after travel")
|
||||
}
|
||||
@@ -301,7 +357,7 @@ final class FuelSavingsEstimateTests: XCTestCase {
|
||||
// £60 buys 37.5 L at 160p; 10p/litre cheaper saves £3.75; 0.4 extra
|
||||
// return miles cost ~£0.09. Net ≈ £3.66.
|
||||
XCTAssertEqual(result.netSavingGBP, 3.659, accuracy: 0.005)
|
||||
XCTAssertEqual(result.headline(compact: true), "Save £3.66 after travel (est.)")
|
||||
XCTAssertEqual(result.headline(compact: true), "~Save £3.66 after travel")
|
||||
}
|
||||
|
||||
func testNeutralResultHeadlineIsBreakEven() {
|
||||
@@ -420,7 +476,7 @@ final class FuelSavingsEstimateTests: XCTestCase {
|
||||
status: .worthTheDrive,
|
||||
distanceBasis: .straightLine
|
||||
)
|
||||
XCTAssertEqual(saving.capsuleHeadline(), "Save £1.00 after travel (est.)")
|
||||
XCTAssertEqual(saving.capsuleHeadline(), "~Save £1.00 after travel")
|
||||
|
||||
let worse = FuelSavingsResult(
|
||||
fillLitres: 30,
|
||||
|
||||
Reference in New Issue
Block a user