Commit Graph

37 Commits

Author SHA1 Message Date
142ec3a150 feat(pl24): Faz 3 — backfill anahtarı + Mitsubishi parça-listesi düzeltmesi
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-09-20 10:51:42 +03:00
ae8a8046bf refactor(prefetch): kapı sırasını en ucuzdan pahalıya diz
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-09-20 08:31:55 +03:00
7644d91407 fix(prefetch): PL24 bütçe/iş-saati kilitlenmesini kır
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-09-20 08:29:55 +03:00
semih
dde6f6a15c feat(pl24): PSA + Volvo'yu P5'e taşı, eksik katalogları ekle, ağaç sınıflandırmasını birleştir
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-09-17 14:00:30 +03:00
semih
02ec3856d5 perf(pl24): reaktif drill derinliğini sınırla, backfill'i jitter'lı aralıkla yavaşlat
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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
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
2df73e6a48 perf(prefetch): pcat günlük bütçe 30k→45k (main lane 36k, fast lane 9k rezerv)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-08-01 14:32:42 +03:00
3597b03c4f fix(prefetch): Phase-2 kilidini aç (pressure vs total gate) + tamamlanma muhasebesi
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-08-01 14:21:44 +03:00
6610bdf720 feat(prefetch): kalıcı vehicles.fully_fetched kolonu (güvenilir tamlık ölçümü)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Redis prefetch:complete:* 21g TTL'li → "% tam çekilmiş"i güvenilir ölçemiyorduk.
Migration 0034: vehicles'a fully_fetched (bool, default false) + fully_fetched_at
(ts) + index. incrementCompleted chain parça ile bitince kalıcı flag'i set eder
(Redis marker'a EK — operasyonel skip mantığı değişmedi). fully_fetched_at her
re-drill tamamlanışında güncellenir (son-doğrulanma). Idempotent SQL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 10:40:40 +03:00
ed8a6ce6e9 perf(prefetch): backfill'e per-source günlük bütçe (storm guard)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-07-16 13:32:34 +03:00
8dd2d223e9 fix(prefetch): şerit-bazlı kaynak bütçesi — fast lane açlığını bitir
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-07-09 10:01:47 +03:00
c8803ada35 feat(prefetch): ayrı FAST kuyruk — taze araçlar backlog'la yarışmasın
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-07-09 09:13:26 +03:00
7956fc9e9b feat: kategori sağlık paketi — dedup+index, prefetch throughput, v1 taxonomy, Birleşik default
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
(1) migration 0030: 57.780 duplike kategori merge (prod'da uygulandı, idempotent)
+ categories unique index NULLS NOT DISTINCT → decode retry duplike üretimi
KALICI olarak durdu (onConflictDoNothing artık NULL sınıflarında da çalışıyor).
(2) prefetch throughput: backlog %89 pcat + net-büyüyordu (82k→94k). pcat
20→90/dk (DataImpulse rotating gateway), pace 5s→1.5s, conc 3→8, global 180.
pl24/emex dokunulmadı (ban riski).
(3) GET /v1/taxonomy: araçtan bağımsız kanonik yapı kontratı (B2C navigasyon).
(4) Birleşik tekrar varsayılan görünüm (edge-case'ler çözüldü).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 06:26:54 +03:00
3096d35113 perf(prefetch): pcat rate 8→20/dk + pace 15s→5s (ramp adım 1)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
pcat kuyruğun %67'si (~26.5k) ve en yavaş drenajı. Proxy = DataImpulse rotating
gateway (74.81.81.81:10000-10999, ~1000 port-rotated çıkış IP — 2026-06-25
doğrulandı: 5 port→5 farklı Alman residential IP), per-IP ban yükü dağılıyor →
8/dk shared cap gereksiz muhafazakâr. Adım 1: 20/dk + pace 5s. (Coolify env yolu
duplike-girdi yüzünden container'a inmedi; kod-default güvenilir yol.) Env override
hâlâ mümkün (PREFETCH_RATE_PCAT/_PCAT_DELAY_MS). 429/403/tunnel izlenecek.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:56:04 +03:00
b3dd119c99 fix(prefetch/faz6): generic-model skip'i KALDIR — PSA'yı yanlış zehirliyordu
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Tespit: generic-model (model==brand) sadece Opel'i değil PSA'yı (Peugeot/Citroën)
da kapsıyor; ama PSA generic-model PATLAMIYOR (~200 kat) ve PARÇA ALIYOR (512/290).
Skip onları yanlışlıkla poison'lar + prefetch'i keserdi. Tek doğru guard =
CATEGORY_CAP (marka-bağımsız; yalnız gerçek patlamada >3000 tetiklenir → Opel'i
durdurur, küçük PSA'yı korur). isGenericModel + processInit gate kaldırıldı,
spec sadeleşti (cap + scan-skip kaldı).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 06:35:59 +03:00
95061dfa53 feat(prefetch/faz6): anti-poison — generic-model skip + per-araç kategori tavanı
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
5 Opel aracı (model="Opel" decode çözememiş) generic ROOT kataloğa düşüp
419k/102k/21k kategori + 0 PARÇA üretti → 97k kuyruğu zehirledi. İki guard:
- isGenericModel (model==brand veya boş) → processInit skip + markPoison
- CATEGORY_CAP (3000, env PREFETCH_CATEGORY_CAP): processChildren progress.total
  bu tavanı aşınca drill'i durdurur + markPoison
markPoison → prefetch:poison:<id> (30g TTL) + scheduled/progress temizler;
processInit & tryPick poison'ı atlar. +3 spec (generic skip / cap / scan skip).
Full api suite 321 pass. Çöp kategori temizliği ayrıca yapılacak.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 06:33:04 +03:00
5200d5a61d feat(prefetch/faz3): per-source paralel rate limit (PL24/EMEX/pcat bağımsız)
Tek global 20/dk limiter tüm kaynakları paylaştırıyordu → yavaş pcat PL24/emex'i
açlığa düşürüyordu. Artık her kaynak Redis fixed-window ile BAĞIMSIZ kısılıyor
(SOURCE_RATE_MAX: pl24=20, emex=20, pcat=8; env PREFETCH_RATE_PL24/_EMEX/_PCAT).
Global WORKER_RATE_MAX güvenlik tavanı (20→60). RateLimitError yeni cause
"source-rate" → process() per-job defer eder (emex/pcat PL24'ü beklemez); cooldown
hâlâ tüm worker'ı duraklatır. PL24 default'u 20'de KALDI (ban riski; yüksek-rate
Faz 4 rotating proxy ile). +spec: under/over ceiling + unknown-source unlimited.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 11:25:41 +03:00
7c715af0c5 feat(prefetch/faz2): tamamlanan araçları işaretle + Phase-2 rescan'de atla
Chain parça ile tamamlanınca prefetch:complete:<id> set edilir (TTL 21g,
env PREFETCH_COMPLETE_TTL_DAYS). tryPick bu işaretliyi atlar → Phase-2 bitmiş
ağaçları her dalga yeniden gezmez (eski "sonsuz churn" durur). Başarısız job
isFinished'e ulaştırmaz → kısmi ağaç asla complete işaretlenmez, gap-fill korunur.
TTL periyodik re-validasyon sağlar. +spec: complete-marker'lı araç Phase-2'de atlanır.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 11:22:02 +03:00
61436db48f feat(prefetch/faz1): tam-derinlik (MAX_DEPTH 2→12) + no-op children job üretimini durdur
Hedef = decode edilmiş tüm araçların TAM kategori+parça ağacı DB'de. Cap artık
hedef değil güvenlik tavanı (gerçek ağaçlar ~5-7 derin; doğal yaprak tespiti
durduruyor). queueCategoryJob non-leaf dalı `depth < MAX_DEPTH` ile kapılandı:
processChildren'ın early-return'ünü aynalar → kuyruğun ~%85'ini oluşturan
boş depth-job'ları (rate-limit slotu harcayan no-op) hiç üretilmez.
+spec: depth>=cap'te children enqueue YOK, cap altında VAR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 11:17:36 +03:00
3cadf80286 Merge pull request 'Backfill BFS derinligini 5->2'ye indir (sig pre-warm)' (#143) from dev into main 2026-06-21 16:59:54 +03:00
50cef50289 feat(prefetch): backfill BFS derinliğini 5→2'ye indir (sığ pre-warm + tembel derin)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Exhaustive depth-5 pre-warm ~460k'lık sınırsız BFS fan-out üretiyordu, hiç
drenaj olmuyor ve faydalı sığ + reactive işi açlığa düşürüyordu. Üst ~2
seviyeyi her decode edilmiş araç için pre-warm etmek sınırlı iş (günlerde
biter), boş katalog sayfasını öldürür, ilk tıklamaları anında yapar. Derin
kategoriler on-view (getCategoryTree) tembel çekilip kalıcı cache'lenir.
Env-tunable: PREFETCH_MAX_DEPTH. MAX_DEPTH guard mevcut derin job'ları no-op'a
çevirir (PL24'e yük yok).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:05:31 +03:00
7d10107f4b fix(backfill): run hourly scan on an in-process timer (cron marker gets buried)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
The BullMQ cron scan never fired on prod (ic frozen at 28 for days, 0
scans completed) even after the lifo change. Root cause, confirmed live:
a cron scheduler enqueues a DELAYED marker, and BullMQ promotes delayed
jobs to the wait-list HEAD via LPUSH — `promoteDelayedJobs` ignores lifo —
while the worker pops from the tail. Behind the ~444k deep-drill backlog
the scan marker sat at wait position 1 (head) with ~444k jobs ahead of it
→ unreachable for weeks. So scheduler-level lifo cannot work; the marker
must not go through the queue at all.

Run the scan on an in-process hourly timer in PrefetchWorkerService
instead (kickoff 60s after boot, then hourly), prod-host gated and
single-fired via a Redis NX lock. The scan's Phase-1 still enqueues
NON-delayed `lifo` init jobs, which DO jump the wait list (RPUSH tail →
popped next), so newly-decoded / zero-parts vehicles get onboarded ahead
of the backlog while it drains. jobs.module now removes the old cron
scheduler so stale markers stop being produced; the backfill-scan job
handler stays so any already-queued legacy marker runs harmlessly.

Tested: spec covers the NX lock (run when acquired / skip when held) plus
the existing lifo + Phase-1/Phase-2 gating; full api suite 272 passed,
`pnpm build` + biome green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:14:09 +03:00
0de6bd3faa perf(backfill): fast-lane Phase-1 via lifo + run scan despite deep backlog
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
The hourly catalog-backfill scan had gone effectively dead on prod: its
job sat behind a ~430k-deep wait list and, even when it ran, self-skipped
because the queue backlog (431k) was far over the maxBacklog ceiling
(1000). Net result: newly-decoded / zero-parts vehicles were never
onboarded — they starved behind the deep-drill backlog (ic stuck at 28
for days, 0 backfill-scan jobs ever completed).

Root constraint (verified against bullmq 5.68 lua): moveToActive drains
the `wait` list (RPOPLPUSH from the tail) BEFORE the `prioritized` ZSET,
so a `priority` job is starved behind an already-deep wait queue — the
opposite of what's wanted. The lever that works is `lifo`: it RPUSHes to
the tail, where the very next RPOPLPUSH picks it, ahead of the FIFO
backlog. addJobFromScheduler honours lifo too, so the scan job itself can
jump the queue.

Changes:
- Thread a `fast` flag through the init→children→parts chain; fast jobs
  are enqueued with `lifo:true` so the whole chain jumps the backlog.
- Backfill scan: Phase-1 (zero-parts vehicles) now runs every wave in the
  fast lane even when the backlog is over the ceiling; only Phase-2 (the
  rolling rescan that piles on) is suspended while the queue is deep.
- Register the hourly scan job with `lifo:true` so it fires on the next
  tick instead of being buried for days.

Tested: new prefetch-worker.service.spec (lifo wiring + Phase-1/Phase-2
gating), full api suite green (248 passed), typecheck + biome clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:18:37 +03:00
79bbd1f1b3 fix(prefetch): recurse parts-catalogs trees to full depth
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
isLeafLinkPath flagged EVERY pcat node as a leaf (any pcat: linkPath), so the
backfill worker fetched a mid-group folder's "parts" — a 1-level drill that
only revealed its sub-groups — and never recursed. Deep pcat trees (whose top
level is ~100% folders) were left one level shallow: no parts seeded, so the
new tree part-counts stayed 0 and the cross-tree catalog search (DB-only) found
nothing on a freshly decoded vehicle. Exactly why serkan's Ford Mondeo looked
like an empty catalog.

Use the captured hasSubgroups flag: a pcat node is a parts leaf only when it is
NOT a known parent group. Folders now queue a children job and the existing
recursion (queueCategoryJob → processChildren → getChildren) drills to full
depth (MAX_DEPTH=5), seeding parts at every leaf. The rolling backfill rescan
warms existing shallow vehicles wave by wave; new decodes warm deep via the
reactive schedulePrefetch. Rate limiter + PCAT_PACE + backlog guard keep it
bounded (all env-tunable to ramp).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 17:12:33 +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
26efef22d4 feat(emex): seed catalogs from hierarchical Vehicle.aspx tree
emex catalogs were seeded from the flat QuickGroups taxonomy (~351
unsearchable leaves on one page). Switch the seeder to the Vehicle.aspx
OEM-microfiche tree (top groups -> sub groups -> Unit.aspx leaves), which
also reaches units that have no QuickGroups gid (e.g. the torque
converter, OEM 5205547 on the Fiesta).

- emex-tree.parser.ts: parseVehicleTree / parseUnitLeaves — pure,
  GET-based (per-href ssd, no browser/postback). Unit-tested vs fixtures.
- emex.service: fetchVehicleTree + drillVehicleNode; all 3 decode methods
  now populate categoryTree; Unit.aspx leaves fetch parts directly.
- categories.service: revive the dormant tree-insert branch with group
  markers (linkWid="emex-group" / hasSubgroups), add an emex getChildren
  lazy-drill branch + emex-group parent guard, tighten leaf detection,
  disambiguate colliding sub-group names.
- prefetch: emex Vehicle.aspx = parent to drill; Unit/QuickDetails = leaf.

No DDL and no frontend changes — the generic tree UI renders it like
pl24/pcat. Existing flat emex categories re-seed lazily once deleted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 15:28:26 +03:00
bb60a95ec3 perf(backfill): Phase-1 residue exclusion — stop re-picking dead vehicles (Tier 3)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Phase-1 of the backfill scan selects all zero-parts decoded vehicles every wave.
Genuine residue (VINs with no catalog data anywhere — model-indexed HKN,
EMEX-uncovered, etc.) stays zero-parts forever, so it filled the batch every
wave, re-attempting dead vehicles and starving the Phase-2 rolling rescan (its
cursor was stuck for a week).

Track a per-vehicle no-result counter (prefetch:noresult:<id>) incremented when a
prefetch attempt finishes with the vehicle still at zero parts (0 categories in
init, or 0 parts after the whole chain). tryPick skips vehicles past
PREFETCH_NORESULT_MAX (default 2) attempts; the counter has a TTL
(PREFETCH_NORESULT_TTL_DAYS, default 7) so a later catalog fix re-fills them.
Frees capacity for fillable vehicles and lets Phase-2 run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 19:15:57 +03:00
48aea65b5c perf(backfill): raise prefetch worker throughput, env-tunable (Tier 2)
After Tier 1 removed the self-throttle, the 5 jobs/min limiter + concurrency 1
became the bottleneck. Make throughput env-tunable so prod can ramp while
watching the fail rate:
- concurrency 1 -> 3 (PREFETCH_CONCURRENCY): parallelises emex/pl24 so a slow
  parts-catalogs job no longer head-of-line-blocks the queue.
- rate ceiling 5 -> 20 jobs/min (PREFETCH_RATE_MAX).
- parts-catalogs: drop the pathological cumulative index*20s enqueue delay (the
  Nth leaf of a vehicle waited N*20s); keep one per-job pace (PCAT_PACE_MS,
  default 15s, 0 to disable).
- PL24 09-18 scrape window now env-tunable (PREFETCH_PL24_START / _END).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 18:43:10 +03:00
1e3953417e perf(backfill): stop the prefetch worker from throttling itself (Tier 1)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
The worker's own upstream fetches called touchActivity(), setting the
prefetch:activity:<source> cooldown key (TTL 300s) that checkCooldown then
honoured — so after each fetch the worker paused itself for up to ~5 minutes
(re-checking every 60s, ~5 empty cycles per key). At ~1 fetch / 5 min the
3387-job backlog needed ~6 days to drain.

- Wrap each worker job in an AsyncLocalStorage backfill context; touchActivity
  skips the cooldown key when invoked from the worker, so the cooldown reflects
  only real user requests (worker yields to users, never to itself).
- Cooldown TTL 300s -> 90s (a request 5 min ago isn't "active").
- checkCooldown pauses for the key's actual remaining TTL (one wait) instead of
  a fixed 60s re-check loop.

No extra upstream load — only removes the worker's self-imposed idle time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 18:23:54 +03:00
316a5e014c feat(backfill): widen scrape window for pcat + double batch size
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Backfill was sweeping only 20 vehicles/hour and skipping pcat outside
09:00–19:00 Istanbul, leaving the catalog backlog (115 pl24 + 63 pcat +
27 emex zero-parts vehicles) crawling forward at ~528 parts/day and the
biggest user vehicle (Toyota Corolla 2026) unchanged across 24h.

Two related changes — both unblocked by the Redis-persisted warm JWT pool
(aa4d055 + dcf7e06) which keeps pcat captures alive 24/7 instead of
needing the 09:00–19:00 office-hours assumption:

* isWithinTimeWindow: parts-catalogs no longer gated — the warm pool +
  Redis hydration cover the cold-start case the old office-hours rule
  was working around. PL24 keeps its 09:00–18:00 window because the
  upstream rate-limit is still tighter outside it.
* BACKFILL_BATCH_SIZE 20 → 40 — twice as many vehicles per wave, still
  protected by MAX_BACKLOG=1000 self-throttle and per-source cooldown
  (prefetch:activity:<source>) so live-user traffic still gets priority.

Combined effect: pcat goes from ~10h/day to 24h/day, batch doubles —
roughly 3× backfill throughput. Worst-case proxy spend tracked by the
DataImpulse daily cap; if a wave saturates upstream the rate-limit
handler (c7e59b9) defers without burning attempts.
2026-06-02 02:00:16 +03:00
1d9f59ba86 fix(api): pause whole worker on cooldown, only per-job defer for off-hours
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Per-job moveToDelayed for cooldown livelocked the worker: the activity key is
refreshed by every user request, so 60s later the deferred job comes back, key
is still set, defers again. Last 3h on prod logged ~1800 deferrals against 12
real inits and one completion every ~6 min.

Tag RateLimitError with a `cause`. checkCooldown throws "cooldown"; the worker
now calls `this.worker.rateLimit(delayMs)` and throws Worker.RateLimitError() —
the whole queue waits once instead of cycling every job. checkTimeWindow throws
"time-window"; that branch keeps the existing job.moveToDelayed (per-job) so
EMEX (no scrape window) keeps flowing while PL24/pcat jobs sleep till 09:00.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 12:17:05 +03:00
c7e59b90e0 fix(api): defer rate-limited prefetch jobs instead of failing them
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
The worker threw a custom RateLimitError for active-user cooldown and off-hours
windows, but BullMQ treated that as a normal failure: it retried with the
queue's exponential backoff (ignoring retryAfterMs) and dropped the job to
"failed" after attempts:3. So during busy hours sub-jobs were discarded en
masse and the requested defer time (e.g. "wait until 09:00") never applied.

Convert RateLimitError into job.moveToDelayed(now + retryAfterMs, token) +
DelayedError — BullMQ's contract for "not done, not failed, retry later". This
does not consume an attempt and honours the exact delay, so cooldown jobs wait
~60-120s and off-hours jobs wait until the window reopens, then resume. Genuine
errors still fail/retry normally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 18:02:36 +03:00
acd5691f6b fix(api): prefetch sub-job IDs must not contain ':'
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
BullMQ 5.68 rejects custom job IDs containing ':' (its key separator) with
"Custom Id cannot contain :". prefetch-init's addJob built sub-job IDs as
prefetch:<vehicleId>:<categoryId>:<action>, so every attempt to queue a
children/parts job threw and the whole init failed. This was latent in the
reactive path (failures just logged) and surfaced once the hourly backfill
started driving inits at volume. Use '-' as the separator; the IDs only need
to be deterministic for dedup, not parseable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:18:04 +03:00
5f0bdb467d fix(api): gate catalog backfill on prod host, not NODE_ENV
dev.sase.tr (staging) and sase.tr (prod) BOTH run NODE_ENV=production with
SEPARATE databases, so the previous NODE_ENV check would have let the hourly
backfill sweep run against the dev DB too. Gate on the canonical prod host
instead (COOLIFY_FQDN / BETTER_AUTH_URL), with an explicit
CATALOG_BACKFILL_ENABLED override. Default off for any unknown host.

New isCatalogBackfillEnabled() helper used by both the cron registration and
processBackfillScan; dev redeploy now removes the stale scheduler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:29:07 +03:00
d0ee4df57f feat(api): hourly catalog backfill for decoded-but-unfetched vehicles
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
prefetch was reactive-only (on decode) and processInit read top-level
categories straight from DB, so a vehicle decoded but never viewed got
no catalog. Add a production-only sweep so no decoded vehicle is left
without catalog data.

- processInit self-seeds top categories via getCategoryTree when DB has
  none (fetches+inserts top groups from PL24/PSA/EMEX), closing the
  never-viewed gap for both reactive and backfill paths
- new backfill-scan job + hourly cron: phase 1 queues decoded vehicles
  with zero parts, phase 2 rolling createdAt-cursor rescan of all decoded
  vehicles (prefetch-init is idempotent → gap-fills partial ones)
- guardrails: skip wave if queue backlog > 1000, per-source cooldown,
  business-hours window (isWithinTimeWindow), batch <=20, in-flight guard
- PRODUCTION ONLY: gated on NODE_ENV both at cron registration and in
  processBackfillScan; dev has a separate DB and must not scrape

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:04:32 +03:00
Fusion
f4fea1e429 feat(FN-094): add comment line for deployment verification
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
- Added a comment line to main.ts for deployment verification purposes
2026-05-11 02:07:03 +00:00
Sase Dev
f1a27810db feat: add parts catalogs integration, catalog prefetch worker, and vehicle select modal
Integrate external parts catalogs API with auth service, add BullMQ-based
catalog prefetch worker for background data caching, expand vehicles service
with shared vehicle support, and add vehicle select modal to frontend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 18:14:36 +00:00