NHTSA vPIC often returns FuelTypePrimary (and sometimes a secondary fuel field) as part of a VIN decode. That string is useful: gasoline, diesel, electric, flexible fuel, and similar catalog labels help a buyer know what the factory intended. The trap is turning "Gasoline" into a range estimate, an MPG badge, or a green marketing claim the API never made.
This post is about honest display of fuel type from DecodeVinValues-style payloads -- pass through the label, refuse to invent efficiency.
What FuelTypePrimary is
It is a manufacturer / catalog attribute associated with the VIN decode. It answers a narrow question: what primary fuel (or energy) type did the decode associate with this vehicle?
It does not answer:
- Real-world MPG or MPGe for this exact car
- EPA sticker values for a specific trim and options package
- Remaining EV range after battery age and temperature
- Whether the tank or charging port still matches the factory build
- Whether a dual-fuel or PHEV secondary mode is active today
Empty FuelTypePrimary does not mean "unknown exotic fuel." It means vPIC did not give you a value for this VIN. Do not invent "Gas" as a default.
The oversell pattern
// Bad: marketing fiction from a fuel label
function rangeClaim(fuel: string | undefined): string {
if ((fuel ?? "").toLowerCase().includes("electric")) {
return "300+ mile range";
}
return "35 MPG highway";
}
Hard-coded MPG and range tables keyed off a free-text fuel string will be wrong for most VINs. Battery packs, axle ratios, and model years vary. A free decode product that prints "300+ miles" next to every electric decode is lying in the UI.
Pass through with provenance
const EMPTY = new Set(["", "null", "undefined", "not applicable", "n/a"]);
export type FuelView = {
primary: string | null;
secondary: string | null;
label: string;
};
export function viewFuelType(fields: {
FuelTypePrimary?: string | null;
FuelTypeSecondary?: string | null;
}): FuelView {
const clean = (raw: string | null | undefined): string | null => {
if (raw == null) return null;
const t = String(raw).trim();
if (!t || EMPTY.has(t.toLowerCase())) return null;
return t;
};
return {
primary: clean(fields.FuelTypePrimary),
secondary: clean(fields.FuelTypeSecondary),
label: "Factory / vPIC catalog (not MPG or range)",
};
}
export function fuelLines(v: FuelView): string[] {
const lines: string[] = [];
if (v.primary) lines.push(`Fuel type (vPIC): ${v.primary}`);
if (v.secondary) lines.push(`Secondary fuel (vPIC): ${v.secondary}`);
if (lines.length === 0) {
return ["Fuel type: not provided by NHTSA for this VIN"];
}
lines.push(v.label);
return lines;
}
Show the API string as returned (after trim). Do not silently rewrite "Flexible Fuel Vehicle (FFV)" into "E85 ready -- save at the pump" unless you have a separate, disclosed data source.
UI copy that stays honest
Good headings:
- "Fuel (NHTSA vPIC)"
- "Reported fuel type (catalog)"
Avoid:
- "Efficiency"
- "EPA estimated"
- "Range"
- "Green score"
- "Cost per mile"
A one-line footnote is enough: "Catalog fuel type from NHTSA vPIC; not an MPG or range measurement."
If your product also shows EPA numbers from another API, keep those on a separate card with their own source label. Mixing sources into one "Fuel & range" chip teaches users that the VIN decode invented the miles.
Do not map fuel strings to MPG enums
// Bad: folklore table
const MPG_BY_FUEL: Record<string, number> = {
Gasoline: 28,
Diesel: 32,
Electric: 100,
};
export function fakeMpg(fuel: string): number {
return MPG_BY_FUEL[fuel] ?? 25;
}
Those constants are product fiction. Two gasoline vehicles can differ by more than the gap you invented between gasoline and diesel. If you need efficiency data, integrate an efficiency dataset keyed by year/make/model/trim -- and still disclose uncertainty.
Pair with other powertrain fields carefully
export function powertrainRows(fields: {
FuelTypePrimary?: string | null;
FuelTypeSecondary?: string | null;
ElectrificationLevel?: string | null;
EngineCylinders?: string | null;
}): Array<{ k: string; v: string }> {
const fuel = viewFuelType(fields);
const rows: Array<{ k: string; v: string }> = [];
rows.push({
k: "Fuel type (vPIC)",
v: fuel.primary ?? "Not provided",
});
if (fuel.secondary) {
rows.push({ k: "Secondary fuel (vPIC)", v: fuel.secondary });
}
const elec = (fields.ElectrificationLevel ?? "").trim();
const cyl = (fields.EngineCylinders ?? "").trim();
if (elec && !EMPTY.has(elec.toLowerCase())) {
rows.push({ k: "Electrification (vPIC)", v: elec });
}
if (cyl && !EMPTY.has(cyl.toLowerCase())) {
rows.push({ k: "Cylinders (vPIC)", v: cyl });
}
return rows;
}
Keep fields on separate rows. Do not concatenate into "PHEV V6 50 MPG" unless every token -- including any MPG -- came from a named source you actually queried.
Tests that lock honesty
import assert from "node:assert/strict";
assert.equal(viewFuelType({ FuelTypePrimary: "" }).primary, null);
assert.equal(viewFuelType({ FuelTypePrimary: "Gasoline" }).primary, "Gasoline");
assert.ok(
fuelLines(viewFuelType({ FuelTypePrimary: "Electric" })).some((l) =>
/not mpg or range/i.test(l),
),
);
assert.ok(
!fuelLines(viewFuelType({ FuelTypePrimary: "Electric" })).some((l) =>
/\d+\s*mpg|mile range/i.test(l),
),
);
Add a review rule: the fuel display module must not contain hardcoded MPG, MPGe, or mile-range literals except inside tests that forbid them.
Takeaway
FuelTypePrimary is a catalog label. Display it with clear sourcing, allow "not provided," and never use it as a key into invented MPG or range tables. Your free VIN UI stays trustworthy when fuel type is either a real vPIC string with a modest footnote -- or absent.
I maintain VIN Lookup, a free VIN decode based on NHTSA data.
Top comments (0)