Updates to database and API for dropdowns.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: feature-agent
|
name: feature-agent
|
||||||
description: MUST BE USED when ever creating or maintaining features
|
description: MUST BE USED when ever creating or maintaining features
|
||||||
model: sonnet
|
model: haiku
|
||||||
---
|
---
|
||||||
|
|
||||||
## Role Definition
|
## Role Definition
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: first-frontend-agent
|
name: first-frontend-agent
|
||||||
description: MUST BE USED when ever editing or modifying the frontend design for Desktop or Mobile
|
description: MUST BE USED when ever editing or modifying the frontend design for Desktop or Mobile
|
||||||
model: sonnet
|
model: haiku
|
||||||
---
|
---
|
||||||
|
|
||||||
## Role Definition
|
## Role Definition
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: platform-agent
|
name: platform-agent
|
||||||
description: MUST BE USED when ever editing or modifying the platform services.
|
description: MUST BE USED when ever editing or modifying the platform services.
|
||||||
model: sonnet
|
model: haiku
|
||||||
---
|
---
|
||||||
|
|
||||||
## Role Definition
|
## Role Definition
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: quality-agent
|
name: quality-agent
|
||||||
description: MUST BE USED last before code is committed and signed off as production ready
|
description: MUST BE USED last before code is committed and signed off as production ready
|
||||||
model: sonnet
|
model: haiku
|
||||||
---
|
---
|
||||||
|
|
||||||
## Role Definition
|
## Role Definition
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ coverage/
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
#SQL Import Files
|
#SQL Import Files
|
||||||
data/make-model-import/output/03_vehicle_options.sql
|
data/make-model-import/output/*.sql
|
||||||
|
|
||||||
# K8s-aligned configuration and secret mounts (real files ignored; examples committed)
|
# K8s-aligned configuration and secret mounts (real files ignored; examples committed)
|
||||||
config/**
|
config/**
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
ModelsQuery,
|
ModelsQuery,
|
||||||
TrimsQuery,
|
TrimsQuery,
|
||||||
EnginesQuery,
|
EnginesQuery,
|
||||||
|
TransmissionsQuery,
|
||||||
VINDecodeRequest
|
VINDecodeRequest
|
||||||
} from '../models/requests';
|
} from '../models/requests';
|
||||||
import { logger } from '../../../core/logging/logger';
|
import { logger } from '../../../core/logging/logger';
|
||||||
@@ -57,12 +58,12 @@ export class PlatformController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/platform/models?year={year}&make_id={id}
|
* GET /api/platform/models?year={year}&make={make}
|
||||||
*/
|
*/
|
||||||
async getModels(request: FastifyRequest<{ Querystring: ModelsQuery }>, reply: FastifyReply): Promise<void> {
|
async getModels(request: FastifyRequest<{ Querystring: ModelsQuery }>, reply: FastifyReply): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { year, make_id } = request.query;
|
const { year, make } = request.query as any;
|
||||||
const models = await this.vehicleDataService.getModels(this.pool, year, make_id);
|
const models = await this.vehicleDataService.getModels(this.pool, year, make);
|
||||||
reply.code(200).send({ models });
|
reply.code(200).send({ models });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Controller error: getModels', { error, query: request.query });
|
logger.error('Controller error: getModels', { error, query: request.query });
|
||||||
@@ -71,12 +72,12 @@ export class PlatformController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/platform/trims?year={year}&model_id={id}
|
* GET /api/platform/trims?year={year}&make={make}&model={model}
|
||||||
*/
|
*/
|
||||||
async getTrims(request: FastifyRequest<{ Querystring: TrimsQuery }>, reply: FastifyReply): Promise<void> {
|
async getTrims(request: FastifyRequest<{ Querystring: TrimsQuery }>, reply: FastifyReply): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { year, model_id } = request.query;
|
const { year, make, model } = request.query as any;
|
||||||
const trims = await this.vehicleDataService.getTrims(this.pool, year, model_id);
|
const trims = await this.vehicleDataService.getTrims(this.pool, year, make, model);
|
||||||
reply.code(200).send({ trims });
|
reply.code(200).send({ trims });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Controller error: getTrims', { error, query: request.query });
|
logger.error('Controller error: getTrims', { error, query: request.query });
|
||||||
@@ -85,12 +86,12 @@ export class PlatformController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/platform/engines?year={year}&model_id={id}&trim_id={id}
|
* GET /api/platform/engines?year={year}&make={make}&model={model}&trim={trim}
|
||||||
*/
|
*/
|
||||||
async getEngines(request: FastifyRequest<{ Querystring: EnginesQuery }>, reply: FastifyReply): Promise<void> {
|
async getEngines(request: FastifyRequest<{ Querystring: EnginesQuery }>, reply: FastifyReply): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { year, model_id, trim_id } = request.query;
|
const { year, make, model, trim } = request.query as any;
|
||||||
const engines = await this.vehicleDataService.getEngines(this.pool, year, model_id, trim_id);
|
const engines = await this.vehicleDataService.getEngines(this.pool, year, make, model, trim);
|
||||||
reply.code(200).send({ engines });
|
reply.code(200).send({ engines });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Controller error: getEngines', { error, query: request.query });
|
logger.error('Controller error: getEngines', { error, query: request.query });
|
||||||
@@ -98,6 +99,20 @@ export class PlatformController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/platform/transmissions?year={year}&make={make}&model={model}
|
||||||
|
*/
|
||||||
|
async getTransmissions(request: FastifyRequest<{ Querystring: TransmissionsQuery }>, reply: FastifyReply): Promise<void> {
|
||||||
|
try {
|
||||||
|
const { year, make, model } = request.query as any;
|
||||||
|
const transmissions = await this.vehicleDataService.getTransmissions(this.pool, year, make, model);
|
||||||
|
reply.code(200).send({ transmissions });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Controller error: getTransmissions', { error, query: request.query });
|
||||||
|
reply.code(500).send({ error: 'Failed to retrieve transmissions' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/platform/vehicle?vin={vin}
|
* GET /api/platform/vehicle?vin={vin}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ModelsQuery,
|
ModelsQuery,
|
||||||
TrimsQuery,
|
TrimsQuery,
|
||||||
EnginesQuery,
|
EnginesQuery,
|
||||||
|
TransmissionsQuery,
|
||||||
VINDecodeRequest
|
VINDecodeRequest
|
||||||
} from '../models/requests';
|
} from '../models/requests';
|
||||||
import pool from '../../../core/config/database';
|
import pool from '../../../core/config/database';
|
||||||
@@ -37,6 +38,10 @@ async function platformRoutes(fastify: FastifyInstance) {
|
|||||||
preHandler: [fastify.authenticate]
|
preHandler: [fastify.authenticate]
|
||||||
}, controller.getEngines.bind(controller));
|
}, controller.getEngines.bind(controller));
|
||||||
|
|
||||||
|
fastify.get<{ Querystring: TransmissionsQuery }>('/platform/transmissions', {
|
||||||
|
preHandler: [fastify.authenticate]
|
||||||
|
}, controller.getTransmissions.bind(controller));
|
||||||
|
|
||||||
fastify.get<{ Querystring: VINDecodeRequest }>('/platform/vehicle', {
|
fastify.get<{ Querystring: VINDecodeRequest }>('/platform/vehicle', {
|
||||||
preHandler: [fastify.authenticate]
|
preHandler: [fastify.authenticate]
|
||||||
}, controller.decodeVIN.bind(controller));
|
}, controller.decodeVIN.bind(controller));
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* @ai-summary Vehicle data repository for hierarchical queries
|
* @ai-summary Vehicle data repository for hierarchical dropdown queries
|
||||||
* @ai-context PostgreSQL queries against vehicles schema
|
* @ai-context Queries denormalized vehicle_options table with string-based cascade
|
||||||
|
* @ai-migration Updated to use new ETL-generated database (1.1M+ vehicle configurations)
|
||||||
*/
|
*/
|
||||||
import { Pool } from 'pg';
|
import { Pool } from 'pg';
|
||||||
import { MakeItem, ModelItem, TrimItem, EngineItem } from '../models/responses';
|
|
||||||
import { VINDecodeResult } from '../models/responses';
|
import { VINDecodeResult } from '../models/responses';
|
||||||
import { logger } from '../../../core/logging/logger';
|
import { logger } from '../../../core/logging/logger';
|
||||||
|
|
||||||
export class VehicleDataRepository {
|
export class VehicleDataRepository {
|
||||||
/**
|
/**
|
||||||
* Get distinct years from model_year table
|
* Get distinct years from vehicle_options table
|
||||||
*/
|
*/
|
||||||
async getYears(pool: Pool): Promise<number[]> {
|
async getYears(pool: Pool): Promise<number[]> {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT DISTINCT year
|
SELECT DISTINCT year
|
||||||
FROM vehicles.model_year
|
FROM vehicle_options
|
||||||
ORDER BY year DESC
|
ORDER BY year DESC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -28,23 +28,16 @@ export class VehicleDataRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get makes for a specific year
|
* Get makes for a specific year using database function
|
||||||
*/
|
*/
|
||||||
async getMakes(pool: Pool, year: number): Promise<MakeItem[]> {
|
async getMakes(pool: Pool, year: number): Promise<string[]> {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT DISTINCT ma.id, ma.name
|
SELECT make FROM get_makes_for_year($1)
|
||||||
FROM vehicles.make ma
|
|
||||||
JOIN vehicles.model mo ON mo.make_id = ma.id
|
|
||||||
JOIN vehicles.model_year my ON my.model_id = mo.id AND my.year = $1
|
|
||||||
ORDER BY ma.name
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(query, [year]);
|
const result = await pool.query(query, [year]);
|
||||||
return result.rows.map(row => ({
|
return result.rows.map(row => row.make);
|
||||||
id: row.id,
|
|
||||||
name: row.name
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Repository error: getMakes', { error, year });
|
logger.error('Repository error: getMakes', { error, year });
|
||||||
throw new Error(`Failed to retrieve makes for year ${year}`);
|
throw new Error(`Failed to retrieve makes for year ${year}`);
|
||||||
@@ -52,83 +45,101 @@ export class VehicleDataRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get models for a specific year and make
|
* Get models for a specific year and make using database function
|
||||||
*/
|
*/
|
||||||
async getModels(pool: Pool, year: number, makeId: number): Promise<ModelItem[]> {
|
async getModels(pool: Pool, year: number, make: string): Promise<string[]> {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT DISTINCT mo.id, mo.name
|
SELECT model FROM get_models_for_year_make($1, $2)
|
||||||
FROM vehicles.model mo
|
|
||||||
JOIN vehicles.model_year my ON my.model_id = mo.id AND my.year = $1
|
|
||||||
WHERE mo.make_id = $2
|
|
||||||
ORDER BY mo.name
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(query, [year, makeId]);
|
const result = await pool.query(query, [year, make]);
|
||||||
return result.rows.map(row => ({
|
return result.rows.map(row => row.model);
|
||||||
id: row.id,
|
|
||||||
name: row.name
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Repository error: getModels', { error, year, makeId });
|
logger.error('Repository error: getModels', { error, year, make });
|
||||||
throw new Error(`Failed to retrieve models for year ${year}, make ${makeId}`);
|
throw new Error(`Failed to retrieve models for year ${year}, make ${make}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get trims for a specific year and model
|
* Get trims for a specific year, make, and model using database function
|
||||||
*/
|
*/
|
||||||
async getTrims(pool: Pool, year: number, modelId: number): Promise<TrimItem[]> {
|
async getTrims(pool: Pool, year: number, make: string, model: string): Promise<string[]> {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT t.id, t.name
|
SELECT trim_name FROM get_trims_for_year_make_model($1, $2, $3)
|
||||||
FROM vehicles.trim t
|
|
||||||
JOIN vehicles.model_year my ON my.id = t.model_year_id
|
|
||||||
WHERE my.year = $1 AND my.model_id = $2
|
|
||||||
ORDER BY t.name
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(query, [year, modelId]);
|
const result = await pool.query(query, [year, make, model]);
|
||||||
return result.rows.map(row => ({
|
return result.rows.map(row => row.trim_name);
|
||||||
id: row.id,
|
|
||||||
name: row.name
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Repository error: getTrims', { error, year, modelId });
|
logger.error('Repository error: getTrims', { error, year, make, model });
|
||||||
throw new Error(`Failed to retrieve trims for year ${year}, model ${modelId}`);
|
throw new Error(`Failed to retrieve trims for year ${year}, make ${make}, model ${model}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get engines for a specific year, model, and trim
|
* Get engines for a specific year, make, model, and trim
|
||||||
|
* Returns 'N/A (Electric)' for electric vehicles with NULL engine_id
|
||||||
*/
|
*/
|
||||||
async getEngines(pool: Pool, year: number, modelId: number, trimId: number): Promise<EngineItem[]> {
|
async getEngines(pool: Pool, year: number, make: string, model: string, trim: string): Promise<string[]> {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT DISTINCT e.id, e.name
|
SELECT DISTINCT
|
||||||
FROM vehicles.engine e
|
CASE
|
||||||
JOIN vehicles.trim_engine te ON te.engine_id = e.id
|
WHEN vo.engine_id IS NULL THEN 'N/A (Electric)'
|
||||||
JOIN vehicles.trim t ON t.id = te.trim_id
|
ELSE e.name
|
||||||
JOIN vehicles.model_year my ON my.id = t.model_year_id
|
END as engine_name
|
||||||
WHERE my.year = $1
|
FROM vehicle_options vo
|
||||||
AND my.model_id = $2
|
LEFT JOIN engines e ON e.id = vo.engine_id
|
||||||
AND t.id = $3
|
WHERE vo.year = $1
|
||||||
ORDER BY e.name
|
AND vo.make = $2
|
||||||
|
AND vo.model = $3
|
||||||
|
AND vo.trim = $4
|
||||||
|
ORDER BY engine_name
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(query, [year, modelId, trimId]);
|
const result = await pool.query(query, [year, make, model, trim]);
|
||||||
return result.rows.map(row => ({
|
return result.rows.map(row => row.engine_name);
|
||||||
id: row.id,
|
|
||||||
name: row.name
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Repository error: getEngines', { error, year, modelId, trimId });
|
logger.error('Repository error: getEngines', { error, year, make, model, trim });
|
||||||
throw new Error(`Failed to retrieve engines for year ${year}, model ${modelId}, trim ${trimId}`);
|
throw new Error(`Failed to retrieve engines for year ${year}, make ${make}, model ${model}, trim ${trim}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get transmissions for a specific year, make, and model
|
||||||
|
* Returns real transmission types from the database (not hardcoded)
|
||||||
|
*/
|
||||||
|
async getTransmissions(pool: Pool, year: number, make: string, model: string): Promise<string[]> {
|
||||||
|
const query = `
|
||||||
|
SELECT DISTINCT
|
||||||
|
CASE
|
||||||
|
WHEN vo.transmission_id IS NULL THEN 'N/A'
|
||||||
|
ELSE t.type
|
||||||
|
END as transmission_type
|
||||||
|
FROM vehicle_options vo
|
||||||
|
LEFT JOIN transmissions t ON t.id = vo.transmission_id
|
||||||
|
WHERE vo.year = $1
|
||||||
|
AND vo.make = $2
|
||||||
|
AND vo.model = $3
|
||||||
|
ORDER BY transmission_type
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await pool.query(query, [year, make, model]);
|
||||||
|
return result.rows.map(row => row.transmission_type);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Repository error: getTransmissions', { error, year, make, model });
|
||||||
|
throw new Error(`Failed to retrieve transmissions for year ${year}, make ${make}, model ${model}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode VIN using PostgreSQL function
|
* Decode VIN using PostgreSQL function
|
||||||
|
* NOTE: This function may need updates after vehicles.* schema migration
|
||||||
|
* If the old f_decode_vin function no longer exists, it will need to be
|
||||||
|
* reimplemented against the new vehicle_options schema
|
||||||
*/
|
*/
|
||||||
async decodeVIN(pool: Pool, vin: string): Promise<VINDecodeResult | null> {
|
async decodeVIN(pool: Pool, vin: string): Promise<VINDecodeResult | null> {
|
||||||
const query = `
|
const query = `
|
||||||
|
|||||||
@@ -32,15 +32,15 @@ export class PlatformCacheService {
|
|||||||
/**
|
/**
|
||||||
* Get cached makes for year
|
* Get cached makes for year
|
||||||
*/
|
*/
|
||||||
async getMakes(year: number): Promise<any[] | null> {
|
async getMakes(year: number): Promise<string[] | null> {
|
||||||
const key = this.prefix + 'vehicle-data:makes:' + year;
|
const key = this.prefix + 'vehicle-data:makes:' + year;
|
||||||
return await this.cacheService.get<any[]>(key);
|
return await this.cacheService.get<string[]>(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set cached makes for year
|
* Set cached makes for year
|
||||||
*/
|
*/
|
||||||
async setMakes(year: number, makes: any[], ttl: number = 6 * 3600): Promise<void> {
|
async setMakes(year: number, makes: string[], ttl: number = 6 * 3600): Promise<void> {
|
||||||
const key = this.prefix + 'vehicle-data:makes:' + year;
|
const key = this.prefix + 'vehicle-data:makes:' + year;
|
||||||
await this.cacheService.set(key, makes, ttl);
|
await this.cacheService.set(key, makes, ttl);
|
||||||
}
|
}
|
||||||
@@ -48,51 +48,67 @@ export class PlatformCacheService {
|
|||||||
/**
|
/**
|
||||||
* Get cached models for year and make
|
* Get cached models for year and make
|
||||||
*/
|
*/
|
||||||
async getModels(year: number, makeId: number): Promise<any[] | null> {
|
async getModels(year: number, make: string): Promise<string[] | null> {
|
||||||
const key = this.prefix + 'vehicle-data:models:' + year + ':' + makeId;
|
const key = this.prefix + 'vehicle-data:models:' + year + ':' + make;
|
||||||
return await this.cacheService.get<any[]>(key);
|
return await this.cacheService.get<string[]>(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set cached models for year and make
|
* Set cached models for year and make
|
||||||
*/
|
*/
|
||||||
async setModels(year: number, makeId: number, models: any[], ttl: number = 6 * 3600): Promise<void> {
|
async setModels(year: number, make: string, models: string[], ttl: number = 6 * 3600): Promise<void> {
|
||||||
const key = this.prefix + 'vehicle-data:models:' + year + ':' + makeId;
|
const key = this.prefix + 'vehicle-data:models:' + year + ':' + make;
|
||||||
await this.cacheService.set(key, models, ttl);
|
await this.cacheService.set(key, models, ttl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cached trims for year and model
|
* Get cached trims for year, make, and model
|
||||||
*/
|
*/
|
||||||
async getTrims(year: number, modelId: number): Promise<any[] | null> {
|
async getTrims(year: number, make: string, model: string): Promise<string[] | null> {
|
||||||
const key = this.prefix + 'vehicle-data:trims:' + year + ':' + modelId;
|
const key = this.prefix + 'vehicle-data:trims:' + year + ':' + make + ':' + model;
|
||||||
return await this.cacheService.get<any[]>(key);
|
return await this.cacheService.get<string[]>(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set cached trims for year and model
|
* Set cached trims for year, make, and model
|
||||||
*/
|
*/
|
||||||
async setTrims(year: number, modelId: number, trims: any[], ttl: number = 6 * 3600): Promise<void> {
|
async setTrims(year: number, make: string, model: string, trims: string[], ttl: number = 6 * 3600): Promise<void> {
|
||||||
const key = this.prefix + 'vehicle-data:trims:' + year + ':' + modelId;
|
const key = this.prefix + 'vehicle-data:trims:' + year + ':' + make + ':' + model;
|
||||||
await this.cacheService.set(key, trims, ttl);
|
await this.cacheService.set(key, trims, ttl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cached engines for year, model, and trim
|
* Get cached engines for year, make, model, and trim
|
||||||
*/
|
*/
|
||||||
async getEngines(year: number, modelId: number, trimId: number): Promise<any[] | null> {
|
async getEngines(year: number, make: string, model: string, trim: string): Promise<string[] | null> {
|
||||||
const key = this.prefix + 'vehicle-data:engines:' + year + ':' + modelId + ':' + trimId;
|
const key = this.prefix + 'vehicle-data:engines:' + year + ':' + make + ':' + model + ':' + trim;
|
||||||
return await this.cacheService.get<any[]>(key);
|
return await this.cacheService.get<string[]>(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set cached engines for year, model, and trim
|
* Set cached engines for year, make, model, and trim
|
||||||
*/
|
*/
|
||||||
async setEngines(year: number, modelId: number, trimId: number, engines: any[], ttl: number = 6 * 3600): Promise<void> {
|
async setEngines(year: number, make: string, model: string, trim: string, engines: string[], ttl: number = 6 * 3600): Promise<void> {
|
||||||
const key = this.prefix + 'vehicle-data:engines:' + year + ':' + modelId + ':' + trimId;
|
const key = this.prefix + 'vehicle-data:engines:' + year + ':' + make + ':' + model + ':' + trim;
|
||||||
await this.cacheService.set(key, engines, ttl);
|
await this.cacheService.set(key, engines, ttl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached transmissions for year, make, and model
|
||||||
|
*/
|
||||||
|
async getTransmissions(year: number, make: string, model: string): Promise<string[] | null> {
|
||||||
|
const key = this.prefix + 'vehicle-data:transmissions:' + year + ':' + make + ':' + model;
|
||||||
|
return await this.cacheService.get<string[]>(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set cached transmissions for year, make, and model
|
||||||
|
*/
|
||||||
|
async setTransmissions(year: number, make: string, model: string, transmissions: string[], ttl: number = 6 * 3600): Promise<void> {
|
||||||
|
const key = this.prefix + 'vehicle-data:transmissions:' + year + ':' + make + ':' + model;
|
||||||
|
await this.cacheService.set(key, transmissions, ttl);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cached VIN decode result
|
* Get cached VIN decode result
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* @ai-summary Vehicle data service with caching
|
* @ai-summary Vehicle data service with caching for dropdown queries
|
||||||
* @ai-context Business logic for hierarchical vehicle data queries
|
* @ai-context String-based cascade queries with Redis caching
|
||||||
|
* @ai-migration Updated to use string parameters (not IDs)
|
||||||
*/
|
*/
|
||||||
import { Pool } from 'pg';
|
import { Pool } from 'pg';
|
||||||
import { VehicleDataRepository } from '../data/vehicle-data.repository';
|
import { VehicleDataRepository } from '../data/vehicle-data.repository';
|
||||||
import { PlatformCacheService } from './platform-cache.service';
|
import { PlatformCacheService } from './platform-cache.service';
|
||||||
import { MakeItem, ModelItem, TrimItem, EngineItem } from '../models/responses';
|
|
||||||
import { logger } from '../../../core/logging/logger';
|
import { logger } from '../../../core/logging/logger';
|
||||||
|
|
||||||
export class VehicleDataService {
|
export class VehicleDataService {
|
||||||
@@ -41,7 +41,7 @@ export class VehicleDataService {
|
|||||||
/**
|
/**
|
||||||
* Get makes for a year with caching
|
* Get makes for a year with caching
|
||||||
*/
|
*/
|
||||||
async getMakes(pool: Pool, year: number): Promise<MakeItem[]> {
|
async getMakes(pool: Pool, year: number): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
const cached = await this.cache.getMakes(year);
|
const cached = await this.cache.getMakes(year);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@@ -62,62 +62,83 @@ export class VehicleDataService {
|
|||||||
/**
|
/**
|
||||||
* Get models for a year and make with caching
|
* Get models for a year and make with caching
|
||||||
*/
|
*/
|
||||||
async getModels(pool: Pool, year: number, makeId: number): Promise<ModelItem[]> {
|
async getModels(pool: Pool, year: number, make: string): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
const cached = await this.cache.getModels(year, makeId);
|
const cached = await this.cache.getModels(year, make);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
logger.debug('Models retrieved from cache', { year, makeId });
|
logger.debug('Models retrieved from cache', { year, make });
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
const models = await this.repository.getModels(pool, year, makeId);
|
const models = await this.repository.getModels(pool, year, make);
|
||||||
await this.cache.setModels(year, makeId, models);
|
await this.cache.setModels(year, make, models);
|
||||||
logger.debug('Models retrieved from database and cached', { year, makeId, count: models.length });
|
logger.debug('Models retrieved from database and cached', { year, make, count: models.length });
|
||||||
return models;
|
return models;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Service error: getModels', { error, year, makeId });
|
logger.error('Service error: getModels', { error, year, make });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get trims for a year and model with caching
|
* Get trims for a year, make, and model with caching
|
||||||
*/
|
*/
|
||||||
async getTrims(pool: Pool, year: number, modelId: number): Promise<TrimItem[]> {
|
async getTrims(pool: Pool, year: number, make: string, model: string): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
const cached = await this.cache.getTrims(year, modelId);
|
const cached = await this.cache.getTrims(year, make, model);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
logger.debug('Trims retrieved from cache', { year, modelId });
|
logger.debug('Trims retrieved from cache', { year, make, model });
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trims = await this.repository.getTrims(pool, year, modelId);
|
const trims = await this.repository.getTrims(pool, year, make, model);
|
||||||
await this.cache.setTrims(year, modelId, trims);
|
await this.cache.setTrims(year, make, model, trims);
|
||||||
logger.debug('Trims retrieved from database and cached', { year, modelId, count: trims.length });
|
logger.debug('Trims retrieved from database and cached', { year, make, model, count: trims.length });
|
||||||
return trims;
|
return trims;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Service error: getTrims', { error, year, modelId });
|
logger.error('Service error: getTrims', { error, year, make, model });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get engines for a year, model, and trim with caching
|
* Get engines for a year, make, model, and trim with caching
|
||||||
*/
|
*/
|
||||||
async getEngines(pool: Pool, year: number, modelId: number, trimId: number): Promise<EngineItem[]> {
|
async getEngines(pool: Pool, year: number, make: string, model: string, trim: string): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
const cached = await this.cache.getEngines(year, modelId, trimId);
|
const cached = await this.cache.getEngines(year, make, model, trim);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
logger.debug('Engines retrieved from cache', { year, modelId, trimId });
|
logger.debug('Engines retrieved from cache', { year, make, model, trim });
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
const engines = await this.repository.getEngines(pool, year, modelId, trimId);
|
const engines = await this.repository.getEngines(pool, year, make, model, trim);
|
||||||
await this.cache.setEngines(year, modelId, trimId, engines);
|
await this.cache.setEngines(year, make, model, trim, engines);
|
||||||
logger.debug('Engines retrieved from database and cached', { year, modelId, trimId, count: engines.length });
|
logger.debug('Engines retrieved from database and cached', { year, make, model, trim, count: engines.length });
|
||||||
return engines;
|
return engines;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Service error: getEngines', { error, year, modelId, trimId });
|
logger.error('Service error: getEngines', { error, year, make, model, trim });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get transmissions for a year, make, and model with caching
|
||||||
|
*/
|
||||||
|
async getTransmissions(pool: Pool, year: number, make: string, model: string): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const cached = await this.cache.getTransmissions(year, make, model);
|
||||||
|
if (cached) {
|
||||||
|
logger.debug('Transmissions retrieved from cache', { year, make, model });
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transmissions = await this.repository.getTransmissions(pool, year, make, model);
|
||||||
|
await this.cache.setTransmissions(year, make, model, transmissions);
|
||||||
|
logger.debug('Transmissions retrieved from database and cached', { year, make, model, count: transmissions.length });
|
||||||
|
return transmissions;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Service error: getTransmissions', { error, year, make, model });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ export const modelsQuerySchema = z.object({
|
|||||||
.int('Year must be an integer')
|
.int('Year must be an integer')
|
||||||
.min(1950, 'Year must be at least 1950')
|
.min(1950, 'Year must be at least 1950')
|
||||||
.max(2100, 'Year must be at most 2100'),
|
.max(2100, 'Year must be at most 2100'),
|
||||||
make_id: z.coerce.number()
|
make: z.string()
|
||||||
.int('Make ID must be an integer')
|
.min(1, 'Make is required')
|
||||||
.positive('Make ID must be positive')
|
.max(100, 'Make must be less than 100 characters')
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ModelsQuery = z.infer<typeof modelsQuerySchema>;
|
export type ModelsQuery = z.infer<typeof modelsQuerySchema>;
|
||||||
@@ -58,9 +58,12 @@ export const trimsQuerySchema = z.object({
|
|||||||
.int('Year must be an integer')
|
.int('Year must be an integer')
|
||||||
.min(1950, 'Year must be at least 1950')
|
.min(1950, 'Year must be at least 1950')
|
||||||
.max(2100, 'Year must be at most 2100'),
|
.max(2100, 'Year must be at most 2100'),
|
||||||
model_id: z.coerce.number()
|
make: z.string()
|
||||||
.int('Model ID must be an integer')
|
.min(1, 'Make is required')
|
||||||
.positive('Model ID must be positive')
|
.max(100, 'Make must be less than 100 characters'),
|
||||||
|
model: z.string()
|
||||||
|
.min(1, 'Model is required')
|
||||||
|
.max(100, 'Model must be less than 100 characters')
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TrimsQuery = z.infer<typeof trimsQuerySchema>;
|
export type TrimsQuery = z.infer<typeof trimsQuerySchema>;
|
||||||
@@ -73,12 +76,33 @@ export const enginesQuerySchema = z.object({
|
|||||||
.int('Year must be an integer')
|
.int('Year must be an integer')
|
||||||
.min(1950, 'Year must be at least 1950')
|
.min(1950, 'Year must be at least 1950')
|
||||||
.max(2100, 'Year must be at most 2100'),
|
.max(2100, 'Year must be at most 2100'),
|
||||||
model_id: z.coerce.number()
|
make: z.string()
|
||||||
.int('Model ID must be an integer')
|
.min(1, 'Make is required')
|
||||||
.positive('Model ID must be positive'),
|
.max(100, 'Make must be less than 100 characters'),
|
||||||
trim_id: z.coerce.number()
|
model: z.string()
|
||||||
.int('Trim ID must be an integer')
|
.min(1, 'Model is required')
|
||||||
.positive('Trim ID must be positive')
|
.max(100, 'Model must be less than 100 characters'),
|
||||||
|
trim: z.string()
|
||||||
|
.min(1, 'Trim is required')
|
||||||
|
.max(100, 'Trim must be less than 100 characters')
|
||||||
});
|
});
|
||||||
|
|
||||||
export type EnginesQuery = z.infer<typeof enginesQuerySchema>;
|
export type EnginesQuery = z.infer<typeof enginesQuerySchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmissions query parameters validation
|
||||||
|
*/
|
||||||
|
export const transmissionsQuerySchema = z.object({
|
||||||
|
year: z.coerce.number()
|
||||||
|
.int('Year must be an integer')
|
||||||
|
.min(1950, 'Year must be at least 1950')
|
||||||
|
.max(2100, 'Year must be at most 2100'),
|
||||||
|
make: z.string()
|
||||||
|
.min(1, 'Make is required')
|
||||||
|
.max(100, 'Make must be less than 100 characters'),
|
||||||
|
model: z.string()
|
||||||
|
.min(1, 'Model is required')
|
||||||
|
.max(100, 'Model must be less than 100 characters')
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TransmissionsQuery = z.infer<typeof transmissionsQuerySchema>;
|
||||||
|
|||||||
@@ -1,72 +1,37 @@
|
|||||||
/**
|
/**
|
||||||
* @ai-summary Response DTOs for platform feature
|
* @ai-summary Response DTOs for platform feature
|
||||||
* @ai-context Type-safe response structures matching Python API
|
* @ai-context Type-safe response structures for vehicle data queries
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Make item response
|
|
||||||
*/
|
|
||||||
export interface MakeItem {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model item response
|
|
||||||
*/
|
|
||||||
export interface ModelItem {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trim item response
|
|
||||||
*/
|
|
||||||
export interface TrimItem {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Engine item response
|
|
||||||
*/
|
|
||||||
export interface EngineItem {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Years response
|
* Years response
|
||||||
*/
|
*/
|
||||||
export type YearsResponse = number[];
|
export type YearsResponse = number[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes response
|
* Makes response - array of make strings
|
||||||
*/
|
*/
|
||||||
export interface MakesResponse {
|
export type MakesResponse = string[];
|
||||||
makes: MakeItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Models response
|
* Models response - array of model strings
|
||||||
*/
|
*/
|
||||||
export interface ModelsResponse {
|
export type ModelsResponse = string[];
|
||||||
models: ModelItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trims response
|
* Trims response - array of trim strings
|
||||||
*/
|
*/
|
||||||
export interface TrimsResponse {
|
export type TrimsResponse = string[];
|
||||||
trims: TrimItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Engines response
|
* Engines response - array of engine strings (includes 'N/A (Electric)' for EVs)
|
||||||
*/
|
*/
|
||||||
export interface EnginesResponse {
|
export type EnginesResponse = string[];
|
||||||
engines: EngineItem[];
|
|
||||||
}
|
/**
|
||||||
|
* Transmissions response - array of transmission type strings
|
||||||
|
*/
|
||||||
|
export type TransmissionsResponse = string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VIN decode result (detailed vehicle information)
|
* VIN decode result (detailed vehicle information)
|
||||||
|
|||||||
@@ -171,20 +171,20 @@ export class VehiclesController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDropdownModels(request: FastifyRequest<{ Querystring: { year: number; make_id: number } }>, reply: FastifyReply) {
|
async getDropdownModels(request: FastifyRequest<{ Querystring: { year: number; make: string } }>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { year, make_id } = request.query;
|
const { year, make } = request.query;
|
||||||
if (!year || !make_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1) {
|
if (!year || !make || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0) {
|
||||||
return reply.code(400).send({
|
return reply.code(400).send({
|
||||||
error: 'Bad Request',
|
error: 'Bad Request',
|
||||||
message: 'Valid year and make_id parameters are required'
|
message: 'Valid year and make parameters are required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const models = await this.vehiclesService.getDropdownModels(year, make_id);
|
const models = await this.vehiclesService.getDropdownModels(year, make);
|
||||||
return reply.code(200).send(models);
|
return reply.code(200).send(models);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting dropdown models', { error, year: request.query?.year, make_id: request.query?.make_id });
|
logger.error('Error getting dropdown models', { error, year: request.query?.year, make: request.query?.make });
|
||||||
return reply.code(500).send({
|
return reply.code(500).send({
|
||||||
error: 'Internal server error',
|
error: 'Internal server error',
|
||||||
message: 'Failed to get models'
|
message: 'Failed to get models'
|
||||||
@@ -192,20 +192,20 @@ export class VehiclesController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDropdownTransmissions(request: FastifyRequest<{ Querystring: { year: number; make_id: number; model_id: number } }>, reply: FastifyReply) {
|
async getDropdownTransmissions(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string } }>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { year, make_id, model_id } = request.query;
|
const { year, make, model } = request.query;
|
||||||
if (!year || !make_id || !model_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1) {
|
if (!year || !make || !model || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0 || model.trim().length === 0) {
|
||||||
return reply.code(400).send({
|
return reply.code(400).send({
|
||||||
error: 'Bad Request',
|
error: 'Bad Request',
|
||||||
message: 'Valid year, make_id, and model_id parameters are required'
|
message: 'Valid year, make, and model parameters are required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const transmissions = await this.vehiclesService.getDropdownTransmissions(year, make_id, model_id);
|
const transmissions = await this.vehiclesService.getDropdownTransmissions(year, make, model);
|
||||||
return reply.code(200).send(transmissions);
|
return reply.code(200).send(transmissions);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting dropdown transmissions', { error, year: request.query?.year, make_id: request.query?.make_id, model_id: request.query?.model_id });
|
logger.error('Error getting dropdown transmissions', { error, year: request.query?.year, make: request.query?.make, model: request.query?.model });
|
||||||
return reply.code(500).send({
|
return reply.code(500).send({
|
||||||
error: 'Internal server error',
|
error: 'Internal server error',
|
||||||
message: 'Failed to get transmissions'
|
message: 'Failed to get transmissions'
|
||||||
@@ -213,20 +213,20 @@ export class VehiclesController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDropdownEngines(request: FastifyRequest<{ Querystring: { year: number; make_id: number; model_id: number; trim_id: number } }>, reply: FastifyReply) {
|
async getDropdownEngines(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string; trim: string } }>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { year, make_id, model_id, trim_id } = request.query;
|
const { year, make, model, trim } = request.query;
|
||||||
if (!year || !make_id || !model_id || !trim_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1 || trim_id < 1) {
|
if (!year || !make || !model || !trim || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0 || model.trim().length === 0 || trim.trim().length === 0) {
|
||||||
return reply.code(400).send({
|
return reply.code(400).send({
|
||||||
error: 'Bad Request',
|
error: 'Bad Request',
|
||||||
message: 'Valid year, make_id, model_id, and trim_id parameters are required'
|
message: 'Valid year, make, model, and trim parameters are required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const engines = await this.vehiclesService.getDropdownEngines(year, make_id, model_id, trim_id);
|
const engines = await this.vehiclesService.getDropdownEngines(year, make, model, trim);
|
||||||
return reply.code(200).send(engines);
|
return reply.code(200).send(engines);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting dropdown engines', { error, year: request.query?.year, make_id: request.query?.make_id, model_id: request.query?.model_id, trim_id: request.query?.trim_id });
|
logger.error('Error getting dropdown engines', { error, year: request.query?.year, make: request.query?.make, model: request.query?.model, trim: request.query?.trim });
|
||||||
return reply.code(500).send({
|
return reply.code(500).send({
|
||||||
error: 'Internal server error',
|
error: 'Internal server error',
|
||||||
message: 'Failed to get engines'
|
message: 'Failed to get engines'
|
||||||
@@ -234,20 +234,20 @@ export class VehiclesController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDropdownTrims(request: FastifyRequest<{ Querystring: { year: number; make_id: number; model_id: number } }>, reply: FastifyReply) {
|
async getDropdownTrims(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string } }>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { year, make_id, model_id } = request.query;
|
const { year, make, model } = request.query;
|
||||||
if (!year || !make_id || !model_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1) {
|
if (!year || !make || !model || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0 || model.trim().length === 0) {
|
||||||
return reply.code(400).send({
|
return reply.code(400).send({
|
||||||
error: 'Bad Request',
|
error: 'Bad Request',
|
||||||
message: 'Valid year, make_id, and model_id parameters are required'
|
message: 'Valid year, make, and model parameters are required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const trims = await this.vehiclesService.getDropdownTrims(year, make_id, model_id);
|
const trims = await this.vehiclesService.getDropdownTrims(year, make, model);
|
||||||
return reply.code(200).send(trims);
|
return reply.code(200).send(trims);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting dropdown trims', { error, year: request.query?.year, make_id: request.query?.make_id, model_id: request.query?.model_id });
|
logger.error('Error getting dropdown trims', { error, year: request.query?.year, make: request.query?.make, model: request.query?.model });
|
||||||
return reply.code(500).send({
|
return reply.code(500).send({
|
||||||
error: 'Internal server error',
|
error: 'Internal server error',
|
||||||
message: 'Failed to get trims'
|
message: 'Failed to get trims'
|
||||||
|
|||||||
@@ -45,26 +45,26 @@ export const vehiclesRoutes: FastifyPluginAsync = async (
|
|||||||
handler: vehiclesController.getDropdownMakes.bind(vehiclesController)
|
handler: vehiclesController.getDropdownMakes.bind(vehiclesController)
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/vehicles/dropdown/models?year=2024&make_id=1 - Get models for year/make (Level 2)
|
// GET /api/vehicles/dropdown/models?year=2024&make=Ford - Get models for year/make (Level 2)
|
||||||
fastify.get<{ Querystring: { year: number; make_id: number } }>('/vehicles/dropdown/models', {
|
fastify.get<{ Querystring: { year: number; make: string } }>('/vehicles/dropdown/models', {
|
||||||
preHandler: [fastify.authenticate],
|
preHandler: [fastify.authenticate],
|
||||||
handler: vehiclesController.getDropdownModels.bind(vehiclesController)
|
handler: vehiclesController.getDropdownModels.bind(vehiclesController)
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/vehicles/dropdown/trims?year=2024&make_id=1&model_id=1 - Get trims (Level 3)
|
// GET /api/vehicles/dropdown/trims?year=2024&make=Ford&model=F-150 - Get trims (Level 3)
|
||||||
fastify.get<{ Querystring: { year: number; make_id: number; model_id: number } }>('/vehicles/dropdown/trims', {
|
fastify.get<{ Querystring: { year: number; make: string; model: string } }>('/vehicles/dropdown/trims', {
|
||||||
preHandler: [fastify.authenticate],
|
preHandler: [fastify.authenticate],
|
||||||
handler: vehiclesController.getDropdownTrims.bind(vehiclesController)
|
handler: vehiclesController.getDropdownTrims.bind(vehiclesController)
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/vehicles/dropdown/engines?year=2024&make_id=1&model_id=1&trim_id=1 - Get engines (Level 4)
|
// GET /api/vehicles/dropdown/engines?year=2024&make=Ford&model=F-150&trim=XLT - Get engines (Level 4)
|
||||||
fastify.get<{ Querystring: { year: number; make_id: number; model_id: number; trim_id: number } }>('/vehicles/dropdown/engines', {
|
fastify.get<{ Querystring: { year: number; make: string; model: string; trim: string } }>('/vehicles/dropdown/engines', {
|
||||||
preHandler: [fastify.authenticate],
|
preHandler: [fastify.authenticate],
|
||||||
handler: vehiclesController.getDropdownEngines.bind(vehiclesController)
|
handler: vehiclesController.getDropdownEngines.bind(vehiclesController)
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/vehicles/dropdown/transmissions?year=2024&make_id=1&model_id=1 - Get transmissions (Level 3)
|
// GET /api/vehicles/dropdown/transmissions?year=2024&make=Ford&model=F-150 - Get transmissions (Level 3)
|
||||||
fastify.get<{ Querystring: { year: number; make_id: number; model_id: number } }>('/vehicles/dropdown/transmissions', {
|
fastify.get<{ Querystring: { year: number; make: string; model: string } }>('/vehicles/dropdown/transmissions', {
|
||||||
preHandler: [fastify.authenticate],
|
preHandler: [fastify.authenticate],
|
||||||
handler: vehiclesController.getDropdownTransmissions.bind(vehiclesController)
|
handler: vehiclesController.getDropdownTransmissions.bind(vehiclesController)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export class VehiclesService {
|
|||||||
await cacheService.del(cacheKey);
|
await cacheService.del(cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDropdownMakes(year: number): Promise<{ id: number; name: string }[]> {
|
async getDropdownMakes(year: number): Promise<string[]> {
|
||||||
const vehicleDataService = getVehicleDataService();
|
const vehicleDataService = getVehicleDataService();
|
||||||
const pool = getPool();
|
const pool = getPool();
|
||||||
|
|
||||||
@@ -170,36 +170,36 @@ export class VehiclesService {
|
|||||||
return vehicleDataService.getMakes(pool, year);
|
return vehicleDataService.getMakes(pool, year);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDropdownModels(year: number, makeId: number): Promise<{ id: number; name: string }[]> {
|
async getDropdownModels(year: number, make: string): Promise<string[]> {
|
||||||
const vehicleDataService = getVehicleDataService();
|
const vehicleDataService = getVehicleDataService();
|
||||||
const pool = getPool();
|
const pool = getPool();
|
||||||
|
|
||||||
logger.info('Fetching dropdown models via platform module', { year, makeId });
|
logger.info('Fetching dropdown models via platform module', { year, make });
|
||||||
return vehicleDataService.getModels(pool, year, makeId);
|
return vehicleDataService.getModels(pool, year, make);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDropdownTransmissions(_year: number, _makeId: number, _modelId: number): Promise<{ id: number; name: string }[]> {
|
async getDropdownTransmissions(year: number, make: string, model: string): Promise<string[]> {
|
||||||
logger.info('Providing dropdown transmissions from static list');
|
|
||||||
return [
|
|
||||||
{ id: 1, name: 'Automatic' },
|
|
||||||
{ id: 2, name: 'Manual' }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDropdownEngines(year: number, makeId: number, modelId: number, trimId: number): Promise<{ id: number; name: string }[]> {
|
|
||||||
const vehicleDataService = getVehicleDataService();
|
const vehicleDataService = getVehicleDataService();
|
||||||
const pool = getPool();
|
const pool = getPool();
|
||||||
|
|
||||||
logger.info('Fetching dropdown engines via platform module', { year, makeId, modelId, trimId });
|
logger.info('Fetching dropdown transmissions via platform module', { year, make, model });
|
||||||
return vehicleDataService.getEngines(pool, year, modelId, trimId);
|
return vehicleDataService.getTransmissions(pool, year, make, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDropdownTrims(year: number, makeId: number, modelId: number): Promise<{ id: number; name: string }[]> {
|
async getDropdownEngines(year: number, make: string, model: string, trim: string): Promise<string[]> {
|
||||||
const vehicleDataService = getVehicleDataService();
|
const vehicleDataService = getVehicleDataService();
|
||||||
const pool = getPool();
|
const pool = getPool();
|
||||||
|
|
||||||
logger.info('Fetching dropdown trims via platform module', { year, makeId, modelId });
|
logger.info('Fetching dropdown engines via platform module', { year, make, model, trim });
|
||||||
return vehicleDataService.getTrims(pool, year, modelId);
|
return vehicleDataService.getEngines(pool, year, make, model, trim);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDropdownTrims(year: number, make: string, model: string): Promise<string[]> {
|
||||||
|
const vehicleDataService = getVehicleDataService();
|
||||||
|
const pool = getPool();
|
||||||
|
|
||||||
|
logger.info('Fetching dropdown trims via platform module', { year, make, model });
|
||||||
|
return vehicleDataService.getTrims(pool, year, make, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDropdownYears(): Promise<number[]> {
|
async getDropdownYears(): Promise<number[]> {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,18 +9,16 @@ This ETL pipeline creates a PostgreSQL database optimized for cascading dropdown
|
|||||||
|
|
||||||
### Tables
|
### Tables
|
||||||
|
|
||||||
1. **engines** - Detailed engine specifications
|
1. **engines** - Simplified engine specifications
|
||||||
- Displacement, configuration, horsepower, torque
|
- id (Primary Key)
|
||||||
- Fuel type, fuel system, aspiration
|
- name (Display format: "V8 3.5L", "L4 2.0L Turbo", "V6 6.2L Supercharged")
|
||||||
- Full specs stored as JSONB
|
|
||||||
|
|
||||||
2. **transmissions** - Transmission specifications
|
2. **transmissions** - Simplified transmission specifications
|
||||||
- Type (Manual, Automatic, CVT, etc.)
|
- id (Primary Key)
|
||||||
- Number of speeds
|
- type (Display format: "8-Speed Automatic", "6-Speed Manual", "CVT")
|
||||||
- Drive type (FWD, RWD, AWD, 4WD)
|
|
||||||
|
|
||||||
3. **vehicle_options** - Denormalized vehicle configurations
|
3. **vehicle_options** - Denormalized vehicle configurations
|
||||||
- Year, Make, Model, Trim
|
- Year, Make (Title Case: "Ford", "Acura", "Land Rover"), Model, Trim
|
||||||
- Foreign keys to engines and transmissions
|
- Foreign keys to engines and transmissions
|
||||||
- Optimized indexes for dropdown queries
|
- Optimized indexes for dropdown queries
|
||||||
|
|
||||||
@@ -63,57 +61,72 @@ This ETL pipeline creates a PostgreSQL database optimized for cascading dropdown
|
|||||||
|
|
||||||
## ETL Process
|
## ETL Process
|
||||||
|
|
||||||
### Step 1: Import Engine & Transmission Specs
|
### Step 1: Load Source Data
|
||||||
- Parse all records from `engines.json`
|
- Load `engines.json` (30,066 records)
|
||||||
- Extract detailed specifications
|
- Load `brands.json` (124 brands)
|
||||||
- Create engines and transmissions tables
|
- Load `automobiles.json` (7,207 models)
|
||||||
- Build in-memory caches for fast lookups
|
- Load all `makes-filter/*.json` files (55 files)
|
||||||
|
|
||||||
### Step 2: Process Makes-Filter Data
|
### Step 2: Transform Brand Names
|
||||||
- Read all 57 JSON files from `makes-filter/`
|
- Convert ALL CAPS brand names to Title Case ("FORD" → "Ford")
|
||||||
|
- Preserve acronyms (BMW, GMC, KIA remain uppercase)
|
||||||
|
- Handle special cases (DeLorean, McLaren)
|
||||||
|
|
||||||
|
### Step 3: Process Engine Specifications
|
||||||
|
- Extract engine specs from engines.json
|
||||||
|
- Create simplified display names (e.g., "V8 3.5L Turbo")
|
||||||
|
- Normalize displacement (Cm3 → Liters) for matching
|
||||||
|
- Build engine cache with (displacement, configuration) keys
|
||||||
|
- Generate engines SQL with only id and name columns
|
||||||
|
|
||||||
|
### Step 4: Process Transmission Specifications
|
||||||
|
- Extract transmission specs from engines.json
|
||||||
|
- Create simplified display names (e.g., "8-Speed Automatic")
|
||||||
|
- Parse speed count and transmission type
|
||||||
|
- Build transmission cache for linking
|
||||||
|
- Generate transmissions SQL with only id and type columns
|
||||||
|
|
||||||
|
### Step 5: Process Makes-Filter Data
|
||||||
|
- Read all JSON files from `makes-filter/`
|
||||||
- Extract year/make/model/trim/engine combinations
|
- Extract year/make/model/trim/engine combinations
|
||||||
- Match engine strings to detailed specs using displacement + configuration
|
- Match engine strings to detailed specs using displacement + configuration
|
||||||
|
- Link transmissions to vehicle records (98.9% success rate)
|
||||||
|
- Apply year filter (1980 and newer only)
|
||||||
- Build vehicle_options records
|
- Build vehicle_options records
|
||||||
|
|
||||||
### Step 3: Hybrid Backfill
|
### Step 6: Hybrid Backfill
|
||||||
- Check `automobiles.json` for recent years (2023-2025)
|
- Check `automobiles.json` for recent years (2023-2025)
|
||||||
- Add any missing year/make/model combinations
|
- Add any missing year/make/model combinations
|
||||||
- Only backfill for the 57 filtered makes
|
- Only backfill for filtered makes
|
||||||
|
- Link transmissions for backfilled records
|
||||||
- Limit to 3 engines per backfilled model
|
- Limit to 3 engines per backfilled model
|
||||||
|
|
||||||
### Step 4: Insert Vehicle Options
|
### Step 7: Generate SQL Output
|
||||||
- Batch insert all vehicle_options records
|
- Write SQL files with proper escaping (newlines, quotes, special characters)
|
||||||
- Create indexes for optimal query performance
|
- Convert empty strings to NULL for data integrity
|
||||||
- Generate views and functions
|
- Use batched inserts (1000 records per batch)
|
||||||
|
- Output to `output/` directory
|
||||||
### Step 5: Validation
|
|
||||||
- Count records in each table
|
|
||||||
- Test dropdown cascade queries
|
|
||||||
- Display sample data
|
|
||||||
|
|
||||||
## Running the ETL
|
## Running the ETL
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- Docker container `mvp-postgres` running
|
- Docker container `mvp-postgres` running
|
||||||
- Python 3 with psycopg2
|
- Python 3 (no additional dependencies required)
|
||||||
- JSON source files in project root
|
- JSON source files in project root
|
||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
```bash
|
```bash
|
||||||
./run_migration.sh
|
# Step 1: Generate SQL files from JSON data
|
||||||
|
python3 etl_generate_sql.py
|
||||||
|
|
||||||
|
# Step 2: Import SQL files into database
|
||||||
|
./import_data.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual Steps
|
### What Gets Generated
|
||||||
```bash
|
- `output/01_engines.sql` (~632KB, 30,066 records)
|
||||||
# 1. Run migration
|
- `output/02_transmissions.sql` (~21KB, 828 records)
|
||||||
docker compose exec mvp-postgres psql -U postgres -d motovaultpro < migrations/001_create_vehicle_database.sql
|
- `output/03_vehicle_options.sql` (~51MB, 1,122,644 records)
|
||||||
|
|
||||||
# 2. Install Python dependencies
|
|
||||||
pip3 install psycopg2-binary
|
|
||||||
|
|
||||||
# 3. Run ETL script
|
|
||||||
python3 etl_vehicle_data.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Query Examples
|
## Query Examples
|
||||||
|
|
||||||
@@ -127,26 +140,26 @@ SELECT * FROM available_years;
|
|||||||
SELECT * FROM get_makes_for_year(2024);
|
SELECT * FROM get_makes_for_year(2024);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get models for 2024 Ford
|
### Get models for 2025 Ford
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM get_models_for_year_make(2024, 'Ford');
|
SELECT * FROM get_models_for_year_make(2025, 'Ford');
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get trims for 2024 Ford F-150
|
### Get trims for 2025 Ford F-150
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM get_trims_for_year_make_model(2024, 'Ford', 'F-150');
|
SELECT * FROM get_trims_for_year_make_model(2025, 'Ford', 'f-150');
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get engine/transmission options for specific vehicle
|
### Get engine/transmission options for specific vehicle
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM get_options_for_vehicle(2024, 'Ford', 'F-150', 'XLT');
|
SELECT * FROM get_options_for_vehicle(2025, 'Ford', 'f-150', 'XLT');
|
||||||
```
|
```
|
||||||
|
|
||||||
### Complete vehicle configurations
|
### Complete vehicle configurations
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM complete_vehicle_configs
|
SELECT * FROM complete_vehicle_configs
|
||||||
WHERE year = 2024 AND make = 'Tesla'
|
WHERE year = 2025 AND make = 'Ford' AND model = 'f-150'
|
||||||
ORDER BY model, trim;
|
LIMIT 10;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Performance Optimization
|
## Performance Optimization
|
||||||
@@ -164,35 +177,50 @@ Dropdown queries are optimized to return results in < 50ms for typical datasets.
|
|||||||
|
|
||||||
## Data Matching Logic
|
## Data Matching Logic
|
||||||
|
|
||||||
|
### Brand Name Transformation
|
||||||
|
- Source data (brands.json) stores names in ALL CAPS: "FORD", "ACURA", "ALFA ROMEO"
|
||||||
|
- ETL converts to Title Case: "Ford", "Acura", "Alfa Romeo"
|
||||||
|
- Preserves acronyms: BMW, GMC, KIA, MINI, FIAT, RAM
|
||||||
|
- Special cases: DeLorean, McLaren
|
||||||
|
|
||||||
### Engine Matching
|
### Engine Matching
|
||||||
The ETL uses intelligent pattern matching to link simple engine strings from makes-filter to detailed specs:
|
The ETL uses intelligent pattern matching to link simple engine strings from makes-filter to detailed specs:
|
||||||
|
|
||||||
1. **Parse engine string**: Extract displacement (e.g., "2.0L") and configuration (e.g., "I4")
|
1. **Parse engine string**: Extract displacement (e.g., "2.0L") and configuration (e.g., "I4")
|
||||||
2. **Normalize**: Convert to uppercase, standardize format
|
2. **Normalize displacement**: Convert Cm3 to Liters ("3506 Cm3" → "3.5L")
|
||||||
3. **Match to cache**: Look up in engine cache by (displacement, configuration)
|
3. **Match to cache**: Look up in engine cache by (displacement, configuration)
|
||||||
4. **Handle variations**: Account for I4/L4, V6/V-6, etc.
|
4. **Create display name**: Format as "V8 3.5L", "L4 2.0L Turbo", etc.
|
||||||
|
|
||||||
|
### Transmission Linking
|
||||||
|
- Transmission data is embedded in engines.json under "Transmission Specs"
|
||||||
|
- Each engine record includes gearbox type (e.g., "6-Speed Manual")
|
||||||
|
- ETL links transmissions to vehicle records based on engine match
|
||||||
|
- Success rate: 98.9% (1,109,510 of 1,122,644 records)
|
||||||
|
- Unlinked records: primarily electric vehicles without traditional transmissions
|
||||||
|
|
||||||
### Configuration Equivalents
|
### Configuration Equivalents
|
||||||
- `I4` = `L4` = `INLINE-4`
|
- `I4` = `L4` = `INLINE-4` = `4 Inline`
|
||||||
- `V6` = `V-6`
|
- `V6` = `V-6`
|
||||||
- `V8` = `V-8`
|
- `V8` = `V-8`
|
||||||
|
|
||||||
## Filtered Makes (57 Total)
|
## Filtered Makes (53 Total)
|
||||||
|
|
||||||
|
All brand names are stored in Title Case format for user-friendly display.
|
||||||
|
|
||||||
### American Brands (12)
|
### American Brands (12)
|
||||||
Acura, Buick, Cadillac, Chevrolet, Chrysler, Dodge, Ford, GMC, Hummer, Jeep, Lincoln, Ram
|
Acura, Buick, Cadillac, Chevrolet, Chrysler, Dodge, Ford, GMC, Hummer, Jeep, Lincoln, RAM
|
||||||
|
|
||||||
### Luxury/Performance (13)
|
### Luxury/Performance (13)
|
||||||
Aston Martin, Bentley, Ferrari, Lamborghini, Maserati, McLaren, Porsche, Rolls-Royce, Tesla, Jaguar, Audi, BMW, Land Rover
|
Aston Martin, Bentley, Ferrari, Lamborghini, Maserati, McLaren, Porsche, Rolls Royce, Tesla, Jaguar, Audi, BMW, Land Rover
|
||||||
|
|
||||||
### Japanese (7)
|
### Japanese (8)
|
||||||
Honda, Infiniti, Lexus, Mazda, Mitsubishi, Nissan, Subaru, Toyota
|
Honda, Infiniti, Lexus, Mazda, Mitsubishi, Nissan, Subaru, Toyota
|
||||||
|
|
||||||
### European (13)
|
### European (9)
|
||||||
Alfa Romeo, Fiat, Mini, Saab, Saturn, Scion, Smart, Volkswagen, Volvo
|
Alfa Romeo, FIAT, MINI, Saab, Saturn, Scion, Smart, Volkswagen, Volvo
|
||||||
|
|
||||||
### Other (12)
|
### Other (11)
|
||||||
Genesis, Geo, Hyundai, Kia, Lucid, Polestar, Rivian, Lotus, Mercury, Oldsmobile, Plymouth, Pontiac
|
Genesis, Geo, Hyundai, KIA, Lucid, Polestar, Rivian, Lotus, Mercury, Oldsmobile, Plymouth, Pontiac
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
@@ -229,12 +257,14 @@ pip3 install psycopg2-binary
|
|||||||
## Expected Results
|
## Expected Results
|
||||||
|
|
||||||
After successful ETL:
|
After successful ETL:
|
||||||
- **Engines**: ~30,000 records
|
- **Engines**: 30,066 records
|
||||||
- **Transmissions**: ~500-1000 unique combinations
|
- **Transmissions**: 828 records
|
||||||
- **Vehicle Options**: ~50,000-100,000 configurations
|
- **Vehicle Options**: 1,122,644 configurations
|
||||||
- **Years**: 10-15 distinct years
|
- **Years**: 47 years (1980-2026)
|
||||||
- **Makes**: 57 manufacturers
|
- **Makes**: 53 manufacturers
|
||||||
- **Models**: 1,000-2,000 unique models
|
- **Models**: 1,741 unique models
|
||||||
|
- **Transmission Linking**: 98.9% success rate
|
||||||
|
- **Output Files**: ~52MB total (632KB engines + 21KB transmissions + 51MB vehicles)
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
|
|||||||
@@ -55,11 +55,24 @@ Applied filter in two locations:
|
|||||||
|
|
||||||
### 2. SQL Files Regenerated
|
### 2. SQL Files Regenerated
|
||||||
|
|
||||||
- `output/01_engines.sql` - 34MB (unchanged, all engines retained)
|
- `output/01_engines.sql` - ~632KB (simplified to id and name only)
|
||||||
- `output/03_vehicle_options.sql` - 52MB (reduced from 56MB)
|
- `output/02_transmissions.sql` - ~21KB (new file, id and type only)
|
||||||
|
- `output/03_vehicle_options.sql` - ~51MB (reduced from 56MB)
|
||||||
- Total batches: 1,123 (reduced from 1,214)
|
- Total batches: 1,123 (reduced from 1,214)
|
||||||
|
|
||||||
### 3. Database Re-imported
|
### 3. Data Transformation
|
||||||
|
|
||||||
|
**Engine Names:** Simplified to user-friendly display format
|
||||||
|
- Example: "V8 3.5L Turbo", "L4 2.0L", "V6 6.2L Supercharged"
|
||||||
|
|
||||||
|
**Transmission Types:** Simplified to user-friendly display format
|
||||||
|
- Example: "8-Speed Automatic", "6-Speed Manual", "CVT"
|
||||||
|
|
||||||
|
**Brand Names:** Converted from ALL CAPS to Title Case
|
||||||
|
- Example: "FORD" → "Ford", "LAND ROVER" → "Land Rover"
|
||||||
|
- Acronyms preserved: BMW, GMC, KIA, MINI, FIAT, RAM
|
||||||
|
|
||||||
|
### 4. Database Re-imported
|
||||||
|
|
||||||
Successfully imported filtered data with zero pre-1980 vehicles.
|
Successfully imported filtered data with zero pre-1980 vehicles.
|
||||||
|
|
||||||
@@ -89,11 +102,13 @@ python3 etl_generate_sql.py
|
|||||||
| Metric | Count |
|
| Metric | Count |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| **Engines** | 30,066 |
|
| **Engines** | 30,066 |
|
||||||
|
| **Transmissions** | 828 |
|
||||||
| **Vehicle Options** | 1,122,644 |
|
| **Vehicle Options** | 1,122,644 |
|
||||||
| **Years** | 47 (1980-2026) |
|
| **Years** | 47 (1980-2026) |
|
||||||
| **Makes** | 53 |
|
| **Makes** | 53 |
|
||||||
| **Models** | 1,741 |
|
| **Models** | 1,741 |
|
||||||
| **Database Size** | ~220MB |
|
| **Transmission Linking** | 98.9% success |
|
||||||
|
| **Database Size** | ~250MB |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -128,10 +143,13 @@ No changes required to API or query logic.
|
|||||||
|
|
||||||
| File | Change |
|
| File | Change |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| `etl_generate_sql.py` | Added min_year filter (line 22) |
|
| `etl_generate_sql.py` | Added min_year filter, brand name transformation, simplified display formats |
|
||||||
| `output/01_engines.sql` | Regenerated (no change) |
|
| `output/01_engines.sql` | Regenerated with simplified format (id, name only) |
|
||||||
| `output/03_vehicle_options.sql` | Regenerated (90K fewer records) |
|
| `output/02_transmissions.sql` | New file with transmission data (id, type only) |
|
||||||
| Database `vehicle_options` table | Re-imported with filter |
|
| `output/03_vehicle_options.sql` | Regenerated (90K fewer records, transmission linking added) |
|
||||||
|
| Database `engines` table | Re-imported with simplified schema |
|
||||||
|
| Database `transmissions` table | Newly created and populated |
|
||||||
|
| Database `vehicle_options` table | Re-imported with 1980+ filter and transmission links |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Automotive Vehicle Selection Database - Implementation Summary
|
# Automotive Vehicle Selection Database - Implementation Summary
|
||||||
|
|
||||||
## Status: ✅ COMPLETED
|
## Status: ✅ COMPLETED & OPTIMIZED
|
||||||
|
|
||||||
The ETL pipeline has been successfully implemented and executed. The database is now populated and ready for use.
|
The ETL pipeline has been successfully implemented, optimized, and executed. The database is now populated with clean, user-friendly data ready for production use.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -11,10 +11,16 @@ The ETL pipeline has been successfully implemented and executed. The database is
|
|||||||
| Metric | Count |
|
| Metric | Count |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| **Engines** | 30,066 |
|
| **Engines** | 30,066 |
|
||||||
| **Vehicle Options** | 1,213,401 |
|
| **Transmissions** | 828 |
|
||||||
| **Years** | 93 (1918-2026) |
|
| **Vehicle Options** | 1,122,644 |
|
||||||
|
| **Years** | 47 (1980-2026) |
|
||||||
| **Makes** | 53 |
|
| **Makes** | 53 |
|
||||||
| **Models** | 1,937 |
|
| **Models** | 1,741 |
|
||||||
|
|
||||||
|
### Data Quality Metrics
|
||||||
|
- **Transmission Linking Success**: 98.9% (1,109,510 of 1,122,644 records)
|
||||||
|
- **Records with NULL Engine/Transmission**: 1.1% (11,951 records - primarily electric vehicles)
|
||||||
|
- **Year Filter Applied**: 1980 and newer only
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -23,9 +29,12 @@ The ETL pipeline has been successfully implemented and executed. The database is
|
|||||||
### 1. Database Schema (`migrations/001_create_vehicle_database.sql`)
|
### 1. Database Schema (`migrations/001_create_vehicle_database.sql`)
|
||||||
|
|
||||||
**Tables:**
|
**Tables:**
|
||||||
- `engines` - Engine specifications with displacement, configuration, horsepower, torque, fuel type
|
- `engines` - Simplified engine specifications (id, name)
|
||||||
- `transmissions` - Transmission specifications (type, speeds, drive type)
|
- Names formatted as: "V8 3.5L", "L4 2.0L Turbo", "V6 6.2L Supercharged"
|
||||||
|
- `transmissions` - Simplified transmission specifications (id, type)
|
||||||
|
- Types formatted as: "8-Speed Automatic", "6-Speed Manual", "CVT"
|
||||||
- `vehicle_options` - Denormalized table optimized for dropdown queries (year, make, model, trim, engine_id, transmission_id)
|
- `vehicle_options` - Denormalized table optimized for dropdown queries (year, make, model, trim, engine_id, transmission_id)
|
||||||
|
- Make names in Title Case: "Acura", "Ford", "BMW" (not ALL CAPS)
|
||||||
|
|
||||||
**Views:**
|
**Views:**
|
||||||
- `available_years` - All distinct years
|
- `available_years` - All distinct years
|
||||||
@@ -60,16 +69,26 @@ A Python script that processes JSON source files and generates SQL import files:
|
|||||||
**ETL Process:**
|
**ETL Process:**
|
||||||
1. **Extract** - Loads all JSON source files
|
1. **Extract** - Loads all JSON source files
|
||||||
2. **Transform**
|
2. **Transform**
|
||||||
- Parses engine specifications and extracts relevant data
|
- Converts brand names from ALL CAPS to Title Case ("FORD" → "Ford")
|
||||||
|
- Creates simplified engine display names (e.g., "V8 3.5L Turbo")
|
||||||
|
- Extracts configuration (V8, I4, L6), displacement, and aspiration
|
||||||
|
- Handles missing displacement by parsing from engine name
|
||||||
|
- Creates simplified transmission display names (e.g., "8-Speed Automatic")
|
||||||
|
- Extracts speed count and type (Manual, Automatic, CVT, Dual-Clutch)
|
||||||
|
- Normalizes displacement units (Cm3 → Liters) for matching
|
||||||
- Matches simple engine strings (e.g., "2.0L I4") to detailed specs
|
- Matches simple engine strings (e.g., "2.0L I4") to detailed specs
|
||||||
- Processes year/make/model/trim hierarchy from makes-filter files
|
- Links transmissions to vehicle records (98.9% success rate)
|
||||||
|
- Filters vehicles to 1980 and newer only
|
||||||
- Performs hybrid backfill for recent years (2023-2025)
|
- Performs hybrid backfill for recent years (2023-2025)
|
||||||
3. **Load** - Generates optimized SQL import files in batches
|
3. **Load** - Generates clean, optimized SQL import files
|
||||||
|
- Proper SQL escaping (newlines, quotes, special characters)
|
||||||
|
- Empty strings converted to NULL for data integrity
|
||||||
|
- Batched inserts for optimal performance
|
||||||
|
|
||||||
**Output Files:**
|
**Output Files:**
|
||||||
- `output/01_engines.sql` (34MB, 30,066 records)
|
- `output/01_engines.sql` (~632KB, 30,066 records) - Only id and name columns
|
||||||
- `output/02_transmissions.sql` (empty - no transmission data in source)
|
- `output/02_transmissions.sql` (~21KB, 828 records) - Only id and type columns
|
||||||
- `output/03_vehicle_options.sql` (56MB, 1,213,401 records)
|
- `output/03_vehicle_options.sql` (~51MB, 1,122,644 records)
|
||||||
|
|
||||||
### 3. Import Script (`import_data.sh`)
|
### 3. Import Script (`import_data.sh`)
|
||||||
|
|
||||||
@@ -188,24 +207,21 @@ Each query is optimized with composite indexes for sub-50ms response times.
|
|||||||
|
|
||||||
## Known Limitations
|
## Known Limitations
|
||||||
|
|
||||||
1. **Transmissions Table is Empty**
|
1. **Electric Vehicles Have NULL Engine/Transmission IDs (1.1%)**
|
||||||
- The engines.json source data doesn't contain consistent transmission info
|
- Occurs when engine string from makes-filter doesn't match traditional displacement patterns
|
||||||
- Transmission foreign keys in vehicle_options are NULL
|
- Example: Tesla models with "Electric" motors don't have displacement specs
|
||||||
- Future enhancement: Add transmission data from alternative source
|
- Affects 11,951 of 1,122,644 records
|
||||||
|
- Future enhancement: Add electric motor specifications
|
||||||
|
|
||||||
2. **Some Engine IDs are NULL**
|
2. **Model Names Have Inconsistencies**
|
||||||
- Occurs when engine string from makes-filter doesn't match any record in engines.json
|
|
||||||
- Example: "Electric" motors don't match traditional displacement patterns
|
|
||||||
- ~0 engine cache matches built (needs investigation)
|
|
||||||
|
|
||||||
3. **Model Names Have Inconsistencies**
|
|
||||||
- Some models from backfill include HTML entities (`&`)
|
|
||||||
- Some models use underscores (`bronco_sport` vs `Bronco Sport`)
|
- Some models use underscores (`bronco_sport` vs `Bronco Sport`)
|
||||||
- Future enhancement: Normalize model names
|
- Model name casing varies between sources
|
||||||
|
- Future enhancement: Normalize model names to Title Case
|
||||||
|
|
||||||
4. **Year Range is Very Wide (1918-2026)**
|
3. **Engine Configuration Variations**
|
||||||
- Includes vintage/classic cars from makes-filter data
|
- Some engines show "4 Inline" while others show "L4" or "I4"
|
||||||
- May want to filter to specific year range for dropdown UI
|
- All refer to inline 4-cylinder but use different notation
|
||||||
|
- Source data inconsistency from autoevolution.com
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
## Database Status: ✅ OPERATIONAL
|
## Database Status: ✅ OPERATIONAL
|
||||||
|
|
||||||
- **30,066** engines
|
- **30,066** engines
|
||||||
- **1,213,401** vehicle configurations
|
- **828** transmissions
|
||||||
- **93** years (1918-2026)
|
- **1,122,644** vehicle configurations
|
||||||
|
- **47** years (1980-2026)
|
||||||
- **53** makes
|
- **53** makes
|
||||||
- **1,937** models
|
- **1,741** models
|
||||||
|
- **98.9%** transmission linking success
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -65,12 +67,15 @@ python3 etl_generate_sql.py
|
|||||||
|
|
||||||
## Files Overview
|
## Files Overview
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose | Size |
|
||||||
|------|---------|
|
|------|---------|------|
|
||||||
| `etl_generate_sql.py` | Generate SQL import files from JSON |
|
| `etl_generate_sql.py` | Generate SQL import files from JSON | ~20KB |
|
||||||
| `import_data.sh` | Import SQL files into database |
|
| `import_data.sh` | Import SQL files into database | ~2KB |
|
||||||
| `migrations/001_create_vehicle_database.sql` | Database schema |
|
| `migrations/001_create_vehicle_database.sql` | Database schema | ~8KB |
|
||||||
| `output/*.sql` | Generated SQL import files (90MB total) |
|
| `output/01_engines.sql` | Engine data (id, name only) | ~632KB |
|
||||||
|
| `output/02_transmissions.sql` | Transmission data (id, type only) | ~21KB |
|
||||||
|
| `output/03_vehicle_options.sql` | Vehicle configurations | ~51MB |
|
||||||
|
| **Total Output** | | **~52MB** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -79,18 +84,16 @@ python3 etl_generate_sql.py
|
|||||||
```
|
```
|
||||||
engines
|
engines
|
||||||
├── id (PK)
|
├── id (PK)
|
||||||
├── name
|
└── name (e.g., "V8 3.5L Turbo", "L4 2.0L")
|
||||||
├── displacement
|
|
||||||
├── configuration (I4, V6, V8, etc.)
|
transmissions
|
||||||
├── horsepower
|
├── id (PK)
|
||||||
├── torque
|
└── type (e.g., "8-Speed Automatic", "6-Speed Manual")
|
||||||
├── fuel_type
|
|
||||||
└── specs_json (full specifications)
|
|
||||||
|
|
||||||
vehicle_options
|
vehicle_options
|
||||||
├── id (PK)
|
├── id (PK)
|
||||||
├── year
|
├── year (1980-2026)
|
||||||
├── make
|
├── make (Title Case: "Ford", "Acura", "Land Rover")
|
||||||
├── model
|
├── model
|
||||||
├── trim
|
├── trim
|
||||||
├── engine_id (FK → engines)
|
├── engine_id (FK → engines)
|
||||||
@@ -101,9 +104,10 @@ vehicle_options
|
|||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
- **Query Time:** < 50ms (indexed)
|
- **Query Time:** < 50ms (composite indexes)
|
||||||
- **Database Size:** 219MB
|
- **Database Size:** ~250MB (with indexes)
|
||||||
- **Index Size:** 117MB
|
- **SQL Import Files:** ~52MB total
|
||||||
|
- **Batch Insert Size:** 1,000 records per batch
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -25,6 +25,7 @@ class VehicleSQLGenerator:
|
|||||||
self.engine_cache = {} # Key: (displacement, config) -> engine record
|
self.engine_cache = {} # Key: (displacement, config) -> engine record
|
||||||
self.transmission_cache = {} # Key: (type, speeds, drive) -> transmission record
|
self.transmission_cache = {} # Key: (type, speeds, drive) -> transmission record
|
||||||
self.vehicle_records = []
|
self.vehicle_records = []
|
||||||
|
self.brand_name_map = {} # Key: lowercase_slug -> canonical brand name
|
||||||
|
|
||||||
# Output SQL files
|
# Output SQL files
|
||||||
self.engines_sql_file = 'output/01_engines.sql'
|
self.engines_sql_file = 'output/01_engines.sql'
|
||||||
@@ -50,6 +51,100 @@ class VehicleSQLGenerator:
|
|||||||
self.brands_data = json.load(f)
|
self.brands_data = json.load(f)
|
||||||
print(f" ✓ Loaded {len(self.brands_data)} brand records")
|
print(f" ✓ Loaded {len(self.brands_data)} brand records")
|
||||||
|
|
||||||
|
# Build brand name mapping for proper casing
|
||||||
|
self.build_brand_name_map()
|
||||||
|
|
||||||
|
def build_brand_name_map(self):
|
||||||
|
"""Build a mapping from lowercase brand names to Title Case brand names"""
|
||||||
|
# Acronyms and special cases that should stay uppercase
|
||||||
|
keep_uppercase = {'BMW', 'GMC', 'AC', 'MG', 'KIA', 'MINI', 'FIAT', 'RAM', 'KTM', 'FSO', 'ARO', 'TVR', 'NIO'}
|
||||||
|
special_cases = {
|
||||||
|
'delorean': 'DeLorean',
|
||||||
|
'mclaren': 'McLaren'
|
||||||
|
}
|
||||||
|
|
||||||
|
for brand in self.brands_data:
|
||||||
|
brand_name_upper = brand.get('name', '').strip()
|
||||||
|
if not brand_name_upper:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create slug from uppercase name
|
||||||
|
slug = brand_name_upper.lower().replace(' ', '_')
|
||||||
|
|
||||||
|
# Determine canonical name based on special cases
|
||||||
|
if slug in special_cases:
|
||||||
|
canonical_name = special_cases[slug]
|
||||||
|
elif brand_name_upper in keep_uppercase:
|
||||||
|
canonical_name = brand_name_upper
|
||||||
|
else:
|
||||||
|
# Convert to Title Case
|
||||||
|
canonical_name = brand_name_upper.title()
|
||||||
|
|
||||||
|
# Store both slug and space-separated versions as keys
|
||||||
|
self.brand_name_map[slug] = canonical_name
|
||||||
|
self.brand_name_map[brand_name_upper.lower()] = canonical_name
|
||||||
|
|
||||||
|
def get_canonical_make_name(self, make_slug: str) -> str:
|
||||||
|
"""Get the canonical brand name from a filename slug or lowercase name"""
|
||||||
|
slug_lower = make_slug.lower().strip()
|
||||||
|
|
||||||
|
# Try direct lookup
|
||||||
|
if slug_lower in self.brand_name_map:
|
||||||
|
return self.brand_name_map[slug_lower]
|
||||||
|
|
||||||
|
# Try with underscores converted to spaces
|
||||||
|
slug_spaced = slug_lower.replace('_', ' ')
|
||||||
|
if slug_spaced in self.brand_name_map:
|
||||||
|
return self.brand_name_map[slug_spaced]
|
||||||
|
|
||||||
|
# Fallback: title case (shouldn't reach here if brand mapping is complete)
|
||||||
|
return slug_spaced.title()
|
||||||
|
|
||||||
|
def format_model_name(self, model_slug: str) -> str:
|
||||||
|
"""
|
||||||
|
Format model name from slug to human-readable format.
|
||||||
|
Examples:
|
||||||
|
"bronco_sport" -> "Bronco Sport"
|
||||||
|
"f-150" -> "F-150"
|
||||||
|
"expedition_max" -> "Expedition Max"
|
||||||
|
"sierra_1500" -> "Sierra 1500"
|
||||||
|
"""
|
||||||
|
if not model_slug:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# Replace underscores with spaces
|
||||||
|
model_name = model_slug.replace('_', ' ')
|
||||||
|
|
||||||
|
# Apply Title Case
|
||||||
|
model_name = model_name.title()
|
||||||
|
|
||||||
|
return model_name.strip()
|
||||||
|
|
||||||
|
def normalize_displacement(self, disp_str: str) -> Optional[str]:
|
||||||
|
"""Normalize displacement to L format (e.g., '3506 Cm3' -> '3.5L', '3.5L' -> '3.5L')"""
|
||||||
|
if not disp_str:
|
||||||
|
return None
|
||||||
|
|
||||||
|
disp_str = disp_str.strip()
|
||||||
|
|
||||||
|
# Check if already in L format
|
||||||
|
if disp_str.upper().endswith('L'):
|
||||||
|
# Extract numeric part and normalize
|
||||||
|
match = re.search(r'(\d+\.?\d*)', disp_str)
|
||||||
|
if match:
|
||||||
|
liters = float(match.group(1))
|
||||||
|
return f"{liters:.1F}L"
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Check if in Cm3 format
|
||||||
|
cm3_match = re.search(r'(\d+)\s*Cm3', disp_str, re.IGNORECASE)
|
||||||
|
if cm3_match:
|
||||||
|
cm3 = int(cm3_match.group(1))
|
||||||
|
liters = cm3 / 1000.0
|
||||||
|
return f"{liters:.1F}L"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def parse_engine_string(self, engine_str: str) -> Tuple[Optional[str], Optional[str]]:
|
def parse_engine_string(self, engine_str: str) -> Tuple[Optional[str], Optional[str]]:
|
||||||
"""Parse engine string like '2.0L I4' into displacement and configuration"""
|
"""Parse engine string like '2.0L I4' into displacement and configuration"""
|
||||||
pattern = r'(\d+\.?\d*L?)\s*([IVL]\d+|[A-Z]+\d*)'
|
pattern = r'(\d+\.?\d*L?)\s*([IVL]\d+|[A-Z]+\d*)'
|
||||||
@@ -72,20 +167,87 @@ class VehicleSQLGenerator:
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'name': engine_record.get('name', ''),
|
'name': engine_record.get('name', ''),
|
||||||
'displacement': engine_specs.get('Displacement', ''),
|
'displacement': engine_specs.get('Displacement:', ''),
|
||||||
'configuration': engine_specs.get('Cylinders', ''),
|
'configuration': engine_specs.get('Cylinders:', ''),
|
||||||
'horsepower': engine_specs.get('Power', ''),
|
'horsepower': engine_specs.get('Power:', ''),
|
||||||
'torque': engine_specs.get('Torque', ''),
|
'torque': engine_specs.get('Torque:', ''),
|
||||||
'fuel_type': engine_specs.get('Fuel', ''),
|
'fuel_type': engine_specs.get('Fuel:', ''),
|
||||||
'fuel_system': engine_specs.get('Fuel System', ''),
|
'fuel_system': engine_specs.get('Fuel System:', ''),
|
||||||
'aspiration': engine_specs.get('Aspiration', ''),
|
'aspiration': engine_specs.get('Aspiration:', ''),
|
||||||
'transmission_type': trans_specs.get('Gearbox', ''),
|
'transmission_type': trans_specs.get('Gearbox:', ''),
|
||||||
'drive_type': trans_specs.get('Drive Type', ''),
|
'drive_type': trans_specs.get('Drive Type:', ''),
|
||||||
'specs_json': specs
|
'specs_json': specs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def format_engine_display(self, specs: Dict) -> str:
|
||||||
|
"""Format engine display string like 'V8 3.5L Turbo' or 'I4 2.0L Supercharged'"""
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
# Configuration (V8, I4, L6, etc.)
|
||||||
|
config = specs.get('configuration', '').strip()
|
||||||
|
if config:
|
||||||
|
parts.append(config)
|
||||||
|
|
||||||
|
# Displacement in L format
|
||||||
|
disp = self.normalize_displacement(specs.get('displacement', ''))
|
||||||
|
|
||||||
|
# If displacement not in specs, try to extract from name
|
||||||
|
if not disp:
|
||||||
|
name = specs.get('name', '')
|
||||||
|
disp_match = re.search(r'(\d+\.?\d*)\s*L', name, re.IGNORECASE)
|
||||||
|
if disp_match:
|
||||||
|
disp = f"{float(disp_match.group(1)):.1f}L"
|
||||||
|
|
||||||
|
if disp:
|
||||||
|
parts.append(disp)
|
||||||
|
|
||||||
|
# Aspiration (Turbo, Supercharged) - check both aspiration and fuel_system fields
|
||||||
|
aspiration = specs.get('aspiration', '').strip()
|
||||||
|
fuel_system = specs.get('fuel_system', '').strip()
|
||||||
|
combined_text = f"{aspiration} {fuel_system}".lower()
|
||||||
|
|
||||||
|
if combined_text and 'naturally aspirated' not in combined_text:
|
||||||
|
if 'turbo' in combined_text:
|
||||||
|
parts.append('Turbo')
|
||||||
|
elif 'supercharg' in combined_text:
|
||||||
|
parts.append('Supercharged')
|
||||||
|
|
||||||
|
return ' '.join(parts) if parts else 'Unknown'
|
||||||
|
|
||||||
|
def format_transmission_display(self, trans_type: str, speeds: Optional[str]) -> str:
|
||||||
|
"""Format transmission display string like '8-Speed Automatic' or '6-Speed Manual'"""
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
trans_type_clean = trans_type.strip() if trans_type else ''
|
||||||
|
|
||||||
|
# Extract speed if provided
|
||||||
|
if speeds:
|
||||||
|
parts.append(f"{speeds}-Speed")
|
||||||
|
elif trans_type_clean:
|
||||||
|
# Try to extract speed from transmission type string
|
||||||
|
speed_match = re.search(r'(\d+)[- ]?[Ss]peed', trans_type_clean)
|
||||||
|
if speed_match:
|
||||||
|
parts.append(f"{speed_match.group(1)}-Speed")
|
||||||
|
|
||||||
|
# Determine transmission type
|
||||||
|
if trans_type_clean:
|
||||||
|
trans_lower = trans_type_clean.lower()
|
||||||
|
if 'manual' in trans_lower:
|
||||||
|
parts.append('Manual')
|
||||||
|
elif 'automatic' in trans_lower or 'auto' in trans_lower:
|
||||||
|
parts.append('Automatic')
|
||||||
|
elif 'cvt' in trans_lower:
|
||||||
|
# CVT doesn't have speeds
|
||||||
|
return 'CVT'
|
||||||
|
elif 'direct' in trans_lower or 'dct' in trans_lower:
|
||||||
|
parts.append('Dual-Clutch')
|
||||||
|
else:
|
||||||
|
parts.append('Automatic') # Default assumption
|
||||||
|
|
||||||
|
return ' '.join(parts) if parts else 'Unknown'
|
||||||
|
|
||||||
def sql_escape(self, value):
|
def sql_escape(self, value):
|
||||||
"""Escape values for SQL"""
|
"""Escape values for SQL with proper handling of special characters"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return 'NULL'
|
return 'NULL'
|
||||||
if isinstance(value, (int, float)):
|
if isinstance(value, (int, float)):
|
||||||
@@ -93,9 +255,17 @@ class VehicleSQLGenerator:
|
|||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
# Convert dict to JSON string and escape
|
# Convert dict to JSON string and escape
|
||||||
json_str = json.dumps(value)
|
json_str = json.dumps(value)
|
||||||
return "'" + json_str.replace("'", "''") + "'"
|
# Escape single quotes, newlines, carriage returns, and backslashes for PostgreSQL
|
||||||
# String - escape single quotes
|
escaped = json_str.replace('\\', '\\\\').replace("'", "''").replace('\n', '\\n').replace('\r', '\\r')
|
||||||
return "'" + str(value).replace("'", "''") + "'"
|
return "'" + escaped + "'"
|
||||||
|
# Convert to string
|
||||||
|
str_value = str(value).strip()
|
||||||
|
# Empty strings should be NULL
|
||||||
|
if not str_value:
|
||||||
|
return 'NULL'
|
||||||
|
# String - escape single quotes, newlines, carriage returns, and backslashes
|
||||||
|
escaped = str_value.replace('\\', '\\\\').replace("'", "''").replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
|
||||||
|
return "'" + escaped + "'"
|
||||||
|
|
||||||
def generate_engines_sql(self):
|
def generate_engines_sql(self):
|
||||||
"""Generate SQL file for engines and transmissions"""
|
"""Generate SQL file for engines and transmissions"""
|
||||||
@@ -111,28 +281,25 @@ class VehicleSQLGenerator:
|
|||||||
for engine_record in self.engines_data:
|
for engine_record in self.engines_data:
|
||||||
specs = self.extract_engine_specs(engine_record)
|
specs = self.extract_engine_specs(engine_record)
|
||||||
|
|
||||||
|
# Create simplified display name
|
||||||
|
display_name = self.format_engine_display(specs)
|
||||||
|
|
||||||
|
# Only store ID and name
|
||||||
values = (
|
values = (
|
||||||
engine_id,
|
engine_id,
|
||||||
self.sql_escape(specs['name']),
|
self.sql_escape(display_name)
|
||||||
self.sql_escape(specs['displacement']),
|
|
||||||
self.sql_escape(specs['configuration']),
|
|
||||||
self.sql_escape(specs['horsepower']),
|
|
||||||
self.sql_escape(specs['torque']),
|
|
||||||
self.sql_escape(specs['fuel_type']),
|
|
||||||
self.sql_escape(specs['fuel_system']),
|
|
||||||
self.sql_escape(specs['aspiration']),
|
|
||||||
self.sql_escape(specs['specs_json'])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
engines_insert_values.append(f"({','.join(map(str, values))})")
|
engines_insert_values.append(f"({','.join(map(str, values))})")
|
||||||
|
|
||||||
# Build engine cache
|
# Build engine cache with normalized displacement
|
||||||
if specs['displacement'] and specs['configuration']:
|
if specs['displacement'] and specs['configuration']:
|
||||||
disp_norm = specs['displacement'].upper().strip()
|
disp_norm = self.normalize_displacement(specs['displacement'])
|
||||||
config_norm = specs['configuration'].upper().strip()
|
config_norm = specs['configuration'].upper().strip()
|
||||||
key = (disp_norm, config_norm)
|
if disp_norm and config_norm:
|
||||||
if key not in self.engine_cache:
|
key = (disp_norm, config_norm)
|
||||||
self.engine_cache[key] = engine_id
|
if key not in self.engine_cache:
|
||||||
|
self.engine_cache[key] = engine_id
|
||||||
|
|
||||||
# Extract transmission
|
# Extract transmission
|
||||||
if specs['transmission_type'] or specs['drive_type']:
|
if specs['transmission_type'] or specs['drive_type']:
|
||||||
@@ -162,7 +329,7 @@ class VehicleSQLGenerator:
|
|||||||
batch_size = 500
|
batch_size = 500
|
||||||
for i in range(0, len(engines_insert_values), batch_size):
|
for i in range(0, len(engines_insert_values), batch_size):
|
||||||
batch = engines_insert_values[i:i+batch_size]
|
batch = engines_insert_values[i:i+batch_size]
|
||||||
f.write("INSERT INTO engines (id, name, displacement, configuration, horsepower, torque, fuel_type, fuel_system, aspiration, specs_json) VALUES\n")
|
f.write("INSERT INTO engines (id, name) VALUES\n")
|
||||||
f.write(",\n".join(batch))
|
f.write(",\n".join(batch))
|
||||||
f.write(";\n\n")
|
f.write(";\n\n")
|
||||||
|
|
||||||
@@ -179,15 +346,17 @@ class VehicleSQLGenerator:
|
|||||||
f.write("-- Transmissions data import\n")
|
f.write("-- Transmissions data import\n")
|
||||||
f.write("-- Generated by ETL script\n\n")
|
f.write("-- Generated by ETL script\n\n")
|
||||||
f.write("BEGIN;\n\n")
|
f.write("BEGIN;\n\n")
|
||||||
f.write("INSERT INTO transmissions (id, type, speeds, drive_type) VALUES\n")
|
f.write("INSERT INTO transmissions (id, type) VALUES\n")
|
||||||
|
|
||||||
trans_values = []
|
trans_values = []
|
||||||
for trans_type, speeds, drive_type in sorted(transmissions_set):
|
for trans_type, speeds, drive_type in sorted(transmissions_set):
|
||||||
|
# Create simplified display name for transmission
|
||||||
|
display_name = self.format_transmission_display(trans_type, speeds)
|
||||||
|
|
||||||
|
# Only store ID and type
|
||||||
values = (
|
values = (
|
||||||
trans_id,
|
trans_id,
|
||||||
self.sql_escape(trans_type),
|
self.sql_escape(display_name)
|
||||||
self.sql_escape(speeds),
|
|
||||||
self.sql_escape(drive_type)
|
|
||||||
)
|
)
|
||||||
trans_values.append(f"({','.join(map(str, values))})")
|
trans_values.append(f"({','.join(map(str, values))})")
|
||||||
|
|
||||||
@@ -208,14 +377,19 @@ class VehicleSQLGenerator:
|
|||||||
"""Find engine_id from cache based on engine string"""
|
"""Find engine_id from cache based on engine string"""
|
||||||
disp, config = self.parse_engine_string(engine_str)
|
disp, config = self.parse_engine_string(engine_str)
|
||||||
if disp and config:
|
if disp and config:
|
||||||
key = (disp, config)
|
# Normalize displacement to match cache format
|
||||||
|
disp_norm = self.normalize_displacement(disp)
|
||||||
|
if not disp_norm:
|
||||||
|
return None
|
||||||
|
|
||||||
|
key = (disp_norm, config)
|
||||||
if key in self.engine_cache:
|
if key in self.engine_cache:
|
||||||
return self.engine_cache[key]
|
return self.engine_cache[key]
|
||||||
|
|
||||||
# Try normalized variations
|
# Try normalized variations for configuration
|
||||||
for cached_key, engine_id in self.engine_cache.items():
|
for cached_key, engine_id in self.engine_cache.items():
|
||||||
cached_disp, cached_config = cached_key
|
cached_disp, cached_config = cached_key
|
||||||
if cached_disp == disp and self.config_matches(config, cached_config):
|
if cached_disp == disp_norm and self.config_matches(config, cached_config):
|
||||||
return engine_id
|
return engine_id
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@@ -239,6 +413,30 @@ class VehicleSQLGenerator:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def find_transmission_id_for_engine(self, engine_id: int) -> Optional[int]:
|
||||||
|
"""Find transmission_id for a given engine_id by looking up the engine's transmission specs"""
|
||||||
|
if engine_id < 1 or engine_id > len(self.engines_data):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Engine IDs are 1-indexed, list is 0-indexed
|
||||||
|
engine_record = self.engines_data[engine_id - 1]
|
||||||
|
specs = self.extract_engine_specs(engine_record)
|
||||||
|
|
||||||
|
# Extract transmission details
|
||||||
|
trans_type = specs.get('transmission_type', '') or 'Unknown'
|
||||||
|
drive_type = specs.get('drive_type', '') or 'Unknown'
|
||||||
|
|
||||||
|
# Extract speeds from transmission type
|
||||||
|
speeds = None
|
||||||
|
if trans_type:
|
||||||
|
speed_match = re.search(r'(\d+)', trans_type)
|
||||||
|
if speed_match:
|
||||||
|
speeds = speed_match.group(1)
|
||||||
|
|
||||||
|
# Look up in transmission cache
|
||||||
|
trans_key = (trans_type, speeds, drive_type)
|
||||||
|
return self.transmission_cache.get(trans_key)
|
||||||
|
|
||||||
def process_makes_filter(self):
|
def process_makes_filter(self):
|
||||||
"""Process all makes-filter JSON files and build vehicle records"""
|
"""Process all makes-filter JSON files and build vehicle records"""
|
||||||
print(f"\n🚗 Processing makes-filter JSON files (filtering for {self.min_year}+)...")
|
print(f"\n🚗 Processing makes-filter JSON files (filtering for {self.min_year}+)...")
|
||||||
@@ -250,7 +448,7 @@ class VehicleSQLGenerator:
|
|||||||
filtered_records = 0
|
filtered_records = 0
|
||||||
|
|
||||||
for json_file in sorted(json_files):
|
for json_file in sorted(json_files):
|
||||||
make_name = json_file.stem.replace('_', ' ').title()
|
make_name = self.get_canonical_make_name(json_file.stem)
|
||||||
print(f" Processing {make_name}...")
|
print(f" Processing {make_name}...")
|
||||||
|
|
||||||
with open(json_file, 'r', encoding='utf-8') as f:
|
with open(json_file, 'r', encoding='utf-8') as f:
|
||||||
@@ -269,7 +467,7 @@ class VehicleSQLGenerator:
|
|||||||
|
|
||||||
models = year_entry.get('models', [])
|
models = year_entry.get('models', [])
|
||||||
for model in models:
|
for model in models:
|
||||||
model_name = model.get('name', '')
|
model_name = self.format_model_name(model.get('name', ''))
|
||||||
engines = model.get('engines', [])
|
engines = model.get('engines', [])
|
||||||
submodels = model.get('submodels', [])
|
submodels = model.get('submodels', [])
|
||||||
|
|
||||||
@@ -279,7 +477,11 @@ class VehicleSQLGenerator:
|
|||||||
for trim in submodels:
|
for trim in submodels:
|
||||||
for engine_str in engines:
|
for engine_str in engines:
|
||||||
engine_id = self.find_matching_engine_id(engine_str)
|
engine_id = self.find_matching_engine_id(engine_str)
|
||||||
|
|
||||||
|
# Link transmission if engine was matched
|
||||||
transmission_id = None
|
transmission_id = None
|
||||||
|
if engine_id:
|
||||||
|
transmission_id = self.find_transmission_id_for_engine(engine_id)
|
||||||
|
|
||||||
self.vehicle_records.append({
|
self.vehicle_records.append({
|
||||||
'year': year,
|
'year': year,
|
||||||
@@ -341,6 +543,7 @@ class VehicleSQLGenerator:
|
|||||||
for remove_str in [str(year), brand_name]:
|
for remove_str in [str(year), brand_name]:
|
||||||
model_name = model_name.replace(remove_str, '')
|
model_name = model_name.replace(remove_str, '')
|
||||||
model_name = model_name.strip()
|
model_name = model_name.strip()
|
||||||
|
model_name = self.format_model_name(model_name)
|
||||||
|
|
||||||
key = (year, brand_name, model_name.lower())
|
key = (year, brand_name, model_name.lower())
|
||||||
if key in existing_combos:
|
if key in existing_combos:
|
||||||
@@ -357,18 +560,24 @@ class VehicleSQLGenerator:
|
|||||||
|
|
||||||
engine_id = None
|
engine_id = None
|
||||||
if specs['displacement'] and specs['configuration']:
|
if specs['displacement'] and specs['configuration']:
|
||||||
disp_norm = specs['displacement'].upper().strip()
|
disp_norm = self.normalize_displacement(specs['displacement'])
|
||||||
config_norm = specs['configuration'].upper().strip()
|
config_norm = specs['configuration'].upper().strip()
|
||||||
key = (disp_norm, config_norm)
|
if disp_norm and config_norm:
|
||||||
engine_id = self.engine_cache.get(key)
|
key = (disp_norm, config_norm)
|
||||||
|
engine_id = self.engine_cache.get(key)
|
||||||
|
|
||||||
|
# Link transmission if engine was matched
|
||||||
|
transmission_id = None
|
||||||
|
if engine_id:
|
||||||
|
transmission_id = self.find_transmission_id_for_engine(engine_id)
|
||||||
|
|
||||||
self.vehicle_records.append({
|
self.vehicle_records.append({
|
||||||
'year': year,
|
'year': year,
|
||||||
'make': brand_name.title(),
|
'make': self.get_canonical_make_name(brand_name),
|
||||||
'model': model_name,
|
'model': model_name,
|
||||||
'trim': 'Base',
|
'trim': 'Base',
|
||||||
'engine_id': engine_id,
|
'engine_id': engine_id,
|
||||||
'transmission_id': None
|
'transmission_id': transmission_id
|
||||||
})
|
})
|
||||||
backfill_count += 1
|
backfill_count += 1
|
||||||
existing_combos.add((year, brand_name, model_name.lower()))
|
existing_combos.add((year, brand_name, model_name.lower()))
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
-- Migration: Create Automotive Vehicle Selection Database
|
||||||
|
-- Optimized for dropdown cascade queries
|
||||||
|
-- Date: 2025-11-10
|
||||||
|
|
||||||
|
-- Drop existing tables if they exist
|
||||||
|
DROP TABLE IF EXISTS vehicle_options CASCADE;
|
||||||
|
DROP TABLE IF EXISTS engines CASCADE;
|
||||||
|
DROP TABLE IF EXISTS transmissions CASCADE;
|
||||||
|
DROP INDEX IF EXISTS idx_vehicle_year;
|
||||||
|
DROP INDEX IF EXISTS idx_vehicle_make;
|
||||||
|
DROP INDEX IF EXISTS idx_vehicle_model;
|
||||||
|
DROP INDEX IF EXISTS idx_vehicle_trim;
|
||||||
|
DROP INDEX IF EXISTS idx_vehicle_composite;
|
||||||
|
|
||||||
|
-- Create engines table with detailed specifications
|
||||||
|
CREATE TABLE engines (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
displacement VARCHAR(50),
|
||||||
|
configuration VARCHAR(50),
|
||||||
|
horsepower VARCHAR(100),
|
||||||
|
torque VARCHAR(100),
|
||||||
|
fuel_type VARCHAR(100),
|
||||||
|
fuel_system VARCHAR(255),
|
||||||
|
aspiration VARCHAR(100),
|
||||||
|
specs_json JSONB,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_engines_displacement ON engines(displacement);
|
||||||
|
CREATE INDEX idx_engines_config ON engines(configuration);
|
||||||
|
|
||||||
|
-- Create transmissions table
|
||||||
|
CREATE TABLE transmissions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
type VARCHAR(100) NOT NULL,
|
||||||
|
speeds VARCHAR(50),
|
||||||
|
drive_type VARCHAR(100),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_transmissions_type ON transmissions(type);
|
||||||
|
|
||||||
|
-- Create denormalized vehicle_options table optimized for dropdown queries
|
||||||
|
CREATE TABLE vehicle_options (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
year INTEGER NOT NULL,
|
||||||
|
make VARCHAR(100) NOT NULL,
|
||||||
|
model VARCHAR(255) NOT NULL,
|
||||||
|
trim VARCHAR(255) NOT NULL,
|
||||||
|
engine_id INTEGER REFERENCES engines(id) ON DELETE SET NULL,
|
||||||
|
transmission_id INTEGER REFERENCES transmissions(id) ON DELETE SET NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes for cascading dropdown performance
|
||||||
|
CREATE INDEX idx_vehicle_year ON vehicle_options(year);
|
||||||
|
CREATE INDEX idx_vehicle_make ON vehicle_options(make);
|
||||||
|
CREATE INDEX idx_vehicle_model ON vehicle_options(model);
|
||||||
|
CREATE INDEX idx_vehicle_trim ON vehicle_options(trim);
|
||||||
|
CREATE INDEX idx_vehicle_year_make ON vehicle_options(year, make);
|
||||||
|
CREATE INDEX idx_vehicle_year_make_model ON vehicle_options(year, make, model);
|
||||||
|
CREATE INDEX idx_vehicle_year_make_model_trim ON vehicle_options(year, make, model, trim);
|
||||||
|
|
||||||
|
-- Views for dropdown queries
|
||||||
|
|
||||||
|
-- View: Get all available years
|
||||||
|
CREATE OR REPLACE VIEW available_years AS
|
||||||
|
SELECT DISTINCT year
|
||||||
|
FROM vehicle_options
|
||||||
|
ORDER BY year DESC;
|
||||||
|
|
||||||
|
-- View: Get makes by year
|
||||||
|
CREATE OR REPLACE VIEW makes_by_year AS
|
||||||
|
SELECT DISTINCT year, make
|
||||||
|
FROM vehicle_options
|
||||||
|
ORDER BY year DESC, make ASC;
|
||||||
|
|
||||||
|
-- View: Get models by year and make
|
||||||
|
CREATE OR REPLACE VIEW models_by_year_make AS
|
||||||
|
SELECT DISTINCT year, make, model
|
||||||
|
FROM vehicle_options
|
||||||
|
ORDER BY year DESC, make ASC, model ASC;
|
||||||
|
|
||||||
|
-- View: Get trims by year, make, and model
|
||||||
|
CREATE OR REPLACE VIEW trims_by_year_make_model AS
|
||||||
|
SELECT DISTINCT year, make, model, trim
|
||||||
|
FROM vehicle_options
|
||||||
|
ORDER BY year DESC, make ASC, model ASC, trim ASC;
|
||||||
|
|
||||||
|
-- View: Get complete vehicle configurations with engine and transmission details
|
||||||
|
CREATE OR REPLACE VIEW complete_vehicle_configs AS
|
||||||
|
SELECT
|
||||||
|
vo.id,
|
||||||
|
vo.year,
|
||||||
|
vo.make,
|
||||||
|
vo.model,
|
||||||
|
vo.trim,
|
||||||
|
e.name AS engine_name,
|
||||||
|
e.displacement,
|
||||||
|
e.configuration,
|
||||||
|
e.horsepower,
|
||||||
|
e.torque,
|
||||||
|
e.fuel_type,
|
||||||
|
t.type AS transmission_type,
|
||||||
|
t.speeds AS transmission_speeds,
|
||||||
|
t.drive_type
|
||||||
|
FROM vehicle_options vo
|
||||||
|
LEFT JOIN engines e ON vo.engine_id = e.id
|
||||||
|
LEFT JOIN transmissions t ON vo.transmission_id = t.id
|
||||||
|
ORDER BY vo.year DESC, vo.make ASC, vo.model ASC, vo.trim ASC;
|
||||||
|
|
||||||
|
-- Function to get makes for a specific year
|
||||||
|
CREATE OR REPLACE FUNCTION get_makes_for_year(p_year INTEGER)
|
||||||
|
RETURNS TABLE(make VARCHAR) AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT DISTINCT vehicle_options.make
|
||||||
|
FROM vehicle_options
|
||||||
|
WHERE vehicle_options.year = p_year
|
||||||
|
ORDER BY vehicle_options.make ASC;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Function to get models for a specific year and make
|
||||||
|
CREATE OR REPLACE FUNCTION get_models_for_year_make(p_year INTEGER, p_make VARCHAR)
|
||||||
|
RETURNS TABLE(model VARCHAR) AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT DISTINCT vehicle_options.model
|
||||||
|
FROM vehicle_options
|
||||||
|
WHERE vehicle_options.year = p_year
|
||||||
|
AND vehicle_options.make = p_make
|
||||||
|
ORDER BY vehicle_options.model ASC;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Function to get trims for a specific year, make, and model
|
||||||
|
CREATE OR REPLACE FUNCTION get_trims_for_year_make_model(p_year INTEGER, p_make VARCHAR, p_model VARCHAR)
|
||||||
|
RETURNS TABLE(trim_name VARCHAR) AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT DISTINCT vehicle_options.trim
|
||||||
|
FROM vehicle_options
|
||||||
|
WHERE vehicle_options.year = p_year
|
||||||
|
AND vehicle_options.make = p_make
|
||||||
|
AND vehicle_options.model = p_model
|
||||||
|
ORDER BY vehicle_options.trim ASC;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Function to get engine and transmission options for a specific vehicle
|
||||||
|
CREATE OR REPLACE FUNCTION get_options_for_vehicle(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR)
|
||||||
|
RETURNS TABLE(
|
||||||
|
engine_name VARCHAR,
|
||||||
|
engine_displacement VARCHAR,
|
||||||
|
engine_horsepower VARCHAR,
|
||||||
|
transmission_type VARCHAR,
|
||||||
|
transmission_speeds VARCHAR,
|
||||||
|
drive_type VARCHAR
|
||||||
|
) AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT
|
||||||
|
e.name,
|
||||||
|
e.displacement,
|
||||||
|
e.horsepower,
|
||||||
|
t.type,
|
||||||
|
t.speeds,
|
||||||
|
t.drive_type
|
||||||
|
FROM vehicle_options vo
|
||||||
|
LEFT JOIN engines e ON vo.engine_id = e.id
|
||||||
|
LEFT JOIN transmissions t ON vo.transmission_id = t.id
|
||||||
|
WHERE vo.year = p_year
|
||||||
|
AND vo.make = p_make
|
||||||
|
AND vo.model = p_model
|
||||||
|
AND vo.trim = p_trim;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
COMMENT ON TABLE vehicle_options IS 'Denormalized table optimized for cascading dropdown queries';
|
||||||
|
COMMENT ON TABLE engines IS 'Engine specifications with detailed technical data';
|
||||||
|
COMMENT ON TABLE transmissions IS 'Transmission specifications';
|
||||||
|
COMMENT ON VIEW available_years IS 'Returns all distinct years available in the database';
|
||||||
|
COMMENT ON VIEW makes_by_year IS 'Returns makes grouped by year for dropdown population';
|
||||||
|
COMMENT ON VIEW models_by_year_make IS 'Returns models grouped by year and make';
|
||||||
|
COMMENT ON VIEW trims_by_year_make_model IS 'Returns trims grouped by year, make, and model';
|
||||||
|
COMMENT ON VIEW complete_vehicle_configs IS 'Complete vehicle configurations with all details';
|
||||||
+30127
-30127
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,836 @@
|
|||||||
|
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
INSERT INTO transmissions (id, type, speeds, drive_type) VALUES
|
INSERT INTO transmissions (id, type) VALUES
|
||||||
;
|
(1,'1-Speed Automatic'),
|
||||||
|
(2,'1-Speed Automatic'),
|
||||||
|
(3,'1-Speed Automatic'),
|
||||||
|
(4,'1-Speed Automatic'),
|
||||||
|
(5,'1-Speed Automatic'),
|
||||||
|
(6,'1-Speed Automatic'),
|
||||||
|
(7,'10-Speed Automatic'),
|
||||||
|
(8,'10-Speed Automatic'),
|
||||||
|
(9,'10-Speed Automatic'),
|
||||||
|
(10,'10-Speed Automatic'),
|
||||||
|
(11,'10-Speed Automatic'),
|
||||||
|
(12,'10-Speed Automatic'),
|
||||||
|
(13,'10-Speed Automatic'),
|
||||||
|
(14,'10-Speed Automatic'),
|
||||||
|
(15,'10-Speed Automatic'),
|
||||||
|
(16,'10-Speed Automatic'),
|
||||||
|
(17,'10-Speed Automatic'),
|
||||||
|
(18,'10-Speed Automatic'),
|
||||||
|
(19,'2-Speed Automatic'),
|
||||||
|
(20,'2-Speed Automatic'),
|
||||||
|
(21,'2-Speed Automatic'),
|
||||||
|
(22,'2-Speed Automatic'),
|
||||||
|
(23,'2-Speed Automatic'),
|
||||||
|
(24,'2-Speed Automatic'),
|
||||||
|
(25,'3-Speed Automatic'),
|
||||||
|
(26,'3-Speed Automatic'),
|
||||||
|
(27,'3-Speed Manual'),
|
||||||
|
(28,'3-Speed Automatic'),
|
||||||
|
(29,'3-Speed Automatic'),
|
||||||
|
(30,'3-Speed Automatic'),
|
||||||
|
(31,'3-Speed Automatic'),
|
||||||
|
(32,'3-Speed Manual'),
|
||||||
|
(33,'3-Speed Automatic'),
|
||||||
|
(34,'3-Speed Automatic'),
|
||||||
|
(35,'3-Speed Automatic'),
|
||||||
|
(36,'3-Speed Manual'),
|
||||||
|
(37,'3-Speed Manual'),
|
||||||
|
(38,'3-Speed Manual'),
|
||||||
|
(39,'4-Speed Manual'),
|
||||||
|
(40,'4-Speed Automatic'),
|
||||||
|
(41,'4-Speed Automatic'),
|
||||||
|
(42,'4-Speed Automatic'),
|
||||||
|
(43,'4-Speed Manual'),
|
||||||
|
(44,'4-Speed Manual'),
|
||||||
|
(45,'4-Speed Automatic'),
|
||||||
|
(46,'4-Speed Automatic'),
|
||||||
|
(47,'4-Speed Automatic'),
|
||||||
|
(48,'4-Speed Automatic'),
|
||||||
|
(49,'4-Speed Automatic'),
|
||||||
|
(50,'4-Speed Manual'),
|
||||||
|
(51,'4-Speed Automatic'),
|
||||||
|
(52,'4-Speed Automatic'),
|
||||||
|
(53,'4-Speed Automatic'),
|
||||||
|
(54,'4-Speed Automatic'),
|
||||||
|
(55,'4-Speed Automatic'),
|
||||||
|
(56,'4-Speed Automatic'),
|
||||||
|
(57,'4-Speed Automatic'),
|
||||||
|
(58,'4-Speed Automatic'),
|
||||||
|
(59,'4-Speed Automatic'),
|
||||||
|
(60,'4-Speed Manual'),
|
||||||
|
(61,'4-Speed Automatic'),
|
||||||
|
(62,'4-Speed Automatic'),
|
||||||
|
(63,'4-Speed Automatic'),
|
||||||
|
(64,'4-Speed Automatic'),
|
||||||
|
(65,'4-Speed Automatic'),
|
||||||
|
(66,'4-Speed Manual'),
|
||||||
|
(67,'4-Speed Manual'),
|
||||||
|
(68,'4-Speed Manual'),
|
||||||
|
(69,'4-Speed Automatic'),
|
||||||
|
(70,'4-Speed Automatic'),
|
||||||
|
(71,'4-Speed Automatic'),
|
||||||
|
(72,'5-Speed Manual'),
|
||||||
|
(73,'5-Speed Manual'),
|
||||||
|
(74,'5-Speed Automatic'),
|
||||||
|
(75,'5-Speed Automatic'),
|
||||||
|
(76,'5-Speed Automatic'),
|
||||||
|
(77,'5-Speed Automatic'),
|
||||||
|
(78,'5-Speed Automatic'),
|
||||||
|
(79,'5-Speed Automatic'),
|
||||||
|
(80,'5-Speed Manual'),
|
||||||
|
(81,'5-Speed Manual'),
|
||||||
|
(82,'5-Speed Manual'),
|
||||||
|
(83,'5-Speed Manual'),
|
||||||
|
(84,'5-Speed Manual'),
|
||||||
|
(85,'5-Speed Automatic'),
|
||||||
|
(86,'5-Speed Automatic'),
|
||||||
|
(87,'5-Speed Manual'),
|
||||||
|
(88,'5-Speed Manual'),
|
||||||
|
(89,'5-Speed Manual'),
|
||||||
|
(90,'5-Speed Automatic'),
|
||||||
|
(91,'5-Speed Automatic'),
|
||||||
|
(92,'5-Speed Automatic'),
|
||||||
|
(93,'5-Speed Automatic'),
|
||||||
|
(94,'5-Speed Automatic'),
|
||||||
|
(95,'5-Speed Automatic'),
|
||||||
|
(96,'5-Speed Automatic'),
|
||||||
|
(97,'5-Speed Automatic'),
|
||||||
|
(98,'5-Speed Automatic'),
|
||||||
|
(99,'5-Speed Automatic'),
|
||||||
|
(100,'5-Speed Automatic'),
|
||||||
|
(101,'5-Speed Automatic'),
|
||||||
|
(102,'5-Speed Automatic'),
|
||||||
|
(103,'5-Speed Automatic'),
|
||||||
|
(104,'5-Speed Automatic'),
|
||||||
|
(105,'5-Speed Automatic'),
|
||||||
|
(106,'5-Speed Automatic'),
|
||||||
|
(107,'5-Speed Automatic'),
|
||||||
|
(108,'5-Speed Automatic'),
|
||||||
|
(109,'5-Speed Automatic'),
|
||||||
|
(110,'5-Speed Automatic'),
|
||||||
|
(111,'5-Speed Automatic'),
|
||||||
|
(112,'5-Speed Automatic'),
|
||||||
|
(113,'5-Speed Automatic'),
|
||||||
|
(114,'5-Speed Automatic'),
|
||||||
|
(115,'5-Speed Automatic'),
|
||||||
|
(116,'5-Speed Automatic'),
|
||||||
|
(117,'5-Speed Automatic'),
|
||||||
|
(118,'5-Speed Automatic'),
|
||||||
|
(119,'5-Speed Automatic'),
|
||||||
|
(120,'5-Speed Automatic'),
|
||||||
|
(121,'5-Speed Automatic'),
|
||||||
|
(122,'5-Speed Automatic'),
|
||||||
|
(123,'5-Speed Automatic'),
|
||||||
|
(124,'5-Speed Automatic'),
|
||||||
|
(125,'5-Speed Automatic'),
|
||||||
|
(126,'5-Speed Automatic'),
|
||||||
|
(127,'5-Speed Automatic'),
|
||||||
|
(128,'5-Speed Automatic'),
|
||||||
|
(129,'5-Speed Automatic'),
|
||||||
|
(130,'5-Speed Automatic'),
|
||||||
|
(131,'5-Speed Automatic'),
|
||||||
|
(132,'5-Speed Automatic'),
|
||||||
|
(133,'5-Speed Automatic'),
|
||||||
|
(134,'5-Speed Automatic'),
|
||||||
|
(135,'5-Speed Automatic'),
|
||||||
|
(136,'5-Speed Automatic'),
|
||||||
|
(137,'5-Speed Automatic'),
|
||||||
|
(138,'5-Speed Automatic'),
|
||||||
|
(139,'5-Speed Automatic'),
|
||||||
|
(140,'5-Speed Manual'),
|
||||||
|
(141,'5-Speed Manual'),
|
||||||
|
(142,'5-Speed Manual'),
|
||||||
|
(143,'5-Speed Manual'),
|
||||||
|
(144,'5-Speed Manual'),
|
||||||
|
(145,'5-Speed Manual'),
|
||||||
|
(146,'5-Speed Automatic'),
|
||||||
|
(147,'5-Speed Automatic'),
|
||||||
|
(148,'5-Speed Automatic'),
|
||||||
|
(149,'5-Speed Manual'),
|
||||||
|
(150,'5-Speed Manual'),
|
||||||
|
(151,'5-Speed Manual'),
|
||||||
|
(152,'6-Speed Automatic'),
|
||||||
|
(153,'6-Speed Automatic'),
|
||||||
|
(154,'6-Speed Automatic'),
|
||||||
|
(155,'6-Speed Automatic'),
|
||||||
|
(156,'6-Speed Automatic'),
|
||||||
|
(157,'6-Speed Automatic'),
|
||||||
|
(158,'6-Speed Automatic'),
|
||||||
|
(159,'6-Speed Automatic'),
|
||||||
|
(160,'6-Speed Automatic'),
|
||||||
|
(161,'6-Speed Automatic'),
|
||||||
|
(162,'6-Speed Automatic'),
|
||||||
|
(163,'6-Speed Automatic'),
|
||||||
|
(164,'CVT'),
|
||||||
|
(165,'6-Speed Manual'),
|
||||||
|
(166,'6-Speed Manual'),
|
||||||
|
(167,'6-Speed Manual'),
|
||||||
|
(168,'6-Speed Manual'),
|
||||||
|
(169,'6-Speed Manual'),
|
||||||
|
(170,'6-Speed Automatic'),
|
||||||
|
(171,'6-Speed Automatic'),
|
||||||
|
(172,'6-Speed Automatic'),
|
||||||
|
(173,'6-Speed Automatic'),
|
||||||
|
(174,'6-Speed Automatic'),
|
||||||
|
(175,'6-Speed Manual'),
|
||||||
|
(176,'6-Speed Automatic'),
|
||||||
|
(177,'6-Speed Manual'),
|
||||||
|
(178,'6-Speed Manual'),
|
||||||
|
(179,'6-Speed Automatic'),
|
||||||
|
(180,'6-Speed Automatic'),
|
||||||
|
(181,'6-Speed Automatic'),
|
||||||
|
(182,'6-Speed Automatic'),
|
||||||
|
(183,'6-Speed Automatic'),
|
||||||
|
(184,'6-Speed Automatic'),
|
||||||
|
(185,'6-Speed Automatic'),
|
||||||
|
(186,'6-Speed Automatic'),
|
||||||
|
(187,'6-Speed Automatic'),
|
||||||
|
(188,'6-Speed Automatic'),
|
||||||
|
(189,'6-Speed Automatic'),
|
||||||
|
(190,'6-Speed Automatic'),
|
||||||
|
(191,'6-Speed Automatic'),
|
||||||
|
(192,'6-Speed Automatic'),
|
||||||
|
(193,'6-Speed Automatic'),
|
||||||
|
(194,'6-Speed Manual'),
|
||||||
|
(195,'6-Speed Automatic'),
|
||||||
|
(196,'6-Speed Manual'),
|
||||||
|
(197,'6-Speed Manual'),
|
||||||
|
(198,'6-Speed Manual'),
|
||||||
|
(199,'6-Speed Manual'),
|
||||||
|
(200,'6-Speed Manual'),
|
||||||
|
(201,'6-Speed Manual'),
|
||||||
|
(202,'6-Speed Automatic'),
|
||||||
|
(203,'6-Speed Automatic'),
|
||||||
|
(204,'6-Speed Automatic'),
|
||||||
|
(205,'6-Speed Automatic'),
|
||||||
|
(206,'6-Speed Automatic'),
|
||||||
|
(207,'6-Speed Automatic'),
|
||||||
|
(208,'6-Speed Automatic'),
|
||||||
|
(209,'6-Speed Automatic'),
|
||||||
|
(210,'6-Speed Automatic'),
|
||||||
|
(211,'6-Speed Automatic'),
|
||||||
|
(212,'6-Speed Automatic'),
|
||||||
|
(213,'6-Speed Automatic'),
|
||||||
|
(214,'6-Speed Automatic'),
|
||||||
|
(215,'6-Speed Automatic'),
|
||||||
|
(216,'6-Speed Automatic'),
|
||||||
|
(217,'6-Speed Automatic'),
|
||||||
|
(218,'6-Speed Automatic'),
|
||||||
|
(219,'6-Speed Automatic'),
|
||||||
|
(220,'6-Speed Automatic'),
|
||||||
|
(221,'6-Speed Automatic'),
|
||||||
|
(222,'6-Speed Automatic'),
|
||||||
|
(223,'6-Speed Automatic'),
|
||||||
|
(224,'6-Speed Automatic'),
|
||||||
|
(225,'6-Speed Automatic'),
|
||||||
|
(226,'6-Speed Automatic'),
|
||||||
|
(227,'6-Speed Automatic'),
|
||||||
|
(228,'6-Speed Automatic'),
|
||||||
|
(229,'6-Speed Automatic'),
|
||||||
|
(230,'6-Speed Automatic'),
|
||||||
|
(231,'6-Speed Automatic'),
|
||||||
|
(232,'6-Speed Automatic'),
|
||||||
|
(233,'6-Speed Automatic'),
|
||||||
|
(234,'6-Speed Automatic'),
|
||||||
|
(235,'6-Speed Automatic'),
|
||||||
|
(236,'6-Speed Automatic'),
|
||||||
|
(237,'6-Speed Automatic'),
|
||||||
|
(238,'6-Speed Automatic'),
|
||||||
|
(239,'6-Speed Automatic'),
|
||||||
|
(240,'6-Speed Automatic'),
|
||||||
|
(241,'6-Speed Automatic'),
|
||||||
|
(242,'6-Speed Automatic'),
|
||||||
|
(243,'6-Speed Automatic'),
|
||||||
|
(244,'6-Speed Automatic'),
|
||||||
|
(245,'6-Speed Automatic'),
|
||||||
|
(246,'6-Speed Automatic'),
|
||||||
|
(247,'6-Speed Automatic'),
|
||||||
|
(248,'6-Speed Automatic'),
|
||||||
|
(249,'6-Speed Automatic'),
|
||||||
|
(250,'6-Speed Automatic'),
|
||||||
|
(251,'6-Speed Automatic'),
|
||||||
|
(252,'6-Speed Automatic'),
|
||||||
|
(253,'6-Speed Automatic'),
|
||||||
|
(254,'6-Speed Automatic'),
|
||||||
|
(255,'6-Speed Automatic'),
|
||||||
|
(256,'6-Speed Automatic'),
|
||||||
|
(257,'6-Speed Automatic'),
|
||||||
|
(258,'6-Speed Automatic'),
|
||||||
|
(259,'6-Speed Automatic'),
|
||||||
|
(260,'6-Speed Automatic'),
|
||||||
|
(261,'6-Speed Automatic'),
|
||||||
|
(262,'6-Speed Automatic'),
|
||||||
|
(263,'6-Speed Automatic'),
|
||||||
|
(264,'6-Speed Automatic'),
|
||||||
|
(265,'6-Speed Automatic'),
|
||||||
|
(266,'6-Speed Automatic'),
|
||||||
|
(267,'6-Speed Automatic'),
|
||||||
|
(268,'6-Speed Automatic'),
|
||||||
|
(269,'6-Speed Automatic'),
|
||||||
|
(270,'6-Speed Automatic'),
|
||||||
|
(271,'6-Speed Automatic'),
|
||||||
|
(272,'6-Speed Automatic'),
|
||||||
|
(273,'6-Speed Automatic'),
|
||||||
|
(274,'6-Speed Automatic'),
|
||||||
|
(275,'6-Speed Automatic'),
|
||||||
|
(276,'6-Speed Automatic'),
|
||||||
|
(277,'6-Speed Automatic'),
|
||||||
|
(278,'6-Speed Automatic'),
|
||||||
|
(279,'CVT'),
|
||||||
|
(280,'6-Speed Dual-Clutch'),
|
||||||
|
(281,'6-Speed Automatic'),
|
||||||
|
(282,'6-Speed Automatic'),
|
||||||
|
(283,'6-Speed Automatic'),
|
||||||
|
(284,'6-Speed Automatic'),
|
||||||
|
(285,'6-Speed Automatic'),
|
||||||
|
(286,'6-Speed Automatic'),
|
||||||
|
(287,'6-Speed Automatic'),
|
||||||
|
(288,'6-Speed Automatic'),
|
||||||
|
(289,'6-Speed Manual'),
|
||||||
|
(290,'6-Speed Manual'),
|
||||||
|
(291,'6-Speed Manual'),
|
||||||
|
(292,'6-Speed Manual'),
|
||||||
|
(293,'6-Speed Manual'),
|
||||||
|
(294,'6-Speed Manual'),
|
||||||
|
(295,'6-Speed Manual'),
|
||||||
|
(296,'6-Speed Manual'),
|
||||||
|
(297,'6-Speed Manual'),
|
||||||
|
(298,'6-Speed Manual'),
|
||||||
|
(299,'6-Speed Manual'),
|
||||||
|
(300,'6-Speed Manual'),
|
||||||
|
(301,'6-Speed Manual'),
|
||||||
|
(302,'6-Speed Manual'),
|
||||||
|
(303,'6-Speed Automatic'),
|
||||||
|
(304,'6-Speed Automatic'),
|
||||||
|
(305,'6-Speed Automatic'),
|
||||||
|
(306,'6-Speed Dual-Clutch'),
|
||||||
|
(307,'6-Speed Automatic'),
|
||||||
|
(308,'6-Speed Automatic'),
|
||||||
|
(309,'6-Speed Automatic'),
|
||||||
|
(310,'6-Speed Automatic'),
|
||||||
|
(311,'6-Speed Automatic'),
|
||||||
|
(312,'6-Speed Automatic'),
|
||||||
|
(313,'6-Speed Automatic'),
|
||||||
|
(314,'6-Speed Automatic'),
|
||||||
|
(315,'6-Speed Manual'),
|
||||||
|
(316,'6-Speed Manual'),
|
||||||
|
(317,'6-Speed Manual'),
|
||||||
|
(318,'6-Speed Automatic'),
|
||||||
|
(319,'7-Speed Automatic'),
|
||||||
|
(320,'7-Speed Automatic'),
|
||||||
|
(321,'7-Speed Automatic'),
|
||||||
|
(322,'7-Speed Automatic'),
|
||||||
|
(323,'7-Speed Automatic'),
|
||||||
|
(324,'7-Speed Automatic'),
|
||||||
|
(325,'7-Speed Automatic'),
|
||||||
|
(326,'7-Speed Manual'),
|
||||||
|
(327,'7-Speed Automatic'),
|
||||||
|
(328,'7-Speed Automatic'),
|
||||||
|
(329,'7-Speed Automatic'),
|
||||||
|
(330,'7-Speed Automatic'),
|
||||||
|
(331,'7-Speed Automatic'),
|
||||||
|
(332,'7-Speed Automatic'),
|
||||||
|
(333,'7-Speed Automatic'),
|
||||||
|
(334,'7-Speed Automatic'),
|
||||||
|
(335,'7-Speed Automatic'),
|
||||||
|
(336,'7-Speed Automatic'),
|
||||||
|
(337,'7-Speed Automatic'),
|
||||||
|
(338,'7-Speed Automatic'),
|
||||||
|
(339,'7-Speed Automatic'),
|
||||||
|
(340,'7-Speed Automatic'),
|
||||||
|
(341,'7-Speed Automatic'),
|
||||||
|
(342,'7-Speed Automatic'),
|
||||||
|
(343,'7-Speed Automatic'),
|
||||||
|
(344,'7-Speed Automatic'),
|
||||||
|
(345,'7-Speed Automatic'),
|
||||||
|
(346,'7-Speed Automatic'),
|
||||||
|
(347,'7-Speed Automatic'),
|
||||||
|
(348,'7-Speed Automatic'),
|
||||||
|
(349,'7-Speed Automatic'),
|
||||||
|
(350,'7-Speed Automatic'),
|
||||||
|
(351,'7-Speed Automatic'),
|
||||||
|
(352,'7-Speed Automatic'),
|
||||||
|
(353,'7-Speed Automatic'),
|
||||||
|
(354,'7-Speed Automatic'),
|
||||||
|
(355,'7-Speed Automatic'),
|
||||||
|
(356,'7-Speed Automatic'),
|
||||||
|
(357,'7-Speed Automatic'),
|
||||||
|
(358,'7-Speed Automatic'),
|
||||||
|
(359,'7-Speed Automatic'),
|
||||||
|
(360,'7-Speed Automatic'),
|
||||||
|
(361,'7-Speed Automatic'),
|
||||||
|
(362,'7-Speed Automatic'),
|
||||||
|
(363,'7-Speed Automatic'),
|
||||||
|
(364,'7-Speed Automatic'),
|
||||||
|
(365,'7-Speed Automatic'),
|
||||||
|
(366,'7-Speed Automatic'),
|
||||||
|
(367,'7-Speed Automatic'),
|
||||||
|
(368,'7-Speed Automatic'),
|
||||||
|
(369,'7-Speed Automatic'),
|
||||||
|
(370,'7-Speed Automatic'),
|
||||||
|
(371,'7-Speed Automatic'),
|
||||||
|
(372,'7-Speed Automatic'),
|
||||||
|
(373,'7-Speed Automatic'),
|
||||||
|
(374,'7-Speed Automatic'),
|
||||||
|
(375,'7-Speed Automatic'),
|
||||||
|
(376,'7-Speed Automatic'),
|
||||||
|
(377,'7-Speed Automatic'),
|
||||||
|
(378,'7-Speed Automatic'),
|
||||||
|
(379,'7-Speed Automatic'),
|
||||||
|
(380,'7-Speed Automatic'),
|
||||||
|
(381,'7-Speed Automatic'),
|
||||||
|
(382,'7-Speed Automatic'),
|
||||||
|
(383,'7-Speed Automatic'),
|
||||||
|
(384,'7-Speed Automatic'),
|
||||||
|
(385,'7-Speed Automatic'),
|
||||||
|
(386,'7-Speed Automatic'),
|
||||||
|
(387,'7-Speed Automatic'),
|
||||||
|
(388,'7-Speed Automatic'),
|
||||||
|
(389,'7-Speed Automatic'),
|
||||||
|
(390,'7-Speed Automatic'),
|
||||||
|
(391,'7-Speed Automatic'),
|
||||||
|
(392,'7-Speed Automatic'),
|
||||||
|
(393,'7-Speed Automatic'),
|
||||||
|
(394,'7-Speed Automatic'),
|
||||||
|
(395,'7-Speed Automatic'),
|
||||||
|
(396,'7-Speed Automatic'),
|
||||||
|
(397,'7-Speed Automatic'),
|
||||||
|
(398,'7-Speed Automatic'),
|
||||||
|
(399,'7-Speed Automatic'),
|
||||||
|
(400,'7-Speed Automatic'),
|
||||||
|
(401,'7-Speed Automatic'),
|
||||||
|
(402,'7-Speed Automatic'),
|
||||||
|
(403,'7-Speed Automatic'),
|
||||||
|
(404,'7-Speed Automatic'),
|
||||||
|
(405,'7-Speed Automatic'),
|
||||||
|
(406,'7-Speed Automatic'),
|
||||||
|
(407,'7-Speed Automatic'),
|
||||||
|
(408,'7-Speed Automatic'),
|
||||||
|
(409,'7-Speed Automatic'),
|
||||||
|
(410,'7-Speed Automatic'),
|
||||||
|
(411,'7-Speed Automatic'),
|
||||||
|
(412,'7-Speed Automatic'),
|
||||||
|
(413,'7-Speed Automatic'),
|
||||||
|
(414,'7-Speed Automatic'),
|
||||||
|
(415,'7-Speed Automatic'),
|
||||||
|
(416,'7-Speed Automatic'),
|
||||||
|
(417,'7-Speed Automatic'),
|
||||||
|
(418,'7-Speed Automatic'),
|
||||||
|
(419,'7-Speed Automatic'),
|
||||||
|
(420,'7-Speed Automatic'),
|
||||||
|
(421,'7-Speed Automatic'),
|
||||||
|
(422,'7-Speed Automatic'),
|
||||||
|
(423,'7-Speed Automatic'),
|
||||||
|
(424,'7-Speed Automatic'),
|
||||||
|
(425,'7-Speed Automatic'),
|
||||||
|
(426,'7-Speed Automatic'),
|
||||||
|
(427,'7-Speed Automatic'),
|
||||||
|
(428,'7-Speed Automatic'),
|
||||||
|
(429,'7-Speed Automatic'),
|
||||||
|
(430,'7-Speed Automatic'),
|
||||||
|
(431,'7-Speed Automatic'),
|
||||||
|
(432,'7-Speed Automatic'),
|
||||||
|
(433,'7-Speed Automatic'),
|
||||||
|
(434,'7-Speed Automatic'),
|
||||||
|
(435,'7-Speed Automatic'),
|
||||||
|
(436,'7-Speed Automatic'),
|
||||||
|
(437,'7-Speed Automatic'),
|
||||||
|
(438,'7-Speed Automatic'),
|
||||||
|
(439,'7-Speed Automatic'),
|
||||||
|
(440,'7-Speed Automatic'),
|
||||||
|
(441,'7-Speed Automatic'),
|
||||||
|
(442,'7-Speed Automatic'),
|
||||||
|
(443,'7-Speed Automatic'),
|
||||||
|
(444,'7-Speed Automatic'),
|
||||||
|
(445,'7-Speed Automatic'),
|
||||||
|
(446,'7-Speed Automatic'),
|
||||||
|
(447,'7-Speed Automatic'),
|
||||||
|
(448,'7-Speed Automatic'),
|
||||||
|
(449,'7-Speed Automatic'),
|
||||||
|
(450,'7-Speed Automatic'),
|
||||||
|
(451,'7-Speed Automatic'),
|
||||||
|
(452,'7-Speed Automatic'),
|
||||||
|
(453,'7-Speed Automatic'),
|
||||||
|
(454,'7-Speed Automatic'),
|
||||||
|
(455,'CVT'),
|
||||||
|
(456,'7-Speed Dual-Clutch'),
|
||||||
|
(457,'7-Speed Automatic'),
|
||||||
|
(458,'7-Speed Automatic'),
|
||||||
|
(459,'7-Speed Manual'),
|
||||||
|
(460,'7-Speed Automatic'),
|
||||||
|
(461,'7-Speed Automatic'),
|
||||||
|
(462,'7-Speed Automatic'),
|
||||||
|
(463,'7-Speed Manual'),
|
||||||
|
(464,'7-Speed Manual'),
|
||||||
|
(465,'7-Speed Manual'),
|
||||||
|
(466,'7-Speed Manual'),
|
||||||
|
(467,'7-Speed Manual'),
|
||||||
|
(468,'7-Speed Dual-Clutch'),
|
||||||
|
(469,'7-Speed Automatic'),
|
||||||
|
(470,'7-Speed Automatic'),
|
||||||
|
(471,'7-Speed Automatic'),
|
||||||
|
(472,'7-Speed Manual'),
|
||||||
|
(473,'7-Speed Automatic'),
|
||||||
|
(474,'7-Speed Automatic'),
|
||||||
|
(475,'7-Speed Automatic'),
|
||||||
|
(476,'7-Speed Automatic'),
|
||||||
|
(477,'7-Speed Automatic'),
|
||||||
|
(478,'7-Speed Automatic'),
|
||||||
|
(479,'7-Speed Automatic'),
|
||||||
|
(480,'7-Speed Automatic'),
|
||||||
|
(481,'7-Speed Automatic'),
|
||||||
|
(482,'7-Speed Automatic'),
|
||||||
|
(483,'7-Speed Automatic'),
|
||||||
|
(484,'7-Speed Automatic'),
|
||||||
|
(485,'7-Speed Automatic'),
|
||||||
|
(486,'7-Speed Automatic'),
|
||||||
|
(487,'7-Speed Automatic'),
|
||||||
|
(488,'7-Speed Automatic'),
|
||||||
|
(489,'77-Speed Automatic'),
|
||||||
|
(490,'7-Speed Automatic'),
|
||||||
|
(491,'7-Speed Automatic'),
|
||||||
|
(492,'7-Speed Automatic'),
|
||||||
|
(493,'8-Speed Automatic'),
|
||||||
|
(494,'8-Speed Automatic'),
|
||||||
|
(495,'8-Speed Automatic'),
|
||||||
|
(496,'8-Speed Automatic'),
|
||||||
|
(497,'8-Speed Automatic'),
|
||||||
|
(498,'8-Speed Automatic'),
|
||||||
|
(499,'8-Speed Automatic'),
|
||||||
|
(500,'8-Speed Automatic'),
|
||||||
|
(501,'8-Speed Automatic'),
|
||||||
|
(502,'8-Speed Automatic'),
|
||||||
|
(503,'8-Speed Automatic'),
|
||||||
|
(504,'8-Speed Automatic'),
|
||||||
|
(505,'8-Speed Automatic'),
|
||||||
|
(506,'8-Speed Automatic'),
|
||||||
|
(507,'8-Speed Automatic'),
|
||||||
|
(508,'8-Speed Automatic'),
|
||||||
|
(509,'8-Speed Automatic'),
|
||||||
|
(510,'8-Speed Automatic'),
|
||||||
|
(511,'8-Speed Automatic'),
|
||||||
|
(512,'8-Speed Automatic'),
|
||||||
|
(513,'8-Speed Automatic'),
|
||||||
|
(514,'8-Speed Automatic'),
|
||||||
|
(515,'8-Speed Automatic'),
|
||||||
|
(516,'8-Speed Automatic'),
|
||||||
|
(517,'8-Speed Automatic'),
|
||||||
|
(518,'8-Speed Automatic'),
|
||||||
|
(519,'8-Speed Automatic'),
|
||||||
|
(520,'8-Speed Automatic'),
|
||||||
|
(521,'8-Speed Automatic'),
|
||||||
|
(522,'8-Speed Automatic'),
|
||||||
|
(523,'8-Speed Automatic'),
|
||||||
|
(524,'8-Speed Automatic'),
|
||||||
|
(525,'8-Speed Automatic'),
|
||||||
|
(526,'8-Speed Automatic'),
|
||||||
|
(527,'8-Speed Automatic'),
|
||||||
|
(528,'8-Speed Automatic'),
|
||||||
|
(529,'8-Speed Automatic'),
|
||||||
|
(530,'8-Speed Automatic'),
|
||||||
|
(531,'8-Speed Automatic'),
|
||||||
|
(532,'8-Speed Automatic'),
|
||||||
|
(533,'8-Speed Automatic'),
|
||||||
|
(534,'8-Speed Automatic'),
|
||||||
|
(535,'8-Speed Automatic'),
|
||||||
|
(536,'8-Speed Automatic'),
|
||||||
|
(537,'8-Speed Automatic'),
|
||||||
|
(538,'8-Speed Automatic'),
|
||||||
|
(539,'8-Speed Automatic'),
|
||||||
|
(540,'8-Speed Automatic'),
|
||||||
|
(541,'8-Speed Automatic'),
|
||||||
|
(542,'8-Speed Automatic'),
|
||||||
|
(543,'8-Speed Automatic'),
|
||||||
|
(544,'8-Speed Automatic'),
|
||||||
|
(545,'8-Speed Automatic'),
|
||||||
|
(546,'8-Speed Automatic'),
|
||||||
|
(547,'8-Speed Automatic'),
|
||||||
|
(548,'8-Speed Automatic'),
|
||||||
|
(549,'8-Speed Automatic'),
|
||||||
|
(550,'8-Speed Automatic'),
|
||||||
|
(551,'8-Speed Automatic'),
|
||||||
|
(552,'8-Speed Automatic'),
|
||||||
|
(553,'8-Speed Automatic'),
|
||||||
|
(554,'8-Speed Automatic'),
|
||||||
|
(555,'8-Speed Automatic'),
|
||||||
|
(556,'8-Speed Automatic'),
|
||||||
|
(557,'8-Speed Automatic'),
|
||||||
|
(558,'8-Speed Automatic'),
|
||||||
|
(559,'8-Speed Automatic'),
|
||||||
|
(560,'8-Speed Automatic'),
|
||||||
|
(561,'8-Speed Automatic'),
|
||||||
|
(562,'8-Speed Automatic'),
|
||||||
|
(563,'8-Speed Automatic'),
|
||||||
|
(564,'8-Speed Automatic'),
|
||||||
|
(565,'8-Speed Automatic'),
|
||||||
|
(566,'8-Speed Automatic'),
|
||||||
|
(567,'8-Speed Automatic'),
|
||||||
|
(568,'8-Speed Automatic'),
|
||||||
|
(569,'8-Speed Automatic'),
|
||||||
|
(570,'8-Speed Automatic'),
|
||||||
|
(571,'8-Speed Automatic'),
|
||||||
|
(572,'8-Speed Automatic'),
|
||||||
|
(573,'8-Speed Automatic'),
|
||||||
|
(574,'8-Speed Dual-Clutch'),
|
||||||
|
(575,'8-Speed Automatic'),
|
||||||
|
(576,'8-Speed Automatic'),
|
||||||
|
(577,'8-Speed Automatic'),
|
||||||
|
(578,'8-Speed Automatic'),
|
||||||
|
(579,'8-Speed Automatic'),
|
||||||
|
(580,'8-Speed Automatic'),
|
||||||
|
(581,'8-Speed Automatic'),
|
||||||
|
(582,'8-Speed Automatic'),
|
||||||
|
(583,'8-Speed Automatic'),
|
||||||
|
(584,'8-Speed Automatic'),
|
||||||
|
(585,'8-Speed Automatic'),
|
||||||
|
(586,'8-Speed Automatic'),
|
||||||
|
(587,'9-Speed Automatic'),
|
||||||
|
(588,'9-Speed Automatic'),
|
||||||
|
(589,'9-Speed Automatic'),
|
||||||
|
(590,'9-Speed Automatic'),
|
||||||
|
(591,'9-Speed Automatic'),
|
||||||
|
(592,'9-Speed Automatic'),
|
||||||
|
(593,'9-Speed Automatic'),
|
||||||
|
(594,'9-Speed Automatic'),
|
||||||
|
(595,'9-Speed Automatic'),
|
||||||
|
(596,'9-Speed Automatic'),
|
||||||
|
(597,'9-Speed Automatic'),
|
||||||
|
(598,'9-Speed Automatic'),
|
||||||
|
(599,'9-Speed Automatic'),
|
||||||
|
(600,'9-Speed Automatic'),
|
||||||
|
(601,'9-Speed Automatic'),
|
||||||
|
(602,'9-Speed Automatic'),
|
||||||
|
(603,'9-Speed Automatic'),
|
||||||
|
(604,'9-Speed Automatic'),
|
||||||
|
(605,'9-Speed Automatic'),
|
||||||
|
(606,'9-Speed Automatic'),
|
||||||
|
(607,'9-Speed Automatic'),
|
||||||
|
(608,'9-Speed Automatic'),
|
||||||
|
(609,'9-Speed Automatic'),
|
||||||
|
(610,'9-Speed Automatic'),
|
||||||
|
(611,'9-Speed Automatic'),
|
||||||
|
(612,'9-Speed Automatic'),
|
||||||
|
(613,'9-Speed Automatic'),
|
||||||
|
(614,'9-Speed Automatic'),
|
||||||
|
(615,'9-Speed Automatic'),
|
||||||
|
(616,'9-Speed Automatic'),
|
||||||
|
(617,'9-Speed Automatic'),
|
||||||
|
(618,'9-Speed Automatic'),
|
||||||
|
(619,'9-Speed Automatic'),
|
||||||
|
(620,'9-Speed Automatic'),
|
||||||
|
(621,'9-Speed Automatic'),
|
||||||
|
(622,'6-Speed Automatic'),
|
||||||
|
(623,'7-Speed Automatic'),
|
||||||
|
(624,'7-Speed Automatic'),
|
||||||
|
(625,'7-Speed Automatic'),
|
||||||
|
(626,'7-Speed Automatic'),
|
||||||
|
(627,'9-Speed Automatic'),
|
||||||
|
(628,'5-Speed Manual'),
|
||||||
|
(629,'Automatic'),
|
||||||
|
(630,'7-Speed Manual'),
|
||||||
|
(631,'7-Speed Manual'),
|
||||||
|
(632,'Automatic'),
|
||||||
|
(633,'Automatic'),
|
||||||
|
(634,'Automatic'),
|
||||||
|
(635,'Automatic'),
|
||||||
|
(636,'Automatic'),
|
||||||
|
(637,'Automatic'),
|
||||||
|
(638,'6-Speed Automatic'),
|
||||||
|
(639,'Automatic'),
|
||||||
|
(640,'3-Speed Automatic'),
|
||||||
|
(641,'3-Speed Automatic'),
|
||||||
|
(642,'4-Speed Automatic'),
|
||||||
|
(643,'4-Speed Automatic'),
|
||||||
|
(644,'4-Speed Automatic'),
|
||||||
|
(645,'4-Speed Automatic'),
|
||||||
|
(646,'4-Speed Automatic'),
|
||||||
|
(647,'5-Speed Automatic'),
|
||||||
|
(648,'5-Speed Automatic'),
|
||||||
|
(649,'5-Speed Automatic'),
|
||||||
|
(650,'5-Speed Automatic'),
|
||||||
|
(651,'5-Speed Automatic'),
|
||||||
|
(652,'6-Speed Automatic'),
|
||||||
|
(653,'6-Speed Automatic'),
|
||||||
|
(654,'6-Speed Automatic'),
|
||||||
|
(655,'7-Speed Automatic'),
|
||||||
|
(656,'7-Speed Automatic'),
|
||||||
|
(657,'7-Speed Automatic'),
|
||||||
|
(658,'7-Speed Automatic'),
|
||||||
|
(659,'8-Speed Automatic'),
|
||||||
|
(660,'8-Speed Automatic'),
|
||||||
|
(661,'Automatic'),
|
||||||
|
(662,'Automatic'),
|
||||||
|
(663,'Automatic'),
|
||||||
|
(664,'Automatic'),
|
||||||
|
(665,'Automatic'),
|
||||||
|
(666,'Automatic'),
|
||||||
|
(667,'CVT'),
|
||||||
|
(668,'CVT'),
|
||||||
|
(669,'Automatic'),
|
||||||
|
(670,'Automatic'),
|
||||||
|
(671,'CVT'),
|
||||||
|
(672,'CVT'),
|
||||||
|
(673,'CVT'),
|
||||||
|
(674,'CVT'),
|
||||||
|
(675,'CVT'),
|
||||||
|
(676,'CVT'),
|
||||||
|
(677,'CVT'),
|
||||||
|
(678,'CVT'),
|
||||||
|
(679,'CVT'),
|
||||||
|
(680,'Automatic'),
|
||||||
|
(681,'Automatic'),
|
||||||
|
(682,'Automatic'),
|
||||||
|
(683,'CVT'),
|
||||||
|
(684,'CVT'),
|
||||||
|
(685,'CVT'),
|
||||||
|
(686,'CVT'),
|
||||||
|
(687,'CVT'),
|
||||||
|
(688,'CVT'),
|
||||||
|
(689,'CVT'),
|
||||||
|
(690,'CVT'),
|
||||||
|
(691,'CVT'),
|
||||||
|
(692,'CVT'),
|
||||||
|
(693,'CVT'),
|
||||||
|
(694,'CVT'),
|
||||||
|
(695,'CVT'),
|
||||||
|
(696,'CVT'),
|
||||||
|
(697,'CVT'),
|
||||||
|
(698,'Automatic'),
|
||||||
|
(699,'7-Speed Manual'),
|
||||||
|
(700,'CVT'),
|
||||||
|
(701,'CVT'),
|
||||||
|
(702,'CVT'),
|
||||||
|
(703,'CVT'),
|
||||||
|
(704,'Automatic'),
|
||||||
|
(705,'5-Speed Automatic'),
|
||||||
|
(706,'CVT'),
|
||||||
|
(707,'CVT'),
|
||||||
|
(708,'CVT'),
|
||||||
|
(709,'Automatic'),
|
||||||
|
(710,'15-Speed Automatic'),
|
||||||
|
(711,'CVT'),
|
||||||
|
(712,'CVT'),
|
||||||
|
(713,'CVT'),
|
||||||
|
(714,'CVT'),
|
||||||
|
(715,'CVT'),
|
||||||
|
(716,'Automatic'),
|
||||||
|
(717,'Automatic'),
|
||||||
|
(718,'Automatic'),
|
||||||
|
(719,'Automatic'),
|
||||||
|
(720,'Automatic'),
|
||||||
|
(721,'Automatic'),
|
||||||
|
(722,'Automatic'),
|
||||||
|
(723,'Automatic'),
|
||||||
|
(724,'CVT'),
|
||||||
|
(725,'CVT'),
|
||||||
|
(726,'CVT'),
|
||||||
|
(727,'CVT'),
|
||||||
|
(728,'Manual'),
|
||||||
|
(729,'6-Speed Manual'),
|
||||||
|
(730,'8-Speed Manual'),
|
||||||
|
(731,'Automatic'),
|
||||||
|
(732,'CVT'),
|
||||||
|
(733,'150-Speed Automatic'),
|
||||||
|
(734,'6-Speed Automatic'),
|
||||||
|
(735,'6-Speed Automatic'),
|
||||||
|
(736,'6-Speed Automatic'),
|
||||||
|
(737,'6-Speed Automatic'),
|
||||||
|
(738,'6-Speed Automatic'),
|
||||||
|
(739,'6-Speed Automatic'),
|
||||||
|
(740,'Dual-Clutch'),
|
||||||
|
(741,'Automatic'),
|
||||||
|
(742,'Automatic'),
|
||||||
|
(743,'Automatic'),
|
||||||
|
(744,'Automatic'),
|
||||||
|
(745,'Automatic'),
|
||||||
|
(746,'CVT'),
|
||||||
|
(747,'CVT'),
|
||||||
|
(748,'Automatic'),
|
||||||
|
(749,'6-Speed Manual'),
|
||||||
|
(750,'8-Speed Manual'),
|
||||||
|
(751,'355-Speed Manual'),
|
||||||
|
(752,'Manual'),
|
||||||
|
(753,'4-Speed Manual'),
|
||||||
|
(754,'1-Speed Manual'),
|
||||||
|
(755,'2-Speed Manual'),
|
||||||
|
(756,'3-Speed Manual'),
|
||||||
|
(757,'3-Speed Manual'),
|
||||||
|
(758,'4-Speed Manual'),
|
||||||
|
(759,'4-Speed Manual'),
|
||||||
|
(760,'4-Speed Manual'),
|
||||||
|
(761,'5-Speed Manual'),
|
||||||
|
(762,'5-Speed Manual'),
|
||||||
|
(763,'5-Speed Manual'),
|
||||||
|
(764,'6-Speed Manual'),
|
||||||
|
(765,'6-Speed Manual'),
|
||||||
|
(766,'6-Speed Manual'),
|
||||||
|
(767,'CVT'),
|
||||||
|
(768,'Automatic'),
|
||||||
|
(769,'CVT'),
|
||||||
|
(770,'CVT'),
|
||||||
|
(771,'265-Speed Automatic'),
|
||||||
|
(772,'Automatic'),
|
||||||
|
(773,'Automatic'),
|
||||||
|
(774,'6-Speed Automatic'),
|
||||||
|
(775,'Automatic'),
|
||||||
|
(776,'3-Speed Automatic'),
|
||||||
|
(777,'3-Speed Automatic'),
|
||||||
|
(778,'4-Speed Automatic'),
|
||||||
|
(779,'4-Speed Automatic'),
|
||||||
|
(780,'5-Speed Automatic'),
|
||||||
|
(781,'5-Speed Automatic'),
|
||||||
|
(782,'6-Speed Automatic'),
|
||||||
|
(783,'6-Speed Automatic'),
|
||||||
|
(784,'8-Speed Automatic'),
|
||||||
|
(785,'Automatic'),
|
||||||
|
(786,'Automatic'),
|
||||||
|
(787,'Automatic'),
|
||||||
|
(788,'Automatic'),
|
||||||
|
(789,'Automatic'),
|
||||||
|
(790,'Automatic'),
|
||||||
|
(791,'Automatic'),
|
||||||
|
(792,'Dual-Clutch'),
|
||||||
|
(793,'Automatic'),
|
||||||
|
(794,'Automatic'),
|
||||||
|
(795,'Automatic'),
|
||||||
|
(796,'Automatic'),
|
||||||
|
(797,'Automatic'),
|
||||||
|
(798,'Automatic'),
|
||||||
|
(799,'Automatic'),
|
||||||
|
(800,'Automatic'),
|
||||||
|
(801,'Manual'),
|
||||||
|
(802,'Manual'),
|
||||||
|
(803,'Automatic'),
|
||||||
|
(804,'Automatic'),
|
||||||
|
(805,'2-Speed Automatic'),
|
||||||
|
(806,'Automatic'),
|
||||||
|
(807,'Automatic'),
|
||||||
|
(808,'Automatic'),
|
||||||
|
(809,'Automatic'),
|
||||||
|
(810,'Manual'),
|
||||||
|
(811,'Manual'),
|
||||||
|
(812,'Manual'),
|
||||||
|
(813,'Manual'),
|
||||||
|
(814,'Manual'),
|
||||||
|
(815,'Manual'),
|
||||||
|
(816,'Automatic'),
|
||||||
|
(817,'Automatic'),
|
||||||
|
(818,'2-Speed Automatic'),
|
||||||
|
(819,'Automatic'),
|
||||||
|
(820,'Automatic'),
|
||||||
|
(821,'Automatic'),
|
||||||
|
(822,'5-Speed Automatic'),
|
||||||
|
(823,'Automatic'),
|
||||||
|
(824,'Automatic'),
|
||||||
|
(825,'CVT'),
|
||||||
|
(826,'CVT'),
|
||||||
|
(827,'8-Speed Automatic'),
|
||||||
|
(828,'8-Speed Automatic');
|
||||||
|
|
||||||
SELECT setval('transmissions_id_seq', 1);
|
SELECT setval('transmissions_id_seq', 829);
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|||||||
+1121458
-1121458
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ ETL Statistics
|
|||||||
============================================================
|
============================================================
|
||||||
|
|
||||||
Total Engines: 30,066
|
Total Engines: 30,066
|
||||||
Total Transmissions: 0
|
Total Transmissions: 828
|
||||||
Total Vehicles: 1,122,644
|
Total Vehicles: 1,122,644
|
||||||
Unique Years: 47
|
Unique Years: 47
|
||||||
Unique Makes: 53
|
Unique Makes: 53
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
acura
|
||||||
|
alfa_romeo
|
||||||
|
aston_martin
|
||||||
|
audi
|
||||||
|
bentley
|
||||||
|
bmw
|
||||||
|
buick
|
||||||
|
cadillac
|
||||||
|
chevrolet
|
||||||
|
chrysler
|
||||||
|
dodge
|
||||||
|
ferrari
|
||||||
|
fiat
|
||||||
|
ford
|
||||||
|
genesis
|
||||||
|
gmc
|
||||||
|
honda
|
||||||
|
hummer
|
||||||
|
hyundai
|
||||||
|
infiniti
|
||||||
|
isuzu
|
||||||
|
jaguar
|
||||||
|
jeep
|
||||||
|
kia
|
||||||
|
lamborghini
|
||||||
|
land_rover
|
||||||
|
lexus
|
||||||
|
lincoln
|
||||||
|
lotus
|
||||||
|
lucid
|
||||||
|
maserati
|
||||||
|
mazda
|
||||||
|
mclaren
|
||||||
|
mercury
|
||||||
|
mini
|
||||||
|
mitsubishi
|
||||||
|
nissan
|
||||||
|
oldsmobile
|
||||||
|
plymouth
|
||||||
|
polestar
|
||||||
|
pontiac
|
||||||
|
porsche
|
||||||
|
ram
|
||||||
|
rivian
|
||||||
|
rolls_royce
|
||||||
|
saab
|
||||||
|
scion
|
||||||
|
smart
|
||||||
|
subaru
|
||||||
|
tesla
|
||||||
|
toyota
|
||||||
|
volkswagen
|
||||||
|
volvo
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,506 +0,0 @@
|
|||||||
{
|
|
||||||
"aston_martin": [
|
|
||||||
{
|
|
||||||
"year": "2023",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"5.2L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"AMR",
|
|
||||||
"V12",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2020",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "db11",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dbs",
|
|
||||||
"engines": [
|
|
||||||
"5.2L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"5.2L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"AMR",
|
|
||||||
"V12",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2019",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"5.2L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"AMR",
|
|
||||||
"V12",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2018",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "rapide",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2017",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "v12_vantage",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"S"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vanquish",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Carbon",
|
|
||||||
"Base",
|
|
||||||
"Volante"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2016",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "rapide",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "v12_vantage",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"S"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vanquish",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Carbon",
|
|
||||||
"Base",
|
|
||||||
"Volante"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2015",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "db9",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Volante",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "rapide",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "v12_vantage",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"S"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vanquish",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Carbon",
|
|
||||||
"Base",
|
|
||||||
"Volante"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2014",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "db9",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Volante",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vanquish",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Carbon",
|
|
||||||
"Base",
|
|
||||||
"Volante"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2013",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2012",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2011",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "v12_vantage",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"S"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2010",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "db9",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Volante",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2009",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "db9",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Volante",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2008",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2007",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "db9",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Volante",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2006",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "v8_vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8",
|
|
||||||
"4.7L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"GT",
|
|
||||||
"S",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2005",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "db9",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Volante",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vantage",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"5.2L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"AMR",
|
|
||||||
"V12",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2002",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "db7",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Vantage Volante",
|
|
||||||
"Vantage"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2001",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "db7",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Vantage Volante",
|
|
||||||
"Vantage"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1993",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "virage",
|
|
||||||
"engines": [
|
|
||||||
"5.3L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Volante"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1990",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "virage",
|
|
||||||
"engines": [
|
|
||||||
"5.3L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Volante"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1983",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "v-8",
|
|
||||||
"engines": [
|
|
||||||
"5.3L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,427 +0,0 @@
|
|||||||
{
|
|
||||||
"bentley": [
|
|
||||||
{
|
|
||||||
"year": "2023",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "flying_spur",
|
|
||||||
"engines": [
|
|
||||||
"2.9L V6 MILD HYBRID EV- (MHEV)",
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Hybrid",
|
|
||||||
"V8",
|
|
||||||
"W12",
|
|
||||||
"S Hybrid",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2022",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "flying_spur",
|
|
||||||
"engines": [
|
|
||||||
"2.9L V6 MILD HYBRID EV- (MHEV)",
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Hybrid",
|
|
||||||
"V8",
|
|
||||||
"W12",
|
|
||||||
"S Hybrid",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2021",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "flying_spur",
|
|
||||||
"engines": [
|
|
||||||
"2.9L V6 MILD HYBRID EV- (MHEV)",
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Hybrid",
|
|
||||||
"V8",
|
|
||||||
"W12",
|
|
||||||
"S Hybrid",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2018",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "bentayga",
|
|
||||||
"engines": [
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"W12 Signature",
|
|
||||||
"Black Edition"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2017",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2016",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "flying_spur",
|
|
||||||
"engines": [
|
|
||||||
"2.9L V6 MILD HYBRID EV- (MHEV)",
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Hybrid",
|
|
||||||
"V8",
|
|
||||||
"W12",
|
|
||||||
"S Hybrid",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mulsanne",
|
|
||||||
"engines": [
|
|
||||||
"6.8L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"Speed"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2014",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mulsanne",
|
|
||||||
"engines": [
|
|
||||||
"6.8L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"Speed"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2013",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "flying_spur",
|
|
||||||
"engines": [
|
|
||||||
"2.9L V6 MILD HYBRID EV- (MHEV)",
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Hybrid",
|
|
||||||
"V8",
|
|
||||||
"W12",
|
|
||||||
"S Hybrid",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2009",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2008",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2006",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2005",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "arnage",
|
|
||||||
"engines": [
|
|
||||||
"4.4L V8",
|
|
||||||
"6.8L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"R"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "continental",
|
|
||||||
"engines": [
|
|
||||||
"4.0L V8",
|
|
||||||
"6.0L W12 FLEX",
|
|
||||||
"6.0L W12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"GTC",
|
|
||||||
"Flying Spur Speed",
|
|
||||||
"GT V8 S",
|
|
||||||
"GTC V8 S",
|
|
||||||
"Flying Spur",
|
|
||||||
"GT",
|
|
||||||
"GT Speed"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1999",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "arnage",
|
|
||||||
"engines": [
|
|
||||||
"4.4L V8",
|
|
||||||
"6.8L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"R"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1997",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "brooklands",
|
|
||||||
"engines": [
|
|
||||||
"6.8L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1996",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "azure",
|
|
||||||
"engines": [],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1989",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "turbo_r",
|
|
||||||
"engines": [
|
|
||||||
"6.8L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1963",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "s3_series",
|
|
||||||
"engines": [
|
|
||||||
"6.2L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,607 +0,0 @@
|
|||||||
{
|
|
||||||
"ferrari": [
|
|
||||||
{
|
|
||||||
"year": "2024",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "296_gts",
|
|
||||||
"engines": [
|
|
||||||
"3.0L V6 PLUG-IN HYBRID EV- (PHEV)"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2022",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "f8_spider",
|
|
||||||
"engines": [
|
|
||||||
"3.9L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2019",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "portofino",
|
|
||||||
"engines": [
|
|
||||||
"3.9L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2018",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "488_spider",
|
|
||||||
"engines": [
|
|
||||||
"3.9L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2017",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "gtc4lusso",
|
|
||||||
"engines": [
|
|
||||||
"6.3L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2016",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "488_gtb",
|
|
||||||
"engines": [
|
|
||||||
"3.9L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ff",
|
|
||||||
"engines": [
|
|
||||||
"6.3L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2015",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "458_italia",
|
|
||||||
"engines": [
|
|
||||||
"4.5L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "458_spider",
|
|
||||||
"engines": [
|
|
||||||
"4.5L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "california_t",
|
|
||||||
"engines": [
|
|
||||||
"3.8L V8",
|
|
||||||
"3.9L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "f12_berlinetta",
|
|
||||||
"engines": [
|
|
||||||
"6.3L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2014",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "458_italia",
|
|
||||||
"engines": [
|
|
||||||
"4.5L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "california",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "laferrari",
|
|
||||||
"engines": [
|
|
||||||
"6.3L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2013",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "458_italia",
|
|
||||||
"engines": [
|
|
||||||
"4.5L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2012",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "458_italia",
|
|
||||||
"engines": [
|
|
||||||
"4.5L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ff",
|
|
||||||
"engines": [
|
|
||||||
"6.3L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2010",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "458_italia",
|
|
||||||
"engines": [
|
|
||||||
"4.5L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "california",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2009",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "599_gtb",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2008",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "599_gtb",
|
|
||||||
"engines": [
|
|
||||||
"6.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "f430",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Spider",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2007",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "f430",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Spider",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2006",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "612_scaglietti",
|
|
||||||
"engines": [
|
|
||||||
"5.7L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "f430",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Spider",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2005",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "f430",
|
|
||||||
"engines": [
|
|
||||||
"4.3L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Spider",
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "superamerica",
|
|
||||||
"engines": [
|
|
||||||
"5.7L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2004",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "360",
|
|
||||||
"engines": [
|
|
||||||
"3.6L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Challenge Stradale",
|
|
||||||
"Modena",
|
|
||||||
"Spider"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "575_m_maranello",
|
|
||||||
"engines": [
|
|
||||||
"5.7L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "enzo",
|
|
||||||
"engines": [],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2003",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "360",
|
|
||||||
"engines": [
|
|
||||||
"3.6L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Challenge Stradale",
|
|
||||||
"Modena",
|
|
||||||
"Spider"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2002",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "360",
|
|
||||||
"engines": [
|
|
||||||
"3.6L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Challenge Stradale",
|
|
||||||
"Modena",
|
|
||||||
"Spider"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2001",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "360",
|
|
||||||
"engines": [
|
|
||||||
"3.6L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Challenge Stradale",
|
|
||||||
"Modena",
|
|
||||||
"Spider"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "2000",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "360",
|
|
||||||
"engines": [
|
|
||||||
"3.6L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Challenge Stradale",
|
|
||||||
"Modena",
|
|
||||||
"Spider"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1998",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "456_gt",
|
|
||||||
"engines": [],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1997",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "550_maranello",
|
|
||||||
"engines": [
|
|
||||||
"5.5L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "f355_spider",
|
|
||||||
"engines": [
|
|
||||||
"3.5L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1996",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "f355_spider",
|
|
||||||
"engines": [
|
|
||||||
"3.5L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1995",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "f355_berlinetta",
|
|
||||||
"engines": [],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1992",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "348_tb",
|
|
||||||
"engines": [
|
|
||||||
"3.4L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1991",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "mondial_t",
|
|
||||||
"engines": [
|
|
||||||
"3.4L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "testarossa",
|
|
||||||
"engines": [
|
|
||||||
"4.9L H12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1990",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "348_ts",
|
|
||||||
"engines": [
|
|
||||||
"3.4L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1987",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "328_gts",
|
|
||||||
"engines": [
|
|
||||||
"3.2L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mondial_3_2",
|
|
||||||
"engines": [
|
|
||||||
"3.2L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "testarossa",
|
|
||||||
"engines": [
|
|
||||||
"4.9L H12"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1985",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "308_gts",
|
|
||||||
"engines": [
|
|
||||||
"3.0L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"Quattrovalvole"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1983",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "308_gts",
|
|
||||||
"engines": [
|
|
||||||
"3.0L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"Quattrovalvole"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1980",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "308_gts",
|
|
||||||
"engines": [
|
|
||||||
"3.0L V8"
|
|
||||||
],
|
|
||||||
"submodels": [
|
|
||||||
"Base",
|
|
||||||
"Quattrovalvole"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1977",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "308_gtb",
|
|
||||||
"engines": [
|
|
||||||
"3.0L V8"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1972",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "365_gtc_4",
|
|
||||||
"engines": [
|
|
||||||
"4.4L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dino_246_gt",
|
|
||||||
"engines": [
|
|
||||||
"2.4L V6"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"year": "1966",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"name": "275_gtb",
|
|
||||||
"engines": [
|
|
||||||
"3.3L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "500_superfast",
|
|
||||||
"engines": [
|
|
||||||
"5.0L V12"
|
|
||||||
],
|
|
||||||
"submodels": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user