import { sql } from 'drizzle-orm'; import { boolean, index, integer, jsonb, real, smallint, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'; import { carsSchema } from './schemas'; import { id, timestamps } from './helpers'; // Public car "pokedex" (/trackers/cars). Read-only catalogue built from open data by // scripts/cars-import.ts (re-runnable; upserts brands/models by slug so Dex numbers stay // stable, and replaces variants). Sources: // - fueleconomy.gov vehicles.csv (US EPA, 1984-now): one `variants` row per EPA test car. // - RDW open data (opendata.rdw.nl, Gekentekende voertuigen + brandstof): per make/model // aggregates of passenger cars registered in NL (count, years, list price, mass, body), // plus fuel/power/consumption read from one sample registration per model-year. // Unknown stays null; nothing is estimated. /** Shape of `models.nl_years`: registrations per first-registration year. */ export type CarNlYear = { y: number; n: number; price: number | null }; /** Shape of `models.nl_names`: RDW trade names (handelsbenaming) folded into this model. */ export type CarNlName = { name: string; n: number }; export const carBrands = carsSchema.table( 'brands', { id: id(), slug: text('slug').notNull(), name: text('name').notNull(), country: text('country'), modelCount: integer('model_count').notNull().default(0), nlCount: integer('nl_count').notNull().default(0), firstYear: smallint('first_year'), lastYear: smallint('last_year'), sources: text('sources').array().notNull().default([]), /** Wikidata item of the brand/manufacturer and its enwiki article (scripts/cars-photos.ts). */ wikidata: text('wikidata'), wikipedia: text('wikipedia'), /** Logo thumbnail (Wikimedia Commons, 250px) and its credit. */ logo: text('logo'), logoFile: text('logo_file'), logoAuthor: text('logo_author'), logoLicense: text('logo_license'), /** Hero photo: lead photo of the brand's most common model. */ photo: text('photo'), ...timestamps }, (t) => [uniqueIndex('brands_slug_key').on(t.slug)] ); export const carModels = carsSchema.table( 'models', { id: id(), dex: integer('dex').notNull(), brandId: integer('brand_id') .notNull() .references(() => carBrands.id, { onDelete: 'cascade' }), slug: text('slug').notNull(), name: text('name').notNull(), /** Lowercase brand + name + trade names, for ILIKE / trigram search. */ search: text('search').notNull(), firstYear: smallint('first_year'), lastYear: smallint('last_year'), /** Consumer body types: hatchback, sedan, wagon, suv, mpv, van, pickup, coupe, convertible, sports. */ bodies: text('bodies').array().notNull().default([]), /** US EPA size classes (e.g. "Compact Cars", "Small SUV"). */ sizeClasses: text('size_classes').array().notNull().default([]), /** petrol, diesel, hybrid, plug-in hybrid, electric, lpg, cng, hydrogen. */ fuels: text('fuels').array().notNull().default([]), /** fwd, rwd, awd, 4wd. */ drives: text('drives').array().notNull().default([]), transmissions: text('transmissions').array().notNull().default([]), engineLMin: real('engine_l_min'), engineLMax: real('engine_l_max'), cylindersMin: smallint('cylinders_min'), cylindersMax: smallint('cylinders_max'), turbo: boolean('turbo'), powerKwMin: real('power_kw_min'), powerKwMax: real('power_kw_max'), /** Combined consumption, litres per 100 km (RDW type approval, NEDC or WLTP). */ l100Min: real('l100_min'), l100Max: real('l100_max'), co2Min: smallint('co2_min'), // g/km (RDW) co2Max: smallint('co2_max'), mpgMin: real('mpg_min'), // combined (EPA; MPGe for EVs) mpgMax: real('mpg_max'), rangeKmMax: smallint('range_km_max'), // EV range (EPA) seats: smallint('seats'), doors: smallint('doors'), massKg: smallint('mass_kg'), // NL avg kerb weight (massa rijklaar) nlCount: integer('nl_count').notNull().default(0), nlRank: integer('nl_rank'), nlFirstYear: smallint('nl_first_year'), nlLastYear: smallint('nl_last_year'), nlPrice: integer('nl_price'), // avg catalogue price (EUR, incl. taxes) over all NL registrations nlPriceLatest: integer('nl_price_latest'), // avg catalogue price in the latest year with data nlPriceLatestYear: smallint('nl_price_latest_year'), nlYears: jsonb('nl_years').$type().notNull().default([]), nlNames: jsonb('nl_names').$type().notNull().default([]), usYears: smallint('us_years').array().notNull().default([]), variantCount: integer('variant_count').notNull().default(0), sources: text('sources').array().notNull().default([]), /** Matched Wikidata item / enwiki article (scripts/cars-photos.ts); null = no confident match. */ wikidata: text('wikidata'), wikipedia: text('wikipedia'), /** Lead photo thumbnail URL (960px), denormalised from cars.photos for the card grid. */ photo: text('photo'), photoCount: smallint('photo_count').notNull().default(0), photosCheckedAt: timestamp('photos_checked_at', { withTimezone: true }), ...timestamps }, (t) => [ uniqueIndex('models_dex_key').on(t.dex), uniqueIndex('models_brand_slug_key').on(t.brandId, t.slug), index('models_nl_count_idx').on(t.nlCount), index('models_search_trgm_idx').using('gin', sql`${t.search} public.gin_trgm_ops`) ] ); /** One row per fueleconomy.gov test vehicle (model-year x engine x transmission x drive). */ export const carVariants = carsSchema.table( 'variants', { id: id(), modelId: integer('model_id') .notNull() .references(() => carModels.id, { onDelete: 'cascade' }), source: text('source').notNull().default('fueleconomy.gov'), sourceId: integer('source_id'), year: smallint('year').notNull(), name: text('name').notNull(), engineL: real('engine_l'), cylinders: smallint('cylinders'), transmission: text('transmission'), drive: text('drive'), fuel: text('fuel'), sizeClass: text('size_class'), mpgCity: real('mpg_city'), mpgHwy: real('mpg_hwy'), mpgComb: real('mpg_comb'), co2Gpm: smallint('co2_gpm'), // tailpipe g/mile rangeMi: smallint('range_mi'), evMotor: text('ev_motor'), turbo: boolean('turbo').notNull().default(false), supercharged: boolean('supercharged').notNull().default(false) }, (t) => [index('variants_model_year_idx').on(t.modelId, t.year)] ); /** Freely licensed photos from Wikimedia Commons; only thumbnail URLs are stored, nothing is downloaded. */ export const carPhotos = carsSchema.table( 'photos', { id: id(), modelId: integer('model_id') .notNull() .references(() => carModels.id, { onDelete: 'cascade' }), position: smallint('position').notNull(), /** lead (Wikidata image), generation, article (enwiki), commons (Commons category). */ kind: text('kind').notNull(), /** Commons file name without the "File:" prefix. */ file: text('file').notNull(), caption: text('caption'), /** 960px thumbnail on upload.wikimedia.org; other standard widths are derived from it. */ thumb: text('thumb').notNull(), width: integer('width').notNull(), height: integer('height').notNull(), author: text('author'), license: text('license'), licenseUrl: text('license_url') }, (t) => [uniqueIndex('photos_model_position_key').on(t.modelId, t.position)] );