NHTSA vPIC often returns an EngineCylinders field on DecodeVinValues-style payloads: a count such as "4", "6", or "8" when the pattern includes it. That number is useful on a free VIN decode card. The trap is turning it into a performance pitch -- "V8 power," "sporty four-banger," "tows like a diesel six" -- that a cylinder count never asserted.
This post is about honest display: show EngineCylinders when vPIC provides it, refuse performance inventing, and allow a clean "not provided" state when the field is empty. Keep cylinder catalog values separate from EngineHP, displacement, ForcedInduction, and brochure 0-60 claims. (Horsepower honesty is a separate topic; here the focus is the cylinder field alone.)
What EngineCylinders is (and is not)
EngineCylinders is a catalog attribute associated with the VIN decode. It answers a narrow question: which cylinder count did the decode associate with this pattern?
It does not answer:
- Horsepower, torque, or dyno results
- Whether the layout is V, inline, flat, or rotary marketing language you prefer
- Forced induction, tune status, or fuel system health
- Towing capacity or 0-60 times
- Whether the engine in the listed vehicle is original and unmodified
Empty EngineCylinders does not authorize a default "4" badge, and "8" does not mean "V8 muscle." Do not fill gaps from Make/Model/Year folklore or a chart keyed only by cylinder count.
Normalize empties, do not invent power
Treat blank, "Not Applicable", "N/A", and similar tokens as missing. Keep a sourced integer when valid; do not rewrite "6" into "V6 Twin-Turbo" or "high output."
const EMPTY = new Set([
"",
"not applicable",
"n/a",
"na",
"null",
"none",
"unknown",
]);
export type EngineCylindersView = {
cylinders: number | null;
raw: string | null;
source: "vpic" | "missing";
};
export function viewEngineCylinders(fields: {
EngineCylinders?: string | null;
}): EngineCylindersView {
const raw = (fields.EngineCylinders ?? "").trim();
if (!raw || EMPTY.has(raw.toLowerCase())) {
return { cylinders: null, raw: null, source: "missing" };
}
// Digits only become an int; other catalog noise stays raw-sourced.
if (!/^\d+$/.test(raw)) {
return { cylinders: null, raw, source: "vpic" };
}
const n = Number.parseInt(raw, 10);
if (!Number.isFinite(n) || n <= 0) {
return { cylinders: null, raw, source: "vpic" };
}
return { cylinders: n, raw, source: "vpic" };
}
export function engineCylindersLines(
view: EngineCylindersView,
): string[] {
if (view.source === "missing") {
return [
"Engine cylinders: not provided by vPIC for this VIN",
];
}
if (view.cylinders != null) {
return [
`Engine cylinders (vPIC): ${view.cylinders}`,
"Catalog count only -- not a horsepower or tow rating claim",
];
}
return [
`Engine cylinders (vPIC): ${view.raw}`,
"Catalog value only -- not a horsepower or tow rating claim",
];
}
The footnote matters. Buyers over-read "8" as a V8 package. Show the sourced count; if blank, say "not provided" -- no greyed "V6" and no invented HP from cylinders alone.
Forbidden upgrades
Product pressure often asks for:
- Mapping 4/6/8 into "efficient / balanced / powerful" marketing adjectives
- Prefixing "V" or "I" without a sourced engine configuration field
- Inferring tow ratings or 0-60 from cylinder count
- Defaulting blank
EngineCylindersto "4" so economy cars look complete - Merging cylinders with FuelTypePrimary into "diesel V6 torque monster"
Refuse those. If you show EngineHP, DisplacementL, or FuelTypePrimary from other sourced fields, show each on its own row. Never invent performance arithmetic in the cylinders mapper.
export function assertNoCylPerformance(moduleSource: string): void {
const banned = [
/v8 power/i,
/0-60/i,
/tows like/i,
/muscle/i,
/sporty four/i,
/torque monster/i,
];
for (const re of banned) {
if (re.test(moduleSource)) {
throw new Error(
`cylinders module must not invent performance: ${re}`,
);
}
}
}
export type CardRow = { label: string; value: string };
export function cardRows(fields: {
EngineCylinders?: string | null;
EngineHP?: string | null;
DisplacementL?: string | null;
}): CardRow[] {
const view = viewEngineCylinders(fields);
const out: CardRow[] = [];
if (view.source === "missing") {
out.push({
label: "Engine cylinders",
value: "not provided",
});
} else if (view.cylinders != null) {
out.push({
label: "Engine cylinders",
value: String(view.cylinders),
});
} else {
out.push({
label: "Engine cylinders",
value: String(view.raw),
});
}
const hp = (fields.EngineHP ?? "").trim();
if (hp && !EMPTY.has(hp.toLowerCase())) {
out.push({ label: "Engine HP (vPIC)", value: hp });
}
const displ = (fields.DisplacementL ?? "").trim();
if (displ && !EMPTY.has(displ.toLowerCase())) {
out.push({ label: "Displacement L (vPIC)", value: displ });
}
return out;
}
Keep HP and displacement on their own labeled rows. Never concatenate cylinders with liters into "V8 muscle."
UI copy that stays honest
Prefer:
- "Engine cylinders (vPIC): 6"
- "Engine cylinders: not provided by vPIC for this VIN"
- A short footnote: catalog count, not a performance claim
Avoid:
- "V8 Power Package verified"
- "Sporty I4 -- zippy commute"
- Grey placeholders that look like real cylinder data when the field was empty ## Quick checks
import assert from "node:assert/strict";
assert.equal(viewEngineCylinders({}).source, "missing");
assert.deepEqual(
engineCylindersLines(viewEngineCylinders({})),
["Engine cylinders: not provided by vPIC for this VIN"],
);
const six = viewEngineCylinders({ EngineCylinders: "6" });
assert.equal(six.cylinders, 6);
assert.ok(
engineCylindersLines(six).some((l) =>
/not a horsepower or tow/i.test(l),
),
);
assert.ok(
!engineCylindersLines(six).some((l) =>
/v6|muscle|0-60|tows/i.test(l),
),
);
const rows = cardRows({
EngineCylinders: "",
EngineHP: "280",
DisplacementL: "3.5",
});
assert.ok(
rows.some(
(r) =>
r.label.includes("cylinders") && r.value === "not provided",
),
);
assert.ok(rows.some((r) => r.label.includes("Engine HP")));
assert.ok(rows.some((r) => r.label.includes("Displacement")));
assert.ok(!rows.some((r) => /muscle|V8 power/i.test(r.value)));
Review rule: cylinder modules must not contain performance marketing phrases except in forbidding tests. Never upgrade a blank into "V6."
Takeaway
EngineCylinders is a catalog count. Display it with clear sourcing, allow "not provided," and never use it as a key into invented horsepower, layout letters, or tow claims. Your free VIN UI stays trustworthy when cylinder count is either a real vPIC value with a modest footnote -- or absent -- and performance marketing lives somewhere else, clearly labeled, or not at all.
I maintain VIN Lookup, a free VIN decode based on NHTSA data.
Top comments (0)