feat(vehicles): automatic Vinpin VIN-decode fallback (flag-gated, off by default)

When PL24/pcat/emex can't decode a Fiat VIN, decode it via the Vinpin ePER
web catalog (warm-session Playwright worker, single seat, BullMQ concurrency 1),
cache the exact vehicle in vinpin_decodes, match it to PL24's existing
catalog_vehicle for that model, and serve the parts from there. Vinpin = decode
oracle only; PL24 already holds the parts (e.g. Egea/Linea/Doblo).

Strictly gated behind VINPIN_ENABLED (default false) + a Fiat-only brand
allowlist: with the flag off, decodeVin behaviour is byte-identical and the
queue is never touched (covered by tests). Coordinates/selectors in
vinpin.constants.ts are marked TUNE-AGAINST-LIVE-PAID-SEAT.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-26 07:36:39 +03:00
parent 3096d35113
commit dfe90fb968
22 changed files with 1310 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import { describe, expect, it } from "vitest";
import { isUsableParse, parseVinpinModal } from "./vinpin.parser";
const SAMPLE =
"MVS, найденные по vin NM435600006H43436 6J - TIPO - EGEA (2015-2021) 356G37001162 - 3V HIGH PLUS 1.6 120CV D5 CM E6 GS TR Двигатель: 8165300 Автомобиль: 279476 Prod. date: 2/8/2017 Vin: NM435600006H43436";
describe("parseVinpinModal", () => {
it("parses the real ePER modal sample (TIPO-EGEA / 356G37001162 / 2017)", () => {
const p = parseVinpinModal(SAMPLE);
expect(p.model).toBe("TIPO-EGEA");
expect(p.sincom).toBe("356G37001162");
expect(p.modelYear).toBe("2017"); // from Prod. date, not the (2015-2021) range
expect(p.engine).toBe("8165300");
expect(p.trim).toContain("HIGH PLUS");
expect(isUsableParse(p)).toBe(true);
});
it("does not mistake the VIN or short engine number for a SINCOM code", () => {
const p = parseVinpinModal(SAMPLE);
expect(p.sincom).not.toContain("NM4356");
expect(p.sincom).not.toBe("8165300");
});
it("falls back to the year-range start when no production date is present", () => {
const p = parseVinpinModal("9F - LINEA (2007-2017) 323A11005000 - 1.3 MJET");
expect(p.model).toBe("LINEA");
expect(p.sincom).toBe("323A11005000");
expect(p.modelYear).toBe("2007");
});
it("returns all-null for empty/garbage input and reports unusable", () => {
const p = parseVinpinModal("");
expect(p.model).toBeNull();
expect(p.sincom).toBeNull();
expect(isUsableParse(p)).toBe(false);
expect(isUsableParse(parseVinpinModal(null))).toBe(false);
});
});