DEV Community

Vin Lookup
Vin Lookup

Posted on

Soft-Flag Listing Make vs Decoded Make Without Auto-Banning Sellers

Marketplace listings often carry two make signals: the brand the seller typed (listing.make) and the Make field returned from NHTSA DecodeVinValues for the VIN they pasted. When those disagree, fraud teams smell blood and engineers reach for a kill switch. Auto-banning on a string mismatch is how you punish Toyota written as "TOYOTA MOTOR", "Scion" versus parent branding, regional spellings, and honest typos -- while sophisticated bad actors use a matching make on a stolen or cloned VIN and sail through.

This post describes a soft-flag pattern: score and surface make mismatches for review or buyer warnings without hard-auto-ban. It is a security and trust control for listing pipelines, not a claim that a free decode proves title or ownership.

Why exact string equality fails

Decoded make and listing make are different vocabularies:

  • Sellers write marketing names; vPIC writes manufacturer-facing names.
  • Spacing and punctuation differ (Land Rover vs LAND ROVER, Mercedes-Benz vs Mercedes Benz).
  • Sub-brands and historical badges confuse both humans and simple maps.
  • Empty decode Make (partial row) is not proof the listing is lying.

A binary listing.make === decoded.make check produces noisy false positives. Noise trains moderators to ignore alerts, which is worse than no alert.

Normalize before you compare

type Mismatch = {
  severity: "none" | "info" | "warn" | "high";
  listingMake: string;
  decodedMake: string;
  reason: string;
};

const ALIASES: Record<string, string> = {
  "MERCEDES BENZ": "MERCEDES-BENZ",
  "MERCEDES": "MERCEDES-BENZ",
  "VW": "VOLKSWAGEN",
  "CHEVY": "CHEVROLET",
  "LANDROVER": "LAND ROVER",
};

function normMake(s: string): string {
  const u = s.trim().toUpperCase().replace(/[._]/g, " ").replace(/\s+/g, " ");
  return ALIASES[u] ?? u;
}

export function flagMakeMismatch(listingMake: string, decodedMake: string): Mismatch {
  const a = normMake(listingMake);
  const b = normMake(decodedMake);

  if (!a || !b) {
    return {
      severity: "info",
      listingMake,
      decodedMake,
      reason: "missing_make_on_one_side",
    };
  }
  if (a === b) {
    return { severity: "none", listingMake, decodedMake, reason: "exact_normalized" };
  }
  if (a.includes(b) || b.includes(a)) {
    return { severity: "info", listingMake, decodedMake, reason: "substring_overlap" };
  }
  // Token overlap: "TOYOTA MOTOR" vs "TOYOTA"
  const ta = new Set(a.split(" "));
  const tb = new Set(b.split(" "));
  const inter = [...ta].filter((t) => tb.has(t) && t.length > 2);
  if (inter.length > 0) {
    return { severity: "warn", listingMake, decodedMake, reason: "partial_token_overlap" };
  }
  return { severity: "high", listingMake, decodedMake, reason: "no_overlap" };
}
Enter fullscreen mode Exit fullscreen mode

Severity is a signal, not a sentence. high means "queue for review or show a buyer caution," not "delete account."

Soft-flag actions that scale

Map severity to reversible product actions:

Severity Suggested action
none No badge; optional trust note that make matches decode
info Internal log only; incomplete data or benign alias
warn Moderator queue + seller prompt to confirm brand
high Public caution on the listing + manual review; still no auto-ban

Combine with other signals before escalating: WMI vs claimed brand maps, plant sanity checks, photo EXIF oddities, account age. Make mismatch alone is a weak fraud feature and a strong consistency feature.

What not to do

  • Do not auto-ban or auto-delist solely on make mismatch.
  • Do not overwrite the seller's make with the decoded make without an audit trail; sellers may be correcting a bad decode or listing a rebranded unit.
  • Do not show buyers "FRAUD DETECTED" from a string diff. Prefer "Listing brand does not match VIN decode; ask the seller."
  • Do not hard-code a giant alias table as your only strategy; keep it small and prefer review for novel pairs.

Wiring it into a listing pipeline

export type ListingGate = {
  softFlags: Array<{ code: string; severity: string; detail: string }>;
  allowPublish: boolean;
};

export function applyMakeFlag(
  gate: ListingGate,
  listingMake: string,
  decodedMake: string,
): ListingGate {
  const m = flagMakeMismatch(listingMake, decodedMake);
  if (m.severity === "none") return gate;

  gate.softFlags.push({
    code: "MAKE_MISMATCH",
    severity: m.severity,
    detail: m.reason,
  });

  // Soft policy: always allow publish; high severity adds caution UX elsewhere.
  gate.allowPublish = true;
  return gate;
}
Enter fullscreen mode Exit fullscreen mode

Keeping allowPublish: true by default encodes the product decision in code: mismatches annotate, they do not silently drop inventory. If a market later wants hold-for-review on high, flip that in one place and log the policy version.

Buyer-facing and GEO language

For search and AI citations, describe the control accurately: "We compare listing make to NHTSA decode make and flag disagreements for review." Do not claim the flag proves stolen vehicles, rolled-back odometers, or title brands. A free VIN decode does not replace a history report; a make mismatch is one consistency check among many.

Product rules

  • Normalize and alias lightly before compare.
  • Emit severity + reason; never a bare boolean ban.
  • Prefer soft flags and human review over auto-punishment.
  • Combine with other signals before account-level action.
  • Keep buyer copy cautious and specific.
  • Revisit alias tables when moderator false-positive rates climb.

Takeaway

Listing make versus decoded make is a high-value consistency check when you treat it as a soft flag. Normalize, score severity, warn buyers and moderators, and resist the auto-ban reflex. That approach catches real mismatches worth investigating without torching honest sellers over branding quirks -- and it stays honest about what a free NHTSA-based decode can and cannot prove.

I maintain VIN Lookup, a free VIN decode based on NHTSA data.

Top comments (0)