feat: Replace NHTSA VIN decode with Google Gemini via OCR service (#223) #229

Merged
egullickson merged 9 commits from issue-223-replace-nhtsa-vin-decode-gemini into main 2026-02-20 03:10:48 +00:00
7 changed files with 28 additions and 28 deletions
Showing only changes of commit d96736789e - Show all commits

View File

@@ -82,7 +82,7 @@ export const vehiclesApi = {
},
/**
* Decode VIN using NHTSA vPIC API
* Decode VIN using VIN decode service
* Requires Pro or Enterprise tier
*/
decodeVin: async (vin: string): Promise<DecodedVehicleData> => {

View File

@@ -507,7 +507,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
/**
* Handle VIN decode button click
* Calls NHTSA API and populates empty form fields
* Calls VIN decode service and populates empty form fields
*/
const handleDecodeVin = async () => {
// Check tier access first

View File

@@ -95,8 +95,8 @@ const ReviewContent: React.FC<{
const [selectedEngine, setSelectedEngine] = useState('');
const [selectedTransmission, setSelectedTransmission] = useState('');
// NHTSA reference values for unmatched fields
const [nhtsaRefs, setNhtsaRefs] = useState<Record<string, string | null>>({});
// Source reference values for unmatched fields
const [sourceRefs, setSourceRefs] = useState<Record<string, string | null>>({});
// Initialize dropdown options and pre-select decoded values
useEffect(() => {
@@ -109,13 +109,13 @@ const ReviewContent: React.FC<{
if (!decodedVehicle) return;
// Store NHTSA reference values for unmatched fields
setNhtsaRefs({
make: decodedVehicle.make.confidence === 'none' ? decodedVehicle.make.nhtsaValue : null,
model: decodedVehicle.model.confidence === 'none' ? decodedVehicle.model.nhtsaValue : null,
trim: decodedVehicle.trimLevel.confidence === 'none' ? decodedVehicle.trimLevel.nhtsaValue : null,
engine: decodedVehicle.engine.confidence === 'none' ? decodedVehicle.engine.nhtsaValue : null,
transmission: decodedVehicle.transmission.confidence === 'none' ? decodedVehicle.transmission.nhtsaValue : null,
// Store source reference values for unmatched fields
setSourceRefs({
make: decodedVehicle.make.confidence === 'none' ? decodedVehicle.make.sourceValue : null,
model: decodedVehicle.model.confidence === 'none' ? decodedVehicle.model.sourceValue : null,
trim: decodedVehicle.trimLevel.confidence === 'none' ? decodedVehicle.trimLevel.sourceValue : null,
engine: decodedVehicle.engine.confidence === 'none' ? decodedVehicle.engine.sourceValue : null,
transmission: decodedVehicle.transmission.confidence === 'none' ? decodedVehicle.transmission.sourceValue : null,
});
const yearValue = decodedVehicle.year.value;
@@ -277,9 +277,9 @@ const ReviewContent: React.FC<{
});
};
/** Show NHTSA reference when field had no dropdown match */
const nhtsaHint = (field: string) => {
const ref = nhtsaRefs[field];
/** Show source reference when field had no dropdown match */
const sourceHint = (field: string) => {
const ref = sourceRefs[field];
if (!ref) return null;
// Only show hint when no value is currently selected
const selected: Record<string, string> = {
@@ -292,7 +292,7 @@ const ReviewContent: React.FC<{
if (selected[field]) return null;
return (
<p className="mt-1 text-xs text-gray-500 dark:text-titanio">
NHTSA returned: {ref}
Decoded value: {ref}
</p>
);
};
@@ -409,7 +409,7 @@ const ReviewContent: React.FC<{
</option>
))}
</select>
{nhtsaHint('make')}
{sourceHint('make')}
</div>
{/* Model */}
@@ -439,7 +439,7 @@ const ReviewContent: React.FC<{
</option>
))}
</select>
{nhtsaHint('model')}
{sourceHint('model')}
</div>
{/* Trim */}
@@ -469,7 +469,7 @@ const ReviewContent: React.FC<{
</option>
))}
</select>
{nhtsaHint('trim')}
{sourceHint('trim')}
</div>
{/* Engine */}
@@ -499,7 +499,7 @@ const ReviewContent: React.FC<{
</option>
))}
</select>
{nhtsaHint('engine')}
{sourceHint('engine')}
</div>
{/* Transmission */}
@@ -529,7 +529,7 @@ const ReviewContent: React.FC<{
</option>
))}
</select>
{nhtsaHint('transmission')}
{sourceHint('transmission')}
</div>
</div>
</Box>

View File

@@ -1,5 +1,5 @@
/**
* @ai-summary Hook to orchestrate VIN OCR extraction and NHTSA decode
* @ai-summary Hook to orchestrate VIN OCR extraction and VIN decode
* @ai-context Handles camera capture -> OCR extraction -> VIN decode flow
*/
@@ -109,7 +109,7 @@ export function useVinOcr(): UseVinOcrReturn {
);
}
// Step 2: Decode VIN using NHTSA
// Step 2: Decode VIN
setProcessingStep('decoding');
let decodedVehicle: DecodedVehicleData | null = null;
let decodeError: string | null = null;
@@ -121,7 +121,7 @@ export function useVinOcr(): UseVinOcrReturn {
if (err.response?.data?.error === 'TIER_REQUIRED') {
decodeError = 'VIN decode requires Pro or Enterprise subscription';
} else if (err.response?.data?.error === 'INVALID_VIN') {
decodeError = 'VIN format is not recognized by NHTSA';
decodeError = 'VIN format is not recognized';
} else {
decodeError = 'Unable to decode vehicle information';
}

View File

@@ -72,12 +72,12 @@ export type MatchConfidence = 'high' | 'medium' | 'none';
*/
export interface MatchedField<T> {
value: T | null;
nhtsaValue: string | null;
sourceValue: string | null;
confidence: MatchConfidence;
}
/**
* Decoded vehicle data from NHTSA vPIC API
* Decoded vehicle data from VIN decode
* with match confidence per field
*/
export interface DecodedVehicleData {

View File

@@ -43,7 +43,7 @@ export const SubscriptionSection = () => {
</h3>
<p className="text-titanio/70 leading-relaxed mb-4">
<strong className="text-avus">What it does:</strong> Use your device camera to photograph your vehicle's VIN plate, and the system automatically reads the VIN using OCR (Optical Character Recognition) and decodes it from the NHTSA database.
<strong className="text-avus">What it does:</strong> Use your device camera to photograph your vehicle's VIN plate, and the system automatically reads the VIN using OCR (Optical Character Recognition) and decodes it from the vehicle database.
</p>
<p className="text-titanio/70 leading-relaxed mb-4">
@@ -58,7 +58,7 @@ export const SubscriptionSection = () => {
<li>A <strong className="text-avus">VIN OCR Review modal</strong> appears showing the detected VIN with confidence indicators</li>
<li>Confirm or correct the VIN, then click <strong className="text-avus">Accept</strong></li>
<li>Click the <strong className="text-avus">Decode VIN</strong> button</li>
<li>The system queries the NHTSA database and auto-populates: Year, Make, Model, Engine, Transmission, and Trim</li>
<li>The system queries the vehicle database and auto-populates: Year, Make, Model, Engine, Transmission, and Trim</li>
<li>Review the pre-filled fields and complete the remaining details</li>
</ol>

View File

@@ -141,7 +141,7 @@ export const VehiclesSection = () => {
<GuideScreenshot
src="/guide/vin-decode-desktop.png"
alt="VIN Decode feature showing auto-populated vehicle specifications"
caption="The VIN Decode feature automatically fills in vehicle details from the NHTSA database"
caption="The VIN Decode feature automatically fills in vehicle details from the vehicle database"
/>
</div>