Updates to database and API for dropdowns.

This commit is contained in:
Eric Gullickson
2025-11-11 10:29:02 -06:00
parent 3dc0f2a733
commit 8376aee7ed
157 changed files with 2573659 additions and 1548221 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: feature-agent
description: MUST BE USED when ever creating or maintaining features
model: sonnet
model: haiku
---
## Role Definition
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: first-frontend-agent
description: MUST BE USED when ever editing or modifying the frontend design for Desktop or Mobile
model: sonnet
model: haiku
---
## Role Definition
+2 -2
View File
@@ -1,7 +1,7 @@
---
name: platform-agent
description: MUST BE USED when ever editing or modifying the platform services.
model: sonnet
description: MUST BE USED when ever editing or modifying the platform services.
model: haiku
---
## Role Definition
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: quality-agent
description: MUST BE USED last before code is committed and signed off as production ready
model: sonnet
model: haiku
---
## Role Definition
+1 -1
View File
@@ -11,7 +11,7 @@ coverage/
*.swp
*.swo
#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)
config/**
@@ -13,6 +13,7 @@ import {
ModelsQuery,
TrimsQuery,
EnginesQuery,
TransmissionsQuery,
VINDecodeRequest
} from '../models/requests';
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> {
try {
const { year, make_id } = request.query;
const models = await this.vehicleDataService.getModels(this.pool, year, make_id);
const { year, make } = request.query as any;
const models = await this.vehicleDataService.getModels(this.pool, year, make);
reply.code(200).send({ models });
} catch (error) {
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> {
try {
const { year, model_id } = request.query;
const trims = await this.vehicleDataService.getTrims(this.pool, year, model_id);
const { year, make, model } = request.query as any;
const trims = await this.vehicleDataService.getTrims(this.pool, year, make, model);
reply.code(200).send({ trims });
} catch (error) {
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> {
try {
const { year, model_id, trim_id } = request.query;
const engines = await this.vehicleDataService.getEngines(this.pool, year, model_id, trim_id);
const { year, make, model, trim } = request.query as any;
const engines = await this.vehicleDataService.getEngines(this.pool, year, make, model, trim);
reply.code(200).send({ engines });
} catch (error) {
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}
*/
@@ -10,6 +10,7 @@ import {
ModelsQuery,
TrimsQuery,
EnginesQuery,
TransmissionsQuery,
VINDecodeRequest
} from '../models/requests';
import pool from '../../../core/config/database';
@@ -37,6 +38,10 @@ async function platformRoutes(fastify: FastifyInstance) {
preHandler: [fastify.authenticate]
}, controller.getEngines.bind(controller));
fastify.get<{ Querystring: TransmissionsQuery }>('/platform/transmissions', {
preHandler: [fastify.authenticate]
}, controller.getTransmissions.bind(controller));
fastify.get<{ Querystring: VINDecodeRequest }>('/platform/vehicle', {
preHandler: [fastify.authenticate]
}, controller.decodeVIN.bind(controller));
@@ -1,23 +1,23 @@
/**
* @ai-summary Vehicle data repository for hierarchical queries
* @ai-context PostgreSQL queries against vehicles schema
* @ai-summary Vehicle data repository for hierarchical dropdown queries
* @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 { MakeItem, ModelItem, TrimItem, EngineItem } from '../models/responses';
import { VINDecodeResult } from '../models/responses';
import { logger } from '../../../core/logging/logger';
export class VehicleDataRepository {
/**
* Get distinct years from model_year table
* Get distinct years from vehicle_options table
*/
async getYears(pool: Pool): Promise<number[]> {
const query = `
SELECT DISTINCT year
FROM vehicles.model_year
FROM vehicle_options
ORDER BY year DESC
`;
try {
const result = await pool.query(query);
return result.rows.map(row => row.year);
@@ -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 = `
SELECT DISTINCT ma.id, ma.name
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
SELECT make FROM get_makes_for_year($1)
`;
try {
const result = await pool.query(query, [year]);
return result.rows.map(row => ({
id: row.id,
name: row.name
}));
return result.rows.map(row => row.make);
} catch (error) {
logger.error('Repository error: getMakes', { error, year });
throw new Error(`Failed to retrieve makes for year ${year}`);
@@ -52,96 +45,114 @@ 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 = `
SELECT DISTINCT mo.id, mo.name
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
SELECT model FROM get_models_for_year_make($1, $2)
`;
try {
const result = await pool.query(query, [year, makeId]);
return result.rows.map(row => ({
id: row.id,
name: row.name
}));
const result = await pool.query(query, [year, make]);
return result.rows.map(row => row.model);
} catch (error) {
logger.error('Repository error: getModels', { error, year, makeId });
throw new Error(`Failed to retrieve models for year ${year}, make ${makeId}`);
logger.error('Repository error: getModels', { error, year, make });
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 = `
SELECT t.id, t.name
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
SELECT trim_name FROM get_trims_for_year_make_model($1, $2, $3)
`;
try {
const result = await pool.query(query, [year, modelId]);
return result.rows.map(row => ({
id: row.id,
name: row.name
}));
const result = await pool.query(query, [year, make, model]);
return result.rows.map(row => row.trim_name);
} catch (error) {
logger.error('Repository error: getTrims', { error, year, modelId });
throw new Error(`Failed to retrieve trims for year ${year}, model ${modelId}`);
logger.error('Repository error: getTrims', { error, year, make, model });
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 = `
SELECT DISTINCT e.id, e.name
FROM vehicles.engine e
JOIN vehicles.trim_engine te ON te.engine_id = e.id
JOIN vehicles.trim t ON t.id = te.trim_id
JOIN vehicles.model_year my ON my.id = t.model_year_id
WHERE my.year = $1
AND my.model_id = $2
AND t.id = $3
ORDER BY e.name
SELECT DISTINCT
CASE
WHEN vo.engine_id IS NULL THEN 'N/A (Electric)'
ELSE e.name
END as engine_name
FROM vehicle_options vo
LEFT JOIN engines e ON e.id = vo.engine_id
WHERE vo.year = $1
AND vo.make = $2
AND vo.model = $3
AND vo.trim = $4
ORDER BY engine_name
`;
try {
const result = await pool.query(query, [year, modelId, trimId]);
return result.rows.map(row => ({
id: row.id,
name: row.name
}));
const result = await pool.query(query, [year, make, model, trim]);
return result.rows.map(row => row.engine_name);
} catch (error) {
logger.error('Repository error: getEngines', { error, year, modelId, trimId });
throw new Error(`Failed to retrieve engines for year ${year}, model ${modelId}, trim ${trimId}`);
logger.error('Repository error: getEngines', { error, year, make, model, trim });
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
* 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> {
const query = `
SELECT * FROM vehicles.f_decode_vin($1)
`;
try {
const result = await pool.query(query, [vin]);
if (result.rows.length === 0) {
return null;
}
const row = result.rows[0];
return {
make: row.make || null,
@@ -32,15 +32,15 @@ export class PlatformCacheService {
/**
* 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;
return await this.cacheService.get<any[]>(key);
return await this.cacheService.get<string[]>(key);
}
/**
* 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;
await this.cacheService.set(key, makes, ttl);
}
@@ -48,51 +48,67 @@ export class PlatformCacheService {
/**
* Get cached models for year and make
*/
async getModels(year: number, makeId: number): Promise<any[] | null> {
const key = this.prefix + 'vehicle-data:models:' + year + ':' + makeId;
return await this.cacheService.get<any[]>(key);
async getModels(year: number, make: string): Promise<string[] | null> {
const key = this.prefix + 'vehicle-data:models:' + year + ':' + make;
return await this.cacheService.get<string[]>(key);
}
/**
* Set cached models for year and make
*/
async setModels(year: number, makeId: number, models: any[], ttl: number = 6 * 3600): Promise<void> {
const key = this.prefix + 'vehicle-data:models:' + year + ':' + makeId;
async setModels(year: number, make: string, models: string[], ttl: number = 6 * 3600): Promise<void> {
const key = this.prefix + 'vehicle-data:models:' + year + ':' + make;
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> {
const key = this.prefix + 'vehicle-data:trims:' + year + ':' + modelId;
return await this.cacheService.get<any[]>(key);
async getTrims(year: number, make: string, model: string): Promise<string[] | null> {
const key = this.prefix + 'vehicle-data:trims:' + year + ':' + make + ':' + model;
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> {
const key = this.prefix + 'vehicle-data:trims:' + year + ':' + modelId;
async setTrims(year: number, make: string, model: string, trims: string[], ttl: number = 6 * 3600): Promise<void> {
const key = this.prefix + 'vehicle-data:trims:' + year + ':' + make + ':' + model;
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> {
const key = this.prefix + 'vehicle-data:engines:' + year + ':' + modelId + ':' + trimId;
return await this.cacheService.get<any[]>(key);
async getEngines(year: number, make: string, model: string, trim: string): Promise<string[] | null> {
const key = this.prefix + 'vehicle-data:engines:' + year + ':' + make + ':' + model + ':' + trim;
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> {
const key = this.prefix + 'vehicle-data:engines:' + year + ':' + modelId + ':' + trimId;
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 + ':' + make + ':' + model + ':' + trim;
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
*/
@@ -1,11 +1,11 @@
/**
* @ai-summary Vehicle data service with caching
* @ai-context Business logic for hierarchical vehicle data queries
* @ai-summary Vehicle data service with caching for dropdown queries
* @ai-context String-based cascade queries with Redis caching
* @ai-migration Updated to use string parameters (not IDs)
*/
import { Pool } from 'pg';
import { VehicleDataRepository } from '../data/vehicle-data.repository';
import { PlatformCacheService } from './platform-cache.service';
import { MakeItem, ModelItem, TrimItem, EngineItem } from '../models/responses';
import { logger } from '../../../core/logging/logger';
export class VehicleDataService {
@@ -41,7 +41,7 @@ export class VehicleDataService {
/**
* Get makes for a year with caching
*/
async getMakes(pool: Pool, year: number): Promise<MakeItem[]> {
async getMakes(pool: Pool, year: number): Promise<string[]> {
try {
const cached = await this.cache.getMakes(year);
if (cached) {
@@ -62,62 +62,83 @@ export class VehicleDataService {
/**
* 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 {
const cached = await this.cache.getModels(year, makeId);
const cached = await this.cache.getModels(year, make);
if (cached) {
logger.debug('Models retrieved from cache', { year, makeId });
logger.debug('Models retrieved from cache', { year, make });
return cached;
}
const models = await this.repository.getModels(pool, year, makeId);
await this.cache.setModels(year, makeId, models);
logger.debug('Models retrieved from database and cached', { year, makeId, count: models.length });
const models = await this.repository.getModels(pool, year, make);
await this.cache.setModels(year, make, models);
logger.debug('Models retrieved from database and cached', { year, make, count: models.length });
return models;
} catch (error) {
logger.error('Service error: getModels', { error, year, makeId });
logger.error('Service error: getModels', { error, year, make });
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 {
const cached = await this.cache.getTrims(year, modelId);
const cached = await this.cache.getTrims(year, make, model);
if (cached) {
logger.debug('Trims retrieved from cache', { year, modelId });
logger.debug('Trims retrieved from cache', { year, make, model });
return cached;
}
const trims = await this.repository.getTrims(pool, year, modelId);
await this.cache.setTrims(year, modelId, trims);
logger.debug('Trims retrieved from database and cached', { year, modelId, count: trims.length });
const trims = await this.repository.getTrims(pool, year, make, model);
await this.cache.setTrims(year, make, model, trims);
logger.debug('Trims retrieved from database and cached', { year, make, model, count: trims.length });
return trims;
} catch (error) {
logger.error('Service error: getTrims', { error, year, modelId });
logger.error('Service error: getTrims', { error, year, make, model });
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 {
const cached = await this.cache.getEngines(year, modelId, trimId);
const cached = await this.cache.getEngines(year, make, model, trim);
if (cached) {
logger.debug('Engines retrieved from cache', { year, modelId, trimId });
logger.debug('Engines retrieved from cache', { year, make, model, trim });
return cached;
}
const engines = await this.repository.getEngines(pool, year, modelId, trimId);
await this.cache.setEngines(year, modelId, trimId, engines);
logger.debug('Engines retrieved from database and cached', { year, modelId, trimId, count: engines.length });
const engines = await this.repository.getEngines(pool, year, make, model, trim);
await this.cache.setEngines(year, make, model, trim, engines);
logger.debug('Engines retrieved from database and cached', { year, make, model, trim, count: engines.length });
return engines;
} 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;
}
}
@@ -43,9 +43,9 @@ export const modelsQuerySchema = z.object({
.int('Year must be an integer')
.min(1950, 'Year must be at least 1950')
.max(2100, 'Year must be at most 2100'),
make_id: z.coerce.number()
.int('Make ID must be an integer')
.positive('Make ID must be positive')
make: z.string()
.min(1, 'Make is required')
.max(100, 'Make must be less than 100 characters')
});
export type ModelsQuery = z.infer<typeof modelsQuerySchema>;
@@ -58,9 +58,12 @@ export const trimsQuerySchema = z.object({
.int('Year must be an integer')
.min(1950, 'Year must be at least 1950')
.max(2100, 'Year must be at most 2100'),
model_id: z.coerce.number()
.int('Model ID must be an integer')
.positive('Model ID must be positive')
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 TrimsQuery = z.infer<typeof trimsQuerySchema>;
@@ -73,12 +76,33 @@ export const enginesQuerySchema = z.object({
.int('Year must be an integer')
.min(1950, 'Year must be at least 1950')
.max(2100, 'Year must be at most 2100'),
model_id: z.coerce.number()
.int('Model ID must be an integer')
.positive('Model ID must be positive'),
trim_id: z.coerce.number()
.int('Trim ID must be an integer')
.positive('Trim ID must be positive')
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'),
trim: z.string()
.min(1, 'Trim is required')
.max(100, 'Trim must be less than 100 characters')
});
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-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
*/
export type YearsResponse = number[];
/**
* Makes response
* Makes response - array of make strings
*/
export interface MakesResponse {
makes: MakeItem[];
}
export type MakesResponse = string[];
/**
* Models response
* Models response - array of model strings
*/
export interface ModelsResponse {
models: ModelItem[];
}
export type ModelsResponse = string[];
/**
* Trims response
* Trims response - array of trim strings
*/
export interface TrimsResponse {
trims: TrimItem[];
}
export type TrimsResponse = string[];
/**
* Engines response
* Engines response - array of engine strings (includes 'N/A (Electric)' for EVs)
*/
export interface EnginesResponse {
engines: EngineItem[];
}
export type EnginesResponse = string[];
/**
* Transmissions response - array of transmission type strings
*/
export type TransmissionsResponse = string[];
/**
* 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 {
const { year, make_id } = request.query;
if (!year || !make_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1) {
const { year, make } = request.query;
if (!year || !make || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0) {
return reply.code(400).send({
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);
} 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({
error: 'Internal server error',
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 {
const { year, make_id, model_id } = request.query;
if (!year || !make_id || !model_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1) {
const { year, make, model } = request.query;
if (!year || !make || !model || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0 || model.trim().length === 0) {
return reply.code(400).send({
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);
} 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({
error: 'Internal server error',
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 {
const { year, make_id, model_id, trim_id } = 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) {
const { year, make, model, trim } = request.query;
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({
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);
} 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({
error: 'Internal server error',
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 {
const { year, make_id, model_id } = request.query;
if (!year || !make_id || !model_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1) {
const { year, make, model } = request.query;
if (!year || !make || !model || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0 || model.trim().length === 0) {
return reply.code(400).send({
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);
} 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({
error: 'Internal server error',
message: 'Failed to get trims'
@@ -45,26 +45,26 @@ export const vehiclesRoutes: FastifyPluginAsync = async (
handler: vehiclesController.getDropdownMakes.bind(vehiclesController)
});
// GET /api/vehicles/dropdown/models?year=2024&make_id=1 - Get models for year/make (Level 2)
fastify.get<{ Querystring: { year: number; make_id: number } }>('/vehicles/dropdown/models', {
// GET /api/vehicles/dropdown/models?year=2024&make=Ford - Get models for year/make (Level 2)
fastify.get<{ Querystring: { year: number; make: string } }>('/vehicles/dropdown/models', {
preHandler: [fastify.authenticate],
handler: vehiclesController.getDropdownModels.bind(vehiclesController)
});
// GET /api/vehicles/dropdown/trims?year=2024&make_id=1&model_id=1 - Get trims (Level 3)
fastify.get<{ Querystring: { year: number; make_id: number; model_id: number } }>('/vehicles/dropdown/trims', {
// GET /api/vehicles/dropdown/trims?year=2024&make=Ford&model=F-150 - Get trims (Level 3)
fastify.get<{ Querystring: { year: number; make: string; model: string } }>('/vehicles/dropdown/trims', {
preHandler: [fastify.authenticate],
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)
fastify.get<{ Querystring: { year: number; make_id: number; model_id: number; trim_id: number } }>('/vehicles/dropdown/engines', {
// GET /api/vehicles/dropdown/engines?year=2024&make=Ford&model=F-150&trim=XLT - Get engines (Level 4)
fastify.get<{ Querystring: { year: number; make: string; model: string; trim: string } }>('/vehicles/dropdown/engines', {
preHandler: [fastify.authenticate],
handler: vehiclesController.getDropdownEngines.bind(vehiclesController)
});
// GET /api/vehicles/dropdown/transmissions?year=2024&make_id=1&model_id=1 - Get transmissions (Level 3)
fastify.get<{ Querystring: { year: number; make_id: number; model_id: number } }>('/vehicles/dropdown/transmissions', {
// GET /api/vehicles/dropdown/transmissions?year=2024&make=Ford&model=F-150 - Get transmissions (Level 3)
fastify.get<{ Querystring: { year: number; make: string; model: string } }>('/vehicles/dropdown/transmissions', {
preHandler: [fastify.authenticate],
handler: vehiclesController.getDropdownTransmissions.bind(vehiclesController)
});
@@ -162,7 +162,7 @@ export class VehiclesService {
await cacheService.del(cacheKey);
}
async getDropdownMakes(year: number): Promise<{ id: number; name: string }[]> {
async getDropdownMakes(year: number): Promise<string[]> {
const vehicleDataService = getVehicleDataService();
const pool = getPool();
@@ -170,36 +170,36 @@ export class VehiclesService {
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 pool = getPool();
logger.info('Fetching dropdown models via platform module', { year, makeId });
return vehicleDataService.getModels(pool, year, makeId);
logger.info('Fetching dropdown models via platform module', { year, make });
return vehicleDataService.getModels(pool, year, make);
}
async getDropdownTransmissions(_year: number, _makeId: number, _modelId: number): Promise<{ id: number; name: 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 }[]> {
async getDropdownTransmissions(year: number, make: string, model: string): Promise<string[]> {
const vehicleDataService = getVehicleDataService();
const pool = getPool();
logger.info('Fetching dropdown engines via platform module', { year, makeId, modelId, trimId });
return vehicleDataService.getEngines(pool, year, modelId, trimId);
logger.info('Fetching dropdown transmissions via platform module', { year, make, model });
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 pool = getPool();
logger.info('Fetching dropdown trims via platform module', { year, makeId, modelId });
return vehicleDataService.getTrims(pool, year, modelId);
logger.info('Fetching dropdown engines via platform module', { year, make, model, trim });
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[]> {
File diff suppressed because it is too large Load Diff
+94 -64
View File
@@ -9,18 +9,16 @@ This ETL pipeline creates a PostgreSQL database optimized for cascading dropdown
### Tables
1. **engines** - Detailed engine specifications
- Displacement, configuration, horsepower, torque
- Fuel type, fuel system, aspiration
- Full specs stored as JSONB
1. **engines** - Simplified engine specifications
- id (Primary Key)
- name (Display format: "V8 3.5L", "L4 2.0L Turbo", "V6 6.2L Supercharged")
2. **transmissions** - Transmission specifications
- Type (Manual, Automatic, CVT, etc.)
- Number of speeds
- Drive type (FWD, RWD, AWD, 4WD)
2. **transmissions** - Simplified transmission specifications
- id (Primary Key)
- type (Display format: "8-Speed Automatic", "6-Speed Manual", "CVT")
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
- Optimized indexes for dropdown queries
@@ -63,57 +61,72 @@ This ETL pipeline creates a PostgreSQL database optimized for cascading dropdown
## ETL Process
### Step 1: Import Engine & Transmission Specs
- Parse all records from `engines.json`
- Extract detailed specifications
- Create engines and transmissions tables
- Build in-memory caches for fast lookups
### Step 1: Load Source Data
- Load `engines.json` (30,066 records)
- Load `brands.json` (124 brands)
- Load `automobiles.json` (7,207 models)
- Load all `makes-filter/*.json` files (55 files)
### Step 2: Process Makes-Filter Data
- Read all 57 JSON files from `makes-filter/`
### Step 2: Transform Brand Names
- 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
- 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
### Step 3: Hybrid Backfill
### Step 6: Hybrid Backfill
- Check `automobiles.json` for recent years (2023-2025)
- 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
### Step 4: Insert Vehicle Options
- Batch insert all vehicle_options records
- Create indexes for optimal query performance
- Generate views and functions
### Step 5: Validation
- Count records in each table
- Test dropdown cascade queries
- Display sample data
### Step 7: Generate SQL Output
- Write SQL files with proper escaping (newlines, quotes, special characters)
- Convert empty strings to NULL for data integrity
- Use batched inserts (1000 records per batch)
- Output to `output/` directory
## Running the ETL
### Prerequisites
- Docker container `mvp-postgres` running
- Python 3 with psycopg2
- Python 3 (no additional dependencies required)
- JSON source files in project root
### Quick Start
```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
```bash
# 1. Run migration
docker compose exec mvp-postgres psql -U postgres -d motovaultpro < migrations/001_create_vehicle_database.sql
# 2. Install Python dependencies
pip3 install psycopg2-binary
# 3. Run ETL script
python3 etl_vehicle_data.py
```
### What Gets Generated
- `output/01_engines.sql` (~632KB, 30,066 records)
- `output/02_transmissions.sql` (~21KB, 828 records)
- `output/03_vehicle_options.sql` (~51MB, 1,122,644 records)
## Query Examples
@@ -127,26 +140,26 @@ SELECT * FROM available_years;
SELECT * FROM get_makes_for_year(2024);
```
### Get models for 2024 Ford
### Get models for 2025 Ford
```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
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
```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
```sql
SELECT * FROM complete_vehicle_configs
WHERE year = 2024 AND make = 'Tesla'
ORDER BY model, trim;
WHERE year = 2025 AND make = 'Ford' AND model = 'f-150'
LIMIT 10;
```
## Performance Optimization
@@ -164,35 +177,50 @@ Dropdown queries are optimized to return results in < 50ms for typical datasets.
## 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
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")
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)
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
- `I4` = `L4` = `INLINE-4`
- `I4` = `L4` = `INLINE-4` = `4 Inline`
- `V6` = `V-6`
- `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)
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)
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
### European (13)
Alfa Romeo, Fiat, Mini, Saab, Saturn, Scion, Smart, Volkswagen, Volvo
### European (9)
Alfa Romeo, FIAT, MINI, Saab, Saturn, Scion, Smart, Volkswagen, Volvo
### Other (12)
Genesis, Geo, Hyundai, Kia, Lucid, Polestar, Rivian, Lotus, Mercury, Oldsmobile, Plymouth, Pontiac
### Other (11)
Genesis, Geo, Hyundai, KIA, Lucid, Polestar, Rivian, Lotus, Mercury, Oldsmobile, Plymouth, Pontiac
## Troubleshooting
@@ -229,12 +257,14 @@ pip3 install psycopg2-binary
## Expected Results
After successful ETL:
- **Engines**: ~30,000 records
- **Transmissions**: ~500-1000 unique combinations
- **Vehicle Options**: ~50,000-100,000 configurations
- **Years**: 10-15 distinct years
- **Makes**: 57 manufacturers
- **Models**: 1,000-2,000 unique models
- **Engines**: 30,066 records
- **Transmissions**: 828 records
- **Vehicle Options**: 1,122,644 configurations
- **Years**: 47 years (1980-2026)
- **Makes**: 53 manufacturers
- **Models**: 1,741 unique models
- **Transmission Linking**: 98.9% success rate
- **Output Files**: ~52MB total (632KB engines + 21KB transmissions + 51MB vehicles)
## Next Steps
+26 -8
View File
@@ -55,11 +55,24 @@ Applied filter in two locations:
### 2. SQL Files Regenerated
- `output/01_engines.sql` - 34MB (unchanged, all engines retained)
- `output/03_vehicle_options.sql` - 52MB (reduced from 56MB)
- `output/01_engines.sql` - ~632KB (simplified to id and name only)
- `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)
### 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.
@@ -89,11 +102,13 @@ python3 etl_generate_sql.py
| Metric | Count |
|--------|-------|
| **Engines** | 30,066 |
| **Transmissions** | 828 |
| **Vehicle Options** | 1,122,644 |
| **Years** | 47 (1980-2026) |
| **Makes** | 53 |
| **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 |
|------|--------|
| `etl_generate_sql.py` | Added min_year filter (line 22) |
| `output/01_engines.sql` | Regenerated (no change) |
| `output/03_vehicle_options.sql` | Regenerated (90K fewer records) |
| Database `vehicle_options` table | Re-imported with filter |
| `etl_generate_sql.py` | Added min_year filter, brand name transformation, simplified display formats |
| `output/01_engines.sql` | Regenerated with simplified format (id, name only) |
| `output/02_transmissions.sql` | New file with transmission data (id, type only) |
| `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
## 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 |
|--------|-------|
| **Engines** | 30,066 |
| **Vehicle Options** | 1,213,401 |
| **Years** | 93 (1918-2026) |
| **Transmissions** | 828 |
| **Vehicle Options** | 1,122,644 |
| **Years** | 47 (1980-2026) |
| **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`)
**Tables:**
- `engines` - Engine specifications with displacement, configuration, horsepower, torque, fuel type
- `transmissions` - Transmission specifications (type, speeds, drive type)
- `engines` - Simplified engine specifications (id, name)
- 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)
- Make names in Title Case: "Acura", "Ford", "BMW" (not ALL CAPS)
**Views:**
- `available_years` - All distinct years
@@ -60,16 +69,26 @@ A Python script that processes JSON source files and generates SQL import files:
**ETL Process:**
1. **Extract** - Loads all JSON source files
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
- 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)
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/01_engines.sql` (34MB, 30,066 records)
- `output/02_transmissions.sql` (empty - no transmission data in source)
- `output/03_vehicle_options.sql` (56MB, 1,213,401 records)
- `output/01_engines.sql` (~632KB, 30,066 records) - Only id and name columns
- `output/02_transmissions.sql` (~21KB, 828 records) - Only id and type columns
- `output/03_vehicle_options.sql` (~51MB, 1,122,644 records)
### 3. Import Script (`import_data.sh`)
@@ -188,24 +207,21 @@ Each query is optimized with composite indexes for sub-50ms response times.
## Known Limitations
1. **Transmissions Table is Empty**
- The engines.json source data doesn't contain consistent transmission info
- Transmission foreign keys in vehicle_options are NULL
- Future enhancement: Add transmission data from alternative source
1. **Electric Vehicles Have NULL Engine/Transmission IDs (1.1%)**
- Occurs when engine string from makes-filter doesn't match traditional displacement patterns
- Example: Tesla models with "Electric" motors don't have displacement specs
- Affects 11,951 of 1,122,644 records
- Future enhancement: Add electric motor specifications
2. **Some Engine IDs are NULL**
- 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 (`&amp;`)
2. **Model Names Have Inconsistencies**
- 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)**
- Includes vintage/classic cars from makes-filter data
- May want to filter to specific year range for dropdown UI
3. **Engine Configuration Variations**
- Some engines show "4 Inline" while others show "L4" or "I4"
- All refer to inline 4-cylinder but use different notation
- Source data inconsistency from autoevolution.com
---
+25 -21
View File
@@ -3,10 +3,12 @@
## Database Status: ✅ OPERATIONAL
- **30,066** engines
- **1,213,401** vehicle configurations
- **93** years (1918-2026)
- **828** transmissions
- **1,122,644** vehicle configurations
- **47** years (1980-2026)
- **53** makes
- **1,937** models
- **1,741** models
- **98.9%** transmission linking success
---
@@ -65,12 +67,15 @@ python3 etl_generate_sql.py
## Files Overview
| File | Purpose |
|------|---------|
| `etl_generate_sql.py` | Generate SQL import files from JSON |
| `import_data.sh` | Import SQL files into database |
| `migrations/001_create_vehicle_database.sql` | Database schema |
| `output/*.sql` | Generated SQL import files (90MB total) |
| File | Purpose | Size |
|------|---------|------|
| `etl_generate_sql.py` | Generate SQL import files from JSON | ~20KB |
| `import_data.sh` | Import SQL files into database | ~2KB |
| `migrations/001_create_vehicle_database.sql` | Database schema | ~8KB |
| `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
├── id (PK)
── name
├── displacement
├── configuration (I4, V6, V8, etc.)
├── horsepower
── torque
├── fuel_type
└── specs_json (full specifications)
── name (e.g., "V8 3.5L Turbo", "L4 2.0L")
transmissions
├── id (PK)
── type (e.g., "8-Speed Automatic", "6-Speed Manual")
vehicle_options
├── id (PK)
├── year
├── make
├── year (1980-2026)
├── make (Title Case: "Ford", "Acura", "Land Rover")
├── model
├── trim
├── engine_id (FK → engines)
@@ -101,9 +104,10 @@ vehicle_options
## Performance
- **Query Time:** < 50ms (indexed)
- **Database Size:** 219MB
- **Index Size:** 117MB
- **Query Time:** < 50ms (composite indexes)
- **Database Size:** ~250MB (with indexes)
- **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
+251 -42
View File
@@ -25,6 +25,7 @@ class VehicleSQLGenerator:
self.engine_cache = {} # Key: (displacement, config) -> engine record
self.transmission_cache = {} # Key: (type, speeds, drive) -> transmission record
self.vehicle_records = []
self.brand_name_map = {} # Key: lowercase_slug -> canonical brand name
# Output SQL files
self.engines_sql_file = 'output/01_engines.sql'
@@ -50,6 +51,100 @@ class VehicleSQLGenerator:
self.brands_data = json.load(f)
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]]:
"""Parse engine string like '2.0L I4' into displacement and configuration"""
pattern = r'(\d+\.?\d*L?)\s*([IVL]\d+|[A-Z]+\d*)'
@@ -72,20 +167,87 @@ class VehicleSQLGenerator:
return {
'name': engine_record.get('name', ''),
'displacement': engine_specs.get('Displacement', ''),
'configuration': engine_specs.get('Cylinders', ''),
'horsepower': engine_specs.get('Power', ''),
'torque': engine_specs.get('Torque', ''),
'fuel_type': engine_specs.get('Fuel', ''),
'fuel_system': engine_specs.get('Fuel System', ''),
'aspiration': engine_specs.get('Aspiration', ''),
'transmission_type': trans_specs.get('Gearbox', ''),
'drive_type': trans_specs.get('Drive Type', ''),
'displacement': engine_specs.get('Displacement:', ''),
'configuration': engine_specs.get('Cylinders:', ''),
'horsepower': engine_specs.get('Power:', ''),
'torque': engine_specs.get('Torque:', ''),
'fuel_type': engine_specs.get('Fuel:', ''),
'fuel_system': engine_specs.get('Fuel System:', ''),
'aspiration': engine_specs.get('Aspiration:', ''),
'transmission_type': trans_specs.get('Gearbox:', ''),
'drive_type': trans_specs.get('Drive Type:', ''),
'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):
"""Escape values for SQL"""
"""Escape values for SQL with proper handling of special characters"""
if value is None:
return 'NULL'
if isinstance(value, (int, float)):
@@ -93,9 +255,17 @@ class VehicleSQLGenerator:
if isinstance(value, dict):
# Convert dict to JSON string and escape
json_str = json.dumps(value)
return "'" + json_str.replace("'", "''") + "'"
# String - escape single quotes
return "'" + str(value).replace("'", "''") + "'"
# Escape single quotes, newlines, carriage returns, and backslashes for PostgreSQL
escaped = json_str.replace('\\', '\\\\').replace("'", "''").replace('\n', '\\n').replace('\r', '\\r')
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):
"""Generate SQL file for engines and transmissions"""
@@ -111,28 +281,25 @@ class VehicleSQLGenerator:
for engine_record in self.engines_data:
specs = self.extract_engine_specs(engine_record)
# Create simplified display name
display_name = self.format_engine_display(specs)
# Only store ID and name
values = (
engine_id,
self.sql_escape(specs['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'])
self.sql_escape(display_name)
)
engines_insert_values.append(f"({','.join(map(str, values))})")
# Build engine cache
# Build engine cache with normalized displacement
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()
key = (disp_norm, config_norm)
if key not in self.engine_cache:
self.engine_cache[key] = engine_id
if disp_norm and config_norm:
key = (disp_norm, config_norm)
if key not in self.engine_cache:
self.engine_cache[key] = engine_id
# Extract transmission
if specs['transmission_type'] or specs['drive_type']:
@@ -162,7 +329,7 @@ class VehicleSQLGenerator:
batch_size = 500
for i in range(0, len(engines_insert_values), 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\n")
@@ -179,15 +346,17 @@ class VehicleSQLGenerator:
f.write("-- Transmissions data import\n")
f.write("-- Generated by ETL script\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 = []
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 = (
trans_id,
self.sql_escape(trans_type),
self.sql_escape(speeds),
self.sql_escape(drive_type)
self.sql_escape(display_name)
)
trans_values.append(f"({','.join(map(str, values))})")
@@ -208,14 +377,19 @@ class VehicleSQLGenerator:
"""Find engine_id from cache based on engine string"""
disp, config = self.parse_engine_string(engine_str)
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:
return self.engine_cache[key]
# Try normalized variations
# Try normalized variations for configuration
for cached_key, engine_id in self.engine_cache.items():
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 None
@@ -239,6 +413,30 @@ class VehicleSQLGenerator:
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):
"""Process all makes-filter JSON files and build vehicle records"""
print(f"\n🚗 Processing makes-filter JSON files (filtering for {self.min_year}+)...")
@@ -250,7 +448,7 @@ class VehicleSQLGenerator:
filtered_records = 0
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}...")
with open(json_file, 'r', encoding='utf-8') as f:
@@ -269,7 +467,7 @@ class VehicleSQLGenerator:
models = year_entry.get('models', [])
for model in models:
model_name = model.get('name', '')
model_name = self.format_model_name(model.get('name', ''))
engines = model.get('engines', [])
submodels = model.get('submodels', [])
@@ -279,7 +477,11 @@ class VehicleSQLGenerator:
for trim in submodels:
for engine_str in engines:
engine_id = self.find_matching_engine_id(engine_str)
# 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({
'year': year,
@@ -341,6 +543,7 @@ class VehicleSQLGenerator:
for remove_str in [str(year), brand_name]:
model_name = model_name.replace(remove_str, '')
model_name = model_name.strip()
model_name = self.format_model_name(model_name)
key = (year, brand_name, model_name.lower())
if key in existing_combos:
@@ -357,18 +560,24 @@ class VehicleSQLGenerator:
engine_id = None
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()
key = (disp_norm, config_norm)
engine_id = self.engine_cache.get(key)
if disp_norm and config_norm:
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({
'year': year,
'make': brand_name.title(),
'make': self.get_canonical_make_name(brand_name),
'model': model_name,
'trim': 'Base',
'engine_id': engine_id,
'transmission_id': None
'transmission_id': transmission_id
})
backfill_count += 1
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';
File diff suppressed because it is too large Load Diff
@@ -3,9 +3,836 @@
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;
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,7 +3,7 @@ ETL Statistics
============================================================
Total Engines: 30,066
Total Transmissions: 0
Total Transmissions: 828
Total Vehicles: 1,122,644
Unique Years: 47
Unique Makes: 53
+53
View File
@@ -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
-506
View File
@@ -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
-427
View File
@@ -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
-607
View File
@@ -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