feat: add station matching from receipt merchant name (refs #132)

Add Google Places Text Search to match receipt merchant names (e.g.
"Shell", "COSTCO #123") to real gas stations. Backend exposes
POST /api/stations/match endpoint. Frontend calls it after OCR
extraction and pre-fills locationData with matched station's placeId,
name, and address. Users can clear the match in the review modal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-02-11 09:45:13 -06:00
parent bc91fbad79
commit d8dec64538
10 changed files with 530 additions and 10 deletions

View File

@@ -10,6 +10,7 @@ import { pool } from '../../../core/config/database';
import { logger } from '../../../core/logging/logger';
import {
StationSearchBody,
StationMatchBody,
SaveStationBody,
StationParams,
UpdateSavedStationBody
@@ -53,6 +54,29 @@ export class StationsController {
}
}
async matchStation(request: FastifyRequest<{ Body: StationMatchBody }>, reply: FastifyReply) {
try {
const { merchantName } = request.body;
if (!merchantName || !merchantName.trim()) {
return reply.code(400).send({
error: 'Bad Request',
message: 'Merchant name is required',
});
}
const result = await this.stationsService.matchStationFromReceipt(merchantName);
return reply.code(200).send(result);
} catch (error: any) {
logger.error('Error matching station from receipt', { error, merchantName: request.body?.merchantName });
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to match station',
});
}
}
async saveStation(request: FastifyRequest<{ Body: SaveStationBody }>, reply: FastifyReply) {
try {
const userId = (request as any).user.sub;