diff --git a/src/BikeTracking.Api.Tests/Application/Dashboard/GetAdvancedDashboardServiceTests.cs b/src/BikeTracking.Api.Tests/Application/Dashboard/GetAdvancedDashboardServiceTests.cs
index f3faf21..f3e8632 100644
--- a/src/BikeTracking.Api.Tests/Application/Dashboard/GetAdvancedDashboardServiceTests.cs
+++ b/src/BikeTracking.Api.Tests/Application/Dashboard/GetAdvancedDashboardServiceTests.cs
@@ -426,7 +426,8 @@ public async Task GetAdvancedDashboardService_WithExpensesInWindow_IncludesExpen
}
[Fact]
- public async Task GetAdvancedDashboardService_WithExpenses_NetSavingsIsCombinedMinusExpenses()
+ public async Task
+ GetAdvancedDashboardService_WithFuelCostAndMileageRate_NetSavingsUsesFuelCostOnly()
{
using var dbContext = CreateDbContext();
var rider = await CreateRiderAsync(dbContext, "NetSavings Rider");
@@ -437,6 +438,8 @@ public async Task GetAdvancedDashboardService_WithExpenses_NetSavingsIsCombinedM
RiderId = rider.UserId,
RideDateTimeLocal = DateTime.Now.AddMonths(-3),
Miles = 100m,
+ SnapshotAverageCarMpg = 20m,
+ GasPricePerGallon = 3m,
SnapshotMileageRateCents = 67m,
CreatedAtUtc = DateTime.UtcNow,
}
@@ -458,10 +461,13 @@ public async Task GetAdvancedDashboardService_WithExpenses_NetSavingsIsCombinedM
var service = new GetAdvancedDashboardService(dbContext, TimeProvider.System);
var result = await service.GetAsync(rider.UserId);
- // 100 miles × 67 = 6700 combined savings - 30 expenses = 6670 net
- Assert.Equal(6700m, result.SavingsWindows.AllTime.CombinedSavings);
+ // 100 miles / 20 mpg × $3 = $15 fuel cost avoided - $30 expenses = -$15 net
+ // Mileage-rate savings still exists, but it does not feed net savings.
+ Assert.Equal(15m, result.SavingsWindows.AllTime.FuelCostAvoided);
+ Assert.Equal(6700m, result.SavingsWindows.AllTime.MileageRateSavings);
+ Assert.Equal(6715m, result.SavingsWindows.AllTime.CombinedSavings);
Assert.Equal(30m, result.SavingsWindows.AllTime.TotalExpenses);
- Assert.Equal(6670m, result.SavingsWindows.AllTime.NetSavings);
+ Assert.Equal(-15m, result.SavingsWindows.AllTime.NetSavings);
}
[Fact]
diff --git a/src/BikeTracking.Api/Application/Dashboard/GetAdvancedDashboardService.cs b/src/BikeTracking.Api/Application/Dashboard/GetAdvancedDashboardService.cs
index 69998d4..5317da0 100644
--- a/src/BikeTracking.Api/Application/Dashboard/GetAdvancedDashboardService.cs
+++ b/src/BikeTracking.Api/Application/Dashboard/GetAdvancedDashboardService.cs
@@ -253,15 +253,12 @@ private static AdvancedSavingsWindow BuildWindow(
oilChangeSavings = crossings > 0 ? RoundTo2(crossings * oilChangePrice.Value) : 0m;
}
- // Net savings: gross savings + oil-change offset − expenses.
- // Null only when all savings are unavailable and there are no expenses.
+ // Net savings: fuel-cost avoided plus oil-change offset minus expenses.
decimal? netSavings = null;
- bool hasSavingsData = combinedSavings.HasValue || oilChangeSavings.HasValue;
+ bool hasSavingsData = fuelCostAvoided.HasValue || oilChangeSavings.HasValue;
if (hasSavingsData || totalExpenses > 0m)
{
- netSavings = RoundTo2(
- (combinedSavings ?? 0m) + (oilChangeSavings ?? 0m) - totalExpenses
- );
+ netSavings = RoundTo2((fuelCostAvoided ?? 0m) + (oilChangeSavings ?? 0m) - totalExpenses);
}
return new AdvancedSavingsWindow(
diff --git a/src/BikeTracking.Api/Contracts/AdvancedDashboardContracts.cs b/src/BikeTracking.Api/Contracts/AdvancedDashboardContracts.cs
index 8bdd48e..9e8c327 100644
--- a/src/BikeTracking.Api/Contracts/AdvancedDashboardContracts.cs
+++ b/src/BikeTracking.Api/Contracts/AdvancedDashboardContracts.cs
@@ -88,8 +88,8 @@ public sealed record AdvancedSavingsWindow(
///
decimal? OilChangeSavings,
///