fix(pl24): correct Volvo/Polestar VIN decode (model from vinInfoTable) via thin brand service

Volvo decoded as "Volvo {VIN}" with no real model: the generic Ford-shaped
parseP4VehicleResponse reads window.vehicles/<title>/<h1>, but Volvo ships model/year/type in a
<table id="vinInfoTable"> caption/value grid (Model="V60 Cross Country (19-)", Model yili=2021,
Türü="V60 CC II"). 9 vehicles affected (3 dev + 6 prod).

- Add a per-brand vehicle-info hook (P4BrandHooks.parseVehicleInfo) to the shared P4 engine
  (PL24FordLegacyService); brand values win, generic fills gaps. Backward-compatible: no hook → identical.
- New thin PL24VolvoService supplies parseVolvoVinInfo (vinInfoTable parser); orchestrator routes
  LEGACY_VOLVO decode to it. Categories/drill unchanged (shared engine).

Verified live vs 6 prod Volvo VINs: all decode real models (S80/S60/S40/V40/EX40·XC40/V60 CC) +
correct years; drill intact (motor → 7 subgroups). First step of the per-brand split
(shared core + thin brand services).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 13:12:29 +03:00
parent 223c4bc12f
commit 50fd0b0e26
5 changed files with 166 additions and 21 deletions

View File

@@ -21,6 +21,7 @@ import { StorageService } from "../../storage/storage.service";
import { PL24AuthService } from "./pl24-auth.service";
import { PL24FordLegacyService } from "./pl24-ford-legacy.service";
import { PL24PsaService } from "./pl24-psa.service";
import { PL24VolvoService } from "./pl24-volvo.service";
import { PL24_DEFAULTS } from "./pl24.constants";
import {
type PL24DecodedCategory,
@@ -48,6 +49,7 @@ export class PL24Service {
private readonly authService: PL24AuthService,
private readonly fordLegacyService: PL24FordLegacyService,
private readonly psaService: PL24PsaService,
private readonly volvoService: PL24VolvoService,
private configService: ConfigService,
private redis: RedisService,
private storage: StorageService,
@@ -84,6 +86,10 @@ export class PL24Service {
if (getServiceConfig(serviceName)?.architecture === "LEGACY_PSA") {
return this.psaService.decodeVinForService(cleanVin, serviceName, userId);
}
// Volvo/Polestar: thin brand service supplies the vinInfoTable parser over the shared engine.
if (getServiceConfig(serviceName)?.architecture === "LEGACY_VOLVO") {
return this.volvoService.decodeVinForService(cleanVin, serviceName, userId);
}
if (isLegacyArchitecture(serviceName)) {
return this.fordLegacyService.decodeVinForService(cleanVin, serviceName, userId);
}