Commit Graph

79 Commits

Author SHA1 Message Date
semih
02ec3856d5 perf(pl24): reaktif drill derinliğini sınırla, backfill'i jitter'lı aralıkla yavaşlat
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
Faz 1 / adım 2b (analiz: /home/s/ss/plv2.md, bulgu consumers_jobs-03).

SORUN: Ban'ı süren hacim backfill değil, her yeni decode'da çalışan fast-lane
TAM AĞAÇ drill'iydi. 2026-08-18→09-04 arasında (PL24_TR_DISABLED main lane'i
park etmişken) yeni kategorilerin %100'ü aynı gün decode edilen araçlardan
geldi: günde 3-17 decode → 1.4k-6.8k kategori (bir Passat 1.251, bir L200
2.323). Araç başına drill derinliği sınırsızdı ve PL24 istekleri arasında hiç
bekleme yoktu.

DEĞİŞİKLİK:
- `PREFETCH_PL24_FAST_DEPTH` (varsayılan 1): PL24 reaktif/fast lane yalnız üst
  gruplar + doğrudan çocuklarını gezer. Daha derini ya kullanıcı o düğümü
  açtığında (lazy) ya da bütçeli backfill lane'inde çekilir. Diğer kaynaklar ve
  PL24 main lane MAX_DEPTH kullanmaya devam eder. Fast lane'in derinlik tavanına
  ulaşması normal durum olduğu için log seviyesi warn değil log.
- `PREFETCH_PL24_DELAY_MS` (varsayılan 8000) + ±%50 jitter: PL24 BACKFILL
  işlerine per-job bekleme. Kullanıcının fast lane'i asla yavaşlatılmaz.
  Metronom gibi düzenli akış otomasyon imzasıdır; jitter onu kırar.
- Telemetri düzeltmesi: `proxy_logs`'a ham hesap etiketi ("tr") yerine gerçekte
  kullanılan hesap yazılıyor (PL24_TR_DISABLED altında "de"). Yeni
  `PL24AuthService.activeAccount()`.

Test: `__testables` ile maxDepthFor/jitter dışa açıldı + derinlik tavanı testi.
Etkilenen paketler: 198 test geçti. tsc + biome temiz.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 13:44:59 +03:00
semih
56a8ea09be feat(pl24): günlük HTTP bütçesi, kullanıcı rezervi ve proxy_logs telemetrisi
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
Faz 1 / adım 2 (analiz: /home/s/ss/plv2.md, bulgular consumers_jobs-03/06/07,
deploy_ops-13).

SORUN: İki PL24 hesap banı da günde 5-7 bin kategori yazan (≈10 bin+ istek)
otomatik drill dalgalarının ardından geldi; gerçek kullanıcı decode'u ≤14/gün.
Mevcut korumaların hepsi iş-seviyesinde (iş/dk, iş/gün) — HTTP isteği sayan
hiçbir tavan yoktu. Üstelik PL24 çağrıları `proxy_logs`'a hiç yazılmıyordu
(yalnız pcat/emex), bu yüzden hacim ancak ban'dan SONRA, DB satır sayılarından
geriye dönük çıkarılabildi. Kill switch de yalnız decodeVin'i kapatıyordu; drill,
backfill ve katalog gezinmesi "kapalı" kaynağa istek atmaya devam ediyordu.

DEĞİŞİKLİK:
- Yeni `PL24BudgetService`: tüm PL24 upstream trafiği için tek geçit.
  - Günlük sayaç `pl24:http:<gün>` (Redis, 8 gün TTL); tavan `PL24_HTTP_DAILY_MAX`
    (varsayılan 1200).
  - Kullanıcı rezervi `PL24_HTTP_USER_RESERVE` (varsayılan %40): backfill tavanın
    %60'ında durur, kullanıcı decode'u sonuna kadar akar — kaçak bir backfill
    müşteriyi asla aç bırakamaz. Lane ayrımı mevcut `isBackfillContext()` ile.
  - Reddedilen çağrı ağa hiç çıkmaz ve telemetriye yazılmaz.
  - Redis düşerse fail-open (auth/katalog telemetri yüzünden bloklanmaz).
- Telemetri: `ProxyServiceLeg` += `pl24_http`, `pl24_auth`. Login, authorize ve
  tüm katalog fetch'leri `proxy_logs`'a yazıyor (provider dataimpulse/none,
  sessionKey=hesap, status, süre, 403/429 → banned, taşıma hatası sınıflandırma).
  Artık "hangi gün kaç istek attık, ilk 401/403 ne zaman başladı" sorusu
  ban'dan ÖNCE yanıtlanabilir.
- Kill switch yayıldı: `kill-source-pl24` artık `budgetedFetch` içinde, yani
  decode + drill + backfill + browse + görsel indirme dahil TÜM katalog trafiğini
  kapatıyor (eskiden yalnız decodeVin).
- `fetchWithRetry` 401 çaresi netleşti: yalnız servis token'ı tazelenir; oturumun
  kendisi gerekirse auth katmanında düşürülür.

Test: yeni `pl24-budget.service.spec.ts` (9 test: tavan, kullanıcı rezervi,
reddedilen çağrının ağa çıkmaması, Redis fail-open, telemetri biçimi/leg ayrımı,
ban sinyali, taşıma hatası sınıflandırma). Etkilenen paketler: 197 test geçti.
tsc + biome temiz.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 13:13:05 +03:00
semih
e50a907931 refactor(pl24): oturum modeli — tek login, çerez tabanlı authorize yenileme
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
Faz 1 / adım 1 (analiz: /home/s/ss/plv2.md §4.1, bulgular auth-02/03/05/06/09/11/15,
p4legacy-07, consumers-04).

SORUN: PL24'ün base JWT'si 600 saniye yaşıyor ve kod bunu "oturum" sayıyordu
(isTokenValid → login). Talep varsa her ~9 dakikada bir squeezeOut:true ile
YENİDEN LOGIN atılıyordu (günde ~144 login/hesap). PL24 hesap başına tek oturum
veriyor; bu desen hem kendi oturumumuzu sürekli düşürüyor hem de iki hesap
banından önceki otomasyon imzasını oluşturuyordu. Portalın kendisi bir kez login
olup PL24TOKEN çerezini saklıyor ve yalnız authorize'ı yeniliyor.

DEĞİŞİKLİK:
- Oturum = PL24TOKEN çerezi (süresiz). `ensureSession()` sahibi; bellekte tutulur,
  Redis'te `pl24:auth:session:<hesap>` ile 24 saat paylaşılır (api/worker aynı
  oturumu devralır). Süre kontrolü YOK — oturum yalnız sunucu reddederse düşer.
- Servis token'ı = authorize ile mint edilen 600 s'lik JWT; yalnız `Cookie:
  PL24TOKEN` ile istenir (Bearer gerekmiyor — canlı doğrulandı).
- `session_status:"gone"` ve authorize 401/403 artık okunuyor: oturum düşürülür,
  tek sefer yeniden login + retry.
- Single-flight: süreç içi in-flight promise + Redis kilidi
  (`pl24:auth:login-lock:<hesap>`), kilidi alamayan paylaşılan oturumu bekler.
  Eski prewarm iki paralel login atıyordu ve biri her boot'ta HTTP 500 alıyordu.
- Login sözleşmesi: portal ucu `/auth/ext/api/1.1/login`
  ({account,user,password,squeezeOut} → {loginStatus,sessionToken} + Set-Cookie);
  RFC7807 problem gövdesi tiplendi. Önce squeezeOut:false denenir, yalnız
  "session-limit-exceeded"/USER_ALREADY_LOGGED_IN'de squeezeOut:true ile tekrar.
  `PL24_LOGIN_API=legacy` eski uca döner (bir sürüm geri çekilme bayrağı).
- Hata sınıflandırma: account-not-active / user-not-active / authentication /
  2FA-required → ilk hatada 6 saatlik devre kesici (eskiden ölü hesap 5 dakikada
  bir sonsuza dek deneniyordu: prod'da 376 ardışık 401). Ağ/timeout → 5 dakika.
- Saatlik login tavanı (hesap başına 6) — sağlıklı günde 0-1 login beklenir.
- P4 (.action) sayfaları için authorize kaldırıldı: yalnız çerez gönderiliyor
  (canlı doğrulandı). Ford/PSA'daki 18 "ısıtma" authorize çağrısı ensureSession'a
  indirildi; demo-sayfa/401 çaresi artık servis token temizleme değil oturum
  yenileme.
- Gerçek Chrome User-Agent sabiti (eski değer hiçbir tarayıcıda yok).
- authorize'a `pl24-wmidata` eklendi (portal WMI decode'u için ön hazırlık).
- Ölü kod silindi: getAccessToken*/getSessionCookie* base-JWT yolu, hasService,
  getAvailableServices, AUTH_TOKEN_TTL.

Test: pl24-auth.service.spec.ts yeniden yazıldı (19 test: tek login, single-flight,
Redis devralma, cookie-only authorize, gone/401 yeniden login, 412 squeezeOut,
P4 cookie-only header, kalıcı hata kesicisi, saatlik tavan). Tüm PL24 + tüketici
paketleri: 188 test geçti. tsc + biome temiz.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 00:59:36 +03:00
5d9da95830 fix(pl24): tr hesabı kapalıyken de köprüsü (PL24_TR_DISABLED) + login devre kesici
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
- tr-903645 PL24 tarafında pasif (07-24, "The account is not active"): flag
  açıkken tüm tr auth/proxy/egress şeffaf biçimde de-708171'e maplenir
  (PSA dahil — legacy authorizeService yolu tr'ye hardcode'du)
- PSA fetch'lerine dispatcher eklendi: de oturumu DE proxy'den çıkar
- resolveAccount flag altında herkese de döner (fetch/cache/etiket tutarlı)
- login devre kesici: 3 ardışık hata → 5 dk soğuma (haftalardır süren
  ~6 boş login/dk fırtınasını bitirir)
- prefetch: flag açıkken pl24 backfill üretimi durur + kuyruktaki pl24
  main-lane işleri 15 dk defer'la park edilir (fast/user lane akmaya devam
  eder) — hayatta kalan tek hesabı arka plan yükü yakmasın
- compose: PL24_TR_DISABLED env injection (api+worker)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 14:47:53 +03:00
c70b9b61b9 fix(pl24): Hyundai/Kia illustration açıklamasını kod-yaprağa ekle
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Upstream doğrulandı: illustration satırında td[0]=figür kodu ("20-201A .1"),
td[1]=açıklama ("SUB ENGINE ASSY") — parser ilk td'yi (kod) alıp açıklamayı
düşürüyordu. Artık ilk td figür-koduysa (\d\d-\d\d\d[A-Z]?) açıklamalı sonraki
td eklenir → "20-201A .1 SUB ENGINE ASSY", canonical eşleşir (engine→motor).
Diğer markalar etkilenmez (yalnız kod-only ilk td'de tetiklenir). 10/10 test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 10:02:01 +03:00
6bbc32d9c6 debug(pl24): TEMP Hyundai illustration rows + td dump 2026-07-05 09:55:30 +03:00
05951f9853 debug(pl24): TEMP Hyundai/Kia raw HTML row log (katalogJob Faz 2 upstream doğrulama) 2026-07-05 09:51:46 +03:00
c2dfa5cd2e fix(decode): recognize VXK WMI as Opel (PSA/Stellantis-platform Corsa F, Mokka B)
Customer entered VXKUPHNKSP4032040 (2023 Corsa F) and got a bare
'Marka desteklenmiyor' — VXK was missing from all three WMI maps, so
identifyBasic could not even name the brand. PL24's opel_parts catalog
ends at CORSA-E (2015-2019, verified via the model-picker page), so the
catalog itself stays a gap, but the user now gets the honest
'Opel 2023 olarak tanındı, katalog henüz mevcut değil' message and the
miss is logged for the coverage backlog.

- WMI_BRAND_MAP + PL24_WMI_SERVICE_MAP + EMEX CATALOG_MAP: VXK -> Opel
- DECODE_CHAIN_VERSION 2 -> 3 (brand-mapping change; invalidates the
  stale negative cache for the customer's VIN)
- vin-cache-keys.spec: lock-key assertion checked the bare version char,
  which now appears inside the test VIN — assert the ✌️ segment instead

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:36:51 +03:00
ed45021d71 feat(flags): server-side feature flags + upstream kill switches + live backfill config
Add server-side PostHog feature-flag evaluation to the API and wire three
high-leverage uses. The flags live in PostHog (project 127747), dormant and
fail-open, so this commit is a no-op until POSTHOG_PERSONAL_API_KEY is set and
a switch is flipped.

Phase 1 - upstream kill switches:
  PostHogService gains local flag evaluation (onlyEvaluateLocally +
  sendFeatureFlagEvents:false -> zero decode latency, no VIN leak) and
  isSourceLive(). Guards at each source's decode entry (parts-catalogs, emex,
  pl24 whole-source + per-brand via LEGACY_ARCH_SOURCE_TAG) let a flailing
  upstream be disabled from the PostHog UI in ~5s instead of a
  code-fix -> dev -> prod redeploy. Fail-open: any unresolved flag keeps the
  source live, so a PostHog outage can never black out decoding.

Phase 2 - guarded rollout primitive:
  isEnabled()/variant() with VIN bucketing, ready to ramp a new decode/parser
  path 0->100% behind a decode-*-v2 flag (recipe in feature-flags-strategy.md).

Phase 4 - remote-config ops tuning:
  prefetch-worker reads cfg-backfill-tuning to retune backfill
  batchSize/maxBacklog/businessHoursOnly live; malformed/missing -> the
  compiled-in constants.

POSTHOG_PERSONAL_API_KEY wired into the api + worker compose blocks (empty ->
flags inert, no added latency). Tests updated for the new constructor params.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 00:21:52 +03:00
9ccf0863f0 fix(catalog/ford): disambiguate identical catCode buttons + relabel "Model Yılı" → "Varyant"
Two visible defects when a user picks a Ford model (variant selector step):

1. **N identical buttons.** Ford's `modelFamilyToModelList` gives every sub-catCode
   the same caption — just the family name. parseFordCatCodesForFamily returned
   `[{code:CBV,name:Kuga},{code:CBS,name:Kuga},{code:CTD,name:Kuga}]` for Kuga
   2012-2020. The UI rendered three indistinguishable "Kuga" pills with no way
   for the user to tell them apart. Same on Galaxy ("Galaxy"/"Galaxy"), every
   multi-catCode family.

   Fix: detect duplicate baseNames per family and graft a disambiguator —
   year first (friendliest), catCode as a last resort. Result:
   "Kuga (2013-2016)" / "Kuga (2016-2020)" / "Kuga (CTD)" — or, when year is
   present even for unique entries, always include it for a uniform look.

2. **Section title was lying.** UI label was `catalog.fordVariant.modelYear`
   = "Model Yılı" ("Model Year") but for Ford LEGACY these are catCodes
   (generation identifiers), not years. Rename the key value to "Varyant" /
   "Variant" — semantically correct for both Ford catCodes and Volvo years
   (Volvo flows through the same selector and its codes ARE years; "Varyant"
   covers both).

Cleared the affected Redis cache (`pl24:ford:config:*`) on dev so the next
hit fetches fresh disambiguated data; prod cache will roll over on its own
TTL after promote.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 03:12:20 +03:00
2040686e4d fix(catalog): Hyundai/Kia/Nissan schema images (image-board ticket URL)
These three brands rendered parts with NO schema illustration (and no hotspots):
schema_pics=0 vs 56/29/19 parts on dev. Root cause: extractPsaImageTicketUrl only
read imageViewerParamsUrl from the id="jsinitparams" data-params attribute (where
Ford/PSA/Opel/Volvo put it). Hyundai/Kia/Nissan's jsinitparams carries only a
localization dict ({"commonTxt":{"ok":"TAMAM"}}); their imageViewerParamsUrl lives
in a separate <script> JSON blob → extractor returned null → image pipeline skipped.

Fix: when the jsinitparams-attribute parse yields no URL, fall back to a whole-HTML
scan for "imageViewerParamsUrl":"…" (decoding &). Brand-agnostic and additive;
Ford/PSA/Opel/Volvo unchanged (attribute path still wins). Proven live via the de
client: Hyundai 600x820/3 hotspots, Kia 600x820/11, Nissan 1024x560/13 — all download OK.

Also: drop a pre-existing noParameterAssign in fetchP4Page (derive `account` const
from `accountParam` instead of reassigning the param). +3 extractor unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 21:19:19 +03:00
20cd4fda01 feat(catalog): wire Fiat (p5fiat) browse — families→models two-step + de routing
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Fiat catalog browse returned 0 models because the generic single-endpoint P5
model-list flow can't express Fiat's two-level hierarchy and the drill defaulted
to the tr account (Fiat is licensed only on de-708171).

- fetchVehicleList: dispatch fiatp_parts/fiatt_parts to new fetchFiatVehicleList,
  which expands modelOverview (34 families) → models?modelFamily=N (model codes +
  year ranges) into flat catalog vehicles whose catalogPath is the maingroups
  endpoint. Verified live: 34 families → 123 models, end-to-end drill to parts+image.
- fetchMainGroups / fetchP5Restrictions: account-aware (resolveAccount → de + DE
  proxy for Fiat; tr unchanged for every other P5 brand) so browse maingroups no
  longer hit the tr demo/empty page.
- web: case-insensitive "/maingroup" gate so Fiat (lowercase /mdl/maingroups,
  already a maingroups endpoint) skips the empty restriction selector and loads
  categories directly.
- formatFiatYear: "(2016,2020)" → "2016-2020"; +unit tests.

Subgroups/parts/images already resolve account→de for Fiat (unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 19:52:14 +03:00
67513b356a chore(debug): revert p5fiat model-list probe
Finding: /p5fiat/extern/vehicle/models IS the endpoint (de-licensed, demo:false) but
returns 'Cannot parse null string' regardless of manufacturer/make/brand param — needs
an undiscoverable param from the P5 SPA session (launchCatalog.do flow). Requires a real
PL24 web-UI network capture to resolve. Turkish Tofaş NM4/Egea is not in this catalog
anyway, so catalog-browse value is European-Fiat-only. Kept the generic P5 endpoint
self-discovery fallback. Fiat VIN-decode (European) is unaffected and still works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 18:19:30 +03:00
ca535c6fcf chore(debug): TEMP probe p5fiat models manufacturer param 2026-06-05 18:15:30 +03:00
1995ab6035 chore(debug): TEMP probe p5fiat model-list endpoints + launchCatalog 2026-06-05 18:11:23 +03:00
c991c3846a feat(catalog): Ford de variant-drill — parse json-sub-group → image-board parts
Ford parts are licensed only on the de account. The de drill chain is:
group.action (engine/transmission restriction nodes, HTML) → json-main-group.action
(maingroups JSON) → json-sub-group.action (subgroups JSON) → image-board.action (parts).
The existing json-sub-group handler only knew the Nissan shape ({caption,code,url});
Ford de subgroups are {caption,id,subheader,jsonUrl:null,url:null} — leaves carry no
URL. Add parseFordSubGroupsJson: skip subheader rows, and for each leaf construct the
illustration URL (subGroupId = mainGroupId+subheaderId, bomId = subGroupId+leafId →
image-board.action?bomId=…), which drills to parts via the existing image-board path.
Route Ford (fordp/fordt) to de alongside Hyundai/Kia/Nissan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:36:54 +03:00
74d2c9d480 chore(debug): TEMP inspect Ford selected-subgroup response for inline parts 2026-06-05 17:30:19 +03:00
01d020ffdd chore(debug): TEMP probe Ford group-detail parts page 2026-06-05 17:25:24 +03:00
83170bb40c chore(debug): TEMP probe Ford leaf parts endpoints 2026-06-05 17:19:57 +03:00
9c3f3bf970 chore(debug): TEMP dump Ford subgroup object keys/leaf 2026-06-05 17:15:35 +03:00
e22b8820f9 chore(debug): TEMP follow Ford de json maingroup->subgroup chain 2026-06-05 17:09:38 +03:00
5ad68c2a1e chore(debug): TEMP dump Ford json-main-group raw JSON 2026-06-05 17:05:35 +03:00
44284d5f82 chore(debug): TEMP map de Ford variant-tree drill structure + route ford to de
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:57:13 +03:00
678a33a57a fix(catalog): keep only Hyundai/Kia/Nissan on de (Ford/Polestar not de-fixable)
Tested Ford & Polestar on de: Ford returns a real variant tree but drilling can't
reach parts (needs dedicated Ford variant-drill logic); Polestar returns 0
categories (data gap). Revert them to tr; keep the verified Hyundai/Kia/Nissan
de-routing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:49:20 +03:00
dda3931933 chore(test): TEMP route Ford/Polestar to de to test licensing (revert if no)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:44:20 +03:00
3163cf8a3e fix(catalog): route Hyundai/Kia/Nissan to de account (licensed; tr is demo)
Verified on dev: the tr account serves NOT_LOGGED_IN_DEMO for Hyundai/Kia/Nissan
parts (browse-only), while the de (account 2) subscription is licensed and returns
real data (de: 266 Hyundai / 169 Kia models, non-demo, ~88/64 drill rows). Route
every P4 fetch for these brands to de in fetchP4Page (+ resolveAccount), and ensure
the de service token is authorized. Existing tr-coded catalog_vehicles must be
re-seeded so they carry de catalog codes (ops step on deploy).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:36:54 +03:00
a101fe81c2 chore(debug): TEMP move de-probe into fetchPartsByPath (hyundai drill path)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:29:04 +03:00
5a5ab35e7c chore(debug): TEMP de full-flow probe for Hyundai/Kia/Nissan licensing (revert after)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:24:41 +03:00
9da0d4df75 revert(catalog): drop de-account demo fallback (de lacks EUR/TR catalogs)
Confirmed via dev probes: the de account is non-demo (licensed) for Hyundai
parts but does NOT have the EUR/TR-region catalogs our vehicles use, so the
fallback returned non-demo-but-empty and just doubled upstream load. Root cause
of empty Hyundai/Kia/Nissan parts is PL24 licensing/region (tr account not
licensed for these brands' parts) — a commercial issue, not code-fixable.
Keeping only the explanatory comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:10:29 +03:00
91881d5bd1 fix(catalog): fall back to de account when tr serves a PL24 demo page
Hyundai/Kia/Nissan parts-drilling returned a NOT_LOGGED_IN_DEMO page on the tr
account (license-gated), so live drills yielded 0 parts even though the tree
loads. After the tr re-auth retry still demos, try the de account once; if it
serves real (non-demo) content, use it. Tests whether de holds the brand license.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:02:42 +03:00
fd5c617252 chore(debug): revert TEMP Ford flow-mapping logging
Ford P4 catalog navigation is session-stateful: catCode group.action returns
the full Ford model picker (30 vehicle.action rows), and vehicle.action?modelFamily=X
loops back to the same picker; a bare group.action after select still returns the
picker (parsedGroups=0). Stateless GETs over a rotating proxy can't hold PL24's
server-side model selection → groups are unreachable (explains 0/57 categories_fetched
on prod). Needs session-continuity or browser-based navigation — tracked, not a quick fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:35:43 +03:00
cd5c2e5c43 chore(debug): TEMP Ford session-state test (vehicle.action then bare group.action)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:30:00 +03:00
9772ce1ef4 chore(debug): TEMP recursive Ford selection-chain mapping (revert after)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:25:56 +03:00
ce297893ee chore(debug): TEMP map Ford catCode->vehicle-select->groups flow (revert after)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:21:35 +03:00
27c7a81573 chore(debug): revert TEMP Ford/Volvo diagnostic logging
Diagnosis captured: Ford catCode group.action returns a vehicle-selection
page (30 vehicle.action rows, no group rows) — needs an extra selection
level before groups. Polestar json-model-years returns an HTML landing page,
not JSON — needs a different navigation flow. Both tracked for follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:16:22 +03:00
df3949fa6b chore(debug): TEMP log Ford 0-groups HTML + Volvo model-years raw (revert after diag)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:11:44 +03:00
0e1c0a55d0 style(catalog): biome format fetch options in P5 endpoint fallback
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:02:27 +03:00
d918869742 fix(catalog): self-discover P5 model-list endpoint for unmapped backends
fetchVehicleList fell back to /extern/vehicle/modelfamilies for any backend
not in BACKEND_MODEL_PATH; p5fiat (Fiat) isn't mapped, so it returned 0
models and Fiat seeded nothing (0 catalog_vehicles on dev+prod). When the
primary path yields nothing, try the other known P5 listing endpoints and
use the first that returns models, logging which one worked so it can be
pinned. Only runs on the empty path → mapped backends unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:01:50 +03:00
f83973c628 fix(catalog): filter nav-crumb junk from PL24 legacy group parser
parseFordGroupsFromHtml leaked header/breadcrumb links into the category
list as pseudo-categories that drill to nothing: Hyundai/Kia "Portal"
(portal.action), logout, and Nissan "Repair & Maintenance Information"
(external https://eu.nissan.biz/). Skip these in both the primary tr-row
parser and the <a href> fallback. Real categories (group.action /
group-detail.action / json-(main|sub)-group.action) are unaffected, and
Volvo's vin-group.action?...group1=... real categories are kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:24:21 +03:00
0d64e22c8d feat(pl24): enrich P5 decode for non-VAG OEMs (SEV-2)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
parseVehicleResponse only read VAG/BMW vinfoBasic labels, so Jaguar/Land Rover/Toyota/MAN/Suzuki dropped engine+transmission and Mitsubishi's model showed an internal chassis code.

- engine/transmission: read brand labels (JLR Motor Tipi/Vites Kutusu, Toyota ENGINE 1/ATM,MTM + parens engine code, MAN Yedek motor/Şanzıman, Suzuki Motor No./Şanzıman numarası). - Mitsubishi model: prefer Araç/description (L200), not the internal Model code (KB4T 2500DIESEL...). - value normalize: unescape literal \- + collapse whitespace (was prNr-only) → fixes 'XJ 2010 \- 2019', '2005\-07', 'COROLLA   (TUP)'. VAG/BMW/Mercedes/Fiat unchanged. 40/40 pl24 specs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:48:39 +03:00
01ed1a4480 fix(pl24): P4 decode returns null on model-selection page (Hyundai/Kia/Nissan)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
When PL24 can't identify a VIN, vin-group.action redirects to vehicle.action (a brand-wide model picker). The generic P4 parser collapsed that page's <title> to the brand name with a bogus VIN-char year, and doResolveVin's fastest-source-wins treated that truthy result as a definitive decode — masking the pcat/emex candidate fallback.

parseP4VehicleResponse now returns null when the extracted model is just the brand (or contains the VIN), so the pipeline falls back to pcat/emex instead of storing a fake vehicle. Live discovery: 7/11 Hyundai/Kia/Nissan VINs hit the picker (newer 2024+ / Nissan EU model-pick catalog); the 4 PL24-indexed VINs (GETZ 02, RIO 15/17/20) still decode. Adds pl24-ford-legacy.service.spec.ts; biome-formats two pre-existing lines in the touched file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 02:25:00 +03:00
d575893447 fix(pl24): Mercedes year from 'Teslimat tarihi' (delivery date)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
p5daimler vinfoBasic has no model_yili/üretim tarihi — only "Teslimat tarihi"
(delivery). The parser ignored it, so year fell to the VIN 10th-char and was
wrong (WDD…1… → 2001 even for 2009/2015/2018 cars). Add "teslimat_tarihi" to the
date-year fallback. Live-verified: C 180→2009, E 250 BlueTEC→2015, E 350→2018.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 00:22:38 +03:00
94faa6cdcd fix(pl24): cleaner BMW model — append chassis only, drop redundant line token
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
"Seri" is "{line} {chassis} [{variant}]" and the trim already implies the line
("520i"→5, "X3 sDrive20i"→X3), so append only the chassis(+variant): "520i G30",
"X3 sDrive20i G01" (was "520i 5 G30" / the redundant "X3 sDrive20i X3 G01").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 23:55:22 +03:00
d3ff19ad6a fix(pl24): enrich BMW decode — chassis/generation, body, drive from vinfoBasic
BMW (p5bmw) decode was weak: model was just the trim ("520i") with no
chassis/generation, body_type empty, because BMW has NO prNr segment and keeps
that data in distinct vinfoBasic labels the shared parser ignored. Live-verified
fields: "Seri"="5 G30" (chassis), "Karoseri"="Limousine" (body), "Tahrik"="RWD".

- model: fold the generation ("Seri"/"Model tanimi") into the model when present
  and not already included → "520i 5 G30" (disambiguates E60/F10/G30 for parts).
- bodyType: fall back to vinfoBasic "Karoseri" when there's no prNr K8*.
- series: read "Seri"; driveType: read "Tahrik".
Year already comes from "Üretim tarihi" (P5 year fallback). VW/Audi (prNr) and
Mercedes ("Piyasa adı", no seri/karoseri) verified unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 23:47:00 +03:00
9809012be5 fix(pl24): correct p5fiat vinfoBasic label/value extraction
The Fiat vinfoBasic record carries the row under `values` like p5vwag, but the
inner field names are swapped: p5fiat uses values.key=<label>,
values.description=<value> (vwag uses values.description=<label>,
values.value=<value>). The first pass read values.description as the label, so
model fell back to the numeric description code ("319") and year to 0. Detect
the shape via values.key. Live-verified: ZFA Panda→"Panda POP 1.2 8V 69CV 5M E6"
/2014, Grande Punto/2009, 500L/2015. Spec fixture corrected to the real shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:12:22 +03:00
e2a85ff94f test(pl24): cover p5fiat vinfoBasic parse shape + p5vwag regression
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:02:30 +03:00
4a115db885 fix(pl24): wire Fiat as P5-modern /p5fiat (was dead LEGACY_FIAT /fca)
Fiat (fiatp_parts/fiatt_parts) was misconfigured as LEGACY_FIAT basePath /fca,
which 404s on every request — PL24 Fiat decode was dead. Live discovery
(de-708171) shows Fiat is a standard P5 Modern catalog at /p5fiat: directAccess
+ maingroups/subgroups/parts/images all match the existing P5 flow. Only the
vinfoBasic record shape differs ({key,description} vs {values:{description,value}}).

- types: fiatp_parts/fiatt_parts -> P5_MODERN, apiPath/basePath /p5fiat
- parseVehicleResponse: parse the p5fiat vinfoBasic shape; friendly model from
  "Model bilgisi"; year from MY / production date

Covers European (ZFA) Fiats + some commercial Tofas (fiatt). Turkish Tofas
passenger VINs (NM4, incl. Egea) are not in this catalog. de account separation
(resolveAccount Rule 1) unchanged; de auth handshake proxied, catalog data not.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:58:03 +03:00
45953f6dd5 fix(pl24): correct Opel/Vauxhall + Hyundai/Kia VIN decode via thin brand services
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Same shared-core + thin-brand pattern (P4BrandHooks). Both decoded with polluted models (Opel kept
the platform code "…: P10"; Hyundai kept the VIN breadcrumb), wrong VIN-char years, empty transmission.
Fix reads the vin-group info grid + title:
- Opel: model from <title> (segment after the platform code), year from Model yili, transmission from
  Şanzıman kodu, engine from Motor tipi. Verified 6/6 prod (ASTRA-J/2014, CORSA-D/2011, INSIGNIA-A, AGILA-A…).
- Hyundai/Kia: model from <title>, build-year from Üretim tarihi, transmission/body from the
  ENGLISH-labelled grid (plain lowercase — tr-locale would map I→ı and miss "TRANSMISSION").
  Verified (GETZ 02/2004/5 SPEED MT, RIO / STONIC 17/2019…).

Nissan deferred: both prod Nissan VINs are unresolvable upstream (PL24 returns "Model seçimi", no
model) — nothing to parse. Engine/categories/drill unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 13:49:34 +03:00
836b7c43a6 fix(pl24): correct Ford VIN decode (model/build-year/transmission from info grid) via thin brand service
Ford decoded with a polluted model ("Ford Nutzfahrzeuge {VIN}: Transit Connect - TC7…", from the
page title), a wrong VIN-char year, and empty transmission. The clean data is in the vin-group info
grid (Araç Hattı=model line, Üretim tarihi=build date, Vites Kutusu=transmission, Motor Tipi=engine,
Gövde Tarzı=body). New thin PL24FordService supplies parseFordVinInfo via the shared P4 brand hook;
orchestrator routes LEGACY_FORD to it. Engine/categories/drill unchanged.

Verified live vs 9 prod Ford VINs: 7 now decode clean model + correct build-year + transmission
(Transit Connect/2006, Mondeo/1997 CD4E Otomatik, Focus/2000, …) — VIN-char years were off by up to a
decade and transmission was empty for all. 2 VINs are upstream gaps (no Araç Hattı → unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 13:38:22 +03:00
50fd0b0e26 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>
2026-06-04 13:12:29 +03:00