diff --git a/backend/src/features/ocr/domain/ocr.types.ts b/backend/src/features/ocr/domain/ocr.types.ts index 2f00b4c..1be5c5c 100644 --- a/backend/src/features/ocr/domain/ocr.types.ts +++ b/backend/src/features/ocr/domain/ocr.types.ts @@ -131,3 +131,21 @@ export interface ManualJobResponse { result?: ManualExtractionResult; error?: string; } + +/** Response from VIN decode via Gemini (OCR service) */ +export interface VinDecodeResponse { + success: boolean; + vin: string; + year: number | null; + make: string | null; + model: string | null; + trimLevel: string | null; + bodyType: string | null; + driveType: string | null; + fuelType: string | null; + engine: string | null; + transmission: string | null; + confidence: number; + processingTimeMs: number; + error: string | null; +} diff --git a/backend/src/features/ocr/external/ocr-client.ts b/backend/src/features/ocr/external/ocr-client.ts index 627abf7..4cc3fca 100644 --- a/backend/src/features/ocr/external/ocr-client.ts +++ b/backend/src/features/ocr/external/ocr-client.ts @@ -2,7 +2,7 @@ * @ai-summary HTTP client for OCR service communication */ import { logger } from '../../../core/logging/logger'; -import type { JobResponse, ManualJobResponse, OcrResponse, ReceiptExtractionResponse, VinExtractionResponse } from '../domain/ocr.types'; +import type { JobResponse, ManualJobResponse, OcrResponse, ReceiptExtractionResponse, VinDecodeResponse, VinExtractionResponse } from '../domain/ocr.types'; /** OCR service configuration */ const OCR_SERVICE_URL = process.env.OCR_SERVICE_URL || 'http://mvp-ocr:8000'; @@ -373,6 +373,55 @@ export class OcrClient { return result; } + /** + * Decode a VIN string into structured vehicle data via Gemini. + * + * Unlike other OCR methods, this sends JSON (not multipart) because + * VIN decode has no file upload. + * + * @param vin - 17-character Vehicle Identification Number + * @returns Structured vehicle data from Gemini decode + */ + async decodeVin(vin: string): Promise { + const url = `${this.baseUrl}/decode/vin`; + + logger.info('OCR VIN decode request', { + operation: 'ocr.client.decodeVin', + url, + vin, + }); + + const response = await this.fetchWithTimeout(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ vin }), + }); + + if (!response.ok) { + const errorText = await response.text(); + logger.error('OCR VIN decode failed', { + operation: 'ocr.client.decodeVin.error', + status: response.status, + error: errorText, + }); + const err: any = new Error(`OCR service error: ${response.status} - ${errorText}`); + err.statusCode = response.status; + throw err; + } + + const result = (await response.json()) as VinDecodeResponse; + + logger.info('OCR VIN decode completed', { + operation: 'ocr.client.decodeVin.success', + success: result.success, + vin: result.vin, + confidence: result.confidence, + processingTimeMs: result.processingTimeMs, + }); + + return result; + } + /** * Check if the OCR service is healthy. *