Add station search method to google-maps.client.ts using Places Text Search for merchant name matching
Add backend endpoint or service method that takes merchant name string and returns matched station
After receipt OCR extraction, call station matching with extracted merchantName
Pre-fill locationData in fuel log form with matched station (googlePlaceId, stationName, address)
User can change/clear station selection in review modal
Acceptance Criteria
Extracted merchant name "Shell" or "BP" returns a matching station with placeId
Matched station pre-fills locationData in the fuel log form
User can override or clear the auto-matched station
Station matching failure (no match) does not block receipt acceptance
Station matching works with abbreviated names (e.g., "COSTCO #123")
Tests
Test files: backend/src/features/stations/tests/unit/station-matching.test.ts
Test type: unit (mock Google Places API)
Scenarios:
Normal: Known station name returns match
Edge: Abbreviated/partial name returns best match
Error: No match found returns null gracefully
Relates to #129
## Milestone 3: Station Matching from Receipt
Add Google Places API station matching from extracted receipt merchant name.
### Files
- `backend/src/features/fuel-logs/api/fuel-logs.controller.ts` (or new endpoint)
- `backend/src/features/stations/external/google-maps/google-maps.client.ts`
- `frontend/src/features/fuel-logs/hooks/useReceiptOcr.ts`
- `frontend/src/features/fuel-logs/components/ReceiptOcrReviewModal.tsx`
### Requirements
- Add station search method to google-maps.client.ts using Places Text Search for merchant name matching
- Add backend endpoint or service method that takes merchant name string and returns matched station
- After receipt OCR extraction, call station matching with extracted merchantName
- Pre-fill locationData in fuel log form with matched station (googlePlaceId, stationName, address)
- User can change/clear station selection in review modal
### Acceptance Criteria
- [ ] Extracted merchant name "Shell" or "BP" returns a matching station with placeId
- [ ] Matched station pre-fills locationData in the fuel log form
- [ ] User can override or clear the auto-matched station
- [ ] Station matching failure (no match) does not block receipt acceptance
- [ ] Station matching works with abbreviated names (e.g., "COSTCO #123")
### Tests
- **Test files**: `backend/src/features/stations/tests/unit/station-matching.test.ts`
- **Test type**: unit (mock Google Places API)
- **Scenarios**:
- Normal: Known station name returns match
- Edge: Abbreviated/partial name returns best match
- Error: No match found returns null gracefully
Backend: Google Maps Text Search - Added searchStationByName() to google-maps.client.ts using Places Text Search API. Searches "{merchantName} gas station" with type: gas_station filter. Results cached with 1-hour TTL. Returns null gracefully on API errors/no results.
Backend: Station Matching Endpoint - Added POST /api/stations/match with StationMatchBody { merchantName }. Service method matchStationFromReceipt() trims input, calls text search, caches matched station for future saveStation calls.
Frontend: OCR + Station Matching Flow - After receipt OCR extraction, useReceiptOcr hook calls /api/stations/match with extracted merchantName (non-blocking). MappedFuelLogFields.locationData now includes googlePlaceId and address when station is matched. Review modal shows matched station with place icon and address below the Station field. User can clear the match via close button. Editing merchant name clears any existing match.
Tests - 12 unit tests pass covering: known station name match ("Shell", "BP"), abbreviated name match ("COSTCO #123"), no match returns null, API error returns null gracefully, empty/whitespace input handling, rating/photo inclusion, service caching behavior.
Station matching call after OCR, clearMatchedStation(), enriched locationData
frontend/.../ReceiptOcrReviewModal.tsx
Matched station indicator with clear button
frontend/.../FuelLogForm.tsx
Pass matchedStation/onClearMatchedStation to modal
backend/.../station-matching.test.ts
12 unit tests
Quality
Type-check: PASS (frontend + backend)
Lint: PASS (0 errors)
Tests: 12/12 PASS
Verdict: PASS | Next: Quality review
## Milestone: Station Matching from Receipt (#132)
**Phase**: Execution | **Agent**: Developer | **Status**: PASS
### Completed
- **Backend: Google Maps Text Search** - Added `searchStationByName()` to `google-maps.client.ts` using Places Text Search API. Searches `"{merchantName} gas station"` with `type: gas_station` filter. Results cached with 1-hour TTL. Returns null gracefully on API errors/no results.
- **Backend: Station Matching Endpoint** - Added `POST /api/stations/match` with `StationMatchBody { merchantName }`. Service method `matchStationFromReceipt()` trims input, calls text search, caches matched station for future `saveStation` calls.
- **Frontend: OCR + Station Matching Flow** - After receipt OCR extraction, `useReceiptOcr` hook calls `/api/stations/match` with extracted `merchantName` (non-blocking). `MappedFuelLogFields.locationData` now includes `googlePlaceId` and `address` when station is matched. Review modal shows matched station with place icon and address below the Station field. User can clear the match via close button. Editing merchant name clears any existing match.
- **Tests** - 12 unit tests pass covering: known station name match ("Shell", "BP"), abbreviated name match ("COSTCO #123"), no match returns null, API error returns null gracefully, empty/whitespace input handling, rating/photo inclusion, service caching behavior.
### Files Changed (10)
| File | Change |
|------|--------|
| `backend/.../google-maps.client.ts` | Added `searchStationByName()` + `transformTextSearchResult()` |
| `backend/.../google-maps.types.ts` | Added `GoogleTextSearchResponse` type |
| `backend/.../stations.types.ts` | Added `StationMatchBody`, `StationMatchResponse` |
| `backend/.../stations.service.ts` | Added `matchStationFromReceipt()` |
| `backend/.../stations.controller.ts` | Added `matchStation()` handler |
| `backend/.../stations.routes.ts` | Added `POST /stations/match` route |
| `frontend/.../useReceiptOcr.ts` | Station matching call after OCR, `clearMatchedStation()`, enriched locationData |
| `frontend/.../ReceiptOcrReviewModal.tsx` | Matched station indicator with clear button |
| `frontend/.../FuelLogForm.tsx` | Pass `matchedStation`/`onClearMatchedStation` to modal |
| `backend/.../station-matching.test.ts` | 12 unit tests |
### Quality
- Type-check: PASS (frontend + backend)
- Lint: PASS (0 errors)
- Tests: 12/12 PASS
*Verdict*: PASS | *Next*: Quality review
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.
Relates to #129
Milestone 3: Station Matching from Receipt
Add Google Places API station matching from extracted receipt merchant name.
Files
backend/src/features/fuel-logs/api/fuel-logs.controller.ts(or new endpoint)backend/src/features/stations/external/google-maps/google-maps.client.tsfrontend/src/features/fuel-logs/hooks/useReceiptOcr.tsfrontend/src/features/fuel-logs/components/ReceiptOcrReviewModal.tsxRequirements
Acceptance Criteria
Tests
backend/src/features/stations/tests/unit/station-matching.test.tsMilestone: Station Matching from Receipt (#132)
Phase: Execution | Agent: Developer | Status: PASS
Completed
searchStationByName()togoogle-maps.client.tsusing Places Text Search API. Searches"{merchantName} gas station"withtype: gas_stationfilter. Results cached with 1-hour TTL. Returns null gracefully on API errors/no results.POST /api/stations/matchwithStationMatchBody { merchantName }. Service methodmatchStationFromReceipt()trims input, calls text search, caches matched station for futuresaveStationcalls.useReceiptOcrhook calls/api/stations/matchwith extractedmerchantName(non-blocking).MappedFuelLogFields.locationDatanow includesgooglePlaceIdandaddresswhen station is matched. Review modal shows matched station with place icon and address below the Station field. User can clear the match via close button. Editing merchant name clears any existing match.Files Changed (10)
backend/.../google-maps.client.tssearchStationByName()+transformTextSearchResult()backend/.../google-maps.types.tsGoogleTextSearchResponsetypebackend/.../stations.types.tsStationMatchBody,StationMatchResponsebackend/.../stations.service.tsmatchStationFromReceipt()backend/.../stations.controller.tsmatchStation()handlerbackend/.../stations.routes.tsPOST /stations/matchroutefrontend/.../useReceiptOcr.tsclearMatchedStation(), enriched locationDatafrontend/.../ReceiptOcrReviewModal.tsxfrontend/.../FuelLogForm.tsxmatchedStation/onClearMatchedStationto modalbackend/.../station-matching.test.tsQuality
Verdict: PASS | Next: Quality review