feat: Replace NHTSA VIN decode with Google Gemini via OCR service (#223) #229
@@ -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> => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user