Skip to content

Commit dc47da8

Browse files
aligneddevaligneddev
andauthored
012 dashboard stats (#32)
* plan * dashboard stats * show gallons and goal, cents per mile for mileage --------- Co-authored-by: aligneddev <aligneddev@github.com>
1 parent 620c0e5 commit dc47da8

File tree

58 files changed

+5256
-181
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+5256
-181
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Specification Quality Checklist: Rider Dashboard Statistics
2+
3+
**Purpose**: Validate specification completeness and quality before proceeding to planning
4+
**Created**: 2026-04-06
5+
**Feature**: [spec.md](../spec.md)
6+
7+
## Content Quality
8+
9+
- [x] No implementation details (languages, frameworks, APIs)
10+
- [x] Focused on user value and business needs
11+
- [x] Written for non-technical stakeholders
12+
- [x] All mandatory sections completed
13+
14+
## Requirement Completeness
15+
16+
- [x] No [NEEDS CLARIFICATION] markers remain
17+
- [x] Requirements are testable and unambiguous
18+
- [x] Success criteria are measurable
19+
- [x] Success criteria are technology-agnostic (no implementation details)
20+
- [x] All acceptance scenarios are defined
21+
- [x] Edge cases are identified
22+
- [x] Scope is clearly bounded
23+
- [x] Dependencies and assumptions identified
24+
25+
## Feature Readiness
26+
27+
- [x] All functional requirements have clear acceptance criteria
28+
- [x] User scenarios cover primary flows
29+
- [x] Feature meets measurable outcomes defined in Success Criteria
30+
- [x] No implementation details leak into specification
31+
32+
## Notes
33+
34+
- Clarifications resolved: baseline averages include average miles per ride and average ride duration.
35+
- Clarifications resolved: first optional suggestions are estimated gallons avoided and goal progress.
36+
- Specification is ready for `/speckit.plan`.
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# API Contracts: Rider Dashboard Statistics
2+
3+
**Feature**: 012-dashboard-stats
4+
**Date**: 2026-04-06
5+
**Primary base paths**: `/api/dashboard`, `/api/users/me/settings`
6+
7+
---
8+
9+
## New Endpoint
10+
11+
### `GET /api/dashboard`
12+
13+
Authenticated rider-only endpoint returning the full dashboard view model.
14+
15+
```csharp
16+
public sealed record DashboardResponse(
17+
DashboardTotals Totals,
18+
DashboardAverages Averages,
19+
DashboardCharts Charts,
20+
IReadOnlyList<DashboardMetricSuggestion> Suggestions,
21+
DashboardMissingData MissingData,
22+
DateTime GeneratedAtUtc
23+
);
24+
25+
public sealed record DashboardTotals(
26+
DashboardMileageMetric CurrentMonthMiles,
27+
DashboardMileageMetric YearToDateMiles,
28+
DashboardMileageMetric AllTimeMiles,
29+
DashboardMoneySaved MoneySaved
30+
);
31+
32+
public sealed record DashboardMileageMetric(
33+
decimal Miles,
34+
int RideCount,
35+
string Period
36+
);
37+
38+
public sealed record DashboardMoneySaved(
39+
decimal? MileageRateSavings,
40+
decimal? FuelCostAvoided,
41+
decimal? CombinedSavings,
42+
int QualifiedRideCount
43+
);
44+
45+
public sealed record DashboardAverages(
46+
decimal? AverageTemperature,
47+
decimal? AverageMilesPerRide,
48+
decimal? AverageRideMinutes
49+
);
50+
51+
public sealed record DashboardCharts(
52+
IReadOnlyList<DashboardMileagePoint> MileageByMonth,
53+
IReadOnlyList<DashboardSavingsPoint> SavingsByMonth
54+
);
55+
56+
public sealed record DashboardMileagePoint(
57+
string MonthKey,
58+
string Label,
59+
decimal Miles
60+
);
61+
62+
public sealed record DashboardSavingsPoint(
63+
string MonthKey,
64+
string Label,
65+
decimal? MileageRateSavings,
66+
decimal? FuelCostAvoided,
67+
decimal? CombinedSavings
68+
);
69+
70+
public sealed record DashboardMetricSuggestion(
71+
string MetricKey,
72+
string Title,
73+
string Description,
74+
bool IsEnabled
75+
);
76+
77+
public sealed record DashboardMissingData(
78+
int RidesMissingSavingsSnapshot,
79+
int RidesMissingGasPrice,
80+
int RidesMissingTemperature,
81+
int RidesMissingDuration
82+
);
83+
```
84+
85+
**Behavior**:
86+
- `200 OK` for authenticated riders, even when no rides exist.
87+
- `401 Unauthorized` when the caller is unauthenticated.
88+
- Empty-state responses still contain empty chart arrays or zeroed mileage cards instead of errors.
89+
90+
---
91+
92+
## Modified Existing Contract: `UserSettingsUpsertRequest`
93+
94+
File: `src/BikeTracking.Api/Contracts/UsersContracts.cs`
95+
96+
```csharp
97+
public sealed record UserSettingsUpsertRequest(
98+
decimal? AverageCarMpg,
99+
decimal? YearlyGoalMiles,
100+
decimal? OilChangePrice,
101+
decimal? MileageRateCents,
102+
string? LocationLabel,
103+
decimal? Latitude,
104+
decimal? Longitude,
105+
bool? DashboardGallonsAvoidedEnabled,
106+
bool? DashboardGoalProgressEnabled
107+
);
108+
```
109+
110+
**Semantics**:
111+
- Both new fields participate in the existing partial-update semantics.
112+
- Omitting a field leaves the prior persisted value unchanged.
113+
114+
---
115+
116+
## Modified Existing Contract: `UserSettingsView`
117+
118+
```csharp
119+
public sealed record UserSettingsView(
120+
decimal? AverageCarMpg,
121+
decimal? YearlyGoalMiles,
122+
decimal? OilChangePrice,
123+
decimal? MileageRateCents,
124+
string? LocationLabel,
125+
decimal? Latitude,
126+
decimal? Longitude,
127+
bool DashboardGallonsAvoidedEnabled,
128+
bool DashboardGoalProgressEnabled,
129+
DateTime? UpdatedAtUtc
130+
);
131+
```
132+
133+
These fields allow the frontend to render suggestion state and settings defaults consistently.
134+
135+
---
136+
137+
## Modified Existing Event Payload Factories
138+
139+
Files:
140+
- `RideRecordedEventPayload.cs`
141+
- `RideEditedEventPayload.cs`
142+
143+
New additive optional fields:
144+
145+
```csharp
146+
decimal? SnapshotAverageCarMpg = null,
147+
decimal? SnapshotMileageRateCents = null,
148+
decimal? SnapshotYearlyGoalMiles = null,
149+
decimal? SnapshotOilChangePrice = null
150+
```
151+
152+
These are additive and backwards-compatible for existing call sites.
153+
154+
---
155+
156+
## Frontend TypeScript Contracts
157+
158+
### `dashboard-api.ts`
159+
160+
```typescript
161+
export interface DashboardResponse {
162+
totals: DashboardTotals;
163+
averages: DashboardAverages;
164+
charts: DashboardCharts;
165+
suggestions: DashboardMetricSuggestion[];
166+
missingData: DashboardMissingData;
167+
generatedAtUtc: string;
168+
}
169+
170+
export interface DashboardTotals {
171+
currentMonthMiles: DashboardMileageMetric;
172+
yearToDateMiles: DashboardMileageMetric;
173+
allTimeMiles: DashboardMileageMetric;
174+
moneySaved: DashboardMoneySaved;
175+
}
176+
177+
export interface DashboardMileageMetric {
178+
miles: number;
179+
rideCount: number;
180+
period: string;
181+
}
182+
183+
export interface DashboardMoneySaved {
184+
mileageRateSavings: number | null;
185+
fuelCostAvoided: number | null;
186+
combinedSavings: number | null;
187+
qualifiedRideCount: number;
188+
}
189+
190+
export interface DashboardAverages {
191+
averageTemperature: number | null;
192+
averageMilesPerRide: number | null;
193+
averageRideMinutes: number | null;
194+
}
195+
196+
export interface DashboardCharts {
197+
mileageByMonth: DashboardMileagePoint[];
198+
savingsByMonth: DashboardSavingsPoint[];
199+
}
200+
201+
export interface DashboardMileagePoint {
202+
monthKey: string;
203+
label: string;
204+
miles: number;
205+
}
206+
207+
export interface DashboardSavingsPoint {
208+
monthKey: string;
209+
label: string;
210+
mileageRateSavings: number | null;
211+
fuelCostAvoided: number | null;
212+
combinedSavings: number | null;
213+
}
214+
215+
export interface DashboardMetricSuggestion {
216+
metricKey: "gallonsAvoided" | "goalProgress";
217+
title: string;
218+
description: string;
219+
isEnabled: boolean;
220+
}
221+
222+
export interface DashboardMissingData {
223+
ridesMissingSavingsSnapshot: number;
224+
ridesMissingGasPrice: number;
225+
ridesMissingTemperature: number;
226+
ridesMissingDuration: number;
227+
}
228+
```
229+
230+
### `users-api.ts`
231+
232+
Add to both request and response interfaces:
233+
234+
```typescript
235+
dashboardGallonsAvoidedEnabled?: boolean | null;
236+
dashboardGoalProgressEnabled?: boolean | null;
237+
```
238+
239+
For the view shape:
240+
241+
```typescript
242+
dashboardGallonsAvoidedEnabled: boolean;
243+
dashboardGoalProgressEnabled: boolean;
244+
```
245+
246+
---
247+
248+
## Compatibility Notes
249+
250+
- Existing user settings callers remain compatible because the new fields are additive.
251+
- Existing ride-history API consumers remain unchanged.
252+
- The dashboard no longer depends on ride-history pagination hacks, but `/miles` can be preserved as
253+
a client-side redirect to the new dashboard route for continuity.

0 commit comments

Comments
 (0)