[object Object]

← back to Wallco Ai

feat(generators): wire bad-aesthetic-patterns.jsonl into the tick loop

913798d218ea2f31b7f65a7eef2440d411e07cf7 · 2026-05-24 22:50:36 -0700 · Steve Abrams

Closes Steve's 'I dont want to waste time on bad patterns / learn that those
are bad' loop. The bulk-delete UI + learn-and-purge-flagged.js were already
writing prompt + motifs + category to data/bad-aesthetic-patterns.jsonl, but
no generator was reading it — so the 'learn' half was a no-op.

lib/bad-aesthetic.js — small new module:
- loadRows() / getCategoryFailureCount(cat)
- isCategoryCooldown(cat, threshold=50) — skip the whole tick if too many
  prior failures in this category
- getAvoidanceAddendum(cat) — assembles a category-targeted 'AVOID prior
  failure modes' suffix from the actual reasons logged in past rejections

scripts/drunk_animals_tick.js — pre-flight: count drunk-animals failures.
  ≥ DRUNK_ANIMALS_COOLDOWN (default 50) → skip tick. ≥ 1 → append learned
  avoidance addendum to the freshPrompt.

scripts/generator_tick.js — same pattern, keyed off recipe.style_tags[0].
  GENERATOR_COOLDOWN (default 50) env tunes the bar.

Local data file currently has 0 entries; prod has 4 cactus entries. Both
ticks no-op on missing file, so the wiring is safe to deploy today.

Files touched

Diff

commit 913798d218ea2f31b7f65a7eef2440d411e07cf7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun May 24 22:50:36 2026 -0700

    feat(generators): wire bad-aesthetic-patterns.jsonl into the tick loop
    
    Closes Steve's 'I dont want to waste time on bad patterns / learn that those
    are bad' loop. The bulk-delete UI + learn-and-purge-flagged.js were already
    writing prompt + motifs + category to data/bad-aesthetic-patterns.jsonl, but
    no generator was reading it — so the 'learn' half was a no-op.
    
    lib/bad-aesthetic.js — small new module:
    - loadRows() / getCategoryFailureCount(cat)
    - isCategoryCooldown(cat, threshold=50) — skip the whole tick if too many
      prior failures in this category
    - getAvoidanceAddendum(cat) — assembles a category-targeted 'AVOID prior
      failure modes' suffix from the actual reasons logged in past rejections
    
    scripts/drunk_animals_tick.js — pre-flight: count drunk-animals failures.
      ≥ DRUNK_ANIMALS_COOLDOWN (default 50) → skip tick. ≥ 1 → append learned
      avoidance addendum to the freshPrompt.
    
    scripts/generator_tick.js — same pattern, keyed off recipe.style_tags[0].
      GENERATOR_COOLDOWN (default 50) env tunes the bar.
    
    Local data file currently has 0 entries; prod has 4 cactus entries. Both
    ticks no-op on missing file, so the wiring is safe to deploy today.
---
 lib/bad-aesthetic.js          | 72 +++++++++++++++++++++++++++++++++++++++++++
 scripts/drunk_animals_tick.js | 22 ++++++++++++-
 scripts/generator_tick.js     | 24 ++++++++++++++-
 3 files changed, 116 insertions(+), 2 deletions(-)

diff --git a/lib/bad-aesthetic.js b/lib/bad-aesthetic.js
new file mode 100644
index 0000000..702b1dd
--- /dev/null
+++ b/lib/bad-aesthetic.js
@@ -0,0 +1,72 @@
+// lib/bad-aesthetic.js — closes the marquee-Delete-and-learn loop.
+//
+// Reads data/bad-aesthetic-patterns.jsonl (written by both the Ghost Review UI's
+// bulk-delete endpoint and scripts/learn-and-purge-flagged.js) and exposes a
+// tiny API the generators can call before spending a Gemini/SDXL call on a
+// category that keeps producing defects.
+//
+// Schema per line:
+//   { id, category, prompt, motifs, reason, ts }
+//
+// Behavior:
+//   getCategoryFailureCount(category)   → integer
+//   isCategoryCooldown(category, n=50)  → boolean ("skip this tick entirely")
+//   getAvoidanceAddendum(category)      → string to append to a fresh prompt
+//                                          when the category has ≥1 prior failure
+//
+// File is reloaded on every call (small file, append-only — millisecond cost).
+// If the file is missing the lib returns the no-failures defaults.
+
+'use strict';
+
+const fs   = require('fs');
+const path = require('path');
+
+const FILE = path.join(__dirname, '..', 'data', 'bad-aesthetic-patterns.jsonl');
+
+function loadRows() {
+  try {
+    const raw = fs.readFileSync(FILE, 'utf8');
+    const rows = [];
+    for (const line of raw.split('\n')) {
+      if (!line.trim()) continue;
+      try { rows.push(JSON.parse(line)); } catch {}
+    }
+    return rows;
+  } catch { return []; }
+}
+
+function getCategoryFailureCount(category) {
+  if (!category) return 0;
+  const want = String(category).toLowerCase();
+  return loadRows().filter(r => (r.category || '').toLowerCase() === want).length;
+}
+
+function isCategoryCooldown(category, threshold = 50) {
+  return getCategoryFailureCount(category) >= threshold;
+}
+
+// Build a category-targeted avoidance addendum from the reason strings actually
+// observed in past failures. Falls back to a generic anti-ghost addendum if the
+// category has prior failures but no parseable reasons.
+function getAvoidanceAddendum(category) {
+  const rows = loadRows().filter(r => (r.category || '').toLowerCase() === String(category || '').toLowerCase());
+  if (!rows.length) return '';
+  const reasons = [...new Set(rows.map(r => r.reason || '').filter(Boolean))].slice(0, 3);
+  const n = rows.length;
+  return (
+    `\n\n[LEARNED FROM PRIOR FAILURES — ${n} prior generation${n === 1 ? '' : 's'} in this ` +
+    `"${category}" category were rejected with these reasons: ${reasons.join(' · ')}. ` +
+    `Take EXTRA CARE to avoid: ghost-layer / faded duplicate motifs / atmospheric haze over ` +
+    `flat patterns / multi-opacity copies of the same motif / "halo" pale silhouettes echoing ` +
+    `sharp ones. Render as a SINGLE flat layer, hard razor-sharp silhouette edges, uniform ` +
+    `opacity across every motif, ground is one flat solid color edge-to-edge.]`
+  );
+}
+
+module.exports = {
+  loadRows,
+  getCategoryFailureCount,
+  isCategoryCooldown,
+  getAvoidanceAddendum,
+};
diff --git a/scripts/drunk_animals_tick.js b/scripts/drunk_animals_tick.js
index e578424..665c763 100644
--- a/scripts/drunk_animals_tick.js
+++ b/scripts/drunk_animals_tick.js
@@ -61,7 +61,27 @@ function freshPrompt() {
 // Steve killed the 18:00→05:59 window on 2026-05-13.
 
 const ROOT = path.join(__dirname, '..');
-const prompt = freshPrompt();
+const { isCategoryCooldown, getAvoidanceAddendum, getCategoryFailureCount } = require('../lib/bad-aesthetic');
+
+// ── Bad-aesthetic cooldown ───────────────────────────────────────────────
+// If the drunk-animals category has accumulated ≥ COOLDOWN_THRESHOLD bulk-
+// rejected designs in data/bad-aesthetic-patterns.jsonl, skip this tick to
+// stop burning Gemini calls on an aesthetic that keeps failing review. Steve
+// raises/lowers the bar by tuning DRUNK_ANIMALS_COOLDOWN env var.
+const COOLDOWN_THRESHOLD = parseInt(process.env.DRUNK_ANIMALS_COOLDOWN || '50', 10);
+const failureCount = getCategoryFailureCount('drunk-animals');
+if (isCategoryCooldown('drunk-animals', COOLDOWN_THRESHOLD)) {
+  console.log(`[tick ${new Date().toISOString()}] COOLDOWN — drunk-animals has ${failureCount} prior failures (threshold ${COOLDOWN_THRESHOLD}). Skipping.`);
+  process.exit(0);
+}
+
+let prompt = freshPrompt();
+// If there's any prior failure (even below cooldown threshold), append the
+// learned avoidance addendum so SDXL gets explicit "don't repeat the defect".
+if (failureCount > 0) {
+  prompt = prompt + getAvoidanceAddendum('drunk-animals');
+  console.log(`[tick ${new Date().toISOString()}] LEARNED-AVOID applied (${failureCount} prior failures).`);
+}
 console.log(`[tick ${new Date().toISOString()}] ${prompt.slice(0, 100)}…`);
 
 // ── Settlement pre-generation gate ───────────────────────────────────────
diff --git a/scripts/generator_tick.js b/scripts/generator_tick.js
index 16c411b..b773e50 100644
--- a/scripts/generator_tick.js
+++ b/scripts/generator_tick.js
@@ -112,8 +112,30 @@ function main() {
     console.log(JSON.stringify({ event: 'skip', reason: 'no enabled recipe' }));
     return 0;
   }
+
+  // ── Bad-aesthetic cooldown ─────────────────────────────────────────────
+  // The category we'd ship to generate_designs.js below. If that category
+  // has accumulated ≥ COOLDOWN_THRESHOLD bulk-rejected designs in
+  // data/bad-aesthetic-patterns.jsonl (written by the Ghost Review UI's
+  // bulk-delete endpoint and learn-and-purge-flagged.js), skip the tick
+  // entirely. Stops Gemini/SDXL burn on an aesthetic that keeps failing.
+  const { isCategoryCooldown, getAvoidanceAddendum, getCategoryFailureCount } = require('../lib/bad-aesthetic');
+  const COOLDOWN_THRESHOLD = parseInt(process.env.GENERATOR_COOLDOWN || '50', 10);
+  const category = (recipe.style_tags && recipe.style_tags[0]) || 'mixed';
+  const failureCount = getCategoryFailureCount(category);
+  if (isCategoryCooldown(category, COOLDOWN_THRESHOLD)) {
+    console.log(JSON.stringify({
+      event: 'tick_skipped_cooldown', recipe_id: recipe.id,
+      recipe_name: recipe.name, category, failures: failureCount, threshold: COOLDOWN_THRESHOLD,
+    }));
+    return 0;
+  }
+
   const seeds = recentPaletteSeeds(3);
-  const prompt = buildPrompt(recipe, seeds);
+  let prompt = buildPrompt(recipe, seeds);
+  if (failureCount > 0) {
+    prompt = prompt + getAvoidanceAddendum(category);
+  }
 
   // ── Settlement pre-generation gate ─────────────────────────────────────
   // Hard rule: no pattern is generated until the gate returns OK. BLOCK and

← 1848657 fix(deploy): exclude ghost-scan + bad-aesthetic-patterns + g  ·  back to Wallco Ai  ·  ghost-detector: seam-frame lens — catches plates at the tile c85af21 →