diff --git a/apps/api/src/jobs/processors/canonical-backfill.processor.ts b/apps/api/src/jobs/processors/canonical-backfill.processor.ts index d71e89e..fd19bd6 100644 --- a/apps/api/src/jobs/processors/canonical-backfill.processor.ts +++ b/apps/api/src/jobs/processors/canonical-backfill.processor.ts @@ -31,6 +31,7 @@ export interface CanonicalBackfillStats { mapped: number; ambiguous: number; categoriesUpdated: number; + inherited: number; mapRowsPruned: number; dryRun: boolean; } @@ -114,6 +115,7 @@ export async function processCanonicalBackfill( mapped: 0, ambiguous: 0, categoriesUpdated: 0, + inherited: 0, mapRowsPruned: 0, dryRun, }; @@ -206,8 +208,31 @@ export async function processCanonicalBackfill( `); stats.categoriesUpdated = updated[0]?.n ?? 0; + // 5b. parent-inheritance: a category whose own name didn't map inherits its + // parent's bucket. Handles code/plate-named illustration leaves under a + // descriptive parent (Renault P5 "Alternatör" → X1318_… ; any figure leaf). + // Cascade a few levels (deepest OEM trees are ~5). + for (let depth = 0; depth < 5; depth++) { + const inh = await db.execute<{ n: number }>(sql` + WITH upd AS ( + UPDATE categories c + SET canonical_category_id = p.canonical_category_id + FROM categories p + WHERE c.parent_id = p.id + AND c.canonical_category_id IS NULL + AND p.canonical_category_id IS NOT NULL + ${sourceCond} ${brandExists} + RETURNING 1 + ) + SELECT count(*)::int AS n FROM upd + `); + const n = inh[0]?.n ?? 0; + stats.inherited += n; + if (n === 0) break; + } + console.log( - `[canonical-backfill] done — ${rows.length} map rows upserted (${stats.mapRowsPruned} pruned), ${stats.categoriesUpdated} categories materialized`, + `[canonical-backfill] done — ${rows.length} map rows upserted (${stats.mapRowsPruned} pruned), ${stats.categoriesUpdated} materialized, ${stats.inherited} inherited from parent`, ); return stats; }