Skip to content

Commit bc9a027

Browse files
author
aligneddev
committed
implemented weather lookup
1 parent 2f85024 commit bc9a027

39 files changed

Lines changed: 4366 additions & 42 deletions
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Specification Quality Checklist: Weather-Enriched Ride Entries
2+
3+
**Purpose**: Validate specification completeness and quality before proceeding to planning
4+
**Created**: 2026-04-03
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+
- Validation completed in one iteration. No unresolved issues.
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# API Contracts: Weather-Enriched Ride Entries
2+
3+
**Feature**: 011-ride-weather-data
4+
**Date**: 2026-04-03
5+
**Base path**: `/api/rides`
6+
**Contract file**: `src/BikeTracking.Api/Contracts/RidesContracts.cs`
7+
8+
---
9+
10+
## Modified Contracts
11+
12+
### `RecordRideRequest` (extended)
13+
14+
New optional fields added; all existing fields unchanged.
15+
16+
```csharp
17+
public sealed record RecordRideRequest(
18+
// --- existing fields ---
19+
[Required] DateTime RideDateTimeLocal,
20+
[Required][Range(0.01, 200)] decimal Miles,
21+
[Range(1, int.MaxValue)] int? RideMinutes = null,
22+
decimal? Temperature = null,
23+
[Range(0.01, 999.9999)] decimal? GasPricePerGallon = null,
24+
// --- new weather fields ---
25+
[Range(0, 500, ErrorMessage = "Wind speed must be between 0 and 500 mph")]
26+
decimal? WindSpeedMph = null,
27+
[Range(0, 360, ErrorMessage = "Wind direction must be between 0 and 360 degrees")]
28+
int? WindDirectionDeg = null,
29+
[Range(0, 100, ErrorMessage = "Relative humidity must be between 0 and 100")]
30+
int? RelativeHumidityPercent = null,
31+
[Range(0, 100, ErrorMessage = "Cloud cover must be between 0 and 100")]
32+
int? CloudCoverPercent = null,
33+
[MaxLength(50, ErrorMessage = "Precipitation type must be 50 characters or fewer")]
34+
string? PrecipitationType = null,
35+
bool WeatherUserOverridden = false
36+
);
37+
```
38+
39+
---
40+
41+
### `EditRideRequest` (extended)
42+
43+
Same new fields added alongside existing fields.
44+
45+
```csharp
46+
public sealed record EditRideRequest(
47+
// --- existing fields ---
48+
[Required] DateTime RideDateTimeLocal,
49+
[Required][Range(0.01, 200)] decimal Miles,
50+
[Range(1, int.MaxValue)] int? RideMinutes,
51+
decimal? Temperature,
52+
[Required][Range(1, int.MaxValue)] int ExpectedVersion,
53+
[Range(0.01, 999.9999)] decimal? GasPricePerGallon = null,
54+
// --- new weather fields ---
55+
[Range(0, 500)] decimal? WindSpeedMph = null,
56+
[Range(0, 360)] int? WindDirectionDeg = null,
57+
[Range(0, 100)] int? RelativeHumidityPercent = null,
58+
[Range(0, 100)] int? CloudCoverPercent = null,
59+
[MaxLength(50)] string? PrecipitationType = null,
60+
bool WeatherUserOverridden = false
61+
);
62+
```
63+
64+
---
65+
66+
### `RideHistoryRow` (extended)
67+
68+
New weather fields added to the read-model row for display in ride history.
69+
70+
```csharp
71+
public sealed record RideHistoryRow(
72+
long RideId,
73+
DateTime RideDateTimeLocal,
74+
decimal Miles,
75+
int? RideMinutes = null,
76+
decimal? Temperature = null,
77+
decimal? GasPricePerGallon = null,
78+
// --- new weather fields ---
79+
decimal? WindSpeedMph = null,
80+
int? WindDirectionDeg = null,
81+
int? RelativeHumidityPercent = null,
82+
int? CloudCoverPercent = null,
83+
string? PrecipitationType = null,
84+
bool WeatherUserOverridden = false
85+
);
86+
```
87+
88+
---
89+
90+
### `RideDefaultsResponse` (extended)
91+
92+
Pre-populates weather fields from the most recent ride so the ride form shows prior values as defaults.
93+
94+
```csharp
95+
public sealed record RideDefaultsResponse(
96+
bool HasPreviousRide,
97+
DateTime DefaultRideDateTimeLocal,
98+
decimal? DefaultMiles = null,
99+
int? DefaultRideMinutes = null,
100+
decimal? DefaultTemperature = null,
101+
decimal? DefaultGasPricePerGallon = null,
102+
// --- new weather defaults ---
103+
decimal? DefaultWindSpeedMph = null,
104+
int? DefaultWindDirectionDeg = null,
105+
int? DefaultRelativeHumidityPercent = null,
106+
int? DefaultCloudCoverPercent = null,
107+
string? DefaultPrecipitationType = null
108+
);
109+
```
110+
111+
---
112+
113+
## No New Endpoints
114+
115+
This feature does **not** introduce a new weather API endpoint visible to the frontend. Weather
116+
data is fetched server-side at save time inside `RecordRideService` and `EditRideService`.
117+
The existing `GET /api/rides/gas-price` endpoint pattern is not replicated for weather because
118+
the weather lookup is tightly coupled to save time and user location (which is server-held).
119+
120+
---
121+
122+
## Frontend TypeScript Contracts
123+
124+
File to extend: `src/BikeTracking.Frontend/src/` (locate existing ride service API types)
125+
126+
### `RecordRideRequest` (TypeScript)
127+
128+
```typescript
129+
interface RecordRideRequest {
130+
// existing
131+
rideDateTimeLocal: string; // ISO 8601
132+
miles: number;
133+
rideMinutes?: number;
134+
temperature?: number;
135+
gasPricePerGallon?: number;
136+
// new weather fields
137+
windSpeedMph?: number;
138+
windDirectionDeg?: number;
139+
relativeHumidityPercent?: number;
140+
cloudCoverPercent?: number;
141+
precipitationType?: string;
142+
weatherUserOverridden?: boolean; // default false
143+
}
144+
```
145+
146+
### `EditRideRequest` (TypeScript)
147+
148+
```typescript
149+
interface EditRideRequest {
150+
// existing
151+
rideDateTimeLocal: string;
152+
miles: number;
153+
rideMinutes?: number;
154+
temperature?: number;
155+
expectedVersion: number;
156+
gasPricePerGallon?: number;
157+
// new weather fields
158+
windSpeedMph?: number;
159+
windDirectionDeg?: number;
160+
relativeHumidityPercent?: number;
161+
cloudCoverPercent?: number;
162+
precipitationType?: string;
163+
weatherUserOverridden?: boolean;
164+
}
165+
```
166+
167+
### `RideHistoryRow` (TypeScript)
168+
169+
```typescript
170+
interface RideHistoryRow {
171+
// existing
172+
rideId: number;
173+
rideDateTimeLocal: string;
174+
miles: number;
175+
rideMinutes?: number;
176+
temperature?: number;
177+
gasPricePerGallon?: number;
178+
// new weather fields
179+
windSpeedMph?: number;
180+
windDirectionDeg?: number;
181+
relativeHumidityPercent?: number;
182+
cloudCoverPercent?: number;
183+
precipitationType?: string;
184+
weatherUserOverridden?: boolean;
185+
}
186+
```
187+
188+
### `RideDefaultsResponse` (TypeScript)
189+
190+
```typescript
191+
interface RideDefaultsResponse {
192+
// existing
193+
hasPreviousRide: boolean;
194+
defaultRideDateTimeLocal: string;
195+
defaultMiles?: number;
196+
defaultRideMinutes?: number;
197+
defaultTemperature?: number;
198+
defaultGasPricePerGallon?: number;
199+
// new weather defaults
200+
defaultWindSpeedMph?: number;
201+
defaultWindDirectionDeg?: number;
202+
defaultRelativeHumidityPercent?: number;
203+
defaultCloudCoverPercent?: number;
204+
defaultPrecipitationType?: string;
205+
}
206+
```
207+
208+
---
209+
210+
## Backwards Compatibility
211+
212+
All new fields are optional with null/false defaults. Existing API callers that omit weather
213+
fields will behave exactly as before — the server will attempt to auto-fill weather from the
214+
API and store the result. No breaking changes to existing endpoints.

0 commit comments

Comments
 (0)