Skip to content

Commit e08f6ea

Browse files
aligneddevaligneddevgithub-advanced-security[bot]
authored
011 ride weather data (#29)
* implemented weather lookup * git practices * load weather with button * Potential fix for pull request finding 'CodeQL / Exposure of private information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --------- Co-authored-by: aligneddev <aligneddev@github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 2f85024 commit e08f6ea

Some content is hidden

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

44 files changed

+5077
-50
lines changed

.specify/memory/constitution.md

Lines changed: 110 additions & 6 deletions
Large diffs are not rendered by default.
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: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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+
## Weather Preview Endpoint
121+
122+
The explicit load-weather action uses a server-side preview endpoint so the browser never talks to
123+
Open-Meteo directly.
124+
125+
### `GET /api/rides/weather?rideDateTimeLocal={iso}`
126+
127+
Returns the weather snapshot for the authenticated rider's configured location and the supplied
128+
ride timestamp.
129+
130+
```csharp
131+
public sealed record RideWeatherResponse(
132+
DateTime RideDateTimeLocal,
133+
decimal? Temperature,
134+
decimal? WindSpeedMph,
135+
int? WindDirectionDeg,
136+
int? RelativeHumidityPercent,
137+
int? CloudCoverPercent,
138+
string? PrecipitationType,
139+
bool IsAvailable
140+
);
141+
```
142+
143+
Behavior:
144+
- Returns `200` with `IsAvailable = true` when weather data is found.
145+
- Returns `200` with null weather fields and `IsAvailable = false` when location is missing or no weather is available.
146+
- Returns `400` when `rideDateTimeLocal` is missing or invalid.
147+
- Returns `401` when the caller is unauthenticated.
148+
149+
---
150+
151+
## Frontend TypeScript Contracts
152+
153+
File to extend: `src/BikeTracking.Frontend/src/` (locate existing ride service API types)
154+
155+
### `RecordRideRequest` (TypeScript)
156+
157+
```typescript
158+
interface RecordRideRequest {
159+
// existing
160+
rideDateTimeLocal: string; // ISO 8601
161+
miles: number;
162+
rideMinutes?: number;
163+
temperature?: number;
164+
gasPricePerGallon?: number;
165+
// new weather fields
166+
windSpeedMph?: number;
167+
windDirectionDeg?: number;
168+
relativeHumidityPercent?: number;
169+
cloudCoverPercent?: number;
170+
precipitationType?: string;
171+
weatherUserOverridden?: boolean; // default false
172+
}
173+
```
174+
175+
### `EditRideRequest` (TypeScript)
176+
177+
```typescript
178+
interface EditRideRequest {
179+
// existing
180+
rideDateTimeLocal: string;
181+
miles: number;
182+
rideMinutes?: number;
183+
temperature?: number;
184+
expectedVersion: number;
185+
gasPricePerGallon?: number;
186+
// new weather fields
187+
windSpeedMph?: number;
188+
windDirectionDeg?: number;
189+
relativeHumidityPercent?: number;
190+
cloudCoverPercent?: number;
191+
precipitationType?: string;
192+
weatherUserOverridden?: boolean;
193+
}
194+
```
195+
196+
### `RideHistoryRow` (TypeScript)
197+
198+
```typescript
199+
interface RideHistoryRow {
200+
// existing
201+
rideId: number;
202+
rideDateTimeLocal: string;
203+
miles: number;
204+
rideMinutes?: number;
205+
temperature?: number;
206+
gasPricePerGallon?: number;
207+
// new weather fields
208+
windSpeedMph?: number;
209+
windDirectionDeg?: number;
210+
relativeHumidityPercent?: number;
211+
cloudCoverPercent?: number;
212+
precipitationType?: string;
213+
weatherUserOverridden?: boolean;
214+
}
215+
```
216+
217+
### `RideDefaultsResponse` (TypeScript)
218+
219+
```typescript
220+
interface RideDefaultsResponse {
221+
// existing
222+
hasPreviousRide: boolean;
223+
defaultRideDateTimeLocal: string;
224+
defaultMiles?: number;
225+
defaultRideMinutes?: number;
226+
defaultTemperature?: number;
227+
defaultGasPricePerGallon?: number;
228+
// new weather defaults
229+
defaultWindSpeedMph?: number;
230+
defaultWindDirectionDeg?: number;
231+
defaultRelativeHumidityPercent?: number;
232+
defaultCloudCoverPercent?: number;
233+
defaultPrecipitationType?: string;
234+
}
235+
```
236+
237+
---
238+
239+
## Backwards Compatibility
240+
241+
All new fields are optional with null/false defaults. Existing API callers that omit weather
242+
fields will behave exactly as before — the server will attempt to auto-fill weather from the
243+
API and store the result. No breaking changes to existing endpoints.

0 commit comments

Comments
 (0)