fix(pl24): Volvo alanlarında iç kodu değil okunur değeri kullan
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Volvo vinfoBasic aynı özniteliği iki kez veriyor: "Şanzıman kodu" = "B" ile "Şanzıman" = "6-PSHIFT 2WD / MPS6", "Satis tipi" = "42" ile "Türü" = "S80". VAG için doğru olan kod-önce sırası (VAG'ın "Şanzıman kodu" zaten anlamlı: "MQ200") bu yüzden Volvo'da şanzımanı tek harf, seriyi çıplak sayı yapıyordu. `lookupDescriptive()` eklendi: listedeki ilk *açıklayıcı* değeri döner (2 karakterden uzun ve salt rakam değil), hiçbiri açıklayıcı değilse ilk mevcut değere düşer — yani yalnız kod üreten backend'ler aynen eskisi gibi davranır. Şanzıman ve seri bu yardımcıya geçti. Ayrıca eksik İngilizce etiketler eklendi: `transmission` (İngilizce yanıtta şanzıman yine tek harfe düşüyordu) ve `body_style`. Ölçüm (gerçek keşif yakalamaları, YV1AS84ABD1168166 S80): - şanzıman "B" → "6-PSHIFT 2WD / MPS6" - seri "42" → "S80" - kaporta İngilizce yanıtta null → "Sedan" - PSA İngilizce yanıtta şanzıman null → "BVM5" PSA Türkçe, Subaru ve diğer markalarda çıktı değişmedi. 6 yeni test + iki dilde gerçek Volvo fixture'ı. api 617 test geçiyor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
198
apps/api/src/integrations/pl24/__fixtures__/p5_volvo_en.json
Normal file
198
apps/api/src/integrations/pl24/__fixtures__/p5_volvo_en.json
Normal file
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"link": {
|
||||
"wid": "mainGroupTable",
|
||||
"path": "/p5volvo/extern/groups/vin/mainGroup?lang=en&model=308&modelYear=1620&partnerGroup=46&serviceName=volvo_parts&upds=2026-08-24--11-02&vin=YV1AS84ABD1168166"
|
||||
},
|
||||
"segments": {
|
||||
"vinfoBasic": {
|
||||
"records": [
|
||||
{
|
||||
"values": {
|
||||
"description": "Vehicle Identification No.",
|
||||
"value": "YV1AS84ABD1168166"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Year",
|
||||
"value": "2013"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Model",
|
||||
"value": "S80 (07\\-)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Km/h or mph",
|
||||
"value": "K"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Factory code",
|
||||
"value": "21"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Steering gear prod no",
|
||||
"value": "31360538"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Structure week",
|
||||
"value": "201236"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Type",
|
||||
"value": "S80"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Chassis",
|
||||
"value": "168166"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Partner group",
|
||||
"value": "Europe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Upholstery/interior code",
|
||||
"value": "210100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Upholstery/interior",
|
||||
"value": "LEATHER/ANTHR/QRTZCEIL/NV"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Exterior color",
|
||||
"value": "61400"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Exterior color",
|
||||
"value": "WHITE SOLID ICE WHITE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Body style code",
|
||||
"value": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Body style",
|
||||
"value": "Sedan"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Special vehicle code",
|
||||
"value": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Special vehicles",
|
||||
"value": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Sales type",
|
||||
"value": "42"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Sales type",
|
||||
"value": "SALES VERSION 42"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Market code",
|
||||
"value": "49"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Market",
|
||||
"value": "TR"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Engine Code",
|
||||
"value": "84"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Engine",
|
||||
"value": "D4162T"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Engine part no",
|
||||
"value": "6906309"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Engine serial no",
|
||||
"value": "00000000000004138845 / 0ELD61 2208122233587"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Transmission Code",
|
||||
"value": "B"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Transmission",
|
||||
"value": "6\\-PSHIFT 2WD / MPS6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Transmission part no",
|
||||
"value": "1285041"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Transmission serial no",
|
||||
"value": "00AWBB1 170812170654"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Chassis code",
|
||||
"value": "35659B7A6276"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
198
apps/api/src/integrations/pl24/__fixtures__/p5_volvo_tr.json
Normal file
198
apps/api/src/integrations/pl24/__fixtures__/p5_volvo_tr.json
Normal file
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"link": {
|
||||
"wid": "mainGroupTable",
|
||||
"path": "/p5volvo/extern/groups/vin/mainGroup?lang=tr&model=308&modelYear=1620&partnerGroup=46&serviceName=volvo_parts&upds=2026-08-24--11-02&vin=YV1AS84ABD1168166"
|
||||
},
|
||||
"segments": {
|
||||
"vinfoBasic": {
|
||||
"records": [
|
||||
{
|
||||
"values": {
|
||||
"description": "Sasi numarasi",
|
||||
"value": "YV1AS84ABD1168166"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Model yili",
|
||||
"value": "2013"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Model",
|
||||
"value": "S80 (07\\-)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Hız Birimi",
|
||||
"value": "K"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Fabrika Kodu",
|
||||
"value": "21"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Direksiyon Kutusu Üretim No",
|
||||
"value": "31360538"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Üretim Haftası",
|
||||
"value": "201236"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Türü",
|
||||
"value": "S80"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Şasi",
|
||||
"value": "168166"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Ortak grubu",
|
||||
"value": "Europe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Döşeme/iç mekan kodu",
|
||||
"value": "210100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Döşeme",
|
||||
"value": "LEATHER/ANTHR/QRTZCEIL/NV"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Dis rengi",
|
||||
"value": "61400"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Dis rengi",
|
||||
"value": "WHITE SOLID ICE WHITE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Karoseri Tipi Kodu",
|
||||
"value": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Kaporta Stili",
|
||||
"value": "Sedan"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Özel Araç Kodu",
|
||||
"value": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Özel araçlar",
|
||||
"value": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Satis tipi",
|
||||
"value": "42"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Satis tipi",
|
||||
"value": "SALES VERSION 42"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Piyasa Kodu",
|
||||
"value": "49"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Market",
|
||||
"value": "TR"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Motor kodu",
|
||||
"value": "84"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Motor",
|
||||
"value": "D4162T"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Motor Parça No",
|
||||
"value": "6906309"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Motor Seri Numarası",
|
||||
"value": "00000000000004138845 / 0ELD61 2208122233587"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Şanzıman kodu",
|
||||
"value": "B"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Şanzıman",
|
||||
"value": "6\\-PSHIFT 2WD / MPS6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Şanzıman Parça No",
|
||||
"value": "1285041"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Şanzıman Seri No",
|
||||
"value": "00AWBB1 170812170654"
|
||||
}
|
||||
},
|
||||
{
|
||||
"values": {
|
||||
"description": "Şasi Kodu",
|
||||
"value": "35659B7A6276"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
68
apps/api/src/integrations/pl24/pl24-volvo-vinfo.spec.ts
Normal file
68
apps/api/src/integrations/pl24/pl24-volvo-vinfo.spec.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PL24Service } from "./pl24.service";
|
||||
|
||||
/**
|
||||
* Volvo P5 vinfoBasic regression lock (plv2.md, finding p5core-09).
|
||||
*
|
||||
* Volvo emits BOTH a bare internal code and a readable description for the same
|
||||
* attribute — "Şanzıman kodu" = "B" next to "Şanzıman" = "6-PSHIFT 2WD / MPS6",
|
||||
* and "Satis tipi" = "42" next to "Türü" = "S80". The key order the parser needs
|
||||
* for VAG (whose "Şanzıman kodu" IS the useful value, e.g. "MQ200") therefore
|
||||
* rendered a single letter as the gearbox and a bare number as the series.
|
||||
*
|
||||
* Fixtures are the real 2026-09-16 discovery captures for VIN
|
||||
* YV1AS84ABD1168166 (S80), trimmed to the segments the parser reads, in both
|
||||
* the Turkish and the English label locale.
|
||||
*/
|
||||
|
||||
const fixture = (name: string) =>
|
||||
JSON.parse(readFileSync(join(__dirname, "__fixtures__", `${name}.json`), "utf-8"));
|
||||
|
||||
// parseVehicleResponse only touches the response and the service name.
|
||||
const parse = (data: unknown, serviceName = "volvo_parts") => {
|
||||
const svc = Object.create(PL24Service.prototype) as unknown as {
|
||||
parseVehicleResponse: (v: string, d: unknown, s: string) => Record<string, unknown>;
|
||||
isDaimlerService: (s: string) => boolean;
|
||||
};
|
||||
svc.isDaimlerService = () => false;
|
||||
return svc.parseVehicleResponse("YV1AS84ABD1168166", data, serviceName);
|
||||
};
|
||||
|
||||
describe("Volvo P5 vinfoBasic — kod yerine açıklama", () => {
|
||||
it("Türkçe etiketlerde şanzımanı okunur değerinden alır", () => {
|
||||
const out = parse(fixture("p5_volvo_tr"));
|
||||
expect(out.transmission).toBe("6-PSHIFT 2WD / MPS6");
|
||||
// Tek harflik "Şanzıman kodu" artık kazanmıyor.
|
||||
expect(out.transmission).not.toBe("B");
|
||||
});
|
||||
|
||||
it("İngilizce etiketlerde de aynı sonucu verir", () => {
|
||||
const out = parse(fixture("p5_volvo_en"));
|
||||
expect(out.transmission).toBe("6-PSHIFT 2WD / MPS6");
|
||||
});
|
||||
|
||||
it("seri, çıplak satış kodu yerine gerçek tipi döner", () => {
|
||||
expect(parse(fixture("p5_volvo_tr")).series).toBe("S80");
|
||||
expect(parse(fixture("p5_volvo_en")).series).toBe("S80");
|
||||
expect(parse(fixture("p5_volvo_tr")).series).not.toBe("42");
|
||||
});
|
||||
|
||||
it("kaporta stili iki dilde de çözülür ve '0' kodu sızmaz", () => {
|
||||
expect(parse(fixture("p5_volvo_tr")).bodyType).toBe("Sedan");
|
||||
expect(parse(fixture("p5_volvo_en")).bodyType).toBe("Sedan");
|
||||
});
|
||||
|
||||
it("motor alanları bozulmadan kalır", () => {
|
||||
const out = parse(fixture("p5_volvo_tr"));
|
||||
expect(out.engineCode).toBe("84");
|
||||
expect(out.engineType).toBe("D4162T");
|
||||
});
|
||||
|
||||
it("model ve yıl korunur", () => {
|
||||
const out = parse(fixture("p5_volvo_tr"));
|
||||
expect(out.model).toBe("S80 (07-)");
|
||||
expect(out.year).toBe(2013);
|
||||
});
|
||||
});
|
||||
@@ -1184,6 +1184,31 @@ export class PL24Service {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Like `lookup`, but prefers a human-readable value over a bare internal
|
||||
* code when the record carries both.
|
||||
*
|
||||
* Volvo's vinfoBasic has BOTH "Şanzıman kodu" ("B") and "Şanzıman"
|
||||
* ("6-PSHIFT 2WD / MPS6"), and BOTH "Satis tipi" ("42") and a second
|
||||
* "Satis tipi" ("SALES VERSION 42"). The code-first key order that VAG needs
|
||||
* (its "Şanzıman kodu" IS the useful value, e.g. "MQ200") therefore rendered
|
||||
* a single letter as the Volvo gearbox and a bare number as its series.
|
||||
*
|
||||
* Falls back to the first present value, so a backend that only ever emits
|
||||
* codes behaves exactly as before.
|
||||
*/
|
||||
const lookupDescriptive = (...keys: string[]): string | null => {
|
||||
let firstPresent: string | null = null;
|
||||
for (const k of keys) {
|
||||
const v = vehicleData[k]?.trim();
|
||||
if (!v) continue;
|
||||
if (firstPresent === null) firstPresent = v;
|
||||
// Two characters or fewer, or digits only → an index, not a description.
|
||||
if (v.length > 2 && !/^\d+$/.test(v)) return v;
|
||||
}
|
||||
return firstPresent;
|
||||
};
|
||||
|
||||
// Extract prNr records for richer vehicle attributes
|
||||
const prNrRecords = segments.prNr?.records || [];
|
||||
const prNrByCode: Record<string, string> = {};
|
||||
@@ -1226,13 +1251,17 @@ export class PL24Service {
|
||||
// normalizeLabel folds ı→i and strips diacritics, so "Şanzıman kodu" and
|
||||
// "ŞANZIMAN KODU" both arrive as "sanziman_kodu". PSA uses "AKTARMA
|
||||
// SİSTEMLERİ" ("5 MEKANİK VİTES KUTUSU"), Subaru "Mission".
|
||||
const transmissionCode = lookup(
|
||||
const transmissionCode = lookupDescriptive(
|
||||
"sanziman_kodu",
|
||||
"transmission_code",
|
||||
"vites_kutusu",
|
||||
"atm,mtm",
|
||||
"aktarma_sistemleri",
|
||||
"sanziman",
|
||||
// Volvo in the English locale: "Transmission code" ("B") plus the readable
|
||||
// "Transmission" ("6-PSHIFT 2WD / MPS6"). Without the plain key the
|
||||
// English response falls back to the single-letter code.
|
||||
"transmission",
|
||||
"sanziman_numarasi",
|
||||
"mission",
|
||||
);
|
||||
@@ -1242,7 +1271,10 @@ export class PL24Service {
|
||||
const bodyType =
|
||||
Object.entries(prNrByCode).find(([code]) => code.startsWith("K8"))?.[1] ||
|
||||
// PSA "GÖVDE TİPİ" ("4 KAPILI SEDAN"), Volvo "Kaporta Stili" ("Sedan").
|
||||
lookup("karoseri", "body", "body_type", "govde_tipi", "kaporta_stili") ||
|
||||
// Volvo: TR "Kaporta Stili" / EN "Body style" ("Sedan"). Its
|
||||
// "Karoseri Tipi Kodu" / "Body style code" is a bare "0" — never matched
|
||||
// here because the lookup is exact-key, and that is deliberate.
|
||||
lookup("karoseri", "body", "body_type", "govde_tipi", "kaporta_stili", "body_style") ||
|
||||
null;
|
||||
|
||||
// Engine description from prNr D3* (Motor nitelikleri)
|
||||
@@ -1301,7 +1333,10 @@ export class PL24Service {
|
||||
damToModelYear(lookup("dam")) ||
|
||||
extractModelYear(vin) ||
|
||||
0,
|
||||
series: lookup("seri", "satis_tipi", "sales_type", "turu"),
|
||||
// Volvo's "Satis tipi" is the bare sales code ("42") with the readable
|
||||
// form ("SALES VERSION 42") in a duplicate row, and its "Türü" is the real
|
||||
// series ("S80") — so prefer whichever of these is actually descriptive.
|
||||
series: lookupDescriptive("seri", "satis_tipi", "sales_type", "turu", "type"),
|
||||
bodyType,
|
||||
engineCode:
|
||||
engineCode ||
|
||||
|
||||
Reference in New Issue
Block a user