fix: before admin stations removal
This commit is contained in:
@@ -27,6 +27,7 @@ import { adminRoutes } from './features/admin/api/admin.routes';
|
|||||||
import { notificationsRoutes } from './features/notifications';
|
import { notificationsRoutes } from './features/notifications';
|
||||||
import { userProfileRoutes } from './features/user-profile';
|
import { userProfileRoutes } from './features/user-profile';
|
||||||
import { onboardingRoutes } from './features/onboarding';
|
import { onboardingRoutes } from './features/onboarding';
|
||||||
|
import { userPreferencesRoutes } from './features/user-preferences';
|
||||||
import { pool } from './core/config/database';
|
import { pool } from './core/config/database';
|
||||||
|
|
||||||
async function buildApp(): Promise<FastifyInstance> {
|
async function buildApp(): Promise<FastifyInstance> {
|
||||||
@@ -84,7 +85,7 @@ async function buildApp(): Promise<FastifyInstance> {
|
|||||||
status: 'healthy',
|
status: 'healthy',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
environment: process.env['NODE_ENV'],
|
environment: process.env['NODE_ENV'],
|
||||||
features: ['admin', 'auth', 'onboarding', 'vehicles', 'documents', 'fuel-logs', 'stations', 'maintenance', 'platform', 'notifications', 'user-profile']
|
features: ['admin', 'auth', 'onboarding', 'vehicles', 'documents', 'fuel-logs', 'stations', 'maintenance', 'platform', 'notifications', 'user-profile', 'user-preferences']
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,7 +95,7 @@ async function buildApp(): Promise<FastifyInstance> {
|
|||||||
status: 'healthy',
|
status: 'healthy',
|
||||||
scope: 'api',
|
scope: 'api',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
features: ['admin', 'auth', 'onboarding', 'vehicles', 'documents', 'fuel-logs', 'stations', 'maintenance', 'platform', 'notifications', 'user-profile']
|
features: ['admin', 'auth', 'onboarding', 'vehicles', 'documents', 'fuel-logs', 'stations', 'maintenance', 'platform', 'notifications', 'user-profile', 'user-preferences']
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,6 +133,7 @@ async function buildApp(): Promise<FastifyInstance> {
|
|||||||
await app.register(adminRoutes, { prefix: '/api' });
|
await app.register(adminRoutes, { prefix: '/api' });
|
||||||
await app.register(notificationsRoutes, { prefix: '/api' });
|
await app.register(notificationsRoutes, { prefix: '/api' });
|
||||||
await app.register(userProfileRoutes, { prefix: '/api' });
|
await app.register(userProfileRoutes, { prefix: '/api' });
|
||||||
|
await app.register(userPreferencesRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
// 404 handler
|
// 404 handler
|
||||||
app.setNotFoundHandler(async (_request, reply) => {
|
app.setNotFoundHandler(async (_request, reply) => {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const adminRoutes: FastifyPluginAsync = async (fastify) => {
|
|||||||
// Initialize catalog dependencies
|
// Initialize catalog dependencies
|
||||||
const platformCacheService = new PlatformCacheService(cacheService);
|
const platformCacheService = new PlatformCacheService(cacheService);
|
||||||
const catalogService = new VehicleCatalogService(pool, platformCacheService);
|
const catalogService = new VehicleCatalogService(pool, platformCacheService);
|
||||||
const catalogImportService = new CatalogImportService(pool);
|
const catalogImportService = new CatalogImportService(pool, platformCacheService);
|
||||||
const catalogController = new CatalogController(catalogService);
|
const catalogController = new CatalogController(catalogService);
|
||||||
catalogController.setImportService(catalogImportService);
|
catalogController.setImportService(catalogImportService);
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
import { Pool } from 'pg';
|
import { Pool } from 'pg';
|
||||||
import { logger } from '../../../core/logging/logger';
|
import { logger } from '../../../core/logging/logger';
|
||||||
|
import { PlatformCacheService } from '../../platform/domain/platform-cache.service';
|
||||||
|
|
||||||
export interface ImportRow {
|
export interface ImportRow {
|
||||||
action: 'add' | 'update' | 'delete';
|
|
||||||
year: number;
|
year: number;
|
||||||
make: string;
|
make: string;
|
||||||
model: string;
|
model: string;
|
||||||
@@ -25,7 +25,6 @@ export interface ImportPreviewResult {
|
|||||||
previewId: string;
|
previewId: string;
|
||||||
toCreate: ImportRow[];
|
toCreate: ImportRow[];
|
||||||
toUpdate: ImportRow[];
|
toUpdate: ImportRow[];
|
||||||
toDelete: ImportRow[];
|
|
||||||
errors: ImportError[];
|
errors: ImportError[];
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
}
|
}
|
||||||
@@ -33,7 +32,6 @@ export interface ImportPreviewResult {
|
|||||||
export interface ImportApplyResult {
|
export interface ImportApplyResult {
|
||||||
created: number;
|
created: number;
|
||||||
updated: number;
|
updated: number;
|
||||||
deleted: number;
|
|
||||||
errors: ImportError[];
|
errors: ImportError[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +48,10 @@ export interface ExportRow {
|
|||||||
const previewCache = new Map<string, { data: ImportPreviewResult; expiresAt: number }>();
|
const previewCache = new Map<string, { data: ImportPreviewResult; expiresAt: number }>();
|
||||||
|
|
||||||
export class CatalogImportService {
|
export class CatalogImportService {
|
||||||
constructor(private pool: Pool) {}
|
constructor(
|
||||||
|
private pool: Pool,
|
||||||
|
private platformCacheService?: PlatformCacheService
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse CSV content and validate without applying changes
|
* Parse CSV content and validate without applying changes
|
||||||
@@ -59,7 +60,6 @@ export class CatalogImportService {
|
|||||||
const previewId = uuidv4();
|
const previewId = uuidv4();
|
||||||
const toCreate: ImportRow[] = [];
|
const toCreate: ImportRow[] = [];
|
||||||
const toUpdate: ImportRow[] = [];
|
const toUpdate: ImportRow[] = [];
|
||||||
const toDelete: ImportRow[] = [];
|
|
||||||
const errors: ImportError[] = [];
|
const errors: ImportError[] = [];
|
||||||
|
|
||||||
const lines = csvContent.trim().split('\n');
|
const lines = csvContent.trim().split('\n');
|
||||||
@@ -68,7 +68,6 @@ export class CatalogImportService {
|
|||||||
previewId,
|
previewId,
|
||||||
toCreate,
|
toCreate,
|
||||||
toUpdate,
|
toUpdate,
|
||||||
toDelete,
|
|
||||||
errors: [{ row: 0, error: 'CSV must have a header row and at least one data row' }],
|
errors: [{ row: 0, error: 'CSV must have a header row and at least one data row' }],
|
||||||
valid: false,
|
valid: false,
|
||||||
};
|
};
|
||||||
@@ -78,15 +77,14 @@ export class CatalogImportService {
|
|||||||
const header = this.parseCSVLine(lines[0]);
|
const header = this.parseCSVLine(lines[0]);
|
||||||
const headerLower = header.map(h => h.toLowerCase().trim());
|
const headerLower = header.map(h => h.toLowerCase().trim());
|
||||||
|
|
||||||
// Validate required headers
|
// Validate required headers (no action column required - matches export format)
|
||||||
const requiredHeaders = ['action', 'year', 'make', 'model', 'trim'];
|
const requiredHeaders = ['year', 'make', 'model', 'trim'];
|
||||||
for (const required of requiredHeaders) {
|
for (const required of requiredHeaders) {
|
||||||
if (!headerLower.includes(required)) {
|
if (!headerLower.includes(required)) {
|
||||||
return {
|
return {
|
||||||
previewId,
|
previewId,
|
||||||
toCreate,
|
toCreate,
|
||||||
toUpdate,
|
toUpdate,
|
||||||
toDelete,
|
|
||||||
errors: [{ row: 1, error: `Missing required header: ${required}` }],
|
errors: [{ row: 1, error: `Missing required header: ${required}` }],
|
||||||
valid: false,
|
valid: false,
|
||||||
};
|
};
|
||||||
@@ -95,7 +93,6 @@ export class CatalogImportService {
|
|||||||
|
|
||||||
// Find column indices
|
// Find column indices
|
||||||
const colIndices = {
|
const colIndices = {
|
||||||
action: headerLower.indexOf('action'),
|
|
||||||
year: headerLower.indexOf('year'),
|
year: headerLower.indexOf('year'),
|
||||||
make: headerLower.indexOf('make'),
|
make: headerLower.indexOf('make'),
|
||||||
model: headerLower.indexOf('model'),
|
model: headerLower.indexOf('model'),
|
||||||
@@ -113,7 +110,6 @@ export class CatalogImportService {
|
|||||||
const rowNum = i + 1;
|
const rowNum = i + 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const action = values[colIndices.action]?.toLowerCase().trim();
|
|
||||||
const year = parseInt(values[colIndices.year], 10);
|
const year = parseInt(values[colIndices.year], 10);
|
||||||
const make = values[colIndices.make]?.trim();
|
const make = values[colIndices.make]?.trim();
|
||||||
const model = values[colIndices.model]?.trim();
|
const model = values[colIndices.model]?.trim();
|
||||||
@@ -121,12 +117,6 @@ export class CatalogImportService {
|
|||||||
const engineName = colIndices.engineName >= 0 ? values[colIndices.engineName]?.trim() || null : null;
|
const engineName = colIndices.engineName >= 0 ? values[colIndices.engineName]?.trim() || null : null;
|
||||||
const transmissionType = colIndices.transmissionType >= 0 ? values[colIndices.transmissionType]?.trim() || null : null;
|
const transmissionType = colIndices.transmissionType >= 0 ? values[colIndices.transmissionType]?.trim() || null : null;
|
||||||
|
|
||||||
// Validate action
|
|
||||||
if (!['add', 'update', 'delete'].includes(action)) {
|
|
||||||
errors.push({ row: rowNum, error: `Invalid action: ${action}. Must be add, update, or delete` });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate year
|
// Validate year
|
||||||
if (isNaN(year) || year < 1900 || year > 2100) {
|
if (isNaN(year) || year < 1900 || year > 2100) {
|
||||||
errors.push({ row: rowNum, error: `Invalid year: ${values[colIndices.year]}` });
|
errors.push({ row: rowNum, error: `Invalid year: ${values[colIndices.year]}` });
|
||||||
@@ -148,7 +138,6 @@ export class CatalogImportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const row: ImportRow = {
|
const row: ImportRow = {
|
||||||
action: action as 'add' | 'update' | 'delete',
|
|
||||||
year,
|
year,
|
||||||
make,
|
make,
|
||||||
model,
|
model,
|
||||||
@@ -157,7 +146,7 @@ export class CatalogImportService {
|
|||||||
transmissionType,
|
transmissionType,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if record exists for validation
|
// Check if record exists to determine create vs update (upsert logic)
|
||||||
const existsResult = await this.pool.query(
|
const existsResult = await this.pool.query(
|
||||||
`SELECT id FROM vehicle_options
|
`SELECT id FROM vehicle_options
|
||||||
WHERE year = $1 AND make = $2 AND model = $3 AND trim = $4
|
WHERE year = $1 AND make = $2 AND model = $3 AND trim = $4
|
||||||
@@ -166,26 +155,11 @@ export class CatalogImportService {
|
|||||||
);
|
);
|
||||||
const exists = (existsResult.rowCount || 0) > 0;
|
const exists = (existsResult.rowCount || 0) > 0;
|
||||||
|
|
||||||
if (action === 'add' && exists) {
|
// Auto-detect: if exists -> update, else -> create
|
||||||
errors.push({ row: rowNum, error: `Record already exists: ${year} ${make} ${model} ${trim}` });
|
if (exists) {
|
||||||
continue;
|
toUpdate.push(row);
|
||||||
}
|
} else {
|
||||||
if ((action === 'update' || action === 'delete') && !exists) {
|
toCreate.push(row);
|
||||||
errors.push({ row: rowNum, error: `Record not found: ${year} ${make} ${model} ${trim}` });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort into appropriate bucket
|
|
||||||
switch (action) {
|
|
||||||
case 'add':
|
|
||||||
toCreate.push(row);
|
|
||||||
break;
|
|
||||||
case 'update':
|
|
||||||
toUpdate.push(row);
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
toDelete.push(row);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
errors.push({ row: rowNum, error: error.message || 'Parse error' });
|
errors.push({ row: rowNum, error: error.message || 'Parse error' });
|
||||||
@@ -196,7 +170,6 @@ export class CatalogImportService {
|
|||||||
previewId,
|
previewId,
|
||||||
toCreate,
|
toCreate,
|
||||||
toUpdate,
|
toUpdate,
|
||||||
toDelete,
|
|
||||||
errors,
|
errors,
|
||||||
valid: errors.length === 0,
|
valid: errors.length === 0,
|
||||||
};
|
};
|
||||||
@@ -230,7 +203,6 @@ export class CatalogImportService {
|
|||||||
const result: ImportApplyResult = {
|
const result: ImportApplyResult = {
|
||||||
created: 0,
|
created: 0,
|
||||||
updated: 0,
|
updated: 0,
|
||||||
deleted: 0,
|
|
||||||
errors: [],
|
errors: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -247,7 +219,7 @@ export class CatalogImportService {
|
|||||||
const engineResult = await client.query(
|
const engineResult = await client.query(
|
||||||
`INSERT INTO engines (name, fuel_type)
|
`INSERT INTO engines (name, fuel_type)
|
||||||
VALUES ($1, 'Gas')
|
VALUES ($1, 'Gas')
|
||||||
ON CONFLICT (LOWER(name)) DO UPDATE SET name = EXCLUDED.name
|
ON CONFLICT ((lower(name))) DO UPDATE SET name = EXCLUDED.name
|
||||||
RETURNING id`,
|
RETURNING id`,
|
||||||
[row.engineName]
|
[row.engineName]
|
||||||
);
|
);
|
||||||
@@ -260,7 +232,7 @@ export class CatalogImportService {
|
|||||||
const transResult = await client.query(
|
const transResult = await client.query(
|
||||||
`INSERT INTO transmissions (type)
|
`INSERT INTO transmissions (type)
|
||||||
VALUES ($1)
|
VALUES ($1)
|
||||||
ON CONFLICT (LOWER(type)) DO UPDATE SET type = EXCLUDED.type
|
ON CONFLICT ((lower(type))) DO UPDATE SET type = EXCLUDED.type
|
||||||
RETURNING id`,
|
RETURNING id`,
|
||||||
[row.transmissionType]
|
[row.transmissionType]
|
||||||
);
|
);
|
||||||
@@ -289,7 +261,7 @@ export class CatalogImportService {
|
|||||||
const engineResult = await client.query(
|
const engineResult = await client.query(
|
||||||
`INSERT INTO engines (name, fuel_type)
|
`INSERT INTO engines (name, fuel_type)
|
||||||
VALUES ($1, 'Gas')
|
VALUES ($1, 'Gas')
|
||||||
ON CONFLICT (LOWER(name)) DO UPDATE SET name = EXCLUDED.name
|
ON CONFLICT ((lower(name))) DO UPDATE SET name = EXCLUDED.name
|
||||||
RETURNING id`,
|
RETURNING id`,
|
||||||
[row.engineName]
|
[row.engineName]
|
||||||
);
|
);
|
||||||
@@ -302,7 +274,7 @@ export class CatalogImportService {
|
|||||||
const transResult = await client.query(
|
const transResult = await client.query(
|
||||||
`INSERT INTO transmissions (type)
|
`INSERT INTO transmissions (type)
|
||||||
VALUES ($1)
|
VALUES ($1)
|
||||||
ON CONFLICT (LOWER(type)) DO UPDATE SET type = EXCLUDED.type
|
ON CONFLICT ((lower(type))) DO UPDATE SET type = EXCLUDED.type
|
||||||
RETURNING id`,
|
RETURNING id`,
|
||||||
[row.transmissionType]
|
[row.transmissionType]
|
||||||
);
|
);
|
||||||
@@ -323,41 +295,21 @@ export class CatalogImportService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process deletes
|
|
||||||
for (const row of preview.toDelete) {
|
|
||||||
try {
|
|
||||||
await client.query(
|
|
||||||
`DELETE FROM vehicle_options
|
|
||||||
WHERE year = $1 AND make = $2 AND model = $3 AND trim = $4`,
|
|
||||||
[row.year, row.make, row.model, row.trim]
|
|
||||||
);
|
|
||||||
result.deleted++;
|
|
||||||
} catch (error: any) {
|
|
||||||
result.errors.push({ row: 0, error: `Failed to delete ${row.year} ${row.make} ${row.model} ${row.trim}: ${error.message}` });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
|
|
||||||
// Log the import action
|
|
||||||
await this.pool.query(
|
|
||||||
`INSERT INTO platform_change_log (change_type, resource_type, resource_id, old_value, new_value, changed_by)
|
|
||||||
VALUES ('CREATE', 'import', $1, NULL, $2, $3)`,
|
|
||||||
[
|
|
||||||
previewId,
|
|
||||||
JSON.stringify({ created: result.created, updated: result.updated, deleted: result.deleted }),
|
|
||||||
changedBy,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove preview from cache
|
// Remove preview from cache
|
||||||
previewCache.delete(previewId);
|
previewCache.delete(previewId);
|
||||||
|
|
||||||
|
// Invalidate vehicle data cache so dropdowns reflect new data
|
||||||
|
if (this.platformCacheService) {
|
||||||
|
await this.platformCacheService.invalidateVehicleData();
|
||||||
|
logger.debug('Vehicle data cache invalidated after import');
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('Catalog import completed', {
|
logger.info('Catalog import completed', {
|
||||||
previewId,
|
previewId,
|
||||||
created: result.created,
|
created: result.created,
|
||||||
updated: result.updated,
|
updated: result.updated,
|
||||||
deleted: result.deleted,
|
|
||||||
errors: result.errors.length,
|
errors: result.errors.length,
|
||||||
changedBy,
|
changedBy,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* @ai-summary Fastify route handlers for user preferences API
|
||||||
|
* @ai-context HTTP request/response handling for unit system and currency preferences
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||||
|
import { UserPreferencesRepository } from '../../../core/user-preferences/data/user-preferences.repository';
|
||||||
|
import { UpdateUserPreferencesRequest } from '../../../core/user-preferences/user-preferences.types';
|
||||||
|
import { pool } from '../../../core/config/database';
|
||||||
|
import { logger } from '../../../core/logging/logger';
|
||||||
|
|
||||||
|
export class UserPreferencesController {
|
||||||
|
private repository: UserPreferencesRepository;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.repository = new UserPreferencesRepository(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPreferences(request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const userId = (request as any).user.sub;
|
||||||
|
let preferences = await this.repository.findByUserId(userId);
|
||||||
|
|
||||||
|
// Create default preferences if none exist
|
||||||
|
if (!preferences) {
|
||||||
|
preferences = await this.repository.create({
|
||||||
|
userId,
|
||||||
|
unitSystem: 'imperial',
|
||||||
|
currencyCode: 'USD',
|
||||||
|
timeZone: 'UTC',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.code(200).send({
|
||||||
|
id: preferences.id,
|
||||||
|
userId: preferences.userId,
|
||||||
|
unitSystem: preferences.unitSystem,
|
||||||
|
currencyCode: preferences.currencyCode,
|
||||||
|
timeZone: preferences.timeZone,
|
||||||
|
createdAt: preferences.createdAt,
|
||||||
|
updatedAt: preferences.updatedAt,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error getting user preferences', { error, userId: (request as any).user?.sub });
|
||||||
|
return reply.code(500).send({
|
||||||
|
error: 'Internal server error',
|
||||||
|
message: 'Failed to get preferences',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePreferences(
|
||||||
|
request: FastifyRequest<{ Body: UpdateUserPreferencesRequest }>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userId = (request as any).user.sub;
|
||||||
|
const { unitSystem, currencyCode, timeZone } = request.body;
|
||||||
|
|
||||||
|
// Validate unitSystem if provided
|
||||||
|
if (unitSystem && !['imperial', 'metric'].includes(unitSystem)) {
|
||||||
|
return reply.code(400).send({
|
||||||
|
error: 'Bad Request',
|
||||||
|
message: 'unitSystem must be either "imperial" or "metric"',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate currencyCode if provided (basic 3-letter check)
|
||||||
|
if (currencyCode && !/^[A-Z]{3}$/.test(currencyCode)) {
|
||||||
|
return reply.code(400).send({
|
||||||
|
error: 'Bad Request',
|
||||||
|
message: 'currencyCode must be a 3-letter ISO currency code',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if preferences exist, create if not
|
||||||
|
let preferences = await this.repository.findByUserId(userId);
|
||||||
|
if (!preferences) {
|
||||||
|
preferences = await this.repository.create({
|
||||||
|
userId,
|
||||||
|
unitSystem: unitSystem || 'imperial',
|
||||||
|
currencyCode: currencyCode || 'USD',
|
||||||
|
timeZone: timeZone || 'UTC',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const updated = await this.repository.update(userId, {
|
||||||
|
unitSystem,
|
||||||
|
currencyCode,
|
||||||
|
timeZone,
|
||||||
|
});
|
||||||
|
if (updated) {
|
||||||
|
preferences = updated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.code(200).send({
|
||||||
|
id: preferences.id,
|
||||||
|
userId: preferences.userId,
|
||||||
|
unitSystem: preferences.unitSystem,
|
||||||
|
currencyCode: preferences.currencyCode,
|
||||||
|
timeZone: preferences.timeZone,
|
||||||
|
createdAt: preferences.createdAt,
|
||||||
|
updatedAt: preferences.updatedAt,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error updating user preferences', { error, userId: (request as any).user?.sub });
|
||||||
|
return reply.code(500).send({
|
||||||
|
error: 'Internal server error',
|
||||||
|
message: 'Failed to update preferences',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @ai-summary Fastify routes for user preferences API
|
||||||
|
* @ai-context Route definitions for unit system, currency, and timezone preferences
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||||
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
|
import { UserPreferencesController } from './user-preferences.controller';
|
||||||
|
import { UpdateUserPreferencesRequest } from '../../../core/user-preferences/user-preferences.types';
|
||||||
|
|
||||||
|
export const userPreferencesRoutes: FastifyPluginAsync = async (
|
||||||
|
fastify: FastifyInstance,
|
||||||
|
_opts: FastifyPluginOptions
|
||||||
|
) => {
|
||||||
|
const controller = new UserPreferencesController();
|
||||||
|
|
||||||
|
// GET /api/user/preferences - Get user preferences (creates defaults if none)
|
||||||
|
fastify.get('/user/preferences', {
|
||||||
|
preHandler: [fastify.authenticate],
|
||||||
|
handler: controller.getPreferences.bind(controller),
|
||||||
|
});
|
||||||
|
|
||||||
|
// PUT /api/user/preferences - Update user preferences
|
||||||
|
fastify.put<{ Body: UpdateUserPreferencesRequest }>('/user/preferences', {
|
||||||
|
preHandler: [fastify.authenticate],
|
||||||
|
handler: controller.updatePreferences.bind(controller),
|
||||||
|
});
|
||||||
|
};
|
||||||
6
backend/src/features/user-preferences/index.ts
Normal file
6
backend/src/features/user-preferences/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @ai-summary User preferences feature entry point
|
||||||
|
* @ai-context Exports routes for unit system, currency, and timezone preferences
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { userPreferencesRoutes } from './api/user-preferences.routes';
|
||||||
74
data/vehicle-etl/ACURA.txt
Normal file
74
data/vehicle-etl/ACURA.txt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
groupname | variable | value | itempatternid | itemvinschemaid | itemkeys | itemelementid | itemattributeid | itemcreatedon | itemwmiid | code | datatype | decode | itemsource | itemtobeqced
|
||||||
|
-----------------------------------------------------+-----------------------------------------------+-----------------------------------------------------------------------------------------------------------------+---------------+-----------------+--------------+---------------+-----------------------------------------------------------------------------------------------------------------+-------------------------+-----------+-------------------------------------+----------+--------------+----------------------------------+--------------
|
||||||
|
| Possible Values | | | | | 144 | | | | PossibleValues | string | Decoding | Corrections |
|
||||||
|
| Suggested VIN | | | | | 142 | | | | SuggestedVIN | string | Decoding | Corrections |
|
||||||
|
| Error Text | 0 - VIN decoded clean. Check Digit (9th position) is correct | | | | 191 | 0 - VIN decoded clean. Check Digit (9th position) is correct | | | ErrorText | string | Decoding | Corrections |
|
||||||
|
| Vehicle Descriptor | 5J8YE1H0*SL | | | | 196 | 5J8YE1H0*SL | | | VehicleDescriptor | string | Decoding | Corrections |
|
||||||
|
| Error Code | 0 | | | | 143 | 0 | | | ErrorCode | lookup | Decoding | Corrections |
|
||||||
|
| Additional Error Text | | | | | 156 | | | | AdditionalErrorText | string | Decoding | Corrections |
|
||||||
|
General | Vehicle Type | MULTIPURPOSE PASSENGER VEHICLE (MPV) | | | 5J8 | 39 | 7 | 2015-03-27 15:31:28.273 | 2105 | VehicleType | lookup | WMI | VehType |
|
||||||
|
General | Make | ACURA | 2074168 | 26929 | Y[ED]*H | 26 | 475 | | | Make | lookup | WMI, Pattern | pattern - model |
|
||||||
|
General | Manufacturer Name | AMERICAN HONDA MOTOR CO., INC. | | | 5J8 | 27 | 988 | | 2105 | Manufacturer | lookup | WMI | Manu. Name |
|
||||||
|
General | Model Year | 2025 | | | ***X*|Y | 29 | 2025 | | | ModelYear | int | WMI, Input | ModelYear |
|
||||||
|
General | Model | MDX | 2074168 | 26929 | Y[ED]*H | 28 | 2147 | 2024-05-21 10:17:20.287 | 2105 | Model | lookup | Pattern | Pattern |
|
||||||
|
General | Plant City | EAST LIBERTY | 2117059 | 27341 | *****|*L | 31 | EAST LIBERTY | 2024-08-27 07:36:52.53 | 2105 | PlantCity | string | Pattern | Pattern |
|
||||||
|
General | Plant State | OHIO | 2117056 | 27341 | *****|*[ALY] | 77 | OHIO | 2024-08-27 07:35:26.557 | 2105 | PlantState | string | Pattern | Pattern |
|
||||||
|
General | Plant Country | UNITED STATES (USA) | 2117055 | 27341 | *****|*[ALY] | 75 | 6 | 2024-08-27 07:35:26.54 | 2105 | PlantCountry | lookup | Pattern | Pattern |
|
||||||
|
General | Trim | SH-AWD A-Spec | 2074202 | 26929 | YE1H0 | 38 | SH-AWD A-Spec | 2024-05-21 10:24:09.057 | 2105 | Trim | string | Pattern | Pattern |
|
||||||
|
Exterior / Body | Body Class | Sport Utility Vehicle (SUV)/Multi-Purpose Vehicle (MPV) | 2074165 | 26929 | Y[ED]*H | 5 | 7 | 2024-05-21 10:17:20.143 | 2105 | BodyClass | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Body | Doors | 5 | 2074166 | 26929 | Y[ED]*H | 14 | 5 | 2024-05-21 10:17:20.223 | 2105 | Doors | int | Pattern | Pattern |
|
||||||
|
Exterior / Dimension | Gross Vehicle Weight Rating From | Class 1D: 5,001 - 6,000 lb (2,268 - 2,722 kg) | 2074167 | 26929 | Y[ED]*H | 25 | 13 | 2024-05-21 10:17:20.27 | 2105 | GVWR | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Dimension | Gross Vehicle Weight Rating To | Class 1D: 5,001 - 6,000 lb (2,268 - 2,722 kg) | 2074180 | 26929 | Y[ED]*H | 190 | 13 | 2024-05-21 10:17:20.63 | 2105 | GVWR_to | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Trailer | Trailer Type Connection | Not Applicable | | | | 116 | 0 | 2019-12-21 20:26:30.583 | | TrailerType | lookup | Pattern | Default |
|
||||||
|
Exterior / Trailer | Trailer Body Type | Not Applicable | | | | 117 | 0 | 2019-12-21 20:26:30.583 | | TrailerBodyType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Motorcycle Suspension Type | Not Applicable | | | | 152 | 0 | 2019-12-21 20:26:30.583 | | MotorcycleSuspensionType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Motorcycle Chassis Type | Not Applicable | | | | 153 | 0 | 2019-12-21 20:26:30.583 | | MotorcycleChassisType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Custom Motorcycle Type | Not Applicable | | | | 151 | 0 | 2019-12-21 20:26:30.583 | | CustomMotorcycleType | lookup | Pattern | Default |
|
||||||
|
Exterior / Bus | Bus Floor Configuration Type | Not Applicable | | | | 148 | 0 | 2019-12-21 20:26:30.583 | | BusFloorConfigType | lookup | Pattern | Default |
|
||||||
|
Exterior / Bus | Bus Type | Not Applicable | | | | 149 | 0 | 2019-12-21 20:26:30.583 | | BusType | lookup | Pattern | Default |
|
||||||
|
Mechanical / Transmission | Transmission Speeds | 10 | 2074171 | 26929 | Y[ED]*H | 63 | 10 | 2024-05-21 10:17:20.343 | 2105 | TransmissionSpeeds | int | Pattern | Pattern |
|
||||||
|
Mechanical / Transmission | Transmission Style | Automatic | 2074169 | 26929 | Y[ED]*H | 37 | 2 | 2024-05-21 10:17:20.303 | 2105 | TransmissionStyle | lookup | Pattern | Pattern |
|
||||||
|
Mechanical / Drivetrain | Drive Type | 4WD/4-Wheel Drive/4x4 | 2074181 | 26929 | YE1 | 15 | 2 | 2024-05-21 10:18:30.127 | 2105 | DriveType | lookup | Pattern | Pattern |
|
||||||
|
Engine | Other Engine Info | Direct Fuel Injection | 2074214 | 26929 | Y[ED][19] | 129 | Direct Fuel Injection | 2024-05-21 10:42:05.407 | 2105 | OtherEngineInfo | string | Pattern | Pattern |
|
||||||
|
Engine | Displacement (CC) | 3500.0 | 2074206 | 26929 | Y[ED][19] | 11 | 3500.0 | | 2105 | DisplacementCC | decimal | Pattern | Conversion 4: 3.5 * 1000 |
|
||||||
|
Engine | Displacement (CI) | 213.5831043315629938 | 2074206 | 26929 | Y[ED][19] | 12 | 213.5831043315629938 | | 2105 | DisplacementCI | decimal | Pattern | Conversion 7: 3.5 / 0.016387064 |
|
||||||
|
Engine | Engine Number of Cylinders | 6 | 2074205 | 26929 | Y[ED][19] | 9 | 6 | 2024-05-21 10:42:05.137 | 2105 | EngineCylinders | int | Pattern | Pattern |
|
||||||
|
Engine | Displacement (L) | 3.5 | 2074206 | 26929 | Y[ED][19] | 13 | 3.5 | 2024-05-21 10:42:05.157 | 2105 | DisplacementL | decimal | Pattern | Pattern |
|
||||||
|
Engine | Engine Stroke Cycles | 4 | 2074207 | 26929 | Y[ED][19] | 17 | 4 | 2024-05-21 10:42:05.287 | 2105 | EngineCycles | int | Pattern | Pattern |
|
||||||
|
Engine | Engine Model | J35Y5 | 2074208 | 26929 | Y[ED][19] | 18 | J35Y5 | 2024-05-21 10:42:05.303 | 2105 | EngineModel | string | Pattern | Pattern |
|
||||||
|
Engine | Fuel Type - Primary | Gasoline | 2074209 | 26929 | Y[ED][19] | 24 | 4 | 2024-05-21 10:42:05.32 | 2105 | FuelTypePrimary | lookup | Pattern | Pattern |
|
||||||
|
Engine | Valve Train Design | Single Overhead Cam (SOHC) | 2074210 | 26929 | Y[ED][19] | 62 | 4 | 2024-05-21 10:42:05.337 | 2105 | ValveTrainDesign | lookup | Pattern | Pattern |
|
||||||
|
Engine | Engine Configuration | V-Shaped | 2074211 | 26929 | Y[ED][19] | 64 | 2 | 2024-05-21 10:42:05.353 | 2105 | EngineConfiguration | lookup | Pattern | Pattern |
|
||||||
|
Engine | Engine Brake (hp) From | 290 | 2074212 | 26929 | Y[ED][19] | 71 | 290 | 2024-05-21 10:42:05.37 | 2105 | EngineHP | decimal | Pattern | Pattern |
|
||||||
|
Engine | Cooling Type | Water | 2074213 | 26929 | Y[ED][19] | 122 | 2 | 2024-05-21 10:42:05.39 | 2105 | CoolingType | lookup | Pattern | Pattern |
|
||||||
|
Engine | Engine Manufacturer | Honda | 2074215 | 26929 | Y[ED][19] | 146 | Honda | 2024-05-21 10:42:05.423 | 2105 | EngineManufacturer | string | Pattern | Pattern |
|
||||||
|
Passive Safety System | Other Restraint System Info | Front: Seat Belts, Mid/Rear: Seat Belt and Side Curtain Air Bag (Outer positions) / Seat Belt (Center position) | 2074179 | 26929 | Y[ED]*H | 121 | Front: Seat Belts, Mid/Rear: Seat Belt and Side Curtain Air Bag (Outer positions) / Seat Belt (Center position) | 2024-05-21 10:17:20.61 | 2105 | OtherRestraintSystemInfo | string | Pattern | Pattern |
|
||||||
|
Passive Safety System | Seat Belt Type | Manual | 2074174 | 26929 | Y[ED]*H | 79 | 1 | 2024-05-21 10:17:20.393 | 2105 | SeatBeltsAll | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Side Air Bag Locations | 1st Row (Driver and Passenger) | 2074178 | 26929 | Y[ED]*H | 107 | 3 | 2024-05-21 10:17:20.593 | 2105 | AirBagLocSide | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Knee Air Bag Locations | 1st Row (Driver and Passenger) | 2074173 | 26929 | Y[ED]*H | 69 | 3 | 2024-05-21 10:17:20.377 | 2105 | AirBagLocKnee | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Front Air Bag Locations | 1st Row (Driver and Passenger) | 2074172 | 26929 | Y[ED]*H | 65 | 3 | 2024-05-21 10:17:20.36 | 2105 | AirBagLocFront | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Curtain Air Bag Locations | 1st and 2nd and 3rd Rows | 2074170 | 26929 | Y[ED]*H | 55 | 5 | 2024-05-21 10:17:20.327 | 2105 | AirBagLocCurtain | lookup | Pattern | Pattern |
|
||||||
|
Active Safety System | Keyless Ignition | Standard | 16875 | 5601 | | 176 | 1 | 2025-04-03 14:07:35.83 | | KeylessIgnition | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Electronic Stability Control (ESC) | Standard | 16875 | 5601 | | 99 | 1 | 2025-04-03 14:07:35.587 | | ESC | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Tire Pressure Monitoring System (TPMS) Type | Direct | 16875 | 5601 | | 168 | 1 | 2025-04-03 14:07:35.767 | | TPMS | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Event Data Recorder (EDR) | Standard | 16875 | 5601 | | 175 | 1 | 2025-04-03 14:07:35.817 | | EDR | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Auto-Reverse System for Windows and Sunroofs | Standard | 16875 | 5601 | | 172 | 1 | 2025-04-03 14:07:35.803 | | AutoReverseSystem | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Anti-lock Braking System (ABS) | Standard | 16875 | 5601 | | 86 | 1 | 2025-04-03 14:07:35.56 | | ABS | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Traction Control | Standard | 16875 | 5601 | | 100 | 1 | 2025-04-03 14:07:35.6 | | TractionControl | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Maintaining Safe Distance | Adaptive Cruise Control (ACC) | Standard | 16875 | 5601 | | 81 | 1 | 2025-04-03 14:07:35.55 | | AdaptiveCruiseControl | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Forward Collision Warning (FCW) | Standard | 16875 | 5601 | | 101 | 1 | 2025-04-03 14:07:35.613 | | ForwardCollisionWarning | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Dynamic Brake Support (DBS) | Standard | 16875 | 5601 | | 170 | 1 | 2025-04-03 14:07:35.78 | | DynamicBrakeSupport | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Pedestrian Automatic Emergency Braking (PAEB) | Standard | 16875 | 5601 | | 171 | 1 | 2025-04-03 14:07:35.79 | | PedestrianAutomaticEmergencyBraking | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Crash Imminent Braking (CIB) | Standard | 16875 | 5601 | | 87 | 1 | 2025-04-03 14:07:35.573 | | CIB | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Keeping Assistance (LKA) | Standard | 16875 | 5601 | | 103 | 1 | 2025-04-03 14:07:35.64 | | LaneKeepSystem | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Departure Warning (LDW) | Standard | 16875 | 5601 | | 102 | 1 | 2025-04-03 14:07:35.627 | | LaneDepartureWarning | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Centering Assistance | Standard | 16875 | 5601 | | 194 | 1 | 2025-04-03 14:07:36.043 | | LaneCenteringAssistance | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Blind Spot Intervention (BSI) | Standard | 16875 | 5601 | | 193 | 1 | 2025-04-03 14:07:36.03 | | BlindSpotIntervention | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Backing Up and Parking | Rear Automatic Emergency Braking | Standard | 16875 | 5601 | | 192 | 1 | 2025-04-03 14:07:36.01 | | RearAutomaticEmergencyBraking | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Backing Up and Parking | Rear Cross Traffic Alert | Standard | 16875 | 5601 | | 183 | 1 | 2025-04-03 14:07:35.897 | | RearCrossTrafficAlert | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Backing Up and Parking | Backup Camera | Standard | 16875 | 5601 | | 104 | 1 | 2025-04-03 14:07:35.753 | | RearVisibilitySystem | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lighting Technologies | Headlamp Light Source | LED | 16875 | 5601 | | 178 | 3 | 2025-04-03 14:07:35.863 | | LowerBeamHeadlampLightSource | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lighting Technologies | Daytime Running Light (DRL) | Standard | 16875 | 5601 | | 177 | 1 | 2025-04-03 14:07:35.843 | | DaytimeRunningLight | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lighting Technologies | Semiautomatic Headlamp Beam Switching | Standard | 16875 | 5601 | | 179 | 1 | 2025-04-03 14:07:35.88 | | SemiautomaticHeadlampBeamSwitching | lookup | Pattern | Vehicle Specs |
|
||||||
|
(70 rows)
|
||||||
|
|
||||||
64
data/vehicle-etl/BMW.txt
Normal file
64
data/vehicle-etl/BMW.txt
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
groupname | variable | value | itempatternid | itemvinschemaid | itemkeys | itemelementid | itemattributeid | itemcreatedon | itemwmiid | code | datatype | decode | itemsource | itemtobeqced
|
||||||
|
-----------------------------------------------------+-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+-----------------+----------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------+-----------+-------------------------------------+----------+--------------+----------------------------------+--------------
|
||||||
|
| Possible Values | | | | | 144 | | | | PossibleValues | string | Decoding | Corrections |
|
||||||
|
| Additional Error Text | | | | | 156 | | | | AdditionalErrorText | string | Decoding | Corrections |
|
||||||
|
| Vehicle Descriptor | 5YM13ET0*R9 | | | | 196 | 5YM13ET0*R9 | | | VehicleDescriptor | string | Decoding | Corrections |
|
||||||
|
| Error Code | 0 | | | | 143 | 0 | | | ErrorCode | lookup | Decoding | Corrections |
|
||||||
|
| Error Text | 0 - VIN decoded clean. Check Digit (9th position) is correct | | | | 191 | 0 - VIN decoded clean. Check Digit (9th position) is correct | | | ErrorText | string | Decoding | Corrections |
|
||||||
|
| Suggested VIN | | | | | 142 | | | | SuggestedVIN | string | Decoding | Corrections |
|
||||||
|
General | Vehicle Type | MULTIPURPOSE PASSENGER VEHICLE (MPV) | | | 5YM | 39 | 7 | 2015-03-11 13:50:19.203 | 2020 | VehicleType | lookup | WMI | VehType |
|
||||||
|
General | Plant State | SOUTH CAROLINA | 1765033 | 23981 | *****|*9 | 77 | SOUTH CAROLINA | 2022-06-22 14:28:01.55 | 2020 | PlantState | string | Pattern | Pattern |
|
||||||
|
General | Plant Country | UNITED STATES (USA) | 1765032 | 23981 | *****|*9 | 75 | 6 | 2022-06-22 14:28:01.55 | 2020 | PlantCountry | lookup | Pattern | Pattern |
|
||||||
|
General | Plant City | GREER | 1765031 | 23981 | *****|*9 | 31 | GREER | 2022-06-22 14:28:01.55 | 2020 | PlantCity | string | Pattern | Pattern |
|
||||||
|
General | Manufacturer Name | BMW MANUFACTURER CORPORATION / BMW NORTH AMERICA | | | 5YM | 27 | 968 | | 2020 | Manufacturer | lookup | WMI | Manu. Name |
|
||||||
|
General | Make | BMW | 1895508 | 25151 | 13ET0 | 26 | 452 | | | Make | lookup | WMI, Pattern | pattern - model |
|
||||||
|
General | Model | X5 | 1895508 | 25151 | 13ET0 | 28 | 1717 | 2023-03-09 15:43:52.33 | 2020 | Model | lookup | Pattern | Pattern | f
|
||||||
|
General | Trim | X5 M Competition | 2031372 | 25151 | 13ET0 | 38 | X5 M Competition | 2024-02-20 12:44:30.22 | 2020 | Trim | string | Pattern | Pattern | f
|
||||||
|
General | Model Year | 2024 | | | ***X*|Y | 29 | 2024 | | | ModelYear | int | WMI, Input | ModelYear |
|
||||||
|
Exterior / Body | Doors | 4 | 1895505 | 25151 | 13ET0 | 14 | 4 | 2023-03-09 15:43:52.28 | 2020 | Doors | int | Pattern | Pattern | f
|
||||||
|
Exterior / Body | Body Class | Sport Utility Vehicle (SUV)/Multi-Purpose Vehicle (MPV) | 1895502 | 25151 | 13ET0 | 5 | 7 | 2023-03-09 15:43:52.227 | 2020 | BodyClass | lookup | Pattern | Pattern | f
|
||||||
|
Exterior / Dimension | Gross Vehicle Weight Rating From | Class 2E: 6,001 - 7,000 lb (2,722 - 3,175 kg) | 1895507 | 25151 | 13ET0 | 25 | 14 | 2023-03-09 15:43:52.313 | 2020 | GVWR | lookup | Pattern | Pattern | f
|
||||||
|
Exterior / Trailer | Trailer Body Type | Not Applicable | | | | 117 | 0 | 2019-12-21 20:26:30.583 | | TrailerBodyType | lookup | Pattern | Default |
|
||||||
|
Exterior / Trailer | Trailer Type Connection | Not Applicable | | | | 116 | 0 | 2019-12-21 20:26:30.583 | | TrailerType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Motorcycle Chassis Type | Not Applicable | | | | 153 | 0 | 2019-12-21 20:26:30.583 | | MotorcycleChassisType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Custom Motorcycle Type | Not Applicable | | | | 151 | 0 | 2019-12-21 20:26:30.583 | | CustomMotorcycleType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Motorcycle Suspension Type | Not Applicable | | | | 152 | 0 | 2019-12-21 20:26:30.583 | | MotorcycleSuspensionType | lookup | Pattern | Default |
|
||||||
|
Exterior / Bus | Bus Type | Not Applicable | | | | 149 | 0 | 2019-12-21 20:26:30.583 | | BusType | lookup | Pattern | Default |
|
||||||
|
Exterior / Bus | Bus Floor Configuration Type | Not Applicable | | | | 148 | 0 | 2019-12-21 20:26:30.583 | | BusFloorConfigType | lookup | Pattern | Default |
|
||||||
|
Engine | Engine Number of Cylinders | 8 | 1895503 | 25151 | 13ET0 | 9 | 8 | 2023-03-09 15:43:52.243 | 2020 | EngineCylinders | int | Pattern | Pattern | f
|
||||||
|
Engine | Displacement (CC) | 4400.0 | 1895504 | 25151 | 13ET0 | 11 | 4400.0 | | 2020 | DisplacementCC | decimal | Pattern | Conversion 4: 4.4 * 1000 |
|
||||||
|
Engine | Displacement (CI) | 268.5044740168220494 | 1895504 | 25151 | 13ET0 | 12 | 268.5044740168220494 | | 2020 | DisplacementCI | decimal | Pattern | Conversion 7: 4.4 / 0.016387064 |
|
||||||
|
Engine | Displacement (L) | 4.4 | 1895504 | 25151 | 13ET0 | 13 | 4.4 | 2023-03-09 15:43:52.26 | 2020 | DisplacementL | decimal | Pattern | Pattern | f
|
||||||
|
Engine | Fuel Type - Primary | Gasoline | 1895506 | 25151 | 13ET0 | 24 | 4 | 2023-03-09 15:43:52.297 | 2020 | FuelTypePrimary | lookup | Pattern | Pattern | f
|
||||||
|
Engine | Engine Brake (hp) From | 617 | 1895513 | 25151 | 13ET0 | 71 | 617 | 2023-03-09 15:43:52.537 | 2020 | EngineHP | decimal | Pattern | Pattern | f
|
||||||
|
Passive Safety System | Seat Belt Type | Manual | 1895515 | 25151 | 13ET0 | 79 | 1 | 2023-03-09 15:43:52.57 | 2020 | SeatBeltsAll | lookup | Pattern | Pattern | f
|
||||||
|
Passive Safety System | Other Restraint System Info | Seat Belt: All positions / Pretensioners, Side Airbags, Head Inflatable Restraint: Front Row & Rear Outboard Driver-side, Rear Outboard Passenger-side | 1895520 | 25151 | 13ET0 | 121 | Seat Belt: All positions / Pretensioners, Side Airbags, Head Inflatable Restraint: Front Row & Rear Outboard Driver-side, Rear Outboard Passenger-side | 2023-03-09 15:43:52.77 | 2020 | OtherRestraintSystemInfo | string | Pattern | Pattern | f
|
||||||
|
Passive Safety System | Pretensioner | Yes | 1895514 | 25151 | 13ET0 | 78 | 1 | 2023-03-09 15:43:52.553 | 2020 | Pretensioner | lookup | Pattern | Pattern | f
|
||||||
|
Passive Safety System / Air Bag Location | Knee Air Bag Locations | 1st Row (Driver and Passenger) | 1895512 | 25151 | 13ET0 | 69 | 3 | 2023-03-09 15:43:52.517 | 2020 | AirBagLocKnee | lookup | Pattern | Pattern | f
|
||||||
|
Passive Safety System / Air Bag Location | Side Air Bag Locations | 1st and 2nd Rows | 1895519 | 25151 | 13ET0 | 107 | 4 | 2023-03-09 15:43:52.633 | 2020 | AirBagLocSide | lookup | Pattern | Pattern | f
|
||||||
|
Passive Safety System / Air Bag Location | Curtain Air Bag Locations | 1st and 2nd Rows | 1895510 | 25151 | 13ET0 | 55 | 4 | 2023-03-09 15:43:52.483 | 2020 | AirBagLocCurtain | lookup | Pattern | Pattern | f
|
||||||
|
Passive Safety System / Air Bag Location | Front Air Bag Locations | 1st Row (Driver and Passenger) | 1895511 | 25151 | 13ET0 | 65 | 3 | 2023-03-09 15:43:52.5 | 2020 | AirBagLocFront | lookup | Pattern | Pattern | f
|
||||||
|
Active Safety System | Tire Pressure Monitoring System (TPMS) Type | Direct | 14937 | 4656 | | 168 | 1 | 2024-03-12 13:41:17.253 | | TPMS | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Anti-lock Braking System (ABS) | Standard | 14937 | 4656 | | 86 | 1 | 2024-03-12 13:41:17.01 | | ABS | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Keyless Ignition | Standard | 14937 | 4656 | | 176 | 1 | 2024-03-12 13:41:17.317 | | KeylessIgnition | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Auto-Reverse System for Windows and Sunroofs | Standard | 14937 | 4656 | | 172 | 1 | 2024-03-12 13:41:17.3 | | AutoReverseSystem | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Electronic Stability Control (ESC) | Standard | 14937 | 4656 | | 99 | 1 | 2024-03-12 13:41:17.05 | | ESC | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Traction Control | Standard | 14937 | 4656 | | 100 | 1 | 2024-03-12 13:41:17.183 | | TractionControl | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Maintaining Safe Distance | Adaptive Cruise Control (ACC) | Optional | 14937 | 4656 | | 81 | 3 | 2024-03-12 13:41:16.993 | | AdaptiveCruiseControl | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Dynamic Brake Support (DBS) | Standard | 14937 | 4656 | | 170 | 1 | 2024-03-12 13:41:17.267 | | DynamicBrakeSupport | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Forward Collision Warning (FCW) | Standard | 14937 | 4656 | | 101 | 1 | 2024-03-12 13:41:17.2 | | ForwardCollisionWarning | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Crash Imminent Braking (CIB) | Standard | 14937 | 4656 | | 87 | 1 | 2024-03-12 13:41:17.02 | | CIB | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Pedestrian Automatic Emergency Braking (PAEB) | Standard | 14937 | 4656 | | 171 | 1 | 2024-03-12 13:41:17.28 | | PedestrianAutomaticEmergencyBraking | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Centering Assistance | Optional | 14937 | 4656 | | 194 | 3 | 2024-03-12 13:41:17.543 | | LaneCenteringAssistance | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Blind Spot Warning (BSW) | Standard | 14937 | 4656 | | 88 | 1 | 2024-03-12 13:41:17.037 | | BlindSpotMon | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Keeping Assistance (LKA) | Optional | 14937 | 4656 | | 103 | 5 | 2024-03-12 13:41:17.227 | | LaneKeepSystem | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Blind Spot Intervention (BSI) | Optional | 14937 | 4656 | | 193 | 3 | 2024-03-12 13:41:17.53 | | BlindSpotIntervention | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Departure Warning (LDW) | Standard | 14937 | 4656 | | 102 | 1 | 2024-03-12 13:41:17.213 | | LaneDepartureWarning | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Backing Up and Parking | Rear Automatic Emergency Braking | Standard | 14937 | 4656 | | 192 | 1 | 2024-03-12 13:41:17.517 | | RearAutomaticEmergencyBraking | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Backing Up and Parking | Rear Cross Traffic Alert | Optional | 14937 | 4656 | | 183 | 2 | 2024-03-12 13:41:17.503 | | RearCrossTrafficAlert | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Backing Up and Parking | Backup Camera | Standard | 14937 | 4656 | | 104 | 1 | 2024-03-12 13:41:17.24 | | RearVisibilitySystem | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lighting Technologies | Semiautomatic Headlamp Beam Switching | Standard | 14937 | 4656 | | 179 | 1 | 2024-03-12 13:41:17.49 | | SemiautomaticHeadlampBeamSwitching | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lighting Technologies | Daytime Running Light (DRL) | Standard | 14937 | 4656 | | 177 | 1 | 2024-03-12 13:41:17.33 | | DaytimeRunningLight | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lighting Technologies | Headlamp Light Source | LED | 14937 | 4656 | | 178 | 3 | 2024-03-12 13:41:17.477 | | LowerBeamHeadlampLightSource | lookup | Pattern | Vehicle Specs |
|
||||||
|
(60 rows)
|
||||||
|
|
||||||
68
data/vehicle-etl/GMC.txt
Normal file
68
data/vehicle-etl/GMC.txt
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
groupname | variable | value | itempatternid | itemvinschemaid | itemkeys | itemelementid | itemattributeid | itemcreatedon | itemwmiid | code | datatype | decode | itemsource | itemtobeqced
|
||||||
|
-----------------------------------------------------+-----------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+-----------------+----------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------+-----------+-------------------------------------+----------+--------------+----------------------------------+--------------
|
||||||
|
| Suggested VIN | | | | | 142 | | | | SuggestedVIN | string | Decoding | Corrections |
|
||||||
|
| Error Text | 0 - VIN decoded clean. Check Digit (9th position) is correct | | | | 191 | 0 - VIN decoded clean. Check Digit (9th position) is correct | | | ErrorText | string | Decoding | Corrections |
|
||||||
|
| Additional Error Text | The Model Year decoded for this VIN may be incorrect. If you know the Model year, please enter it and decode again to get more accurate information. | | | | 156 | The Model Year decoded for this VIN may be incorrect. If you know the Model year, please enter it and decode again to get more accurate information. | | | AdditionalErrorText | string | Decoding | Corrections |
|
||||||
|
| Error Code | 0 | | | | 143 | 0 | | | ErrorCode | lookup | Decoding | Corrections |
|
||||||
|
| Vehicle Descriptor | 3GTUUFEL*PG | | | | 196 | 3GTUUFEL*PG | | | VehicleDescriptor | string | Decoding | Corrections |
|
||||||
|
| Possible Values | | | | | 144 | | | | PossibleValues | string | Decoding | Corrections |
|
||||||
|
General | Manufacturer Name | GENERAL MOTORS LLC | | | 3GT | 27 | 984 | | 2066 | Manufacturer | lookup | WMI | Manu. Name |
|
||||||
|
General | Model Year | 2023 | | | ***X*|Y | 29 | 2023 | | | ModelYear | int | WMI, Input | ModelYear |
|
||||||
|
General | Make | GMC | 1947758 | 25646 | [NPRUV][HU] | 26 | 472 | | | Make | lookup | WMI, Pattern | pattern - model |
|
||||||
|
General | Plant City | SILAO | 1947761 | 25646 | *****|*G | 31 | SILAO | 2023-07-11 14:39:30.503 | 2066 | PlantCity | string | Pattern | Pattern |
|
||||||
|
General | Series | 1500 | 1947762 | 25646 | [NPRUV][HU] | 34 | 1500 | 2023-07-11 14:39:30.503 | 2066 | Series | string | Pattern | Pattern |
|
||||||
|
General | Trim | AT4X | 1947769 | 25646 | *UF | 38 | AT4X | 2023-07-11 14:39:30.503 | 2066 | Trim | string | Pattern | Pattern |
|
||||||
|
General | Plant Country | MEXICO | 1947783 | 25646 | *****|*G | 75 | 12 | 2023-07-11 14:39:30.503 | 2066 | PlantCountry | lookup | Pattern | Pattern |
|
||||||
|
General | Plant State | GUANAJUATO | 1947786 | 25646 | *****|*G | 77 | GUANAJUATO | 2023-07-11 14:39:30.503 | 2066 | PlantState | string | Pattern | Pattern |
|
||||||
|
General | Vehicle Type | TRUCK | | | 3GT | 39 | 3 | 2015-03-24 15:16:38.563 | 2066 | VehicleType | lookup | WMI | VehType |
|
||||||
|
General | Model | Sierra | 1947758 | 25646 | [NPRUV][HU] | 28 | 1857 | 2023-07-11 14:46:06.22 | 2066 | Model | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Body | Body Class | Pickup | 1947732 | 25646 | [NPRUV][HU] | 5 | 60 | 2023-07-11 14:46:06.22 | 2066 | BodyClass | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Dimension | Gross Vehicle Weight Rating From | Class 2F: 7,001 - 8,000 lb (3,175 - 3,629 kg) | 1947756 | 25646 | [UV][HU] | 25 | 15 | 2023-07-11 14:46:18.457 | 2066 | GVWR | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Trailer | Trailer Body Type | Not Applicable | | | | 117 | 0 | 2019-12-21 20:26:30.583 | | TrailerBodyType | lookup | Pattern | Default |
|
||||||
|
Exterior / Trailer | Trailer Type Connection | Not Applicable | | | | 116 | 0 | 2019-12-21 20:26:30.583 | | TrailerType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Motorcycle Chassis Type | Not Applicable | | | | 153 | 0 | 2019-12-21 20:26:30.583 | | MotorcycleChassisType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Custom Motorcycle Type | Not Applicable | | | | 151 | 0 | 2019-12-21 20:26:30.583 | | CustomMotorcycleType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Motorcycle Suspension Type | Not Applicable | | | | 152 | 0 | 2019-12-21 20:26:30.583 | | MotorcycleSuspensionType | lookup | Pattern | Default |
|
||||||
|
Exterior / Bus | Bus Type | Not Applicable | | | | 149 | 0 | 2019-12-21 20:26:30.583 | | BusType | lookup | Pattern | Default |
|
||||||
|
Exterior / Bus | Bus Floor Configuration Type | Not Applicable | | | | 148 | 0 | 2019-12-21 20:26:30.583 | | BusFloorConfigType | lookup | Pattern | Default |
|
||||||
|
Mechanical / Drivetrain | Drive Type | 4WD/4-Wheel Drive/4x4 | 1947745 | 25646 | [NPRUV]U[A-J9] | 15 | 2 | 2023-07-11 14:39:30.503 | 2066 | DriveType | lookup | Pattern | Pattern |
|
||||||
|
Mechanical / Brake | Brake System Type | Hydraulic | 1947772 | 25646 | [NPRUV][HU] | 42 | 2 | 2023-07-11 14:46:06.22 | 2066 | BrakeSystemType | lookup | Pattern | Pattern |
|
||||||
|
Engine | Other Engine Info | DI DFM, ALUM, GEN 5 | 1947794 | 25646 | *[HU]**L | 129 | DI DFM, ALUM, GEN 5 | 2023-07-11 14:39:30.503 | 2066 | OtherEngineInfo | string | Pattern | Pattern |
|
||||||
|
Engine | Displacement (CI) | 378.3472133873401605 | 1947739 | 25646 | *[HU]**L | 12 | 378.3472133873401605 | | 2066 | DisplacementCI | decimal | Pattern | Conversion 7: 6.2 / 0.016387064 |
|
||||||
|
Engine | Engine Configuration | V-Shaped | 1947780 | 25646 | *[HU]**L | 64 | 2 | 2023-07-11 14:39:30.503 | 2066 | EngineConfiguration | lookup | Pattern | Pattern |
|
||||||
|
Engine | Fuel Type - Primary | Gasoline | 1947755 | 25646 | *[HU]**L | 24 | 4 | 2023-07-11 14:39:30.503 | 2066 | FuelTypePrimary | lookup | Pattern | Pattern |
|
||||||
|
Engine | Engine Model | L87 | 1947746 | 25646 | *[HU]**L | 18 | L87 | 2023-07-11 14:39:30.503 | 2066 | EngineModel | string | Pattern | Pattern |
|
||||||
|
Engine | Displacement (L) | 6.2 | 1947739 | 25646 | *[HU]**L | 13 | 6.2 | 2023-07-11 14:39:30.503 | 2066 | DisplacementL | decimal | Pattern | Pattern |
|
||||||
|
Engine | Engine Number of Cylinders | 8 | 1947733 | 25646 | *[HU]**L | 9 | 8 | 2023-07-11 14:39:30.503 | 2066 | EngineCylinders | int | Pattern | Pattern |
|
||||||
|
Engine | Displacement (CC) | 6200.0 | 1947739 | 25646 | *[HU]**L | 11 | 6200.0 | | 2066 | DisplacementCC | decimal | Pattern | Conversion 4: 6.2 * 1000 |
|
||||||
|
Passive Safety System | Seat Belt Type | Manual | 1947788 | 25646 | *[HU]*E | 79 | 1 | 2023-07-11 14:39:30.503 | 2066 | SeatBeltsAll | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System | Other Restraint System Info | AY0 - Active Manual Belts | 1947793 | 25646 | *[HU]*E | 121 | AY0 - Active Manual Belts | 2023-07-11 14:39:30.503 | 2066 | OtherRestraintSystemInfo | string | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Curtain Air Bag Locations | All Rows | 1947773 | 25646 | *[HU]*E | 55 | 6 | 2023-07-11 14:39:30.503 | 2066 | AirBagLocCurtain | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Front Air Bag Locations | 1st Row (Driver and Passenger) | 1947781 | 25646 | *[HU]*E | 65 | 3 | 2023-07-11 14:39:30.503 | 2066 | AirBagLocFront | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Side Air Bag Locations | 1st Row (Driver and Passenger) | 1947792 | 25646 | *[HU]*E | 107 | 3 | 2023-07-11 14:39:30.503 | 2066 | AirBagLocSide | lookup | Pattern | Pattern |
|
||||||
|
Active Safety System | Auto-Reverse System for Windows and Sunroofs | Optional | 13609 | 4140 | | 172 | 3 | 2024-01-31 16:04:17.207 | | AutoReverseSystem | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System | Electronic Stability Control (ESC) | Standard | 13609 | 4140 | | 99 | 1 | 2024-01-31 16:04:16.897 | | ESC | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System | Anti-lock Braking System (ABS) | Standard | 13609 | 4140 | | 86 | 1 | 2024-01-31 16:04:16.85 | | ABS | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System | Traction Control | Standard | 13609 | 4140 | | 100 | 1 | 2024-01-31 16:04:16.91 | | TractionControl | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System | Keyless Ignition | Standard | 13609 | 4140 | | 176 | 1 | 2024-01-31 16:04:17.257 | | KeylessIgnition | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System | Tire Pressure Monitoring System (TPMS) Type | Direct | 13609 | 4140 | | 168 | 1 | 2024-01-31 16:04:17.157 | | TPMS | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System | Event Data Recorder (EDR) | Standard | 13609 | 4140 | | 175 | 1 | 2024-01-31 16:04:17.24 | | EDR | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Maintaining Safe Distance | Adaptive Cruise Control (ACC) | Optional | 13609 | 4140 | | 81 | 3 | 2024-01-31 16:04:16.837 | | AdaptiveCruiseControl | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Forward Collision Prevention | Dynamic Brake Support (DBS) | Standard | 13609 | 4140 | | 170 | 1 | 2024-01-31 16:04:17.173 | | DynamicBrakeSupport | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Forward Collision Prevention | Crash Imminent Braking (CIB) | Standard | 13609 | 4140 | | 87 | 1 | 2024-01-31 16:04:16.863 | | CIB | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Forward Collision Prevention | Pedestrian Automatic Emergency Braking (PAEB) | Standard | 13609 | 4140 | | 171 | 1 | 2024-01-31 16:04:17.19 | | PedestrianAutomaticEmergencyBraking | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Forward Collision Prevention | Forward Collision Warning (FCW) | Standard | 13609 | 4140 | | 101 | 1 | 2024-01-31 16:04:16.923 | | ForwardCollisionWarning | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Keeping Assistance (LKA) | Standard | 13609 | 4140 | | 103 | 1 | 2024-01-31 16:04:17.103 | | LaneKeepSystem | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Departure Warning (LDW) | Standard | 13609 | 4140 | | 102 | 1 | 2024-01-31 16:04:16.94 | | LaneDepartureWarning | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Lane and Side Assist | Blind Spot Warning (BSW) | Optional | 13609 | 4140 | | 88 | 3 | 2024-01-31 16:04:16.88 | | BlindSpotMon | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Backing Up and Parking | Rear Automatic Emergency Braking | Optional | 13609 | 4140 | | 192 | 3 | 2024-01-31 16:04:17.487 | | RearAutomaticEmergencyBraking | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Backing Up and Parking | Backup Camera | Standard | 13609 | 4140 | | 104 | 1 | 2024-01-31 16:04:17.12 | | RearVisibilitySystem | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Backing Up and Parking | Rear Cross Traffic Alert | Optional | 13609 | 4140 | | 183 | 2 | 2024-01-31 16:04:17.47 | | RearCrossTrafficAlert | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Backing Up and Parking | Parking Assist | Optional | 13609 | 4140 | | 105 | 3 | 2024-01-31 16:04:17.14 | | ParkAssist | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / 911 Notification | Automatic Crash Notification (ACN) / Advanced Automatic Crash Notification (AACN) | Standard | 13609 | 4140 | | 174 | 1 | 2024-01-31 16:04:17.22 | | CAN_AACN | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Lighting Technologies | Headlamp Light Source | LED | 13609 | 4140 | | 178 | 3 | 2024-01-31 16:04:17.427 | | LowerBeamHeadlampLightSource | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Lighting Technologies | Adaptive Driving Beam (ADB) | Standard | 13609 | 4140 | | 180 | 1 | 2024-01-31 16:04:17.453 | | AdaptiveDrivingBeam | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Lighting Technologies | Daytime Running Light (DRL) | Standard | 13609 | 4140 | | 177 | 1 | 2024-01-31 16:04:17.273 | | DaytimeRunningLight | lookup | Pattern | Vehicle Specs | f
|
||||||
|
Active Safety System / Lighting Technologies | Semiautomatic Headlamp Beam Switching | Standard | 13609 | 4140 | | 179 | 1 | 2024-01-31 16:04:17.44 | | SemiautomaticHeadlampBeamSwitching | lookup | Pattern | Vehicle Specs | f
|
||||||
|
(64 rows)
|
||||||
|
|
||||||
79
data/vehicle-etl/HONDA.txt
Normal file
79
data/vehicle-etl/HONDA.txt
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
groupname | variable | value | itempatternid | itemvinschemaid | itemkeys | itemelementid | itemattributeid | itemcreatedon | itemwmiid | code | datatype | decode | itemsource | itemtobeqced
|
||||||
|
-----------------------------------------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------+---------------+-----------------+----------+---------------+----------------------------------------------------------------------------------------------------------------------------+-------------------------+-----------+-------------------------------------+----------+--------------+--------------------------------+--------------
|
||||||
|
| Error Text | 0 - VIN decoded clean. Check Digit (9th position) is correct | | | | 191 | 0 - VIN decoded clean. Check Digit (9th position) is correct | | | ErrorText | string | Decoding | Corrections |
|
||||||
|
| Additional Error Text | | | | | 156 | | | | AdditionalErrorText | string | Decoding | Corrections |
|
||||||
|
| Error Code | 0 | | | | 143 | 0 | | | ErrorCode | lookup | Decoding | Corrections |
|
||||||
|
| Possible Values | | | | | 144 | | | | PossibleValues | string | Decoding | Corrections |
|
||||||
|
| Vehicle Descriptor | 2HGFE4F8*SH | | | | 196 | 2HGFE4F8*SH | | | VehicleDescriptor | string | Decoding | Corrections |
|
||||||
|
| Suggested VIN | | | | | 142 | | | | SuggestedVIN | string | Decoding | Corrections |
|
||||||
|
General | Plant City | ALLISTON | 2123543 | 27387 | *****|*H | 31 | ALLISTON | 2024-09-16 08:37:51.523 | 2096 | PlantCity | string | Pattern | Pattern |
|
||||||
|
General | Trim | Sport Hybrid / Sport Touring Hybrid | 2086003 | 27007 | FE4F8 | 38 | Sport Hybrid / Sport Touring Hybrid | 2024-06-17 10:36:46.323 | 2096 | Trim | string | Pattern | Pattern |
|
||||||
|
General | Plant Country | CANADA | 2123544 | 27387 | *****|*H | 75 | 1 | 2024-09-16 08:37:51.54 | 2096 | PlantCountry | lookup | Pattern | Pattern |
|
||||||
|
General | Plant State | ONTARIO | 2123545 | 27387 | *****|*H | 77 | ONTARIO | 2024-09-16 08:37:51.553 | 2096 | PlantState | string | Pattern | Pattern |
|
||||||
|
General | Model Year | 2025 | | | ***X*|Y | 29 | 2025 | | | ModelYear | int | WMI, Input | ModelYear |
|
||||||
|
General | Manufacturer Name | HONDA OF CANADA MFG., A DIVISION OF HONDA CANADA INC. | | | 2HG | 27 | 990 | | 2096 | Manufacturer | lookup | WMI | Manu. Name |
|
||||||
|
General | Make | HONDA | 2086001 | 27007 | FE4F8 | 26 | 474 | | | Make | lookup | WMI, Pattern | pattern - model |
|
||||||
|
General | Vehicle Type | PASSENGER CAR | | | 2HG | 39 | 2 | 2015-03-26 16:57:39.147 | 2096 | VehicleType | lookup | WMI | VehType |
|
||||||
|
General | Model | Civic | 2086001 | 27007 | FE4F8 | 28 | 1863 | 2024-06-17 10:36:46.287 | 2096 | Model | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Body | Doors | 4 | 2085995 | 27007 | FE4F8 | 14 | 4 | 2024-06-17 10:36:46.163 | 2096 | Doors | int | Pattern | Pattern |
|
||||||
|
Exterior / Body | Body Class | Sedan/Saloon | 2085992 | 27007 | FE4F8 | 5 | 13 | 2024-06-17 10:36:45.88 | 2096 | BodyClass | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Dimension | Gross Vehicle Weight Rating From | Class 1C: 4,001 - 5,000 lb (1,814 - 2,268 kg) | 2086000 | 27007 | FE4F8 | 25 | 12 | 2024-06-17 10:36:46.267 | 2096 | GVWR | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Dimension | Gross Vehicle Weight Rating To | Class 1C: 4,001 - 5,000 lb (1,814 - 2,268 kg) | 2086015 | 27007 | FE4F8 | 190 | 12 | 2024-06-17 10:36:46.713 | 2096 | GVWR_to | lookup | Pattern | Pattern |
|
||||||
|
Exterior / Truck | Bed Type | Not Applicable | | | | 3 | 0 | 2019-12-21 20:26:30.583 | | BedType | lookup | Pattern | Default |
|
||||||
|
Exterior / Truck | Cab Type | Not Applicable | | | | 4 | 0 | 2019-12-21 20:26:30.583 | | BodyCabType | lookup | Pattern | Default |
|
||||||
|
Exterior / Trailer | Trailer Body Type | Not Applicable | | | | 117 | 0 | 2019-12-21 20:26:30.583 | | TrailerBodyType | lookup | Pattern | Default |
|
||||||
|
Exterior / Trailer | Trailer Type Connection | Not Applicable | | | | 116 | 0 | 2019-12-21 20:26:30.583 | | TrailerType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Motorcycle Suspension Type | Not Applicable | | | | 152 | 0 | 2019-12-21 20:26:30.583 | | MotorcycleSuspensionType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Custom Motorcycle Type | Not Applicable | | | | 151 | 0 | 2019-12-21 20:26:30.583 | | CustomMotorcycleType | lookup | Pattern | Default |
|
||||||
|
Exterior / Motorcycle | Motorcycle Chassis Type | Not Applicable | | | | 153 | 0 | 2019-12-21 20:26:30.583 | | MotorcycleChassisType | lookup | Pattern | Default |
|
||||||
|
Exterior / Bus | Bus Floor Configuration Type | Not Applicable | | | | 148 | 0 | 2019-12-21 20:26:30.583 | | BusFloorConfigType | lookup | Pattern | Default |
|
||||||
|
Exterior / Bus | Bus Type | Not Applicable | | | | 149 | 0 | 2019-12-21 20:26:30.583 | | BusType | lookup | Pattern | Default |
|
||||||
|
Mechanical / Transmission | Transmission Style | Electronic Continuously Variable (e-CVT) | 2086002 | 27007 | FE4F8 | 37 | 4 | 2024-06-17 10:36:46.307 | 2096 | TransmissionStyle | lookup | Pattern | Pattern |
|
||||||
|
Mechanical / Drivetrain | Drive Type | 4x2 | 2085996 | 27007 | FE4F8 | 15 | 7 | 2024-06-17 10:36:46.18 | 2096 | DriveType | lookup | Pattern | Pattern |
|
||||||
|
Mechanical / Battery | EV Drive Unit | Single Motor | 2086065 | 27007 | FE4F8 | 72 | 2 | 2024-06-17 10:59:49.297 | 2096 | EVDriveUnit | lookup | Pattern | Pattern |
|
||||||
|
Engine | Engine Manufacturer | Honda | 2086014 | 27007 | FE4F8 | 146 | Honda | 2024-06-17 10:36:46.69 | 2096 | EngineManufacturer | string | Pattern | Pattern |
|
||||||
|
Engine | Displacement (CC) | 2000 | 2085994 | 27007 | FE4F8 | 11 | 2000 | | 2096 | DisplacementCC | decimal | Pattern | Conversion 4: 2 * 1000 |
|
||||||
|
Engine | Displacement (CI) | 122.0474881894645679 | 2085994 | 27007 | FE4F8 | 12 | 122.0474881894645679 | | 2096 | DisplacementCI | decimal | Pattern | Conversion 7: 2 / 0.016387064 |
|
||||||
|
Engine | Engine Number of Cylinders | 4 | 2085993 | 27007 | FE4F8 | 9 | 4 | 2024-06-17 10:36:45.96 | 2096 | EngineCylinders | int | Pattern | Pattern |
|
||||||
|
Engine | Displacement (L) | 2 | 2085994 | 27007 | FE4F8 | 13 | 2 | 2024-06-17 10:36:46.023 | 2096 | DisplacementL | decimal | Pattern | Pattern |
|
||||||
|
Engine | Engine Stroke Cycles | 4 | 2085997 | 27007 | FE4F8 | 17 | 4 | 2024-06-17 10:36:46.203 | 2096 | EngineCycles | int | Pattern | Pattern |
|
||||||
|
Engine | Engine Model | LFC3 | 2085998 | 27007 | FE4F8 | 18 | LFC3 | 2024-06-17 10:36:46.223 | 2096 | EngineModel | string | Pattern | Pattern |
|
||||||
|
Engine | Fuel Type - Primary | Gasoline | 2085999 | 27007 | FE4F8 | 24 | 4 | 2024-06-17 10:36:46.247 | 2096 | FuelTypePrimary | lookup | Pattern | Pattern |
|
||||||
|
Engine | Valve Train Design | Dual Overhead Cam (DOHC) | 2086004 | 27007 | FE4F8 | 62 | 2 | 2024-06-17 10:36:46.343 | 2096 | ValveTrainDesign | lookup | Pattern | Pattern |
|
||||||
|
Engine | Engine Configuration | In-Line | 2086005 | 27007 | FE4F8 | 64 | 1 | 2024-06-17 10:36:46.363 | 2096 | EngineConfiguration | lookup | Pattern | Pattern |
|
||||||
|
Engine | Fuel Type - Secondary | Electric | 2086006 | 27007 | FE4F8 | 66 | 2 | 2024-06-17 10:36:46.527 | 2096 | FuelTypeSecondary | lookup | Pattern | Pattern |
|
||||||
|
Engine | Engine Brake (hp) From | 141 | 2086007 | 27007 | FE4F8 | 71 | 141 | 2024-06-17 10:36:46.543 | 2096 | EngineHP | decimal | Pattern | Pattern |
|
||||||
|
Engine | Cooling Type | Water | 2086011 | 27007 | FE4F8 | 122 | 2 | 2024-06-17 10:36:46.63 | 2096 | CoolingType | lookup | Pattern | Pattern |
|
||||||
|
Engine | Electrification Level | Strong HEV (Hybrid Electric Vehicle) | 2086012 | 27007 | FE4F8 | 126 | 2 | 2024-06-17 10:36:46.65 | 2096 | ElectrificationLevel | lookup | Pattern | Pattern |
|
||||||
|
Engine | Other Engine Info | Direct Fuel Injection / Motor: 135kW | 2086013 | 27007 | FE4F8 | 129 | Direct Fuel Injection / Motor: 135kW | 2024-06-17 10:36:46.67 | 2096 | OtherEngineInfo | string | Pattern | Pattern |
|
||||||
|
Passive Safety System | Other Restraint System Info | Front: Seat Belt / Rear: Seat Belt , Side Air Bag and Side Curtain Air Bag (Outer positions) / Seat Belt (Center position) | 2083833 | 27007 | FE[24]F | 121 | Front: Seat Belt / Rear: Seat Belt , Side Air Bag and Side Curtain Air Bag (Outer positions) / Seat Belt (Center position) | 2024-06-17 10:20:27.853 | 2096 | OtherRestraintSystemInfo | string | Pattern | Pattern |
|
||||||
|
Passive Safety System | Seat Belt Type | Manual | 2083828 | 27007 | FE[24]F | 79 | 1 | 2024-06-17 10:20:27.853 | 2096 | SeatBeltsAll | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Side Air Bag Locations | 1st and 2nd Rows | 2083832 | 27007 | FE[24]F | 107 | 4 | 2024-06-17 10:20:27.853 | 2096 | AirBagLocSide | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Knee Air Bag Locations | 1st Row (Driver and Passenger) | 2083826 | 27007 | FE[24]F | 69 | 3 | 2024-06-17 10:05:30.837 | 2096 | AirBagLocKnee | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Front Air Bag Locations | 1st Row (Driver and Passenger) | 2083825 | 27007 | FE[24]F | 65 | 3 | 2024-06-17 10:05:30.837 | 2096 | AirBagLocFront | lookup | Pattern | Pattern |
|
||||||
|
Passive Safety System / Air Bag Location | Curtain Air Bag Locations | 1st and 2nd Rows | 2083822 | 27007 | FE[24]F | 55 | 4 | 2024-06-17 10:05:30.837 | 2096 | AirBagLocCurtain | lookup | Pattern | Pattern |
|
||||||
|
Active Safety System | Anti-lock Braking System (ABS) | Standard | 16871 | 5597 | | 86 | 1 | 2025-04-03 14:40:02.47 | | ABS | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Traction Control | Standard | 16871 | 5597 | | 100 | 1 | 2025-04-03 14:40:02.52 | | TractionControl | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Automatic Pedestrian Alerting Sound (for Hybrid and EV only) | Standard | 17451 | 5597 | | 173 | 1 | 2025-04-01 10:29:03.217 | | AutomaticPedestrianAlertingSound | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Tire Pressure Monitoring System (TPMS) Type | Indirect | 16871 | 5597 | | 168 | 2 | 2025-04-03 14:40:02.6 | | TPMS | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Keyless Ignition | Standard | 16871 | 5597 | | 176 | 1 | 2025-04-03 14:40:02.793 | | KeylessIgnition | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Electronic Stability Control (ESC) | Standard | 16871 | 5597 | | 99 | 1 | 2025-04-03 14:40:02.503 | | ESC | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Auto-Reverse System for Windows and Sunroofs | Standard | 16871 | 5597 | | 172 | 1 | 2025-04-03 14:40:02.763 | | AutoReverseSystem | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System | Event Data Recorder (EDR) | Standard | 16871 | 5597 | | 175 | 1 | 2025-04-03 14:40:02.78 | | EDR | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Maintaining Safe Distance | Adaptive Cruise Control (ACC) | Standard | 16871 | 5597 | | 81 | 1 | 2025-04-03 14:40:02.337 | | AdaptiveCruiseControl | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Forward Collision Warning (FCW) | Standard | 16871 | 5597 | | 101 | 1 | 2025-04-03 14:40:02.537 | | ForwardCollisionWarning | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Dynamic Brake Support (DBS) | Standard | 16871 | 5597 | | 170 | 1 | 2025-04-03 14:40:02.61 | | DynamicBrakeSupport | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Crash Imminent Braking (CIB) | Standard | 16871 | 5597 | | 87 | 1 | 2025-04-03 14:40:02.487 | | CIB | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Forward Collision Prevention | Pedestrian Automatic Emergency Braking (PAEB) | Standard | 16871 | 5597 | | 171 | 1 | 2025-04-03 14:40:02.627 | | PedestrianAutomaticEmergencyBraking | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Keeping Assistance (LKA) | Standard | 16871 | 5597 | | 103 | 1 | 2025-04-03 14:40:02.567 | | LaneKeepSystem | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Blind Spot Intervention (BSI) | Standard | 17451 | 5597 | | 193 | 1 | 2025-04-01 10:29:03.243 | | BlindSpotIntervention | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Blind Spot Warning (BSW) | Standard | 17451 | 5597 | | 88 | 1 | 2025-04-01 10:29:03.203 | | BlindSpotMon | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lane and Side Assist | Lane Departure Warning (LDW) | Standard | 16871 | 5597 | | 102 | 1 | 2025-04-03 14:40:02.55 | | LaneDepartureWarning | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Backing Up and Parking | Backup Camera | Standard | 16871 | 5597 | | 104 | 1 | 2025-04-03 14:40:02.583 | | RearVisibilitySystem | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Backing Up and Parking | Rear Automatic Emergency Braking | Standard | 16871 | 5597 | | 192 | 1 | 2025-04-03 14:40:02.877 | | RearAutomaticEmergencyBraking | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Backing Up and Parking | Rear Cross Traffic Alert | Standard | 17451 | 5597 | | 183 | 1 | 2025-04-01 10:29:03.23 | | RearCrossTrafficAlert | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lighting Technologies | Semiautomatic Headlamp Beam Switching | Standard | 16871 | 5597 | | 179 | 1 | 2025-04-03 14:40:02.843 | | SemiautomaticHeadlampBeamSwitching | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lighting Technologies | Daytime Running Light (DRL) | Standard | 16871 | 5597 | | 177 | 1 | 2025-04-03 14:40:02.81 | | DaytimeRunningLight | lookup | Pattern | Vehicle Specs |
|
||||||
|
Active Safety System / Lighting Technologies | Headlamp Light Source | LED | 16871 | 5597 | | 178 | 3 | 2025-04-03 14:40:02.83 | | LowerBeamHeadlampLightSource | lookup | Pattern | Vehicle Specs |
|
||||||
|
(75 rows)
|
||||||
|
|
||||||
@@ -33,9 +33,8 @@ Step 1: Fetch Data from VehAPI
|
|||||||
|
|
||||||
python3 vehapi_fetch_snapshot.py --min-year 2020 --max-year 2020
|
python3 vehapi_fetch_snapshot.py --min-year 2020 --max-year 2020
|
||||||
|
|
||||||
# Full ETL workflow
|
# Full ETL workflow with cached results
|
||||||
./reset_database.sh # Clear old data
|
./reset_database.sh # Clear old data
|
||||||
python3 vehapi_fetch_snapshot.py # Fetch from API
|
|
||||||
python3 etl_generate_sql.py --snapshot-path snapshots/*.sqlite # Generate SQL
|
python3 etl_generate_sql.py --snapshot-path snapshots/*.sqlite # Generate SQL
|
||||||
./import_data.sh # Import to Postgres
|
./import_data.sh # Import to Postgres
|
||||||
docker compose exec mvp-redis redis-cli FLUSHALL # Flush Redis Cache for front end
|
docker compose exec mvp-redis redis-cli FLUSHALL # Flush Redis Cache for front end
|
||||||
|
|||||||
514
data/vehicle-etl/logical-plotting-hartmanis.md
Normal file
514
data/vehicle-etl/logical-plotting-hartmanis.md
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
# vPIC ETL Implementation Plan v2
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Extract vehicle dropdown data from NHTSA vPIC database for MY2022+ to supplement existing VehAPI data. This revised plan uses a make-specific extraction approach with proper VIN schema parsing.
|
||||||
|
|
||||||
|
## Key Changes from v1
|
||||||
|
|
||||||
|
1. **Limit to VehAPI makes only** - Only extract the 48 makes that exist in VehAPI data
|
||||||
|
2. **VIN schema-based extraction** - Extract directly from VIN patterns, not defs_model
|
||||||
|
3. **Proper field formatting** - Match VehAPI display string formats
|
||||||
|
4. **Make-specific logic** - Handle different manufacturers' data patterns
|
||||||
|
|
||||||
|
## Critical Discovery: WMI Linkage
|
||||||
|
|
||||||
|
**Must use `wmi_make` junction table (many-to-many), NOT `wmi.makeid` (one-to-many):**
|
||||||
|
```sql
|
||||||
|
-- CORRECT: via wmi_make (finds all makes including Toyota, Hyundai, etc.)
|
||||||
|
FROM vpic.make m
|
||||||
|
JOIN vpic.wmi_make wm ON wm.makeid = m.id
|
||||||
|
JOIN vpic.wmi w ON w.id = wm.wmiid
|
||||||
|
|
||||||
|
-- WRONG: via wmi.makeid (misses many major brands)
|
||||||
|
FROM vpic.make m
|
||||||
|
JOIN vpic.wmi w ON w.makeid = m.id
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Make Availability Summary
|
||||||
|
|
||||||
|
| Status | Count | Makes |
|
||||||
|
|--------|-------|-------|
|
||||||
|
| **Available (2022+ schemas)** | 46 | See table below |
|
||||||
|
| **No 2022+ data** | 2 | Hummer (discontinued 2010), Scion (discontinued 2016) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Per-Make Analysis
|
||||||
|
|
||||||
|
### Group 1: Japanese Manufacturers (Honda/Acura, Toyota/Lexus, Nissan/Infiniti)
|
||||||
|
|
||||||
|
| Make | VehAPI Name | vPIC Name | Schemas (2022+) | Status |
|
||||||
|
|------|-------------|-----------|-----------------|--------|
|
||||||
|
| Acura | Acura | Acura | 48 | Ready |
|
||||||
|
| Honda | Honda | Honda | 238 | Ready |
|
||||||
|
| Lexus | Lexus | Lexus | 90 | Ready |
|
||||||
|
| Toyota | Toyota | Toyota | 152 | Ready |
|
||||||
|
| Infiniti | INFINITI | Infiniti | 76 | Ready |
|
||||||
|
| Nissan | Nissan | Nissan | 85 | Ready |
|
||||||
|
| Mazda | Mazda | Mazda | 37 | Ready |
|
||||||
|
| Mitsubishi | Mitsubishi | Mitsubishi | 11 | Ready |
|
||||||
|
| Subaru | Subaru | Subaru | 75 | Ready |
|
||||||
|
| Isuzu | Isuzu | Isuzu | 11 | Ready |
|
||||||
|
|
||||||
|
### Group 2: Korean Manufacturers (Hyundai/Kia/Genesis)
|
||||||
|
|
||||||
|
| Make | VehAPI Name | vPIC Name | Schemas (2022+) | Status |
|
||||||
|
|------|-------------|-----------|-----------------|--------|
|
||||||
|
| Genesis | Genesis | Genesis | 74 | Ready |
|
||||||
|
| Hyundai | Hyundai | Hyundai | 177 | Ready |
|
||||||
|
| Kia | Kia | Kia | 72 | Ready |
|
||||||
|
|
||||||
|
### Group 3: American - GM (Chevrolet, GMC, Buick, Cadillac)
|
||||||
|
|
||||||
|
| Make | VehAPI Name | vPIC Name | Schemas (2022+) | Status |
|
||||||
|
|------|-------------|-----------|-----------------|--------|
|
||||||
|
| Buick | Buick | Buick | 20 | Ready |
|
||||||
|
| Cadillac | Cadillac | Cadillac | 50 | Ready |
|
||||||
|
| Chevrolet | Chevrolet | Chevrolet | 185 | Ready |
|
||||||
|
| GMC | GMC | GMC | 107 | Ready |
|
||||||
|
| Oldsmobile | Oldsmobile | Oldsmobile | 1 | Limited |
|
||||||
|
| Pontiac | Pontiac | Pontiac | 5 | Limited (2022-2024) |
|
||||||
|
|
||||||
|
### Group 4: American - Stellantis (Chrysler, Dodge, Jeep, Ram, Fiat)
|
||||||
|
|
||||||
|
| Make | VehAPI Name | vPIC Name | Schemas (2022+) | Status |
|
||||||
|
|------|-------------|-----------|-----------------|--------|
|
||||||
|
| Chrysler | Chrysler | Chrysler | 81 | Ready |
|
||||||
|
| Dodge | Dodge | Dodge | 86 | Ready |
|
||||||
|
| FIAT | FIAT | Fiat | 91 | Ready (case diff) |
|
||||||
|
| Jeep | Jeep | Jeep | 81 | Ready |
|
||||||
|
| RAM | RAM | Ram | 81 | Ready (case diff) |
|
||||||
|
| Plymouth | Plymouth | Plymouth | 4 | Limited |
|
||||||
|
|
||||||
|
### Group 5: American - Ford
|
||||||
|
|
||||||
|
| Make | VehAPI Name | vPIC Name | Schemas (2022+) | Status |
|
||||||
|
|------|-------------|-----------|-----------------|--------|
|
||||||
|
| Ford | Ford | Ford | 108 | Ready |
|
||||||
|
| Lincoln | Lincoln | Lincoln | 21 | Ready |
|
||||||
|
| Mercury | Mercury | Mercury | 0 | No data (discontinued 2011) |
|
||||||
|
|
||||||
|
### Group 6: American - EV Startups
|
||||||
|
|
||||||
|
| Make | VehAPI Name | vPIC Name | Schemas (2022+) | Status |
|
||||||
|
|------|-------------|-----------|-----------------|--------|
|
||||||
|
| Polestar | Polestar | Polestar | 12 | Ready |
|
||||||
|
| Rivian | Rivian | RIVIAN | 10 | Ready (case diff) |
|
||||||
|
| Tesla | Tesla | Tesla | 14 | Ready |
|
||||||
|
|
||||||
|
### Group 7: German Manufacturers
|
||||||
|
|
||||||
|
| Make | VehAPI Name | vPIC Name | Schemas (2022+) | Status |
|
||||||
|
|------|-------------|-----------|-----------------|--------|
|
||||||
|
| Audi | Audi | Audi | 55 | Ready |
|
||||||
|
| BMW | BMW | BMW | 61 | Ready |
|
||||||
|
| Mercedes-Benz | Mercedes-Benz | Mercedes-Benz | 39 | Ready |
|
||||||
|
| MINI | MINI | MINI | 10 | Ready |
|
||||||
|
| Porsche | Porsche | Porsche | 23 | Ready |
|
||||||
|
| smart | smart | smart | 5 | Ready |
|
||||||
|
| Volkswagen | Volkswagen | Volkswagen | 134 | Ready |
|
||||||
|
|
||||||
|
### Group 8: European Luxury
|
||||||
|
|
||||||
|
| Make | VehAPI Name | vPIC Name | Schemas (2022+) | Status |
|
||||||
|
|------|-------------|-----------|-----------------|--------|
|
||||||
|
| Bentley | Bentley | Bentley | 48 | Ready |
|
||||||
|
| Ferrari | Ferrari | Ferrari | 9 | Ready |
|
||||||
|
| Jaguar | Jaguar | Jaguar | 17 | Ready |
|
||||||
|
| Lamborghini | Lamborghini | Lamborghini | 10 | Ready |
|
||||||
|
| Lotus | Lotus | Lotus | 5 | Ready |
|
||||||
|
| Maserati | Maserati | Maserati | 19 | Ready |
|
||||||
|
| McLaren | McLaren | McLaren | 4 | Ready |
|
||||||
|
| Volvo | Volvo | Volvo | 80 | Ready |
|
||||||
|
|
||||||
|
### Group 9: Discontinued (No 2022+ Data)
|
||||||
|
|
||||||
|
| Make | VehAPI Name | Reason | Action |
|
||||||
|
|------|-------------|--------|--------|
|
||||||
|
| Hummer | Hummer | Discontinued 2010 (new EV under GMC) | Skip - use existing VehAPI |
|
||||||
|
| Scion | Scion | Discontinued 2016 | Skip - use existing VehAPI |
|
||||||
|
| Saab | Saab | Discontinued 2012 | Limited schemas (9) |
|
||||||
|
| Mercury | Mercury | Discontinued 2011 | No schemas |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Extraction Architecture
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
```
|
||||||
|
vPIC VIN Schemas → Pattern Extraction → Format Transformation → SQLite Pairs
|
||||||
|
↓
|
||||||
|
Filter by:
|
||||||
|
- 48 VehAPI makes only
|
||||||
|
- Year >= 2022
|
||||||
|
- Vehicle types (exclude motorcycles, trailers, buses)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core Query Strategy
|
||||||
|
|
||||||
|
For each allowed make:
|
||||||
|
1. Find WMIs linked to that make
|
||||||
|
2. Get VIN schemas for years 2022+
|
||||||
|
3. Extract from patterns:
|
||||||
|
- Model (from schema name or pattern)
|
||||||
|
- Trim (Element: Trim)
|
||||||
|
- Displacement (Element: DisplacementL)
|
||||||
|
- Horsepower (Element: EngineHP)
|
||||||
|
- Cylinders (Element: EngineCylinders)
|
||||||
|
- Engine Config (Element: EngineConfiguration)
|
||||||
|
- Transmission Style (Element: TransmissionStyle)
|
||||||
|
- Transmission Speeds (Element: TransmissionSpeeds)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acura Extraction Template
|
||||||
|
|
||||||
|
This pattern applies to Honda/Acura and similar well-structured manufacturers.
|
||||||
|
|
||||||
|
### Sample VIN Schema: Acura MDX 2025 (schema_id: 26929)
|
||||||
|
|
||||||
|
| Element | Code | Values |
|
||||||
|
|---------|------|--------|
|
||||||
|
| Trim | Trim | MDX, Technology, SH-AWD, SH-AWD Technology, SH-AWD A-Spec, SH-AWD Advance, SH-AWD A-Spec Advance, SH-AWD TYPE S ADVANCE |
|
||||||
|
| Displacement | DisplacementL | 3.5, 3.0 |
|
||||||
|
| Horsepower | EngineHP | 290, 355 |
|
||||||
|
| Cylinders | EngineCylinders | 6 |
|
||||||
|
| Engine Config | EngineConfiguration | V-Shaped |
|
||||||
|
| Trans Style | TransmissionStyle | Automatic |
|
||||||
|
| Trans Speeds | TransmissionSpeeds | 10 |
|
||||||
|
|
||||||
|
### Output Format
|
||||||
|
|
||||||
|
**Engine Display** (match VehAPI):
|
||||||
|
```
|
||||||
|
{DisplacementL}L {EngineHP} hp V{EngineCylinders}
|
||||||
|
→ "3.5L 290 hp V6"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Transmission Display** (match VehAPI):
|
||||||
|
```
|
||||||
|
{TransmissionSpeeds}-Speed {TransmissionStyle}
|
||||||
|
→ "10-Speed Automatic"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extraction SQL Template
|
||||||
|
|
||||||
|
```sql
|
||||||
|
WITH schema_data AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
vs.id AS schema_id,
|
||||||
|
vs.name AS schema_name,
|
||||||
|
wvs.yearfrom,
|
||||||
|
COALESCE(wvs.yearto, 2027) AS yearto,
|
||||||
|
m.name AS make_name
|
||||||
|
FROM vpic.wmi w
|
||||||
|
JOIN vpic.make m ON w.makeid = m.id
|
||||||
|
JOIN vpic.wmi_vinschema wvs ON w.id = wvs.wmiid
|
||||||
|
JOIN vpic.vinschema vs ON wvs.vinschemaid = vs.id
|
||||||
|
WHERE LOWER(m.name) IN ('acura', 'honda', ...) -- VehAPI makes
|
||||||
|
AND wvs.yearfrom >= 2022 OR (wvs.yearto >= 2022)
|
||||||
|
),
|
||||||
|
trim_data AS (
|
||||||
|
SELECT DISTINCT sd.schema_id, p.attributeid AS trim
|
||||||
|
FROM schema_data sd
|
||||||
|
JOIN vpic.pattern p ON p.vinschemaid = sd.schema_id
|
||||||
|
JOIN vpic.element e ON p.elementid = e.id
|
||||||
|
WHERE e.code = 'Trim'
|
||||||
|
),
|
||||||
|
engine_data AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
sd.schema_id,
|
||||||
|
MAX(CASE WHEN e.code = 'DisplacementL' THEN p.attributeid END) AS displacement,
|
||||||
|
MAX(CASE WHEN e.code = 'EngineHP' THEN p.attributeid END) AS hp,
|
||||||
|
MAX(CASE WHEN e.code = 'EngineCylinders' THEN p.attributeid END) AS cylinders,
|
||||||
|
MAX(CASE WHEN e.code = 'EngineConfiguration' THEN ec.name END) AS config
|
||||||
|
FROM schema_data sd
|
||||||
|
JOIN vpic.pattern p ON p.vinschemaid = sd.schema_id
|
||||||
|
JOIN vpic.element e ON p.elementid = e.id
|
||||||
|
LEFT JOIN vpic.engineconfiguration ec ON e.code = 'EngineConfiguration'
|
||||||
|
AND p.attributeid ~ '^[0-9]+$' AND ec.id = CAST(p.attributeid AS INT)
|
||||||
|
WHERE e.code IN ('DisplacementL', 'EngineHP', 'EngineCylinders', 'EngineConfiguration')
|
||||||
|
GROUP BY sd.schema_id, p.keys -- Group by VIN pattern position
|
||||||
|
),
|
||||||
|
trans_data AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
sd.schema_id,
|
||||||
|
t.name AS style,
|
||||||
|
MAX(CASE WHEN e.code = 'TransmissionSpeeds' THEN p.attributeid END) AS speeds
|
||||||
|
FROM schema_data sd
|
||||||
|
JOIN vpic.pattern p ON p.vinschemaid = sd.schema_id
|
||||||
|
JOIN vpic.element e ON p.elementid = e.id
|
||||||
|
LEFT JOIN vpic.transmission t ON e.code = 'TransmissionStyle'
|
||||||
|
AND p.attributeid ~ '^[0-9]+$' AND t.id = CAST(p.attributeid AS INT)
|
||||||
|
WHERE e.code IN ('TransmissionStyle', 'TransmissionSpeeds')
|
||||||
|
GROUP BY sd.schema_id, t.name
|
||||||
|
)
|
||||||
|
SELECT ...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Allowed Makes (48 from VehAPI)
|
||||||
|
|
||||||
|
```python
|
||||||
|
ALLOWED_MAKES = [
|
||||||
|
'Acura', 'Audi', 'Bentley', 'BMW', 'Buick', 'Cadillac', 'Chevrolet',
|
||||||
|
'Chrysler', 'Dodge', 'Ferrari', 'FIAT', 'Ford', 'Genesis', 'GMC',
|
||||||
|
'Honda', 'Hummer', 'Hyundai', 'INFINITI', 'Isuzu', 'Jaguar', 'Jeep',
|
||||||
|
'Kia', 'Lamborghini', 'Lexus', 'Lincoln', 'Lotus', 'Maserati', 'Mazda',
|
||||||
|
'McLaren', 'Mercedes-Benz', 'Mercury', 'MINI', 'Mitsubishi', 'Nissan',
|
||||||
|
'Oldsmobile', 'Plymouth', 'Polestar', 'Pontiac', 'Porsche', 'RAM',
|
||||||
|
'Rivian', 'Saab', 'Scion', 'smart', 'Subaru', 'Tesla', 'Toyota',
|
||||||
|
'Volkswagen', 'Volvo'
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Some makes may have different names in vPIC (case variations, abbreviations).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### Phase 1: Rewrite vpic_extract.py
|
||||||
|
|
||||||
|
**File:** `vpic_extract.py`
|
||||||
|
|
||||||
|
Core extraction query (uses wmi_make junction table):
|
||||||
|
```sql
|
||||||
|
WITH base AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
m.name AS make_name,
|
||||||
|
vs.id AS schema_id,
|
||||||
|
vs.name AS schema_name,
|
||||||
|
generate_series(
|
||||||
|
GREATEST(wvs.yearfrom, 2022),
|
||||||
|
COALESCE(wvs.yearto, EXTRACT(YEAR FROM NOW()) + 2)
|
||||||
|
)::INT AS year
|
||||||
|
FROM vpic.make m
|
||||||
|
JOIN vpic.wmi_make wm ON wm.makeid = m.id
|
||||||
|
JOIN vpic.wmi w ON w.id = wm.wmiid
|
||||||
|
JOIN vpic.wmi_vinschema wvs ON w.id = wvs.wmiid
|
||||||
|
JOIN vpic.vinschema vs ON wvs.vinschemaid = vs.id
|
||||||
|
WHERE LOWER(m.name) IN ({allowed_makes})
|
||||||
|
AND (wvs.yearfrom >= 2022 OR wvs.yearto >= 2022)
|
||||||
|
)
|
||||||
|
SELECT ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key functions to implement:**
|
||||||
|
1. `extract_model_from_schema_name(schema_name)` - Parse "Acura MDX Schema..." → "MDX"
|
||||||
|
2. `get_schema_patterns(schema_id)` - Get all pattern data for a schema
|
||||||
|
3. `format_engine_display(disp, hp, cyl, config)` - Format as "3.5L 290 hp V6"
|
||||||
|
4. `format_trans_display(style, speeds)` - Format as "10-Speed Automatic"
|
||||||
|
5. `generate_trans_records(has_data, style, speeds)` - Return 1 or 2 records
|
||||||
|
|
||||||
|
**Make name normalization:**
|
||||||
|
```python
|
||||||
|
MAKE_MAPPING = {
|
||||||
|
'INFINITI': 'INFINITI', # VehAPI uses all-caps
|
||||||
|
'FIAT': 'FIAT',
|
||||||
|
'RAM': 'RAM',
|
||||||
|
'RIVIAN': 'Rivian', # vPIC uses all-caps, normalize
|
||||||
|
# ... etc
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Test Extraction
|
||||||
|
|
||||||
|
Test with validated VINs:
|
||||||
|
```bash
|
||||||
|
source .venv/bin/activate
|
||||||
|
python3 vpic_extract.py --test-vin 5J8YE1H05SL018611 # Acura MDX
|
||||||
|
python3 vpic_extract.py --test-vin 5TFJA5DB4SX327537 # Toyota Tundra
|
||||||
|
python3 vpic_extract.py --test-vin 3GTUUFEL6PG140748 # GMC Sierra
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Full Extraction
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 vpic_extract.py --min-year 2022 --output-dir snapshots/vpic-2025-12
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Merge & Import
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Merge vPIC with existing VehAPI data
|
||||||
|
sqlite3 snapshots/merged/snapshot.sqlite "
|
||||||
|
CREATE TABLE pairs(...);
|
||||||
|
ATTACH 'snapshots/vehicle-drop-down.sqlite' AS db1;
|
||||||
|
ATTACH 'snapshots/vpic-2025-12/snapshot.sqlite' AS db2;
|
||||||
|
INSERT OR IGNORE INTO pairs SELECT * FROM db1.pairs WHERE year < 2022;
|
||||||
|
INSERT OR IGNORE INTO pairs SELECT * FROM db2.pairs;
|
||||||
|
"
|
||||||
|
|
||||||
|
# Generate SQL and import
|
||||||
|
python3 etl_generate_sql.py --snapshot-path snapshots/merged/snapshot.sqlite
|
||||||
|
./import_data.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
| File | Changes |
|
||||||
|
|------|---------|
|
||||||
|
| `vpic_extract.py` | Complete rewrite: VIN schema extraction, dual-record trans logic |
|
||||||
|
| `README.md` | Already updated with workflow |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
1. Extract all 41 makes with 2022+ VIN schemas
|
||||||
|
2. ~2,500-5,000 unique vehicle configurations (Year/Make/Model/Trim/Engine)
|
||||||
|
3. Transmission: Use vPIC data where available (7 makes), dual-record elsewhere
|
||||||
|
4. Output format matches VehAPI: "3.5L 290 hp V6" / "10-Speed Automatic"
|
||||||
|
5. Merge preserves 2015-2021 VehAPI data
|
||||||
|
6. QA validation passes after import
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Make Analysis Status (All Families Validated)
|
||||||
|
|
||||||
|
| Family | Makes | Status | Trans Data | Strategy |
|
||||||
|
|--------|-------|--------|------------|----------|
|
||||||
|
| Honda/Acura | Acura, Honda | VALIDATED | YES (93-97%) | Use vPIC trans data |
|
||||||
|
| Toyota/Lexus | Toyota, Lexus | VALIDATED | PARTIAL (Toyota 23%, Lexus 0%) | Dual-record for Lexus |
|
||||||
|
| Nissan/Infiniti | Nissan, Infiniti, Mitsubishi | VALIDATED | LOW (5%) | Dual-record |
|
||||||
|
| GM | Chevrolet, GMC, Buick, Cadillac | VALIDATED | LOW (0-7%) | Dual-record |
|
||||||
|
| Stellantis | Chrysler, Dodge, Jeep, Ram, Fiat | VALIDATED | NONE (0%) | Dual-record |
|
||||||
|
| Ford | Ford, Lincoln | VALIDATED | NONE (0%) | Dual-record |
|
||||||
|
| VW Group | Volkswagen, Audi, Porsche, Bentley, Lamborghini | VALIDATED | MIXED (0-84%) | VW/Audi use vPIC; others dual-record |
|
||||||
|
| BMW | BMW, MINI | VALIDATED | NONE (0%) | Dual-record |
|
||||||
|
| Mercedes | Mercedes-Benz, smart | VALIDATED | YES (52%) | Use vPIC trans data |
|
||||||
|
| Hyundai/Kia/Genesis | Hyundai, Kia, Genesis | VALIDATED | NONE (0%) | Dual-record |
|
||||||
|
| Subaru | Subaru | VALIDATED | YES (64%) | Use vPIC trans data |
|
||||||
|
| Mazda | Mazda | VALIDATED | LOW (11%) | Dual-record |
|
||||||
|
| Volvo | Volvo, Polestar | VALIDATED | LOW (3%/0%) | Dual-record |
|
||||||
|
| Exotics | Ferrari, Maserati, Jaguar, Lotus, McLaren | VALIDATED | MIXED | Per-make handling |
|
||||||
|
| EV | Tesla, Rivian | VALIDATED | NONE (0%) | Dual-record (though EVs don't have "manual") |
|
||||||
|
|
||||||
|
### Special Cases
|
||||||
|
|
||||||
|
1. **Electric Vehicles** (Tesla, Rivian, Polestar): Don't have manual transmissions
|
||||||
|
- Still create dual-record for consistency with dropdown
|
||||||
|
- User can select "Automatic" (single-speed EV)
|
||||||
|
|
||||||
|
2. **Luxury Exotics** (Ferrari, Lamborghini, etc.): Mix of automated manual/DCT
|
||||||
|
- Dual-record covers all options
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CRITICAL FINDING: Transmission Data Availability
|
||||||
|
|
||||||
|
**Most manufacturers do NOT encode transmission info in VINs.**
|
||||||
|
|
||||||
|
### VIN Decode Validation Results (12 Families)
|
||||||
|
|
||||||
|
| Family | VIN | Make | Model | Year | Trim | Engine | Trans |
|
||||||
|
|--------|-----|------|-------|------|------|--------|-------|
|
||||||
|
| Honda/Acura | 5J8YE1H05SL018611 | ACURA | MDX | 2025 | SH-AWD A-Spec | 3.5L V6 290hp | 10-Spd Auto |
|
||||||
|
| Honda/Acura | 2HGFE4F88SH315466 | HONDA | Civic | 2025 | Sport Hybrid | 2.0L I4 141hp | e-CVT |
|
||||||
|
| Toyota/Lexus | 5TFJA5DB4SX327537 | TOYOTA | Tundra | 2025 | Limited | 3.4L V6 389hp | 10-Spd Auto |
|
||||||
|
| Nissan/Infiniti | 5N1AL1FW9TC332353 | INFINITI | QX60 | 2026 | Luxe | 2.0L (no cyl/hp) | **MISSING** |
|
||||||
|
| GM | 3GTUUFEL6PG140748 | GMC | Sierra | 2023 | AT4X | 6.2L V8 (no hp) | **MISSING** |
|
||||||
|
| Stellantis | 1C4HJXEG7PW506480 | JEEP | Wrangler | 2023 | Sahara | 3.6L V6 285hp | **MISSING** |
|
||||||
|
| Ford | 1FTFW4L59SFC03038 | FORD | F-150 | 2025 | Tremor | 5.0L V8 (no hp) | **MISSING** |
|
||||||
|
| VW Group | WVWEB7CD9RW229116 | VOLKSWAGEN | Golf R | 2024 | **MISSING** | 2.0L 4cyl 315hp | Auto (no spd) |
|
||||||
|
| BMW | 5YM13ET06R9S31554 | BMW | X5 | 2024 | X5 M Competition | 4.4L 8cyl 617hp | **MISSING** |
|
||||||
|
| Mercedes | W1KAF4HB1SR287126 | MERCEDES-BENZ | C-Class | 2025 | C300 4MATIC | 2.0L I4 255hp | 9-Spd Auto |
|
||||||
|
| Hyundai/Kia | 5XYRLDJC0SG336002 | KIA | Sorento | 2025 | S | 2.5L 4cyl 191hp | **MISSING** |
|
||||||
|
| Subaru | JF1VBAF67P9806852 | SUBARU | WRX | 2023 | Premium | 2.4L 4cyl 271hp | 6-Spd Manual |
|
||||||
|
| Mazda | JM3KFBCL3R0522361 | MAZDA | CX-5 | 2024 | Preferred Pkg | 2.5L I4 187hp | 6-Spd Auto |
|
||||||
|
| Volvo | YV4M12RJ9S1094167 | VOLVO | XC60 | 2025 | Core | 2.0L 4cyl 247hp | 8-Spd Auto |
|
||||||
|
|
||||||
|
### Transmission Data Coverage in vPIC Schemas
|
||||||
|
|
||||||
|
| Coverage | Makes | Trans Schemas / Total |
|
||||||
|
|----------|-------|----------------------|
|
||||||
|
| **HIGH (>40%)** | Honda, Acura, Subaru, Audi, VW, Mercedes, Jaguar | 225/233, 42/45, 47/74, 46/55, 47/132, 13/25, 17/17 |
|
||||||
|
| **LOW (<10%)** | Chevrolet, Cadillac, Nissan, Infiniti, Mazda, Volvo | 4/164, 7/43, 4/82, 4/74, 4/36, 2/72 |
|
||||||
|
| **NONE (0%)** | GMC, Buick, Ford, Lincoln, Jeep, Dodge, Chrysler, Ram, Fiat, BMW, MINI, Porsche, Hyundai, Kia, Genesis, Lexus, Tesla, Rivian, Polestar | 0% |
|
||||||
|
|
||||||
|
### Makes WITHOUT Transmission Data (22 of 41 makes = 54%)
|
||||||
|
- **ALL Stellantis**: Chrysler, Dodge, Jeep, Ram, Fiat
|
||||||
|
- **ALL Ford**: Ford, Lincoln
|
||||||
|
- **ALL Korean**: Hyundai, Kia, Genesis
|
||||||
|
- **ALL BMW Group**: BMW, MINI
|
||||||
|
- **GM (partial)**: GMC, Buick (Chevy/Cadillac have minimal)
|
||||||
|
- **Others**: Lexus, Porsche, Bentley, Lamborghini, Tesla, Rivian, Polestar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Extraction Strategy (SELECTED)
|
||||||
|
|
||||||
|
### Dual-Record Strategy for Missing Transmission Data
|
||||||
|
|
||||||
|
When transmission data is NOT available from vPIC:
|
||||||
|
- **Create TWO records** for each vehicle configuration
|
||||||
|
- One with `trans_display = "Automatic"`, `trans_canon = "automatic"`
|
||||||
|
- One with `trans_display = "Manual"`, `trans_canon = "manual"`
|
||||||
|
|
||||||
|
This ensures:
|
||||||
|
- All transmission options available in dropdown for user selection
|
||||||
|
- User can select the correct transmission type
|
||||||
|
- No false "Unknown" values that break filtering
|
||||||
|
|
||||||
|
### Implementation Logic
|
||||||
|
|
||||||
|
```python
|
||||||
|
def generate_trans_records(has_trans_data: bool, trans_style: str, trans_speeds: str):
|
||||||
|
if has_trans_data:
|
||||||
|
# Use actual vPIC data
|
||||||
|
return [(format_trans_display(trans_style, trans_speeds),
|
||||||
|
canonicalize_trans(trans_style))]
|
||||||
|
else:
|
||||||
|
# Generate both options
|
||||||
|
return [
|
||||||
|
("Automatic", "automatic"),
|
||||||
|
("Manual", "manual")
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expected Output Growth
|
||||||
|
|
||||||
|
For makes without trans data, record count approximately doubles:
|
||||||
|
- GMC Sierra AT4X + 6.2L V8 → 2 records (Auto + Manual)
|
||||||
|
- Ford F-150 Tremor + 5.0L V8 → 2 records (Auto + Manual)
|
||||||
|
|
||||||
|
This is acceptable as it provides complete dropdown coverage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validated Extraction Examples
|
||||||
|
|
||||||
|
### Acura MDX 2025 (VIN: 5J8YE1H05SL018611)
|
||||||
|
- **vPIC**: Make=ACURA, Model=MDX, Trim=SH-AWD A-Spec, Engine=3.5L V6 290hp, Trans=10-Speed Automatic
|
||||||
|
- **Output**: `3.5L 290 hp V6` | `10-Speed Automatic`
|
||||||
|
|
||||||
|
### Honda Civic 2025 (VIN: 2HGFE4F88SH315466)
|
||||||
|
- **vPIC**: Make=HONDA, Model=Civic, Trim=Sport Hybrid / Sport Touring Hybrid, Engine=2L I4 141hp, Trans=e-CVT
|
||||||
|
- **Output**: `2.0L 141 hp I4` | `Electronic Continuously Variable (e-CVT)`
|
||||||
|
|
||||||
|
### Toyota Tundra 2025 (VIN: 5TFJA5DB4SX327537)
|
||||||
|
- **vPIC**: Make=TOYOTA, Model=Tundra, Trim=Limited, Engine=3.4L V6 389hp, Trans=10-Speed Automatic
|
||||||
|
- **Output**: `3.4L 389 hp V6` | `10-Speed Automatic`
|
||||||
|
|
||||||
|
### Mercedes C-Class 2025 (VIN: W1KAF4HB1SR287126)
|
||||||
|
- **vPIC**: Make=MERCEDES-BENZ, Model=C-Class, Trim=C300 4MATIC, Engine=2.0L I4 255hp, Trans=9-Speed Automatic
|
||||||
|
- **Output**: `2.0L 255 hp I4` | `9-Speed Automatic`
|
||||||
|
|
||||||
|
### Subaru WRX 2023 (VIN: JF1VBAF67P9806852)
|
||||||
|
- **vPIC**: Make=SUBARU, Model=WRX, Trim=Premium, Engine=2.4L 4cyl 271hp, Trans=6-Speed Manual
|
||||||
|
- **Output**: `2.4L 271 hp 4cyl` | `6-Speed Manual`
|
||||||
|
|
||||||
|
### Mazda CX-5 2024 (VIN: JM3KFBCL3R0522361)
|
||||||
|
- **vPIC**: Make=MAZDA, Model=CX-5, Trim=Preferred Package, Engine=2.5L I4 187hp, Trans=6-Speed Automatic
|
||||||
|
- **Output**: `2.5L 187 hp I4` | `6-Speed Automatic`
|
||||||
|
|
||||||
|
### Volvo XC60 2025 (VIN: YV4M12RJ9S1094167)
|
||||||
|
- **vPIC**: Make=VOLVO, Model=XC60, Trim=Core, Engine=2.0L 4cyl 247hp, Trans=8-Speed Automatic
|
||||||
|
- **Output**: `2.0L 247 hp 4cyl` | `8-Speed Automatic`
|
||||||
@@ -19,18 +19,17 @@ comprehensive spec.md - containing requirements, architecture decisions, data mo
|
|||||||
You are a senior software engineer specializsing in NodeJS, Typescript, front end and back end development. You will be delegating tasks to the platform-agent, feature-agent, first-frontend-agent and quality-agent when appropriate.
|
You are a senior software engineer specializsing in NodeJS, Typescript, front end and back end development. You will be delegating tasks to the platform-agent, feature-agent, first-frontend-agent and quality-agent when appropriate.
|
||||||
|
|
||||||
*** ACTION ***
|
*** ACTION ***
|
||||||
- You will be fixing a workflow logic in the new user sign up wizard.
|
|
||||||
- Make no assumptions.
|
- Make no assumptions.
|
||||||
- Ask clarifying questions.
|
- Ask clarifying questions.
|
||||||
- Ultrathink
|
- Ultrathink
|
||||||
|
- You will be removing functionality that was never implemented.
|
||||||
|
|
||||||
*** CONTEXT ***
|
*** CONTEXT ***
|
||||||
- This is a modern web app for managing a vehicle fleet. It has both a desktop and mobile versions of the site that both need to maintain feature parity. It's currently deployed via docker compose but in the future will be deployed via k8s.
|
- This is a modern web app for managing a vehicle fleet. It has both a desktop and mobile versions of the site that both need to maintain feature parity. It's currently deployed via docker compose but in the future will be deployed via k8s.
|
||||||
- Read README.md CLAUDE.md and AI-INDEX.md and follow relevant instructions to understand this code repository in the context of this change.
|
- Read README.md CLAUDE.md and AI-INDEX.md and follow relevant instructions to understand this code repository in the context of this change.
|
||||||
- When a new user signs up, they are immediately redirected to https://motovaultpro.com/verify-email which is supposed to send them through a new user wizard.
|
- There is a "station management" section for admin users that was never implemented and needs to be removed.
|
||||||
- The user is also allowed to login before the email is confirmed. There are API errors but the login is allowed.
|
- The route is located at https://motovaultpro.com/garage/settings/admin/stations
|
||||||
- It should not even let people login without a verified email.
|
- Remove the front end and any associated routes or logic in the code.
|
||||||
- The new user wizard exists. It worked in the past. Recent user changes must have broken the workflow.
|
|
||||||
|
|
||||||
*** CHANGES TO IMPLEMENT ***
|
*** CHANGES TO IMPLEMENT ***
|
||||||
- Research this code base and ask iterative questions to compile a complete plan.
|
- Research this code base and ask iterative questions to compile a complete plan.
|
||||||
@@ -53,3 +52,30 @@ You are a senior software engineer specializsing in NodeJS, Typescript, front en
|
|||||||
|
|
||||||
*** ACTION - CHANGES TO IMPLEMENT ***
|
*** ACTION - CHANGES TO IMPLEMENT ***
|
||||||
- Replicate the same secrets process that's implemented with the Google API and Auth0 API keys.
|
- Replicate the same secrets process that's implemented with the Google API and Auth0 API keys.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*** ROLE ***
|
||||||
|
- You are a senior DBA with expert knowledge in Postgres SQL.
|
||||||
|
|
||||||
|
*** ACTION ***
|
||||||
|
- Make no assumptions.
|
||||||
|
- Ask clarifying questions.
|
||||||
|
- Ultrathink
|
||||||
|
- You will be implementing an ETL process that takes a export of the NHTSA vPIC database in Postgres and transforming it for use in this application.
|
||||||
|
|
||||||
|
*** CONTEXT ***
|
||||||
|
- This is a modern web app for managing a vehicle fleet. It has both a desktop and mobile versions of the site that both need to maintain feature parity. It's currently deployed via docker compose but in the future will be deployed via k8s.
|
||||||
|
- Read README.md CLAUDE.md and AI-INDEX.md and follow relevant instructions to understand this code repository in the context of this change.
|
||||||
|
- There is an existing database import process in this directory. This process works and should not be changed.
|
||||||
|
- The source database from the NHTSA vPIC dataset is located in the @vpic-source directory
|
||||||
|
- Deep research needs to be conducted on how to execute this ETL process.
|
||||||
|
- The source database is designed for VIN decoding only.
|
||||||
|
- Example VIN: 2025 Honda Civic Hybrid - 2HGFE4F88SH315466
|
||||||
|
- Example VIN: 2023 GMC Sierra 1500 AT4x - 3GTUUFEL6PG140748
|
||||||
|
- Example VIN: 2017 Chevrolet Corvette Z06 - 1G1YU3D64H5602799
|
||||||
|
|
||||||
|
*** CHANGES TO IMPLEMENT ***
|
||||||
|
- Research this code base and ask iterative questions to compile a complete plan.
|
||||||
|
- generate a project plan
|
||||||
|
- break into bite-sized tasks and milestones
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* @ai-summary React context for unit system preferences
|
* @ai-summary React context for unit system preferences with backend sync
|
||||||
* @ai-context Provides unit preferences and conversion functions throughout the app
|
* @ai-context Provides unit preferences, conversion functions, and currency symbol throughout the app
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
|
||||||
import { UnitSystem, UnitPreferences } from './units.types';
|
import { UnitSystem, UnitPreferences } from './units.types';
|
||||||
import { safeStorage } from '../utils/safe-storage';
|
import { safeStorage } from '../utils/safe-storage';
|
||||||
import {
|
import {
|
||||||
@@ -18,11 +18,16 @@ import {
|
|||||||
getVolumeUnit,
|
getVolumeUnit,
|
||||||
getFuelEfficiencyUnit
|
getFuelEfficiencyUnit
|
||||||
} from './units.utils';
|
} from './units.utils';
|
||||||
|
import { usePreferences, useUpdatePreferences } from '../../features/settings/hooks/usePreferences';
|
||||||
|
import { useAuth0 } from '@auth0/auth0-react';
|
||||||
|
|
||||||
interface UnitsContextType {
|
interface UnitsContextType {
|
||||||
unitSystem: UnitSystem;
|
unitSystem: UnitSystem;
|
||||||
setUnitSystem: (system: UnitSystem) => void;
|
setUnitSystem: (system: UnitSystem) => void;
|
||||||
preferences: UnitPreferences;
|
preferences: UnitPreferences;
|
||||||
|
currencySymbol: string;
|
||||||
|
currencyCode: string;
|
||||||
|
isLoading: boolean;
|
||||||
|
|
||||||
// Conversion functions
|
// Conversion functions
|
||||||
convertDistance: (miles: number) => number;
|
convertDistance: (miles: number) => number;
|
||||||
@@ -33,7 +38,7 @@ interface UnitsContextType {
|
|||||||
formatDistance: (miles: number, precision?: number) => string;
|
formatDistance: (miles: number, precision?: number) => string;
|
||||||
formatVolume: (gallons: number, precision?: number) => string;
|
formatVolume: (gallons: number, precision?: number) => string;
|
||||||
formatFuelEfficiency: (mpg: number, precision?: number) => string;
|
formatFuelEfficiency: (mpg: number, precision?: number) => string;
|
||||||
formatPrice: (pricePerGallon: number, currency?: string, precision?: number) => string;
|
formatPrice: (pricePerGallon: number, precision?: number) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UnitsContext = createContext<UnitsContextType | undefined>(undefined);
|
const UnitsContext = createContext<UnitsContextType | undefined>(undefined);
|
||||||
@@ -43,33 +48,77 @@ interface UnitsProviderProps {
|
|||||||
initialSystem?: UnitSystem;
|
initialSystem?: UnitSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Currency symbol mapping
|
||||||
|
const getCurrencySymbol = (system: UnitSystem): string => {
|
||||||
|
return system === 'metric' ? '\u20AC' : '$'; // EUR or USD
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCurrencyCode = (system: UnitSystem): string => {
|
||||||
|
return system === 'metric' ? 'EUR' : 'USD';
|
||||||
|
};
|
||||||
|
|
||||||
export const UnitsProvider: React.FC<UnitsProviderProps> = ({
|
export const UnitsProvider: React.FC<UnitsProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
initialSystem = 'imperial'
|
initialSystem = 'imperial'
|
||||||
}) => {
|
}) => {
|
||||||
const [unitSystem, setUnitSystem] = useState<UnitSystem>(initialSystem);
|
const [unitSystem, setUnitSystemState] = useState<UnitSystem>(initialSystem);
|
||||||
|
const [hasInitialized, setHasInitialized] = useState(false);
|
||||||
|
|
||||||
// Load unit preference from storage on mount
|
const { isAuthenticated } = useAuth0();
|
||||||
|
const { data: backendPreferences, isLoading: preferencesLoading } = usePreferences();
|
||||||
|
const updatePreferencesMutation = useUpdatePreferences();
|
||||||
|
|
||||||
|
// Load unit preference from localStorage on mount (fallback)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
if (!hasInitialized) {
|
||||||
const stored = safeStorage.getItem('motovaultpro-unit-system');
|
try {
|
||||||
if (stored === 'imperial' || stored === 'metric') {
|
const stored = safeStorage.getItem('motovaultpro-unit-system');
|
||||||
setUnitSystem(stored);
|
if (stored === 'imperial' || stored === 'metric') {
|
||||||
|
setUnitSystemState(stored);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[Units] Failed to load unit system preference from storage:', error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.warn('[Units] Failed to load unit system preference:', error);
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, [hasInitialized]);
|
||||||
|
|
||||||
// Save unit preference to storage when changed
|
// Sync with backend preferences when they load (takes priority over localStorage)
|
||||||
const handleSetUnitSystem = (system: UnitSystem) => {
|
useEffect(() => {
|
||||||
setUnitSystem(system);
|
if (backendPreferences?.unitSystem && !hasInitialized) {
|
||||||
|
setUnitSystemState(backendPreferences.unitSystem);
|
||||||
|
// Also update localStorage to keep them in sync
|
||||||
|
try {
|
||||||
|
safeStorage.setItem('motovaultpro-unit-system', backendPreferences.unitSystem);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[Units] Failed to sync unit system to storage:', error);
|
||||||
|
}
|
||||||
|
setHasInitialized(true);
|
||||||
|
}
|
||||||
|
}, [backendPreferences, hasInitialized]);
|
||||||
|
|
||||||
|
// Mark as initialized when not authenticated (localStorage is the source of truth)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAuthenticated && !hasInitialized) {
|
||||||
|
setHasInitialized(true);
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, hasInitialized]);
|
||||||
|
|
||||||
|
// Handle unit system change with backend sync
|
||||||
|
const handleSetUnitSystem = useCallback((system: UnitSystem) => {
|
||||||
|
setUnitSystemState(system);
|
||||||
|
|
||||||
|
// Always save to localStorage (offline fallback)
|
||||||
try {
|
try {
|
||||||
safeStorage.setItem('motovaultpro-unit-system', system);
|
safeStorage.setItem('motovaultpro-unit-system', system);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[Units] Failed to save unit system preference:', error);
|
console.warn('[Units] Failed to save unit system preference:', error);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
// Sync to backend if authenticated
|
||||||
|
if (isAuthenticated) {
|
||||||
|
updatePreferencesMutation.mutate({ unitSystem: system });
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, updatePreferencesMutation]);
|
||||||
|
|
||||||
// Generate preferences object based on current system
|
// Generate preferences object based on current system
|
||||||
const preferences: UnitPreferences = {
|
const preferences: UnitPreferences = {
|
||||||
@@ -79,28 +128,35 @@ export const UnitsProvider: React.FC<UnitsProviderProps> = ({
|
|||||||
fuelEfficiency: getFuelEfficiencyUnit(unitSystem),
|
fuelEfficiency: getFuelEfficiencyUnit(unitSystem),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Currency based on unit system
|
||||||
|
const currencySymbol = getCurrencySymbol(unitSystem);
|
||||||
|
const currencyCode = getCurrencyCode(unitSystem);
|
||||||
|
|
||||||
// Conversion functions using current unit system
|
// Conversion functions using current unit system
|
||||||
const convertDistance = (miles: number) => convertDistanceBySystem(miles, unitSystem);
|
const convertDistance = useCallback((miles: number) => convertDistanceBySystem(miles, unitSystem), [unitSystem]);
|
||||||
const convertVolume = (gallons: number) => convertVolumeBySystem(gallons, unitSystem);
|
const convertVolume = useCallback((gallons: number) => convertVolumeBySystem(gallons, unitSystem), [unitSystem]);
|
||||||
const convertFuelEfficiency = (mpg: number) => convertFuelEfficiencyBySystem(mpg, unitSystem);
|
const convertFuelEfficiency = useCallback((mpg: number) => convertFuelEfficiencyBySystem(mpg, unitSystem), [unitSystem]);
|
||||||
|
|
||||||
// Formatting functions using current unit system
|
// Formatting functions using current unit system
|
||||||
const formatDistance = (miles: number, precision?: number) =>
|
const formatDistance = useCallback((miles: number, precision?: number) =>
|
||||||
formatDistanceBySystem(miles, unitSystem, precision);
|
formatDistanceBySystem(miles, unitSystem, precision), [unitSystem]);
|
||||||
|
|
||||||
const formatVolume = (gallons: number, precision?: number) =>
|
const formatVolume = useCallback((gallons: number, precision?: number) =>
|
||||||
formatVolumeBySystem(gallons, unitSystem, precision);
|
formatVolumeBySystem(gallons, unitSystem, precision), [unitSystem]);
|
||||||
|
|
||||||
const formatFuelEfficiency = (mpg: number, precision?: number) =>
|
const formatFuelEfficiency = useCallback((mpg: number, precision?: number) =>
|
||||||
formatFuelEfficiencyBySystem(mpg, unitSystem, precision);
|
formatFuelEfficiencyBySystem(mpg, unitSystem, precision), [unitSystem]);
|
||||||
|
|
||||||
const formatPrice = (pricePerGallon: number, currency?: string, precision?: number) =>
|
const formatPrice = useCallback((pricePerGallon: number, precision?: number) =>
|
||||||
formatPriceBySystem(pricePerGallon, unitSystem, currency, precision);
|
formatPriceBySystem(pricePerGallon, unitSystem, currencyCode, precision), [unitSystem, currencyCode]);
|
||||||
|
|
||||||
const value: UnitsContextType = {
|
const value: UnitsContextType = {
|
||||||
unitSystem,
|
unitSystem,
|
||||||
setUnitSystem: handleSetUnitSystem,
|
setUnitSystem: handleSetUnitSystem,
|
||||||
preferences,
|
preferences,
|
||||||
|
currencySymbol,
|
||||||
|
currencyCode,
|
||||||
|
isLoading: preferencesLoading && isAuthenticated,
|
||||||
convertDistance,
|
convertDistance,
|
||||||
convertVolume,
|
convertVolume,
|
||||||
convertFuelEfficiency,
|
convertFuelEfficiency,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
export type UnitSystem = 'imperial' | 'metric';
|
export type UnitSystem = 'imperial' | 'metric';
|
||||||
export type DistanceUnit = 'miles' | 'km';
|
export type DistanceUnit = 'miles' | 'km';
|
||||||
export type VolumeUnit = 'gallons' | 'liters';
|
export type VolumeUnit = 'gallons' | 'liters';
|
||||||
export type FuelEfficiencyUnit = 'mpg' | 'kml';
|
export type FuelEfficiencyUnit = 'mpg' | 'l100km';
|
||||||
|
|
||||||
export interface UnitPreferences {
|
export interface UnitPreferences {
|
||||||
system: UnitSystem;
|
system: UnitSystem;
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ const MILES_TO_KM = 1.60934;
|
|||||||
const KM_TO_MILES = 0.621371;
|
const KM_TO_MILES = 0.621371;
|
||||||
const GALLONS_TO_LITERS = 3.78541;
|
const GALLONS_TO_LITERS = 3.78541;
|
||||||
const LITERS_TO_GALLONS = 0.264172;
|
const LITERS_TO_GALLONS = 0.264172;
|
||||||
// For km/L conversion
|
// For L/100km conversion (inverse relationship: lower L/100km = better efficiency)
|
||||||
const MPG_TO_KML = 1.60934 / 3.78541; // ≈ 0.425144
|
const MPG_TO_L100KM_FACTOR = 235.214;
|
||||||
const KML_TO_MPG = 3.78541 / 1.60934; // ≈ 2.352145
|
|
||||||
|
|
||||||
// Distance Conversions
|
// Distance Conversions
|
||||||
export function convertDistance(value: number, fromUnit: DistanceUnit, toUnit: DistanceUnit): number {
|
export function convertDistance(value: number, fromUnit: DistanceUnit, toUnit: DistanceUnit): number {
|
||||||
@@ -58,16 +57,16 @@ export function convertVolumeBySystem(gallons: number, toSystem: UnitSystem): nu
|
|||||||
return gallons;
|
return gallons;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fuel Efficiency Conversions
|
// Fuel Efficiency Conversions (L/100km is inverse: lower = better)
|
||||||
export function convertFuelEfficiency(value: number, fromUnit: FuelEfficiencyUnit, toUnit: FuelEfficiencyUnit): number {
|
export function convertFuelEfficiency(value: number, fromUnit: FuelEfficiencyUnit, toUnit: FuelEfficiencyUnit): number {
|
||||||
if (fromUnit === toUnit) return value;
|
if (fromUnit === toUnit) return value;
|
||||||
|
|
||||||
if (fromUnit === 'mpg' && toUnit === 'kml') {
|
if (fromUnit === 'mpg' && toUnit === 'l100km') {
|
||||||
return value * MPG_TO_KML;
|
return value === 0 ? 0 : MPG_TO_L100KM_FACTOR / value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromUnit === 'kml' && toUnit === 'mpg') {
|
if (fromUnit === 'l100km' && toUnit === 'mpg') {
|
||||||
return value * KML_TO_MPG;
|
return value === 0 ? 0 : MPG_TO_L100KM_FACTOR / value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@@ -75,7 +74,7 @@ export function convertFuelEfficiency(value: number, fromUnit: FuelEfficiencyUni
|
|||||||
|
|
||||||
export function convertFuelEfficiencyBySystem(mpg: number, toSystem: UnitSystem): number {
|
export function convertFuelEfficiencyBySystem(mpg: number, toSystem: UnitSystem): number {
|
||||||
if (toSystem === 'metric') {
|
if (toSystem === 'metric') {
|
||||||
return convertFuelEfficiency(mpg, 'mpg', 'kml');
|
return convertFuelEfficiency(mpg, 'mpg', 'l100km');
|
||||||
}
|
}
|
||||||
return mpg;
|
return mpg;
|
||||||
}
|
}
|
||||||
@@ -111,7 +110,7 @@ export function formatVolume(value: number, unit: VolumeUnit, precision = 2): st
|
|||||||
|
|
||||||
export function formatFuelEfficiency(value: number, unit: FuelEfficiencyUnit, precision = 1): string {
|
export function formatFuelEfficiency(value: number, unit: FuelEfficiencyUnit, precision = 1): string {
|
||||||
if (typeof value !== 'number' || isNaN(value)) {
|
if (typeof value !== 'number' || isNaN(value)) {
|
||||||
return unit === 'mpg' ? '0 MPG' : '0 km/L';
|
return unit === 'mpg' ? '0 MPG' : '0 L/100km';
|
||||||
}
|
}
|
||||||
|
|
||||||
const rounded = parseFloat(value.toFixed(precision));
|
const rounded = parseFloat(value.toFixed(precision));
|
||||||
@@ -119,7 +118,7 @@ export function formatFuelEfficiency(value: number, unit: FuelEfficiencyUnit, pr
|
|||||||
if (unit === 'mpg') {
|
if (unit === 'mpg') {
|
||||||
return `${rounded} MPG`;
|
return `${rounded} MPG`;
|
||||||
} else {
|
} else {
|
||||||
return `${rounded} km/L`;
|
return `${rounded} L/100km`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,8 +167,8 @@ export function formatVolumeBySystem(gallons: number, system: UnitSystem, precis
|
|||||||
|
|
||||||
export function formatFuelEfficiencyBySystem(mpg: number, system: UnitSystem, precision = 1): string {
|
export function formatFuelEfficiencyBySystem(mpg: number, system: UnitSystem, precision = 1): string {
|
||||||
if (system === 'metric') {
|
if (system === 'metric') {
|
||||||
const kml = convertFuelEfficiencyBySystem(mpg, system);
|
const l100km = convertFuelEfficiencyBySystem(mpg, system);
|
||||||
return formatFuelEfficiency(kml, 'kml', precision);
|
return formatFuelEfficiency(l100km, 'l100km', precision);
|
||||||
}
|
}
|
||||||
return formatFuelEfficiency(mpg, 'mpg', precision);
|
return formatFuelEfficiency(mpg, 'mpg', precision);
|
||||||
}
|
}
|
||||||
@@ -192,5 +191,5 @@ export function getVolumeUnit(system: UnitSystem): VolumeUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getFuelEfficiencyUnit(system: UnitSystem): FuelEfficiencyUnit {
|
export function getFuelEfficiencyUnit(system: UnitSystem): FuelEfficiencyUnit {
|
||||||
return system === 'metric' ? 'kml' : 'mpg';
|
return system === 'metric' ? 'l100km' : 'mpg';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ export const useImportApply = () => {
|
|||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['catalogSearch'] });
|
queryClient.invalidateQueries({ queryKey: ['catalogSearch'] });
|
||||||
toast.success(
|
toast.success(
|
||||||
`Import completed: ${result.created} created, ${result.updated} updated, ${result.deleted} deleted`
|
`Import completed: ${result.created} created, ${result.updated} updated`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError: (error: ApiError) => {
|
onError: (error: ApiError) => {
|
||||||
|
|||||||
@@ -462,9 +462,6 @@ export const AdminCatalogMobileScreen: React.FC = () => {
|
|||||||
<div className="bg-blue-100 text-blue-800 px-3 py-2 rounded-lg">
|
<div className="bg-blue-100 text-blue-800 px-3 py-2 rounded-lg">
|
||||||
<strong>{importPreview.toUpdate.length}</strong> to update
|
<strong>{importPreview.toUpdate.length}</strong> to update
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-red-100 text-red-800 px-3 py-2 rounded-lg">
|
|
||||||
<strong>{importPreview.toDelete.length}</strong> to delete
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Errors */}
|
{/* Errors */}
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ export interface CatalogSearchResponse {
|
|||||||
|
|
||||||
// Catalog import types
|
// Catalog import types
|
||||||
export interface ImportRow {
|
export interface ImportRow {
|
||||||
action: 'add' | 'update' | 'delete';
|
|
||||||
year: number;
|
year: number;
|
||||||
make: string;
|
make: string;
|
||||||
model: string;
|
model: string;
|
||||||
@@ -199,7 +198,6 @@ export interface ImportPreviewResult {
|
|||||||
previewId: string;
|
previewId: string;
|
||||||
toCreate: ImportRow[];
|
toCreate: ImportRow[];
|
||||||
toUpdate: ImportRow[];
|
toUpdate: ImportRow[];
|
||||||
toDelete: ImportRow[];
|
|
||||||
errors: ImportError[];
|
errors: ImportError[];
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
}
|
}
|
||||||
@@ -207,7 +205,6 @@ export interface ImportPreviewResult {
|
|||||||
export interface ImportApplyResult {
|
export interface ImportApplyResult {
|
||||||
created: number;
|
created: number;
|
||||||
updated: number;
|
updated: number;
|
||||||
deleted: number;
|
|
||||||
errors: ImportError[];
|
errors: ImportError[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, CardContent, Typography, Box, Chip } from '@mui/material';
|
import { Card, CardContent, Typography, Box, Chip } from '@mui/material';
|
||||||
import { UnitSystem } from '../types/fuel-logs.types';
|
import { useUnits } from '../../../core/units/UnitsContext';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fuelUnits?: number;
|
fuelUnits?: number;
|
||||||
costPerUnit?: number;
|
costPerUnit?: number;
|
||||||
calculatedCost: number;
|
calculatedCost: number;
|
||||||
unitSystem?: UnitSystem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CostCalculator: React.FC<Props> = ({ fuelUnits, costPerUnit, calculatedCost, unitSystem = 'imperial' }) => {
|
export const CostCalculator: React.FC<Props> = ({ fuelUnits, costPerUnit, calculatedCost }) => {
|
||||||
|
const { unitSystem, currencySymbol } = useUnits();
|
||||||
const unitLabel = unitSystem === 'imperial' ? 'gallons' : 'liters';
|
const unitLabel = unitSystem === 'imperial' ? 'gallons' : 'liters';
|
||||||
|
|
||||||
// Ensure we have valid numbers
|
// Ensure we have valid numbers
|
||||||
@@ -30,8 +30,8 @@ export const CostCalculator: React.FC<Props> = ({ fuelUnits, costPerUnit, calcul
|
|||||||
<Chip label="Real-time" size="small" color="primary" variant="outlined" />
|
<Chip label="Real-time" size="small" color="primary" variant="outlined" />
|
||||||
</Box>
|
</Box>
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||||
<Typography variant="body2">{safeUnits.toFixed(3)} {unitLabel} × ${safeCostPerUnit.toFixed(3)}</Typography>
|
<Typography variant="body2">{safeUnits.toFixed(3)} {unitLabel} x {currencySymbol}{safeCostPerUnit.toFixed(3)}</Typography>
|
||||||
<Typography variant="h6" color="primary.main" fontWeight={700}>${safeCost.toFixed(2)}</Typography>
|
<Typography variant="h6" color="primary.main" fontWeight={700}>{currencySymbol}{safeCost.toFixed(2)}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DistanceInput: React.FC<Props> = ({ type, value, onChange, unitSystem = 'imperial', error, disabled }) => {
|
export const DistanceInput: React.FC<Props> = ({ type, value, onChange, unitSystem = 'imperial', error, disabled }) => {
|
||||||
const units = unitSystem === 'imperial' ? 'miles' : 'kilometers';
|
const units = unitSystem === 'imperial' ? 'miles' : 'km';
|
||||||
const label = type === 'odometer' ? `Odometer (${units})` : `Trip Distance (${units})`;
|
const label = type === 'odometer' ? 'Odometer' : 'Trip Distance';
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<TextField
|
<TextField
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ const FuelLogFormComponent: React.FC<{ onSuccess?: () => void; initial?: Partial
|
|||||||
)} />
|
)} />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Row 2: Date/Time | MPG/km/L */}
|
{/* Row 2: Date/Time | MPG/L/100km */}
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<Controller name="dateTime" control={control} render={({ field }) => (
|
<Controller name="dateTime" control={control} render={({ field }) => (
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
@@ -182,7 +182,7 @@ const FuelLogFormComponent: React.FC<{ onSuccess?: () => void; initial?: Partial
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<TextField
|
<TextField
|
||||||
label={`${userSettings?.unitSystem === 'metric' ? 'km/L' : 'MPG'}`}
|
label={`${userSettings?.unitSystem === 'metric' ? 'L/100km' : 'MPG'}`}
|
||||||
value={calculatedEfficiency > 0 ? calculatedEfficiency.toFixed(3) : ''}
|
value={calculatedEfficiency > 0 ? calculatedEfficiency.toFixed(3) : ''}
|
||||||
fullWidth
|
fullWidth
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@@ -283,7 +283,7 @@ const FuelLogFormComponent: React.FC<{ onSuccess?: () => void; initial?: Partial
|
|||||||
)} />
|
)} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<CostCalculator fuelUnits={fuelUnits} costPerUnit={costPerUnit} calculatedCost={calculatedCost} unitSystem={userSettings?.unitSystem} />
|
<CostCalculator fuelUnits={fuelUnits} costPerUnit={costPerUnit} calculatedCost={calculatedCost} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Controller name="locationData" control={control} render={({ field }) => (
|
<Controller name="locationData" control={control} render={({ field }) => (
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ interface FuelLogsListProps {
|
|||||||
export const FuelLogsList: React.FC<FuelLogsListProps> = ({ logs, onEdit, onDelete }) => {
|
export const FuelLogsList: React.FC<FuelLogsListProps> = ({ logs, onEdit, onDelete }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
const { unitSystem, convertDistance, convertVolume } = useUnits();
|
const { unitSystem, convertDistance, convertVolume, currencySymbol } = useUnits();
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [logToDelete, setLogToDelete] = useState<FuelLogResponse | null>(null);
|
const [logToDelete, setLogToDelete] = useState<FuelLogResponse | null>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
@@ -145,7 +145,7 @@ export const FuelLogsList: React.FC<FuelLogsListProps> = ({ logs, onEdit, onDele
|
|||||||
if (unitSystem === 'metric') {
|
if (unitSystem === 'metric') {
|
||||||
const km = convertDistance(miles);
|
const km = convertDistance(miles);
|
||||||
const liters = convertVolume(gallons);
|
const liters = convertVolume(gallons);
|
||||||
if (liters > 0) localEffLabel = `${(km / liters).toFixed(1)} km/L`;
|
if (km > 0) localEffLabel = `${((liters / km) * 100).toFixed(1)} L/100km`;
|
||||||
} else {
|
} else {
|
||||||
localEffLabel = `${(miles / gallons).toFixed(1)} MPG`;
|
localEffLabel = `${(miles / gallons).toFixed(1)} MPG`;
|
||||||
}
|
}
|
||||||
@@ -173,8 +173,8 @@ export const FuelLogsList: React.FC<FuelLogsListProps> = ({ logs, onEdit, onDele
|
|||||||
gap: isMobile ? 0.5 : 1
|
gap: isMobile ? 0.5 : 1
|
||||||
}}>
|
}}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={`${dateText} – $${totalCost}`}
|
primary={`${dateText} \u2013 ${currencySymbol}${totalCost}`}
|
||||||
secondary={`${fuelUnits} @ $${costPerUnit} • ${distanceText}`}
|
secondary={`${fuelUnits} @ ${currencySymbol}${costPerUnit} \u2022 ${distanceText}`}
|
||||||
sx={{ flex: 1, minWidth: 0 }}
|
sx={{ flex: 1, minWidth: 0 }}
|
||||||
/>
|
/>
|
||||||
{(log.efficiency && typeof log.efficiency === 'number' && !isNaN(log.efficiency) && log.efficiencyLabel) || localEffLabel ? (
|
{(log.efficiency && typeof log.efficiency === 'number' && !isNaN(log.efficiency) && log.efficiencyLabel) || localEffLabel ? (
|
||||||
@@ -262,7 +262,7 @@ export const FuelLogsList: React.FC<FuelLogsListProps> = ({ logs, onEdit, onDele
|
|||||||
</Typography>
|
</Typography>
|
||||||
{logToDelete && (
|
{logToDelete && (
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||||
{new Date(logToDelete.dateTime).toLocaleString()} - ${logToDelete.totalCost.toFixed(2)}
|
{new Date(logToDelete.dateTime).toLocaleString()} - {currencySymbol}{logToDelete.totalCost.toFixed(2)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { FuelLogResponse } from '../types/fuel-logs.types';
|
|||||||
import { useUnits } from '../../../core/units/UnitsContext';
|
import { useUnits } from '../../../core/units/UnitsContext';
|
||||||
|
|
||||||
export const FuelStatsCard: React.FC<{ logs?: FuelLogResponse[] }> = ({ logs }) => {
|
export const FuelStatsCard: React.FC<{ logs?: FuelLogResponse[] }> = ({ logs }) => {
|
||||||
const { unitSystem } = useUnits();
|
const { unitSystem, currencySymbol } = useUnits();
|
||||||
const stats = useMemo(() => {
|
const stats = useMemo(() => {
|
||||||
if (!logs || logs.length === 0) return { count: 0, totalUnits: 0, totalCost: 0 };
|
if (!logs || logs.length === 0) return { count: 0, totalUnits: 0, totalCost: 0 };
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export const FuelStatsCard: React.FC<{ logs?: FuelLogResponse[] }> = ({ logs })
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={4}>
|
<Grid item xs={4}>
|
||||||
<Typography variant="overline" color="text.secondary">Total Cost</Typography>
|
<Typography variant="overline" color="text.secondary">Total Cost</Typography>
|
||||||
<Typography variant="h6">${(typeof stats.totalCost === 'number' && !isNaN(stats.totalCost) ? stats.totalCost : 0).toFixed(2)}</Typography>
|
<Typography variant="h6">{currencySymbol}{(typeof stats.totalCost === 'number' && !isNaN(stats.totalCost) ? stats.totalCost : 0).toFixed(2)}</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { UnitSystem } from '../types/fuel-logs.types';
|
|||||||
|
|
||||||
export const UnitSystemDisplay: React.FC<{ unitSystem?: UnitSystem; showLabel?: string }> = ({ unitSystem, showLabel }) => {
|
export const UnitSystemDisplay: React.FC<{ unitSystem?: UnitSystem; showLabel?: string }> = ({ unitSystem, showLabel }) => {
|
||||||
if (!unitSystem) return null;
|
if (!unitSystem) return null;
|
||||||
const label = unitSystem === 'imperial' ? 'Imperial (miles, gallons, MPG)' : 'Metric (km, liters, km/L)';
|
const label = unitSystem === 'imperial' ? 'Imperial (miles, gallons, MPG)' : 'Metric (km, liters, L/100km)';
|
||||||
return (
|
return (
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
{showLabel ? `${showLabel} ` : ''}{label}
|
{showLabel ? `${showLabel} ` : ''}{label}
|
||||||
|
|||||||
13
frontend/src/features/settings/api/preferences.api.ts
Normal file
13
frontend/src/features/settings/api/preferences.api.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @ai-summary API client for user preferences endpoints
|
||||||
|
* @ai-context Handles unit system, currency, and timezone preferences sync
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { apiClient } from '../../../core/api/client';
|
||||||
|
import { UserPreferences, UpdatePreferencesRequest } from '../types/preferences.types';
|
||||||
|
|
||||||
|
export const preferencesApi = {
|
||||||
|
getPreferences: () => apiClient.get<UserPreferences>('/user/preferences'),
|
||||||
|
updatePreferences: (data: UpdatePreferencesRequest) =>
|
||||||
|
apiClient.put<UserPreferences>('/user/preferences', data),
|
||||||
|
};
|
||||||
61
frontend/src/features/settings/hooks/usePreferences.ts
Normal file
61
frontend/src/features/settings/hooks/usePreferences.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* @ai-summary React hooks for user preferences management
|
||||||
|
* @ai-context Handles unit system, currency, and timezone preference sync with backend
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useAuth0 } from '@auth0/auth0-react';
|
||||||
|
import { preferencesApi } from '../api/preferences.api';
|
||||||
|
import { UpdatePreferencesRequest } from '../types/preferences.types';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
interface ApiError {
|
||||||
|
response?: {
|
||||||
|
data?: {
|
||||||
|
error?: string;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePreferences = () => {
|
||||||
|
const { isAuthenticated, isLoading } = useAuth0();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['user-preferences'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await preferencesApi.getPreferences();
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
enabled: isAuthenticated && !isLoading,
|
||||||
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||||
|
gcTime: 10 * 60 * 1000, // 10 minutes cache time
|
||||||
|
retry: (failureCount, error: any) => {
|
||||||
|
// Retry 401s a few times (auth token might be refreshing)
|
||||||
|
if (error?.response?.status === 401 && failureCount < 3) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdatePreferences = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (data: UpdatePreferencesRequest) => preferencesApi.updatePreferences(data),
|
||||||
|
onSuccess: (response) => {
|
||||||
|
queryClient.setQueryData(['user-preferences'], response.data);
|
||||||
|
// Don't show toast for unit system changes - context handles UI feedback
|
||||||
|
},
|
||||||
|
onError: (error: ApiError) => {
|
||||||
|
const message = error.response?.data?.message || error.response?.data?.error || 'Failed to update preferences';
|
||||||
|
toast.error(message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useSettingsPersistence, SettingsState } from './useSettingsPersistence';
|
import { useSettingsPersistence, SettingsState } from './useSettingsPersistence';
|
||||||
|
import { useUnits } from '../../../core/units/UnitsContext';
|
||||||
|
|
||||||
const defaultSettings: SettingsState = {
|
const defaultSettings: SettingsState = {
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
@@ -13,10 +14,12 @@ const defaultSettings: SettingsState = {
|
|||||||
|
|
||||||
export const useSettings = () => {
|
export const useSettings = () => {
|
||||||
const { loadSettings, saveSettings } = useSettingsPersistence();
|
const { loadSettings, saveSettings } = useSettingsPersistence();
|
||||||
|
const { unitSystem: contextUnitSystem, setUnitSystem: setContextUnitSystem } = useUnits();
|
||||||
const [settings, setSettings] = useState<SettingsState>(defaultSettings);
|
const [settings, setSettings] = useState<SettingsState>(defaultSettings);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Load settings from localStorage on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -33,6 +36,13 @@ export const useSettings = () => {
|
|||||||
}
|
}
|
||||||
}, [loadSettings]);
|
}, [loadSettings]);
|
||||||
|
|
||||||
|
// Sync unitSystem from context (which may load from backend)
|
||||||
|
useEffect(() => {
|
||||||
|
if (contextUnitSystem && contextUnitSystem !== settings.unitSystem) {
|
||||||
|
setSettings(prev => ({ ...prev, unitSystem: contextUnitSystem }));
|
||||||
|
}
|
||||||
|
}, [contextUnitSystem, settings.unitSystem]);
|
||||||
|
|
||||||
const updateSetting = <K extends keyof SettingsState>(
|
const updateSetting = <K extends keyof SettingsState>(
|
||||||
key: K,
|
key: K,
|
||||||
value: SettingsState[K]
|
value: SettingsState[K]
|
||||||
@@ -42,6 +52,11 @@ export const useSettings = () => {
|
|||||||
const newSettings = { ...settings, [key]: value };
|
const newSettings = { ...settings, [key]: value };
|
||||||
setSettings(newSettings);
|
setSettings(newSettings);
|
||||||
saveSettings(newSettings);
|
saveSettings(newSettings);
|
||||||
|
|
||||||
|
// Sync unitSystem changes to context (which syncs to backend)
|
||||||
|
if (key === 'unitSystem' && typeof value === 'string') {
|
||||||
|
setContextUnitSystem(value as 'imperial' | 'metric');
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to save settings');
|
setError(err instanceof Error ? err.message : 'Failed to save settings');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ export const MobileSettingsScreen: React.FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-slate-800">Unit System</p>
|
<p className="font-medium text-slate-800">Unit System</p>
|
||||||
<p className="text-sm text-slate-500">
|
<p className="text-sm text-slate-500">
|
||||||
Currently using {settings.unitSystem === 'imperial' ? 'Miles & Gallons' : 'Kilometers & Liters'}
|
Currently using {settings.unitSystem === 'imperial' ? 'Miles, Gallons, MPG, USD' : 'Km, Liters, L/100km, EUR'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
22
frontend/src/features/settings/types/preferences.types.ts
Normal file
22
frontend/src/features/settings/types/preferences.types.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* @ai-summary Type definitions for user preferences
|
||||||
|
* @ai-context Types for unit system, currency, and timezone preferences
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { UnitSystem } from '../../../core/units/units.types';
|
||||||
|
|
||||||
|
export interface UserPreferences {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
unitSystem: UnitSystem;
|
||||||
|
currencyCode: string;
|
||||||
|
timeZone: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePreferencesRequest {
|
||||||
|
unitSystem?: UnitSystem;
|
||||||
|
currencyCode?: string;
|
||||||
|
timeZone?: string;
|
||||||
|
}
|
||||||
@@ -324,7 +324,7 @@ export const SettingsPage: React.FC = () => {
|
|||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Units for distance and capacity"
|
primary="Units for distance and capacity"
|
||||||
secondary="Choose between Imperial (miles, gallons) or Metric (kilometers, liters)"
|
secondary="Imperial: miles, gallons, MPG, USD | Metric: km, liters, L/100km, EUR"
|
||||||
sx={{ pl: 7 }}
|
sx={{ pl: 7 }}
|
||||||
/>
|
/>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
|
|||||||
@@ -525,9 +525,6 @@ export const AdminCatalogPage: React.FC = () => {
|
|||||||
<Typography>
|
<Typography>
|
||||||
<strong>To Update:</strong> {importPreview.toUpdate.length}
|
<strong>To Update:</strong> {importPreview.toUpdate.length}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>
|
|
||||||
<strong>To Delete:</strong> {importPreview.toDelete.length}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Errors */}
|
{/* Errors */}
|
||||||
|
|||||||
Reference in New Issue
Block a user