fix(pcat): extract real Year param, not the year=All filter axis
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled

pcat decode emits a faceting axis param key="year" value="All" BEFORE the
concrete per-car key="Year" value="2021". extractYearFromPcatCar used
find(includes("year")) which grabbed the axis → parseInt("All")=NaN → null,
so pcat-sourced vehicles (e.g. Opel ASTRA-J W0VPD5EC1MG063839) saved with a
blank year despite the year being present in raw_data. Scan all year-ish
params for a plausible value, then fall back to production_date.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 16:07:11 +03:00
parent 6c6b74a8e5
commit e57a9968b2
2 changed files with 60 additions and 6 deletions

View File

@@ -631,6 +631,45 @@ describe("VehiclesService", () => {
});
});
describe("extractYearFromPcatCar (filter-axis shadowing)", () => {
// Real prod payload for Opel ASTRA-J W0VPD5EC1MG063839: the faceting axis
// `year`="All" precedes the concrete `Year`="2021" param. The old find()
// grabbed "All" → NaN → null, leaving the vehicle year blank.
const astraJParams = [
{ key: "year", name: "Year", value: "All" },
{ key: "sales_region", name: "Region", value: "Another region" },
{ key: "Model", name: "Model", value: "D69 (4 Door Saloon) (Enjoy / Exclusiv)" },
{ key: "Engine", name: "Engine", value: "A14NET (LUJ)" },
{ key: "Year", name: "Year", value: "2021" },
{ key: "production_date", name: "Production date", value: "2020/10/05" },
];
it("picks the concrete Year param, not the year=All filter axis", () => {
const { service } = createService();
const year = (
service as unknown as {
extractYearFromPcatCar: (c: { parameters: unknown[] }) => number | null;
}
).extractYearFromPcatCar({ parameters: astraJParams });
expect(year).toBe(2021);
});
it("falls back to production_date when no numeric Year param exists", () => {
const { service } = createService();
const year = (
service as unknown as {
extractYearFromPcatCar: (c: { parameters: unknown[] }) => number | null;
}
).extractYearFromPcatCar({
parameters: [
{ key: "year", name: "Year", value: "All" },
{ key: "production_date", name: "Production date", value: "2020/10/05" },
],
});
expect(year).toBe(2020);
});
});
// ─── Q2: PL24 circuit breaker must only count transient transport faults ───
describe("PL24 circuit breaker fault classification (Q2)", () => {
it("does NOT trip on a definitive upstream negative (non-transient)", async () => {

View File

@@ -1186,12 +1186,27 @@ export class VehiclesService {
private extractYearFromPcatCar(car: PcatCar): number | null {
if (!car.parameters) return null;
const yearParam = car.parameters.find(
(p) => p.key.toLowerCase().includes("year") || p.key.toLowerCase().includes("model_year"),
);
if (yearParam?.value) {
const num = Number.parseInt(yearParam.value, 10);
if (num > 1900 && num < 2100) return num;
// pcat returns a faceting AXIS param `year` (often value "All") BEFORE the
// concrete per-car `Year` param, so a naive find(includes("year")) grabs the
// axis → parseInt("All") = NaN → null. Scan every year-ish param and take the
// first that parses to a plausible year; only then fall back to a
// production_date like "2020/10/05".
const plausible = (raw: string | undefined): number | null => {
const num = Number.parseInt(raw ?? "", 10);
return num > 1900 && num < 2100 ? num : null;
};
for (const p of car.parameters) {
const key = p.key.toLowerCase();
if (key.includes("year") || key.includes("model_year")) {
const year = plausible(p.value);
if (year) return year;
}
}
for (const p of car.parameters) {
if (p.key.toLowerCase().includes("production_date")) {
const year = plausible(p.value);
if (year) return year;
}
}
return null;
}