Per the audit performed for #241, the stations features have the same numeric-as-string bug. node-postgres returns numeric/decimal columns as strings, but the TypeScript interfaces declare them as number. The mappers pass values through raw.
Unlike maintenance and ownership-costs, the stations price fields may be consumed in places that already work around the string-as-number behavior (e.g., parsing back to number for sorting, displaying as-is in formatted strings). Before fixing the mapper:
Find every consumer of priceRegular, pricePremium, priceDiesel, rating, and price93 — backend and frontend.
For each consumer, decide whether coercing to number changes behavior (e.g., a <sort by price> UI that does Number(a.priceRegular) - Number(b.priceRegular) will continue to work; a UI that does priceRegular.includes(".") would break).
Suggested Fix (mapper layer)
Match the pattern used in vehicles.repository.ts and the fix applied in PR #242:
Audit downstream consumers of every affected field (backend services, frontend components, tests).
Decide which fields to coerce based on consumer analysis.
Update mappers accordingly.
Remove any defensive Number() calls in consumers that become unnecessary.
Mobile + desktop verification of any UI surfaces that display these prices.
Linting, type-check, and tests pass.
Related
#241 / PR #242 — fixed the same pattern in maintenance and ownership-costs.
## Summary
Per the audit performed for #241, the stations features have the same numeric-as-string bug. node-postgres returns `numeric`/`decimal` columns as strings, but the TypeScript interfaces declare them as `number`. The mappers pass values through raw.
## Affected
### `backend/src/features/stations/data/stations.repository.ts`
Cache row mapper returns these fields without coercion:
- `priceRegular` — `DECIMAL(10,2)`
- `pricePremium` — `DECIMAL(10,2)`
- `priceDiesel` — `DECIMAL(10,2)`
- `rating` — `DECIMAL(2,1)`
### `backend/src/features/stations/data/community-stations.repository.ts`
- `mapRow.price93` — `DECIMAL(5,3)`
## Investigation Required (before fixing)
Unlike maintenance and ownership-costs, the stations price fields may be consumed in places that already work around the string-as-number behavior (e.g., parsing back to number for sorting, displaying as-is in formatted strings). Before fixing the mapper:
- Find every consumer of `priceRegular`, `pricePremium`, `priceDiesel`, `rating`, and `price93` — backend and frontend.
- For each consumer, decide whether coercing to `number` changes behavior (e.g., a `<sort by price>` UI that does `Number(a.priceRegular) - Number(b.priceRegular)` will continue to work; a UI that does `priceRegular.includes(".")` would break).
## Suggested Fix (mapper layer)
Match the pattern used in `vehicles.repository.ts` and the fix applied in PR #242:
```typescript
priceRegular: row.price_regular != null ? Number(row.price_regular) : undefined,
priceDiesel: row.price_diesel != null ? Number(row.price_diesel) : undefined,
priceRegular: row.price_premium != null ? Number(row.price_premium) : undefined,
rating: row.rating != null ? Number(row.rating) : undefined,
// community-stations:
price93: row.price_93 != null ? Number(row.price_93) : undefined,
```
## Acceptance Criteria
- [ ] Audit downstream consumers of every affected field (backend services, frontend components, tests).
- [ ] Decide which fields to coerce based on consumer analysis.
- [ ] Update mappers accordingly.
- [ ] Remove any defensive `Number()` calls in consumers that become unnecessary.
- [ ] Mobile + desktop verification of any UI surfaces that display these prices.
- [ ] Linting, type-check, and tests pass.
## Related
- #241 / PR #242 — fixed the same pattern in maintenance and ownership-costs.
Investigated this before fixing and discovered the mappers already coerce these fields. The audit that originated this issue (the one performed for #241) claimed the stations mappers "return raw rows" but it was inferring from the audit summary table without actually reading the files.
A grep across the stations feature confirms there is no path that bypasses these mappers — every consumer accesses the camelCase mapped object, the domain types declare number, and the API validator (api/community-stations.validation.ts:19) uses z.number(). End-to-end the type contract is already honest.
Minor code-quality detail (not fixing here)
The truthiness check pattern row.field ? parseFloat(...) : undefined would drop a legitimate 0 value (e.g., a station rated 0.0 by Google Maps, or — implausibly — free fuel). Same pattern exists in vehicles.repository.ts already on main. If you want to tighten this to != null everywhere, that's a separate code-quality issue worth filing on its own. It is not what #243 was reporting, so closing this as not-a-bug rather than expanding scope.
Lesson for future audits
The audit table that fed #241 marked stations as "needs fix: YES" based on phrases like "(mapCacheRow) not shown but inferred to return raw rows" — i.e. it never opened the file. Future audits should not flag a file as broken without reading it. The fuel-logs audit done for #244 caught its own errors precisely because the agent did read the file.
Closing.
## Closing as not-a-bug — original audit was wrong
Investigated this before fixing and discovered the mappers **already coerce these fields**. The audit that originated this issue (the one performed for #241) claimed the stations mappers "return raw rows" but it was inferring from the audit summary table without actually reading the files.
### Actual state of the code
**`backend/src/features/stations/data/stations.repository.ts:157-172`** — `mapCacheRow`:
```typescript
priceRegular: row.price_regular ? parseFloat(row.price_regular) : undefined,
pricePremium: row.price_premium ? parseFloat(row.price_premium) : undefined,
priceDiesel: row.price_diesel ? parseFloat(row.price_diesel) : undefined,
rating: row.rating ? parseFloat(row.rating) : undefined,
```
**`backend/src/features/stations/data/community-stations.repository.ts:308-333`** — `mapRow`:
```typescript
price93: row.price_93 ? parseFloat(row.price_93) : undefined,
```
A grep across the stations feature confirms there is no path that bypasses these mappers — every consumer accesses the camelCase mapped object, the domain types declare `number`, and the API validator (`api/community-stations.validation.ts:19`) uses `z.number()`. End-to-end the type contract is already honest.
### Minor code-quality detail (not fixing here)
The truthiness check pattern `row.field ? parseFloat(...) : undefined` would drop a legitimate `0` value (e.g., a station rated 0.0 by Google Maps, or — implausibly — free fuel). Same pattern exists in `vehicles.repository.ts` already on `main`. If you want to tighten this to `!= null` everywhere, that's a separate code-quality issue worth filing on its own. **It is not what #243 was reporting, so closing this as not-a-bug rather than expanding scope.**
### Lesson for future audits
The audit table that fed #241 marked stations as "needs fix: YES" based on phrases like "(mapCacheRow) not shown but inferred to return raw rows" — i.e. it never opened the file. Future audits should not flag a file as broken without reading it. The fuel-logs audit done for #244 caught its own errors precisely because the agent did read the file.
Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Per the audit performed for #241, the stations features have the same numeric-as-string bug. node-postgres returns
numeric/decimalcolumns as strings, but the TypeScript interfaces declare them asnumber. The mappers pass values through raw.Affected
backend/src/features/stations/data/stations.repository.tsCache row mapper returns these fields without coercion:
priceRegular—DECIMAL(10,2)pricePremium—DECIMAL(10,2)priceDiesel—DECIMAL(10,2)rating—DECIMAL(2,1)backend/src/features/stations/data/community-stations.repository.tsmapRow.price93—DECIMAL(5,3)Investigation Required (before fixing)
Unlike maintenance and ownership-costs, the stations price fields may be consumed in places that already work around the string-as-number behavior (e.g., parsing back to number for sorting, displaying as-is in formatted strings). Before fixing the mapper:
priceRegular,pricePremium,priceDiesel,rating, andprice93— backend and frontend.numberchanges behavior (e.g., a<sort by price>UI that doesNumber(a.priceRegular) - Number(b.priceRegular)will continue to work; a UI that doespriceRegular.includes(".")would break).Suggested Fix (mapper layer)
Match the pattern used in
vehicles.repository.tsand the fix applied in PR #242:Acceptance Criteria
Number()calls in consumers that become unnecessary.Related
Closing as not-a-bug — original audit was wrong
Investigated this before fixing and discovered the mappers already coerce these fields. The audit that originated this issue (the one performed for #241) claimed the stations mappers "return raw rows" but it was inferring from the audit summary table without actually reading the files.
Actual state of the code
backend/src/features/stations/data/stations.repository.ts:157-172—mapCacheRow:backend/src/features/stations/data/community-stations.repository.ts:308-333—mapRow:A grep across the stations feature confirms there is no path that bypasses these mappers — every consumer accesses the camelCase mapped object, the domain types declare
number, and the API validator (api/community-stations.validation.ts:19) usesz.number(). End-to-end the type contract is already honest.Minor code-quality detail (not fixing here)
The truthiness check pattern
row.field ? parseFloat(...) : undefinedwould drop a legitimate0value (e.g., a station rated 0.0 by Google Maps, or — implausibly — free fuel). Same pattern exists invehicles.repository.tsalready onmain. If you want to tighten this to!= nulleverywhere, that's a separate code-quality issue worth filing on its own. It is not what #243 was reporting, so closing this as not-a-bug rather than expanding scope.Lesson for future audits
The audit table that fed #241 marked stations as "needs fix: YES" based on phrases like "(mapCacheRow) not shown but inferred to return raw rows" — i.e. it never opened the file. Future audits should not flag a file as broken without reading it. The fuel-logs audit done for #244 caught its own errors precisely because the agent did read the file.
Closing.