NHTSA vPIC often returns airbag-location style fields on DecodeVinValues payloads: catalog tokens for front, side, curtain, knee, and similar locations when the pattern includes them. That is useful on a free VIN decode card. The trap is turning those strings into a safety grade -- "5-star equivalent," "fully loaded airbags," "safer than average for this class" -- that location fields never asserted.
This post is about honest display: show airbag location fields when vPIC provides them, refuse grade inventing, and allow a clean "not provided" state when every related field is empty. Keep location catalog labels separate from crash-test scores, recall status, and brochure safety packages.
What airbag location fields are (and are not)
DecodeVinValues-style rows may include fields such as AirBagLocFront, AirBagLocSide, AirBagLocCurtain, AirBagLocKnee, and related location keys depending on the vPIC version and vehicle pattern. They answer a narrow question: which airbag-location labels did the decode associate with this VIN pattern?
They do not answer:
- NHTSA or IIHS star ratings for this exact vehicle
- Whether airbags are present, functional, or never deployed on the unit for sale
- Whether a prior crash replaced modules correctly
- Seat-belt pretensioner or advanced restraint package marketing names
- A numeric "safety score" you invented by counting non-empty location fields
Empty location fields do not mean "no airbags" in a way that authorizes a scary badge, and a full set of location tokens does not mean "top safety pick." It means vPIC did or did not give you those catalog strings. Do not fill gaps from Make/Model/Year folklore or from a static airbag chart keyed only by body class.
Normalize empties, do not invent grades
Treat blank, "Not Applicable", "N/A", and similar tokens as missing. Do not rewrite "1st Row (Driver and Passenger)" into "dual front airbags -- 5-star ready" or append "fully loaded" because a brochure listed that combination for a different trim year.
const EMPTY = new Set([
"",
"not applicable",
"n/a",
"na",
"null",
"none",
"unknown",
]);
export type AirbagLocKey =
| "AirBagLocFront"
| "AirBagLocSide"
| "AirBagLocCurtain"
| "AirBagLocKnee";
export type AirbagLocView = {
rows: { key: AirbagLocKey; label: string }[];
source: "vpic" | "missing";
};
const KEYS: AirbagLocKey[] = [
"AirBagLocFront",
"AirBagLocSide",
"AirBagLocCurtain",
"AirBagLocKnee",
];
export function viewAirbagLocs(
fields: Partial<Record<AirbagLocKey, string | null>>,
): AirbagLocView {
const rows: AirbagLocView["rows"] = [];
for (const key of KEYS) {
const raw = (fields[key] ?? "").trim();
if (!raw || EMPTY.has(raw.toLowerCase())) continue;
rows.push({ key, label: raw });
}
return {
rows,
source: rows.length ? "vpic" : "missing",
};
}
export function airbagLocLines(view: AirbagLocView): string[] {
if (view.source === "missing") {
return [
"Airbag locations: not provided by vPIC for this VIN",
];
}
const lines = view.rows.map(
(r) => `${r.key} (vPIC): ${r.label}`,
);
lines.push(
"Catalog location labels only -- not a crash-test grade or inspection",
);
return lines;
}
The footnote matters. Buyers scanning a decode will over-read any airbag row as a safety rating. If vPIC said a front location string, show that string with sourcing. If every location field is blank, say "not provided" -- no greyed "standard dual front" placeholder and no invented star count from how many keys were filled.
Forbidden upgrades
Product pressure often asks for:
- Counting non-empty location fields into a 1-5 "safety score"
- Mapping location tokens into IIHS or NHTSA star language
- Defaulting blank locations to "Driver and Passenger" so the card looks complete
- Merging airbag rows with BodyClass into "safest crossover in class"
- Turning location strings into lifestyle copy ("family-safe," "worry-free commute")
Refuse those. If you show crash-test data from another sourced API, show it on its own labeled section with its own provenance. Never invent grades inside the airbag location mapper.
export function assertNoAirbagGrading(moduleSource: string): void {
const banned = [
/5[- ]?star/i,
/safety score/i,
/top safety pick/i,
/safer than/i,
/fully loaded airbags/i,
/family-safe/i,
];
for (const re of banned) {
if (re.test(moduleSource)) {
throw new Error(`airbag module must not invent grades: ${re}`);
}
}
}
export type CardRow = { label: string; value: string };
export function cardRows(
fields: Partial<Record<AirbagLocKey, string | null>> & {
BodyClass?: string | null;
},
): CardRow[] {
const view = viewAirbagLocs(fields);
const out: CardRow[] = [];
if (view.source === "missing") {
out.push({
label: "Airbag locations",
value: "not provided",
});
} else {
for (const r of view.rows) {
out.push({ label: r.key, value: r.label });
}
}
const body = (fields.BodyClass ?? "").trim();
if (body && !EMPTY.has(body.toLowerCase())) {
out.push({ label: "Body class", value: body });
}
return out;
}
Keep BodyClass on its own row. Never concatenate "SUV + curtain airbags" into a marketing safety claim inside the decode card renderer.
UI copy that stays honest
Prefer:
- "AirBagLocFront (vPIC): 1st Row (Driver and Passenger)"
- "Airbag locations: not provided by vPIC for this VIN"
- A short footnote: catalog labels, not a crash test
Avoid:
- "Safety rating: Excellent (4 airbag zones)"
- "Fully equipped airbag package verified"
- Grey placeholders that look like real location data when fields were empty
If product wants a separate "Learn about airbags" help link, that is fine -- as long as it does not pretend the VIN decode produced a grade.
Quick checks
import assert from "node:assert/strict";
assert.equal(
viewAirbagLocs({}).source,
"missing",
);
assert.deepEqual(
airbagLocLines(viewAirbagLocs({})),
["Airbag locations: not provided by vPIC for this VIN"],
);
const populated = viewAirbagLocs({
AirBagLocFront: "1st Row (Driver and Passenger)",
AirBagLocSide: "1st Row (Driver and Passenger)",
AirBagLocCurtain: "",
AirBagLocKnee: "Not Applicable",
});
assert.equal(populated.rows.length, 2);
assert.ok(
airbagLocLines(populated).some((l) =>
/not a crash-test grade/i.test(l),
),
);
assert.ok(
!airbagLocLines(populated).some((l) =>
/5[- ]?star|safety score|fully loaded/i.test(l),
),
);
const rows = cardRows({
AirBagLocFront: "",
BodyClass: "Sedan/Saloon",
});
assert.ok(
rows.some(
(r) =>
r.label.includes("Airbag") && r.value === "not provided",
),
);
assert.ok(rows.some((r) => r.label.includes("Body class")));
assert.ok(!rows.some((r) => /star|safer than/i.test(r.value)));
Add a review rule: the airbag location module must not contain star-rating or "fully loaded" marketing phrases except in tests that forbid them. For chips, show the catalog location with the field name -- never upgrade a blank into "standard dual front."
Takeaway
Airbag location fields from vPIC are catalog labels. Display them with clear sourcing, allow "not provided," and never use a count of filled keys as a safety grade. Your free VIN UI stays trustworthy when airbag locations are either real vPIC values with a modest footnote -- or absent -- and crash-test 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)