From 79bbd1f1b3569639027f407b24b6ec46be146e81 Mon Sep 17 00:00:00 2001 From: Semih Yesilyurt Date: Tue, 9 Jun 2026 17:12:33 +0300 Subject: [PATCH] fix(prefetch): recurse parts-catalogs trees to full depth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- apps/api/src/jobs/prefetch-worker.service.ts | 29 ++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/api/src/jobs/prefetch-worker.service.ts b/apps/api/src/jobs/prefetch-worker.service.ts index dbaf17d..d66fa52 100644 --- a/apps/api/src/jobs/prefetch-worker.service.ts +++ b/apps/api/src/jobs/prefetch-worker.service.ts @@ -247,7 +247,7 @@ export class PrefetchWorkerService implements OnModuleInit, OnModuleDestroy { await this.queueCategoryJob(child, vehicleId, source, 1); queued++; } - } else if (this.isLeafLinkPath(cat.linkPath, cat.source)) { + } else if (this.isLeafLinkPath(cat.linkPath, cat.source, cat.hasSubgroups)) { // Leaf — check if parts already fetched const [partCheck] = await this.db .select({ id: parts.id }) @@ -488,14 +488,20 @@ export class PrefetchWorkerService implements OnModuleInit, OnModuleDestroy { // ==================== Helpers ==================== private async queueCategoryJob( - cat: { id: string; linkPath: string | null; source: string; unavailable: boolean }, + cat: { + id: string; + linkPath: string | null; + source: string; + unavailable: boolean; + hasSubgroups?: boolean | null; + }, vehicleId: string, source: string, depth: number, ): Promise { if (cat.unavailable) return; - if (this.isLeafLinkPath(cat.linkPath, cat.source)) { + if (this.isLeafLinkPath(cat.linkPath, cat.source, cat.hasSubgroups)) { // Leaf — check if already has parts const [partCheck] = await this.db .select({ id: parts.id }) @@ -543,12 +549,25 @@ export class PrefetchWorkerService implements OnModuleInit, OnModuleDestroy { } } - private isLeafLinkPath(linkPath: string | null, source: string): boolean { + private isLeafLinkPath( + linkPath: string | null, + source: string, + hasSubgroups?: boolean | null, + ): boolean { if (!linkPath) return false; // EMEX: Vehicle.aspx group nodes are parents to drill; Unit.aspx (hierarchical // tree) and QuickDetails.aspx (legacy flat) leaves carry parts. if (source === "emex") return !linkPath.includes("Vehicle.aspx"); - if (source === "parts-catalogs") return linkPath.startsWith("pcat:"); // pcat: prefix = leaf + // parts-catalogs: EVERY node has a pcat: linkPath, so the old "pcat: prefix = + // leaf" rule mis-flagged every mid-group folder as a leaf — the worker fetched + // its "parts" (a 1-level drill that just revealed sub-groups) and never + // recursed, leaving deep pcat trees a single level shallow (no parts → empty + // part counts, dead cross-tree search). Use the captured hasSubgroups flag + // instead: a node is a parts leaf only when it is NOT a known parent group, so + // folders queue a children job and the recursion drills to full depth. Unknown + // flag (null, rare pre-migration rows) → treated as leaf, preserving the old + // 1-level behaviour for those. + if (source === "parts-catalogs") return hasSubgroups !== true; // PL24 leaf indicators return ( linkPath.includes("/bom/") ||