rpartstore.renault.com (Renault Group bayi portalı) yeni decode kaynağı olarak
eklendi. Portal REST değil STOMP 1.2 over WebSocket konuşuyor; login Okta OIE
(IDX JSON API + PKCE), tarayıcı gerekmiyor. Sıralama Renault/Dacia için:
pcat + PL24 + emex yarışı → rpartstore → (bayrağı açıksa) Vinpin son çare.
- integrations/rpartstore: STOMP codec, Okta login, oturum (trace-id ile
çok-mesajlı cevap eşleme), istemci (Redis'te 1 saatlik token), Renault/Dacia
yönlendirme, RPartStore → PL24 catalog_vehicles model eşleyici (roman rakam,
karoseri niteleyici, yanlış-pazar cezası).
- rpartstore_decodes tablosu (migration 0037) + rpartstore-decode BullMQ
kuyruğu; worker concurrency 1 + 1 iş / 6 s limiter (portal 2 arama / 10 s).
- Günlük sert kota RPARTSTORE_DAILY_CAP (varsayılan 10, İstanbul günü):
işlemci göndermeden önce Redis INCR ile rezervasyon yapar, aşan VIN'ler
`capped` olur ve 24 saat sonra tekrar denenir; API kota doluysa kuyruğa
hiç almaz. Kısa vadeli rate-limit cevabında retryAfter kadar bekleyip bir
kez tekrar dener.
- VehiclesService.tryRpartstoreFallback: Vinpin fallback ile aynı sözleşme
(decoding/catalogVehicle cevapları, tek başarısızlık log satırı).
- Env: RPARTSTORE_ENABLED/USER/PASS/DAILY_CAP/BROKER_URL/APP_VERSION
(compose api+worker). Vinpin koda dokunulmadan pasif kalır (VINPIN_ENABLED).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Faz 3 backfill'i açmadan önce hacmi ölçtüm ve raporun işaret ettiğinden
çok daha büyük bir israf çıktı.
**Mitsubishi'nin parça listesi grup sanılıyordu.**
`/p5mitsubishi/extern/details/vinDetails` yanıtı `partno`/`qty` taşıyan
bir PARÇA listesi (canlı doğrulama: 16 kayıt), ama her kaydın kendi
linki `partInfoTable` ve ne wid ne yol sınıflandırıcıda karşılık
buluyordu. Sonuç (prod ölçümü): 2.193 parça listesi grup düğümüne
döndü, içlerindeki 19.576 tekil parça ("SCREW,LOCK CYLINDER",
"BOLT,STEERING COLUMN WASHER") kategori olarak kaydedildi. Bu 19.576
sahte düğümün TOPLAM 2 tanesinde parça var ve hepsi her prefetch
turunda yeniden çekiliyor. İkisi de %100 Mitsubishi.
- `detailsTable` artık yaprak, `isPl24PartDetailNode()` ile
`partInfoTable` hiç kuyruklanmıyor.
- `isLeafLinkPath` artık `linkWid`'i de geçiriyor. Okuma yolu bu
güvenilir sinyali hep kullanıyordu ama kuyruklama yolu düşürüyordu —
sınıflandırıcı `detailsTable`'ı öğrense bile burada yine grup
sayılacaktı.
- Migration 0036: 19.576 sahte kategori siliniyor. Okuma yolu bir
düğümün ÖNCE çocuklarına baktığı için bu silme düzeltmenin parçası,
ayrı temizlik değil. Kuru çalıştırma: 19.576 kategori, 9 araç, 2
parça, Mitsubishi dışı 0.
**Backfill anahtarı.** `PL24_BACKFILL_ENABLED` eklendi, varsayılan
KAPALI. Eski `PL24_TR_DISABLED` adı "tr hesabı öldü" diyordu ama işi
"toplu yükü tek sağ kalan hesaptan uzak tut"tu. Eski değişken hâlâ
kapatabiliyor — yarım deploy musluğu sessizce açamasın. Kullanıcı
tetikli fast-lane bu anahtardan etkilenmiyor.
9 yeni test. api 647 test geçiyor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pencere kontrolü saf saat aritmetiği, hiç I/O yapmıyor ve pencere
dışındaki iş zaten hiçbir faydalı iş yapamıyor — dolayısıyla dakikalık
Redis sayacından da önce gelmeli. Yeni sıra: pencere → cooldown →
dakikalık tavan → günlük bütçe. Böylece pencere dışında uyanan iş
hiçbir sayacı kirletmiyor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prod'da PL24 prefetch tamamen durmuştu: günlük bütçe 600/600 dolu
görünürken gün boyunca SIFIR katalog isteği ve SIFIR yeni kategori
üretiliyordu (ölçüm 2026-09-20).
İki hata birlikte kapalı bir döngü kuruyordu:
1. Günlük bütçe `process()` içinde düşülüyor, iş-saati (ve cooldown)
kapısı ise her handler'ın başında duruyordu. Pencere dışında uyanan
bir iş önce bütçeden bir birim yiyor, sonra `time-window` fırlatıp
hiçbir iş yapmadan erteleniyordu.
2. Bütçesi biten kaynak "bir sonraki UTC gece yarısı"na erteleniyordu.
PREFETCH_PL24_START=9 ile bu an 03:00 Europe/Istanbul'a denk gelir —
pencere açılmadan altı saat önce. Uyanan iş yine pencereye takılıyor,
yine erteleniyor; taze günlük bütçe daha pencere açılmadan bu boş
uyanmalarla tükeniyordu.
Düzeltme:
- cooldown + iş-saati kapıları `process()` içinde, bütçe düşülmeden
ÖNCE çalışıyor; handler'lardaki kopyaları kaldırıldı.
- `alignToWindow()` eklendi: bütçe ertelemesi pencerenin içine
hizalanıyor. Pencere tanımlı değilse (varsayılan 0–24) no-op.
- `currentIstanbulHour` artık `istanbulHourAt`'e deleg ediyor ve h24
döngüsünün gece yarısı için ürettiği "24" değeri `% 24` ile
normalleniyor (aksi halde saat hiçbir pencereye düşmez).
8 yeni regresyon testi; api paketi 602 test geçiyor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Faz 1 / adım 3-4 (analiz: /home/s/ss/plv2.md §3.3/§3.5, bulgular types_wmi-01/02/03/04/05,
p5core-02/03/04/05, psa-04/05/06, consumers_jobs-01/02).
CANLI GERÇEK (2026-09-16 keşfi): PL24 manufacturers API'si peugeot/citroen/
citroenDs/psa_opel/psa_vauxhall'ı `/p5psa`, volvo/polestar'ı `/p5volvo`,
subaru'yu `/p5subaru` olarak veriyor. Eski Volvo P4 ucu **503** döndürüyor
(ölü); eski PSA P4 ucu 200 döndürüyor ama verisi **2024-02-13'te donmuş**
(P5 upds 2026-09-07). Kodumuz her ikisini de P4'e yönlendiriyordu.
SERVİS TABLOSU:
- peugeot/citroen/citroenDs + YENİ psa_opel_parts, psa_vauxhall_parts → /p5psa
- volvo/polestar → /p5volvo · YENİ subaru_parts → /p5subaru
- YENİ abarth/alfa/jeep/lancia → /p5fiat
- WMI düzeltmeleri: ZAR→alfa, ZLA→lancia (fiatp'den), VXK→psa_opel (PSA-platform
Corsa F/Mokka B), YENİ JF1/JF2→subaru, 1C4/1J4→jeep. SERVICE_TO_BRAND genişledi.
- P4 kodu ve eski yollar DOKUNULMADI: mevcut 322 PSA + 36 Volvo aracı eski
ağaçlarıyla çalışmaya devam eder (veri göçü ayrı iş).
PARSER (canlı yanıt şekillerine göre):
- `normalizeLabel()`: JS toLowerCase Türkçe "İ"yi "i̇" (i + birleşen nokta)
yapıyordu → PSA'nın "AKTARMA SİSTEMLERİ"/"GÖVDE TİPİ" etiketleri HİÇBİR ZAMAN
eşleşmiyordu, şanzıman/gövde sessizce null kalıyordu. Artık tr-locale +
diakritik temizliği + ı→i.
- `parsePsaModelYear()`: "AM 2005" → 2005; "MAJÖR ENDEKS" bir yıl DEĞİL (bu
yüzden Citroën'ler 2001 görünüyordu). `damToModelYear()`: PSA DAM alanı
(1976-01-01'den gün) → üretim yılı, model yılı Temmuz'da döner.
PSA VIN'lerinde 10. karakter model yılı olmadığı için VIN fallback'i en sona.
- Alan listeleri: motor/engine (PSA "MOTOR", Volvo "Motor"), aktarma_sistemleri,
mission, govde_tipi, kaporta_stili, türü→series.
- Alt grup kodu: PSA'da `record.id` = illusPath ve birden çok illüstrasyon
paylaşıyor (canlı: 36 kayıt / 11 id) → benzersiz kod `values.illustration`.
- Parça filtresi: Volvo BOM'unun başlık satırı (id "null_null", link yok,
unavailable) hayalet parça olarak geçiyordu → elendi.
- Adet: qty (VAG/Subaru) · coef (PSA) · unit (Volvo). Uygulanabilirlik:
modelDescription · restriction.
AĞAÇ SINIFLANDIRMASI (yeni `pl24-tree.ts`, tek kaynak):
Dört ayrı sezgisel vardı ve birbiriyle çelişiyordu:
- `linkWid.includes("group")` → p5psa `illusTable`, p5volvo/p5subaru
`illustrationsTable` yakalanmıyordu → o seviye yaprak sanılıp parça olarak
çekiliyor, partno'suz kayıtlar 0 parçaya dönüşüyor, panel sessizce boş
kalıyordu (2026-06 isPsaParent olayının aynısı).
- case-sensitive `includes("/bomdetails")` → p5psa/p5volvo'nun camelCase
`/details/vin/bomDetails` yaprakları grup sanılıyor, parçaları hiç prefetch
edilmiyordu (bugün Mitsubishi %99,9 / Fiat %80 / Renault %70 bomDetails
yaprağı 0 parça — aynı kök neden).
Artık `isPl24LeafNode`/`isPl24GroupNode` tek kaynak; categories.service (2 yer),
catalog.service (2 yer) ve prefetch-worker aynı fonksiyonu kullanıyor.
Test: yeni `pl24-tree.spec.ts` (7 test, canlı VW/PSA/Volvo/Subaru wid+path
çiftleriyle regresyon kilidi). Etkilenen paketler: 208 test geçti. tsc + biome temiz.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
- 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>
Gate fix (PR#254) prod'da doğrulandı: pressure=56 (eskiden backlog=11495),
bütçesi dolan kaynak eligible'dan düşüyor — runaway guard'ları canlı. Bütçenin
kalan tek riski bant genişliği maliyeti.
45k gerekçesi: guard'ı doğuran 2026-07-09 olayı ~74k çağrı/~10 GB idi; 45k onun
%61'i (~6 GB/gün). DAILY_FAST_RESERVE ile backfill main lane 36k alır = eski
efektif 30k'ya göre +%20, kullanıcıya 9k rezerv kalır. Burst hızı DEĞİŞMİYOR
(pcat 90/dk) → per-IP ban baskısı aynı, sadece tempo günün daha uzun bölümünde
sürüyor. Rollback: bir saatte >2 pcat HTTP 402 → 30_000'e dön.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sorun (2026-08-01): wait=0/active=0 iken scan backlog=11495 hesaplıyordu — hepsi
günlük-bütçe ile ertesi gün 00:00'a park edilmiş pcat job'ı. backlog>maxBacklog
olduğu için Phase-2 günün çoğunda askıdaydı; 2143 kısmi araç ilerlemiyordu.
Adversarial analiz kritik uyarı verdi: delayed'ı gate'ten çıkarmak TEK BAŞINA
470k runaway'i tekrarlar (üretim ~288k job/gün vs bütçe 65k/gün; bütçe-defer'leri
skipAttempt ile attempts tüketmediğinden asla düşmez). Bu yüzden üretim kaynağında
kesiliyor + ikinci tavan ekleniyor:
- Gate ikiye ayrıldı: pressure (waiting+active+10dk içinde vadesi gelen delayed,
HER İKİ kuyruk) vs maxBacklog; total (tüm bekleyen) vs HARD_MAX_TOTAL_JOBS=50k.
zcount BullMQ delayed ZSET score'u (dueMs*4096+seq) ile; hata halinde fail-CLOSED.
- ÜRETİMİ KES: scan, günlük main-lane bütçesi dolmuş kaynağı eligible'dan düşürür.
- Admission control JOB cinsinden (EST_JOBS_PER_VEHICLE=400), araç cinsinden değil.
- Günlük bütçe lane-aware (fast lane'e %20 rezerv) + READ-then-INCR (sayaç artık
defer'lerle şişmiyor; 48531 vs 30000 gözlemi) + 45dk jitter (thundering herd).
- KÖK NEDEN: addJob artık boolean, queueCategoryJob sayı döndürüyor; progress.total
yalnız GERÇEKTEN kuyruğa giren job'ı sayıyor. Şişmiş total zinciri asla
"finished"a ulaştırmıyordu → araç fullyFetched işaretlenmiyor → scan sonsuza dek
yeniden seçiyordu (~25/gün platosu). processInit queued===0 ise finalizeVehicle.
- prefetch:scheduled:<id> ÇAKIŞMASI: backfill artık :backfill: namespace'i yazıyor;
eski 6h TTL kullanıcının fresh-decode fast-lane init'ini sessizce atlatıyordu.
TTL 6h→36h (bütçe-park edilmiş zincir ~24h yaşıyor).
- Phase-2 artık kalıcı vehicles.fullyFetched'i hedefliyor (efemeral marker değil).
- CATEGORY_CAP DB'den ölçülüyor (her processInit'te sıfırlanan sayaçtan değil).
537 test geçti. Bütçe artışı (PREFETCH_DAILY_PCAT) BU PR'da DEĞİL — ayrı adım.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-dakika rate cap'i (pcat 90/dk) burst'ü sınırlıyor ama günlük TOPLAM'ı değil —
bir kaynak saatlerce tavanda çalışıp proxy bütçesini boşaltabiliyor (2026-07-09:
backlog drain pcat'i ~74k çağrı / ~10 GB'a çıkardı). SOURCE_DAILY_MAX eklendi:
UTC-gün fixed-window Redis sayacı; kaynak günlük bütçeyi aşınca backfill job'ları
pencere dönene kadar ertelenir. Kullanıcı decode'ları etkilenmez (worker'dan
geçmiyor). Dakikalık gate'ten SONRA sayılır → rate-limitli retry'lar şişirmez.
Default (env-tunable): pcat 30k, emex 20k, pl24 15k/gün. Backlog drain'i
hızlandırmak için yükselt, bütçeyi daha çok korumak için düşür.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the per-decode "launch browser + login + open catalog" model with a
persistent warm Vinpin seat that eliminates cold-start, brand-switch cost and the
seat livelock. Additive + fail-safe: every warm operation degrades to the proven
cold per-decode path, so behaviour never regresses.
Warm daemon (VinpinDaemonService, worker-process singleton):
- Scheduler warms the seat at 08:00 and tears it down at 21:00 Europe/Istanbul
(proper TZ via Intl, no hardcoded offset); warms on worker start if inside hours;
reconcile() every 60s with a reentrancy guard.
- Keepalive nudges the RDS session (mouse.move) every ~75s while warm+idle; it
SKIPS during any active seat op (busy flag) and never takes a lock that blocks a
decode.
- decode(vin): inside hours ensure warm (warm-on-demand once) then delegate to the
driver; off-hours delegate straight to the cold path. Never throws.
Driver warm path (VinpinDriverService):
- warmUp() launches+logs in ONCE and opens Fiat ePER + Renault Rpartstore + Renault
Dialogys windows without closing each other, then OCR-binds each taskbar button
(order read via OCR, not hardcoded; raise self-heals by probing slots + OCR
verify). isWarm()/teardownWarm()/keepalivePing() added.
- warmDecode(): taskbar-raise the brand window (Fiat→ePER, Renault→Rpartstore w/
Dialogys fallback), run the EXISTING in-catalog decode on the warm window, OCR
the modal, parse, then Escape to ready the field for the next VIN. Runs under the
wall-clock budget; a hang still aborts.
- Health-recovery: a dropped seat (Disconnected/no-free-sessions OCR marker) →
teardown + re-warm ONCE, then retry the decode once.
- Refactored runDialogys into openDialogysSubmenu + runDialogysSearch so the warm
path searches without a window-closing reopen; cold Dialogys flow unchanged.
Safety nets retained: VINPIN_DECODE_BUDGET_MS + sessionPoisoned breaker (warm
budget abort → teardown+poison+null), single-seat serialization (runExclusive),
decode() never throws. Gated by VINPIN_ENABLED; VINPIN_WARM_DAEMON=false forces the
legacy cold path (kill-switch). Widened VINPIN_MODAL_REGION to ~900px.
Wiring: processor calls getVinpinDaemon().decode(); worker starts the daemon on
boot and stops it (releasing the seat) on shutdown. BullMQ concurrency 1 +
attempts:1 unchanged.
Tests: business-hours warm/teardown scheduling (injected clock/TZ), brand→taskbar
routing, taskbar OCR-order binding, keepalive-skips-during-decode, and
session-drop→re-warm→retry recovery. All existing vinpin/queue tests stay green.
NOTE: un-dev-testable (prod holds the single seat) — pixel/taskbar coords are
OCR-verified + marked TUNE and need live prod validation; warm falls back to cold
until confirmed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A bad/unresolvable VIN could leave a catalog window open on the shared Vinpin
seat; the next decode's ensureBrandGrid found it "resumed open" and looped
(close→brand-grid-not-confirmed→ePER-open→browser-disconnect→relaunch) forever.
BullMQ attempts:2 + 30s backoff auto-re-fed every failure straight back into the
stuck seat, starving all real decodes for minutes.
- queue: attempts:1, drop the 30s exponential backoff (extract VINPIN_DECODE_JOB_
OPTIONS). The driver already runs its own bounded internal retries; a BullMQ
retry on top is what compounded the livelock. Null decode still persists as
not_found; a hard infra throw stays user-retriable (failed).
- driver: hard per-decode wall-clock budget (VINPIN_DECODE_BUDGET_MS, 150s) via
withDeadline() racing each attempt; on abort → close() + poison seat + return
null (no retry into the stuck state).
- driver: sessionPoisoned flag — set at nav-loop exhaustion (ensureBrandGrid /
establishSession), budget abort, and failed post-decode cleanup; the NEXT
decode forces a full cold re-establish instead of reconnecting to the resumed
desktop. Cleared on any confirmed-clean grid/catalogue.
- driver: finally-cleanup after every decode — on failure/not-found return the
seat to a clean brand grid; if that can't reach the grid, poison + tear down.
- driver: basic VIN sanity (17 alphanumerics) before touching the seat.
Healthy Fiat/Renault happy paths are byte-identical when nothing is stuck.
Cannot be live-tested (seat is on prod) — needs prod validation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
VINPIN köprüsüne Fiat'ın yanına Renault/Dacia desteği eklendi.
- Marka yönlendirme: selectVinpinBrandFlow(vin) WMI'den akışı seçer
(Renault/Dacia → Rpartstore/Dialogys, geri kalan → mevcut Fiat ePER,
Fiat davranışı byte-identical). isVinpinBrandAllowed allowlist'i
fiat + renault + dacia'ya genişletildi.
- Renault akışı durum-toleranslı: koltuk son katalogu (Rpartstore)
sunucu-taraflı hatırlayıp açık resume ediyor; ensureRpartstore grid /
submenu / açık-pencere / yükleniyor durumlarını tanıyıp kendini
toparlıyor. Rpartstore ana akış, Dialogys best-effort fallback.
- Ortak ensureBrandGrid artık resume olmuş yabancı katalog penceresini
(Rpartstore/Dialogys/ePER) kapatıp grid'e dönüyor → Renault↔Fiat
ardışık decode'ları (tek koltuk) artık kırılmıyor.
- parseRenaultHeader/isUsableRenaultParse: OCR başlığından model+marka
(RENAULT/DACIA) + yıl; bilinen-token allowlist ile contiguous model
koşusu. Matcher + processor kararlaştırılan markaya göre PL24
catalog_vehicle eşliyor (Fiat varsayılan korunur).
- Testler: Renault parser + marka-yönlendirme/allowlist birim testleri.
Flag-gated (VINPIN_ENABLED). Canlı doğrulama: 5/5 Renault/Dacia VIN
(Kadjar, Clio II, Clio IV, Dacia Duster, Latitude) doğru decode; Fiat
Egea spot-check korunuyor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fast worker canlıydı ama pcat'in 90/dk penceresini ana worker yiyordu → fast
işler sürekli 18s source-rate defer (BMW init'i slot bulamadı). checkSourceRate
artık şerit-bazlı: fast = total/3 (pcat 30), main = kalan (60); toplam
değişmedi → upstream yükü aynı. Cooldown pause'u da doğru worker'ı duraklatıyor.
Spec fast-queue davranışına güncellendi (17/17).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kök neden: ana kuyruk saatlerce üretim>tüketim rejiminde (children patlaması);
lifo'da sonradan eklenen HER iş bir öncekini gömer → taze araç init'leri bile
açlığa düşer (BMW 320i 58dk 0 parça). Fix: catalog-prefetch-fast kuyruğu +
ikinci worker (conc=4, 120/dk) — decode-tetiği ve fast:true işler (children
zinciri dahil) buraya akar; per-source redis rate gate iki worker'ı da ortak
sınırlar (upstream korunur). vehicles.service fast kuyruğa bağlandı.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kullanıcının orijinal tasarımı: aynı (kaynak, marka)da kategori yapısı birebir
aynı — değişen yalnız parça kodları/şemalar. Önceki şablon sadece kova/alt-yaprak
BAŞLIKLARINI overlay ediyordu → taze araçta "Aydınlatma — 0 kategori". Yeni:
canonical_template_entries (migration 0031) — markanın paylaşılan kaynak-kategori
ADLARI (≥2 araç); backfill'de rebuild. overlayTemplate eksik adları disabled
placeholder olarak basar (unavailable=true, CategoryGrid zaten soluk+tıklanamaz
render ediyor) → decode anında her kova tam adlarıyla dolu; drill tamamlanınca
placeholder'lar gerçek satıra dönüşür.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Faz 2 — decode edilen yeni araçların kategorilerini materyalize tutar:
- Processor'a incremental mod: step-2 yalnız HENÜZ map satırı olmayan yeni
isimleri sınıflandırır (tüm ~113k değil). Materialize (step-5) differential +
inheritance NULL-only olduğundan, adı zaten map'li yeni kategori de id'sini
alır — hiçbir şey atlanmaz. is_manual korunur. reset varsa yok sayılır
(reset tam yeniden-eşleme demek).
- Gecelik cron: 05:00 İstanbul, { incremental: true }. Dev+prod'da güvenli
(harici yan-etki yok, kendi DB'sine yazar). Lexicon iyileştirmesi sonrası
tam yeniden-eşleme hâlâ ELLE { reset:true } global run.
- Dokümante edilen 'backfill trigger yok' boşluğunu kapatır; serving zaten
anlık lexicon fallback yapıyordu, bu cron materyalizasyonu güncel tutar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
canonical_structure_template(source, brand, bucket, sub, vehicle_count): drill
edilmiş araçlardan her (kaynak, marka) için görülen kova/alt-yaprak kümesi.
Backfill sonunda yeniden üretilir (global run). Serving overlayTemplate ile
aracın ağacına markanın bilinen ama bu araçta henüz drill edilmemiş kova/alt-
yapraklarını BOŞ ekler → VIN decode olur olmaz FULL kanonik yapı görünür;
parçalar backfill ile dolar. migration 0029. vehicle + catalog akışı.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /categories/canonical/taxonomy: kova→alt-yaprak + populasyon sayısı (B2C API
kontratı). getCanonicalTree (vehicle + catalog) artık subLeaves geçip her kovayı
alt-gruplara böler (materialize + on-the-fly sub fallback). jant-tire slug fix
(jant-lastik üst-kova ile çakışıyordu). Kategori sub-kapsama: Filtre %88, Fren/
Aydınlatma %74, Motor/Süsp/Elektrik %60-66. Eşleşmeyen → Genel/SP manuel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kendi kanonik yapımıza tam geçiş — B2C API için alt-kategori. migration 0026:
canonical_categories'e 143 alt-yaprak (parent_id=üst kova) + categories/
category_canonical_map'e canonical_subcategory_id. canonical-sublexicon.ts:
bucket başına satılabilir parça yaprakları + Türkçe varyant (kaliyer/hidroliği)
+ Bağlantı&Donanım catch-all. Backfill artık isim→kova→alt-yaprak deterministik
materialize ediyor (inheritance dahil). Eşleşmeyen = null → SP manuel. Testler +.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Opel model-öncelikli: "X 2004-2014" model düğümü parent'ından (MOTOR VE DEBRIYAJ)
Debriyaj miras alıyor, sonra jenerik çocukları (BOYALAR/MÜTEFERRİK/SIZDIRMAZ) de
ondan Debriyaj miras alıyordu → yanlış eşleme zinciri. Inheritance artık yıl-
aralıklı + ^[a-z]{1,3}\d{2} model düğümlerine miras VERMEZ (null kalır, cascade
kırılır). Kod/plaka yaprakları (X1318_/20-201A) bu desenlere uymaz → Renault/
Hyundai etkilenmez. Sadece Opel 58 + Nissan 1 düğüm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
emex'in en sık eşleşmeyen adları (100-250x, tüm araçlarda paylaşılan):
stabilizer/absorber→suspansiyon; esanjor/kondansator/ventilator/kompresor/
sogutucu/uflec→sogutma; hali/halilar→ic-doseme; kilit/yagmurluk→dis-kaporta;
stepne→jant-lastik. Jenerik (BOYALAR/İZOLATÖRLER/Montaj) bırakıldı. 25/25.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
koltugu (koltuk k→ğ eki)→ic-doseme, stabilizator→suspansiyon strong, takoz/
takozu→suspansiyon weak. Küçük markaların (Smart/Mini) kalan gerçek boşluklarını
kapatır. Kalan eşleşmeyenler artık junk/generic/veri-kalitesi (pratik maksimum).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tüm sub-90 markaların global eşleşmeyen adlarından net olanlar (çoğu İngilizce,
markalar arası): diagnostic/entertainment/bilgisayar/elektrigi→elektrik,
heater/ventilation→sogutma, braking→fren, sunroof/exterior/locking→dis-kaporta,
restraint/occupant/konfor→ic-doseme, mekanik→motor, yapistirici→yaglar. 24/24.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BMW (~%81): elektronik terimleri → elektrik (audio/navigasyon/elektronik/
iletisim/multimedya/cruise), "ışık"→aydınlatma; servis/doküman sayfaları →
junk (teknik doküman/servis ve onarım/value parts). Ford (~%74): trans
varyantları speed/spd→sanziman, motor varyantları diesel/dohc→motor, gövde→
panjur. 23/23 test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
^[cd]\d{3,} fazla genişti — "D765300(22H), LS 7 F, MB SERVO DİREKSİYON" gibi
parça bilgisi taşıyan düğümleri yapısal yapıp Direksiyon eşleşmesini bozuyordu
(Mercedes ham kapsama %79→%49). $ ile sabitle → sadece çıplak kod (C442,
C105011). Açıklamalı düğümler keyword'le eşleşmeye devam eder. 19/19 test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Opel gerçek parça-kat kapsaması zaten %85; %38 model-kodu köklerin (yapısal)
denominatörde olmasından. Lexicon: sanzuman/disli→sanziman, suspensiyon/dingil→
suspansiyon (Opel yazımları). Serving: groupCategoriesByCanonical yapısal/çöp
düğümleri "Diğer"e sokmadan atlar (Opel model kökleri Birleşik'te görünmez).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kendi adı eşleşmeyen kategori (Renault P5 X1318_ plaka kodları, figür
illustration'ları) parent'ının kanonik kovasını miras alır ("Alternatör"→
Elektrik). Materyalizasyondan sonra birkaç seviye kaskad. Re-scrape gerektirmez.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pl24 Asyalı markaları (Hyundai/Kia/Nissan/Toyota) ve çevrilmemiş emex/pcat
yaprakları İngilizce kategori adı taşıyor (ENGINE/BODY/BRAKE/TRANSMISSION…) →
%0 kapsama. Sistem kelimeleri strong (engine→motor, brake→fren, transmission→
sanziman, electrical→elektrik…), konum kelimeleri weak (body/chassis/floor/axle)
ki "BODY ELECTRICAL"→elektrik olsun. 17/17 test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
migration 0025: 2 yeni kanonik kova (jant-lastik #19, aksesuar #20).
lexicon: jant/lastik/tekerlek/göbek→jant-lastik, aksesuar→aksesuar (catch-all,
en düşük öncelik). Kullanıcı kararları: Hava Sistemi→Soğutma (hava weak),
Bakım parçaları→Filtreler (bakim weak). "hava yastığı"→direksiyon için yastigi
strong'a alındı. 16/16 test. Yorumlar 18→20 kova güncellendi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kök tam da kanonik başlıkla anılınca ("Süspansiyon, şasi", "Elektrik",
"Aydınlatma") lexicon'da o kelime yoktu → "Diğer"e düşüyordu. suspansiyon/
elektrik/aydinlatma kendi kovalarına strong olarak eklendi. Serving on-the-fly
fallback aynı lexicon'u kullandığı için taze araçlarda backfill'siz düzelir.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>