[object Object]

← back to Trending Dw

gapgen: data-driven style-keyed briefs from live gap items (kill ID-drift landmine) + --dry-run

bedb55d1bfecb4a89dc00737f5a8e89837cb3462 · 2026-07-07 09:29:45 -0700 · Steve

Root cause of the 2026-07-07 misfire: briefs were hardcoded to TR-### ids, but
the feed rebuild reassigns ids, so all 16 briefs mis-targeted trends that had
moved. Now briefs are built from the live item's style/color/hex at run time and
non-gap ids are refused, so brief↔trend drift can't happen. --dry-run validates
targeting for $0 before any Replicate spend.

Files touched

Diff

commit bedb55d1bfecb4a89dc00737f5a8e89837cb3462
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jul 7 09:29:45 2026 -0700

    gapgen: data-driven style-keyed briefs from live gap items (kill ID-drift landmine) + --dry-run
    
    Root cause of the 2026-07-07 misfire: briefs were hardcoded to TR-### ids, but
    the feed rebuild reassigns ids, so all 16 briefs mis-targeted trends that had
    moved. Now briefs are built from the live item's style/color/hex at run time and
    non-gap ids are refused, so brief↔trend drift can't happen. --dry-run validates
    targeting for $0 before any Replicate spend.
---
 scripts/gapgen.js | 161 ++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 103 insertions(+), 58 deletions(-)

diff --git a/scripts/gapgen.js b/scripts/gapgen.js
index 2888943..8f3b8ab 100644
--- a/scripts/gapgen.js
+++ b/scripts/gapgen.js
@@ -9,7 +9,16 @@
  * The concept + settlement vision gates are performed by the calling Claude
  * session (in-session vision) because the Gemini key is billing-capped.
  *
+ * DATA-DRIVEN (fixed 2026-07-07): briefs are built from the LIVE gap item at
+ * each requested id, keyed by the item's *style* (stable) — NOT hardcoded to
+ * TR-### ids. The feed-rebuild step reassigns ids on every refresh, so the old
+ * hardcoded per-id BRIEFS silently mis-targeted paid generation (all 16 briefs
+ * pointed at trends that no longer lived at those ids). Resolving against
+ * data/bestsellers.json at run time makes that drift impossible: an id that is
+ * no longer a gap (has an image) is refused, not generated for.
+ *
  * Usage: node gapgen.js <id> [<id> ...] [--rolls N]
+ *   ids must be current GAP items in data/bestsellers.json (needsGeneration, no image).
  */
 const fs = require('fs');
 const os = require('os');
@@ -45,61 +54,67 @@ function P(motif, texture) {
   return `${motif}. ${SEAM_BASE} ${LUX_BASE.replace('TEXTURE', texture)}`;
 }
 
-// TONE-ON-TONE low-contrast palettes: matches the luxury house aesthetic
-// (median 2 hues, low saturation) AND lowers the local-contrast floor so the
-// seam metric (adjacent-strip ΔE) can reach PASS. mode drives make_tileable.
-const BRIEFS = {
-  'TR-007': { style:'Animal-Skin Print', color:'Blush', hex:'#e7c3c0', cap:5, foliage:false, mode:'motif',
-    prompt:P('Elegant leopard/ocelot animal-SKIN spot texture — soft painterly rosette markings densely scattered allover in a cohesive muted palette of blush and warm taupe with cocoa accents, NOT whole animals, just the pelt marking pattern, gentle but clearly visible contrast, dominant blush #e7c3c0','linen'),
-    concept:'abstract leopard/ocelot animal-skin spot/rosette pelt-marking texture (not whole animals)' },
-  'TR-008': { style:'Animal-Skin Print', color:'Forest Green', hex:'#2f5d3a', cap:5, foliage:false, mode:'motif',
-    prompt:P('Elegant leopard/ocelot animal-SKIN spot texture — soft painterly rosette markings densely scattered allover in a cohesive muted palette of forest green and pine with sage accents, NOT whole animals, just the pelt marking pattern, gentle but clearly visible contrast, dominant forest green #2f5d3a','grasscloth'),
-    concept:'abstract leopard/ocelot animal-skin spot/rosette pelt-marking texture (not whole animals)' },
-  'TR-009': { style:'Animal-Skin Print', color:'Claret', hex:'#76322f', cap:5, foliage:false, mode:'texture',
-    prompt:P('Refined python/snake SCALE skin texture — overlapping diamond serpent scales in a dense allover repeat, cohesive muted claret and oxblood tones with tan highlights, gentle but visible contrast, NOT a whole snake, just the scale pattern, dominant claret #76322f','silk'),
-    concept:'abstract python/snake scale skin texture (overlapping scales, not a whole snake)' },
-  'TR-010': { style:'Animal-Skin Print', color:'Mustard', hex:'#c99a3a', cap:5, foliage:false, mode:'motif',
-    prompt:P('Elegant leopard animal-SKIN rosette texture — soft painterly leopard rosettes and dots densely scattered allover in a cohesive muted palette of mustard and ochre with espresso accents, NOT whole animals, just the pelt pattern, gentle but clearly visible contrast, dominant mustard #c99a3a','raffia'),
-    concept:'abstract leopard animal-skin rosette/spot texture (not whole animals)' },
-  'TR-011': { style:'Art-Deco Geometric', color:'Chartreuse', hex:'#b6c33a', cap:4, foliage:false, mode:'texture',
-    prompt:P('Tone-on-tone Art Deco geometric repeat — radiating sunburst fans and stepped chevrons in soft low-contrast chartreuse and muted olive tones, 1920s deco style, gentle tonal contrast, dominant chartreuse #b6c33a','linen'),
-    concept:'Art Deco fan / sunburst / chevron geometry' },
-  'TR-029': { style:'Art-Deco Geometric', color:'Cream Gold', hex:'#cdb87a', cap:4, foliage:false, mode:'texture',
-    prompt:P('Tone-on-tone Art Deco geometric repeat — elegant radiating sunburst fans and stepped arch motifs in soft cream-gold and antique-gold tones, 1920s deco luxe, gentle tonal contrast, dominant cream gold #cdb87a','mica'),
-    concept:'Art Deco fan / sunburst / stepped geometry' },
-  'TR-013': { style:'Texture-Effect', color:'Greige', hex:'#9a9488', cap:3, foliage:false, mode:'texture',
-    prompt:P('Tonal tactile natural-fiber texture — woven grasscloth and linen weave with subtle horizontal slubs and plaster-like tactile grain, tone-on-tone with no distinct motif, warm greige monochrome, dominant greige #9a9488','grasscloth'),
-    concept:'tonal tactile natural-fiber woven grasscloth/linen texture with no distinct figural motif' },
-  'TR-014': { style:'Texture-Effect', color:'Cloud White', hex:'#efece3', cap:3, foliage:false, mode:'texture',
-    prompt:P('Tonal tactile natural-fiber texture — soft linen and plaster weave with delicate slubs and subtle tactile grain, tone-on-tone with no distinct motif, warm cloud-white monochrome, dominant cloud white #efece3','linen'),
-    concept:'tonal tactile natural-fiber linen/plaster texture with no distinct figural motif' },
-  'TR-021': { style:'Tropical', color:'Sage', hex:'#9bab8b', cap:4, foliage:true, mode:'texture',
-    prompt:P('Whisper tone-on-tone tropical foliage — small fern fronds and slender branches in an EXTREMELY dense edge-to-edge allover fill, near-monochrome soft sage green with only the faintest tonal shading, minimal contrast, flat matte, calm botanical, NO birds NO butterflies NO bananas NO grapes, dominant sage #9bab8b','grasscloth'),
-    concept:'tropical foliage (ferns/leaves with visible tree branches), tone-on-tone sage',
-    settlementNote:'risk lane — must contain NO birds/butterflies/bananas/grapes; branches present as acceptable element' },
-  'TR-022': { style:'Boho', color:'Terracotta', hex:'#c07a4e', cap:5, foliage:false, mode:'texture',
-    prompt:P('Tone-on-tone boho mudcloth folk motif — hand-drawn tribal diamonds, dashes and stitch-marks in an allover mudcloth repeat, soft closely-related terracotta and clay tones, low contrast, artisan feel, dominant terracotta #c07a4e','jute'),
-    concept:'boho mudcloth / folk tribal geometric motif' },
-  'TR-032': { style:'Boho', color:'Terracotta', hex:'#c07a4e', cap:5, foliage:false, mode:'texture',
-    prompt:P('Tone-on-tone boho folk suzani medallion motif — hand-block-printed rosette medallions and scalloped folk florals in a dense allover repeat, soft closely-related terracotta and rust tones, low contrast, artisan feel, dominant terracotta #c07a4e','hemp'),
-    concept:'boho folk suzani / block-print medallion motif' },
-  'TR-030': { style:'Retro Psychedelic', color:'Mustard', hex:'#c99a3a', cap:4, foliage:false, mode:'texture',
-    prompt:P('Tone-on-tone 1970s psychedelic wavy swirl repeat — undulating groovy melting waves in a dense allover retro pattern, soft closely-related mustard and amber tones, low contrast, dominant mustard #c99a3a','linen'),
-    concept:'1970s psychedelic wavy swirl / groovy op-art ribbons' },
-  'TR-034': { style:'Mushroom', color:'Goblincore', hex:'#8a6f5a', cap:6, foliage:false, mode:'motif',
-    prompt:P('Whisper tone-on-tone woodland mushrooms — small toadstool caps and tiny ferns in a dense edge-to-edge allover fill, near-monochrome soft mushroom-brown and taupe with only faint tonal shading, minimal contrast, flat matte, NO butterflies NO birds, dominant mushroom brown #8a6f5a','cork'),
-    concept:'whimsical woodland mushrooms / toadstools / forest-floor motif',
-    settlementNote:'contains foliage-adjacent ferns — must contain NO birds/butterflies' },
-  'TR-036': { style:'Retro Geometric', color:'Pop Pink', hex:'#c85a86', cap:4, foliage:false, mode:'texture',
-    prompt:P('Tone-on-tone 1970s retro geometric repeat — interlocking arches, circles and mod grid shapes in a dense flat op-art pattern, soft closely-related pink and rose tones, low contrast, dominant pop pink #c85a86','linen'),
-    concept:'1970s retro geometric interlocking arches / circles / mod grid' },
-  'TR-048': { style:'Paisley', color:'Nomadic', hex:'#76322f', cap:5, foliage:false, mode:'texture',
-    prompt:P('Tone-on-tone ornate paisley boteh nomadic motif — teardrop paisleys with folk-embroidery detailing in a dense allover repeat, soft closely-related claret and terracotta tones, low contrast, heritage feel, dominant claret #76322f','silk'),
-    concept:'paisley / boteh teardrop nomadic motif' },
-  'TR-050': { style:'Abstract Blur', color:'Ombre Blue', hex:'#5b7fa6', cap:10, foliage:false, noBlurNeg:true, mode:'texture',
-    prompt:`Soft ombre abstract blur — dreamy mottled watercolor wash of closely-related blue tones drifting cloud-like across the surface, gentle low-contrast tonal transitions with no hard edges, no distinct motif, serene atmospheric, dominant dusty blue #5b7fa6. ${SEAM_BASE} Luxury designer wallcovering, archival quality, painted on a real natural silk ground with a subtle sheen. No text, no watermark, no border, no frame.`,
-    concept:'soft ombre / gradient watercolor blur in blue tones' },
+// STYLE-KEYED templates (stable across feed rebuilds). Each maps a trend `style`
+// to a luxury tone-on-tone brief; color/hex come from the LIVE item at run time.
+// `motif(color,hex)` returns the pattern description (wrapped by P()); `raw` is
+// for styles that need a bespoke prompt (e.g. ombre uses a silk-sheen ground).
+// TONE-ON-TONE low-contrast palettes match the house aesthetic AND lower the
+// local-contrast floor so the seam metric (adjacent-strip ΔE) can reach PASS.
+const STYLE_TEMPLATES = {
+  'Texture-Effect': { texture:'grasscloth', mode:'texture', cap:3,
+    motif:(c,h)=>`Tonal tactile natural-fiber texture — woven grasscloth and linen weave with subtle slubs and plaster-like tactile grain, tone-on-tone with no distinct motif, warm ${c} monochrome, dominant ${c} ${h}` },
+  'Retro Geometric': { texture:'linen', mode:'texture', cap:4,
+    motif:(c,h)=>`Tone-on-tone 1970s retro geometric repeat — interlocking arches, circles and mod grid shapes in a dense flat op-art pattern, soft closely-related ${c} tones, low contrast, dominant ${c} ${h}` },
+  'Geometric': { texture:'linen', mode:'texture', cap:4,
+    motif:(c,h)=>`Tone-on-tone geometric repeat — clean interlocking geometry in a dense flat allover pattern, soft closely-related ${c} tones, low contrast, dominant ${c} ${h}` },
+  'Art-Deco Geometric': { texture:'mica', mode:'texture', cap:4,
+    motif:(c,h)=>`Tone-on-tone Art Deco geometric repeat — radiating sunburst fans and stepped chevrons in soft low-contrast ${c} tones, 1920s deco style, gentle tonal contrast, dominant ${c} ${h}` },
+  'Retro Psychedelic': { texture:'linen', mode:'texture', cap:4,
+    motif:(c,h)=>`Tone-on-tone 1970s psychedelic wavy swirl repeat — undulating groovy melting waves in a dense allover retro pattern, soft closely-related ${c} tones, low contrast, dominant ${c} ${h}` },
+  'Gingham / Check': { texture:'linen', mode:'texture', cap:4,
+    motif:(c,h)=>`Tone-on-tone woven gingham check — even crosshatch check weave in soft closely-related ${c} tones, flat matte, low contrast, dominant ${c} ${h}` },
+  'Boho': { texture:'jute', mode:'texture', cap:5,
+    motif:(c,h)=>`Tone-on-tone boho mudcloth folk motif — hand-drawn tribal diamonds, dashes and stitch-marks in an allover mudcloth repeat, soft closely-related ${c} tones, low contrast, artisan feel, dominant ${c} ${h}` },
+  'Paisley / Nomadic': { texture:'silk', mode:'texture', cap:5,
+    motif:(c,h)=>`Tone-on-tone ornate paisley boteh nomadic motif — teardrop paisleys with folk-embroidery detailing in a dense allover repeat, soft closely-related ${c} tones, low contrast, heritage feel, dominant ${c} ${h}` },
+  'Animal-Skin Print': { texture:'linen', mode:'motif', cap:5,
+    motif:(c,h)=>`Elegant leopard/ocelot animal-SKIN spot texture — soft painterly rosette markings densely scattered allover in a cohesive muted ${c} palette, NOT whole animals, just the pelt marking pattern, gentle but clearly visible contrast, dominant ${c} ${h}` },
+  'Damask': { texture:'silk', mode:'texture', cap:4,
+    motif:(c,h)=>`Tone-on-tone classic damask repeat — ogee framework with stylized acanthus and scrollwork fill in a dense allover damask, soft closely-related ${c} tones, low contrast, heritage luxe, dominant ${c} ${h}` },
+  'Tropical': { texture:'grasscloth', mode:'texture', cap:4, foliage:true,
+    settlementNote:'risk lane — NO birds/butterflies/bananas/grapes; branches acceptable',
+    motif:(c,h)=>`Whisper tone-on-tone tropical foliage — small fern fronds and slender branches in an EXTREMELY dense edge-to-edge allover fill, near-monochrome soft ${c} with only faint tonal shading, minimal contrast, flat matte, calm botanical, NO birds NO butterflies NO bananas NO grapes, dominant ${c} ${h}` },
+  'Botanical': { texture:'grasscloth', mode:'texture', cap:5, foliage:true,
+    settlementNote:'foliage lane — NO birds/butterflies/bananas/grapes; branches acceptable',
+    motif:(c,h)=>`Tone-on-tone botanical repeat — dense allover leaves, ferns and visible branches, soft closely-related ${c} tones, flat matte, calm, NO birds NO butterflies NO bananas NO grapes, dominant ${c} ${h}` },
+  'Heritage Floral': { texture:'linen', mode:'texture', cap:5, foliage:true,
+    settlementNote:'foliage lane — NO birds/butterflies/bananas/grapes',
+    motif:(c,h)=>`Tone-on-tone heritage floral repeat — trailing stylized blooms and leafy stems in a dense allover chintz repeat, soft closely-related ${c} tones, low contrast, NO birds NO butterflies NO bananas NO grapes, dominant ${c} ${h}` },
+  'Novelty Conversational': { texture:'linen', mode:'motif', cap:5,
+    motif:(c,h)=>`Tone-on-tone whimsical conversational motif — small charming repeating icons in a dense allover repeat, soft closely-related ${c} tones, low contrast, flat matte, NO birds NO butterflies, dominant ${c} ${h}` },
+  'Mushroom / Goblincore': { texture:'cork', mode:'motif', cap:6,
+    settlementNote:'foliage-adjacent — NO birds/butterflies',
+    motif:(c,h)=>`Whisper tone-on-tone woodland mushrooms — small toadstool caps and tiny ferns in a dense edge-to-edge allover fill, near-monochrome soft ${c} with faint tonal shading, minimal contrast, flat matte, NO butterflies NO birds, dominant ${c} ${h}` },
+  'Abstract Blur': { texture:'silk', mode:'texture', cap:10, noBlurNeg:true,
+    raw:(c,h)=>`Soft ombre abstract blur — dreamy mottled watercolor wash of closely-related ${c} tones drifting cloud-like across the surface, gentle low-contrast tonal transitions with no hard edges, no distinct motif, serene atmospheric, dominant ${c} ${h}. ${SEAM_BASE} Luxury designer wallcovering, archival quality, painted on a real natural silk ground with a subtle sheen. No text, no watermark, no border, no frame.` },
+  _default: { texture:'linen', mode:'texture', cap:5,
+    motif:(c,h)=>`Tone-on-tone luxury wallcovering pattern in soft closely-related ${c} tones, dense allover repeat, flat matte, low contrast, no distinct hero motif, NO birds NO butterflies NO bananas NO grapes, dominant ${c} ${h}` },
 };
+// styles with no template of their own map onto the closest one
+const STYLE_ALIAS = { 'Dark Floral':'Heritage Floral', 'Ditsy Micro-Print':'Novelty Conversational', 'Scenic Mural':'Botanical' };
+
+function briefFor(item) {
+  const style = STYLE_ALIAS[item.style] || item.style;
+  const t = STYLE_TEMPLATES[style] || STYLE_TEMPLATES._default;
+  const color = item.color || 'neutral';
+  const hex = (typeof item.dominantHex === 'string' && /^#[0-9a-f]{6}$/i.test(item.dominantHex)) ? item.dominantHex : '#9a9488';
+  return {
+    id: item.id, style: item.style, color, hex, cap: t.cap, foliage: !!t.foliage,
+    mode: t.mode, noBlurNeg: !!t.noBlurNeg, settlementNote: t.settlementNote || null,
+    concept: item.genBrief || `${item.style} in ${color}`,
+    prompt: t.raw ? t.raw(color, hex) : P(t.motif(color, hex), t.texture),
+  };
+}
 
 // ---- Replicate gen --------------------------------------------------------
 function negFor(b) {
@@ -167,16 +182,46 @@ const args = process.argv.slice(2);
 let rolls = 2;
 const ri = args.indexOf('--rolls');
 if (ri >= 0) { rolls = parseInt(args[ri + 1], 10); args.splice(ri, 2); }
-const ids = args.filter(a => BRIEFS[a]);
-if (!ids.length) { console.error('no valid ids. known:', Object.keys(BRIEFS).join(',')); process.exit(1); }
+// --dry-run: resolve ids + build briefs and print them, but generate NOTHING
+// (no Replicate call, no spend). Use to validate targeting before paying.
+const di = args.indexOf('--dry-run');
+const DRY = di >= 0; if (DRY) args.splice(di, 1);
+
+// DATA-DRIVEN resolution: each requested id is looked up in the LIVE feed and a
+// brief is built from that item's *current* style/color/hex. An id that isn't in
+// the feed, or that already has an image (not a gap), is refused — never
+// generated for. This is what makes brief↔trend drift impossible.
+const BEST = path.join(ROOT, 'data', 'bestsellers.json');
+const liveItems = (loadJSON(BEST, { items: [] }).items) || [];
+const byId = new Map(liveItems.map(i => [i.id, i]));
+const targets = [];
+for (const a of args) {
+  const it = byId.get(a);
+  if (!it) { console.log(`[SKIP] ${a}: not in current feed`); continue; }
+  if (it.image) { console.log(`[SKIP] ${a}: already imaged (${it.style}) — not a gap`); continue; }
+  targets.push(briefFor(it));
+}
+if (!targets.length) {
+  console.error('no current gap ids to generate. pass TR-ids that are GAPS in data/bestsellers.json (needsGeneration, no image).');
+  process.exit(1);
+}
+
+if (DRY) {
+  console.log(`[DRY-RUN] ${targets.length} target(s) resolved from live feed — NO generation, $0 spent:\n`);
+  for (const b of targets) {
+    console.log(`${b.id}  ${b.style} / ${b.color} ${b.hex}  cap=${b.cap}${b.foliage?' foliage':''}${b.settlementNote?'  ⚠ '+b.settlementNote:''}`);
+    console.log(`  prompt: ${b.prompt.slice(0, 160)}…\n`);
+  }
+  process.exit(0);
+}
 
 fs.mkdirSync(CAND_DIR, { recursive: true });
 const ledger = loadJSON(LEDGER, { total: 0, gens: 0 });
 const report = loadJSON(REPORT, {});
 const CAP = 3.0;
 
-for (const id of ids) {
-  const b = BRIEFS[id];
+for (const b of targets) {
+  const id = b.id;
   report[id] = report[id] || { id, style: b.style, color: b.color, hex: b.hex, cap: b.cap, candidates: [], attempts: 0 };
   for (let r = 0; r < rolls; r++) {
     if (ledger.total + GEN_COST > CAP) { console.log(`[BUDGET] would exceed $${CAP.toFixed(2)} — stopping. total=$${ledger.total.toFixed(4)}`); break; }

← c96132f trending: fix infinite-scroll stall (fill while sentinel vis  ·  back to Trending Dw  ·  gapgen: add file-based heal-tile.py (near-miss seam rescue, 52f5a8b →