[object Object]

← back to Wallco Ai

retire pil-mid-heal + pil-edge-heal: delete generators, promoters, curator; guard snapshot

9d836cfc9d50f2c538f4015ea5b88873fb61144d · 2026-05-27 08:13:29 -0700 · Steve Abrams

Steve directive: retire (delete) the band-blur heal pipeline. It applied a 24px
Gaussian blur across H/2 & V/2 — that band IS the visible 'fuzzy cross' defect and
breaks 'solid screen-print only'. On 2026-05-26 it put 4,022 blur-cross designs live
(all pulled 2026-05-27).

Deleted:
  scripts/mid_seam_heal.py        (generator: pil-mid-heal)
  scripts/edge_seam_heal.py       (generator: pil-edge-heal)
  scripts/promote_edge_heal.py    (promoter — dead with the cohort)
  scripts/promote_edge_heals.py   (promoter — dead with the cohort)
  scripts/vision_curate_edgeheals.py (curator — dead with the cohort)

refresh_designs_snapshot.py: hard-exclude both generators (the 'pil-%' prefix would
otherwise re-admit them) so no leftover heal row can re-enter the catalog. designs.json
52,127 -> 41,991 entries; published set unchanged. See memory
feedback_wallco_heal_blur_band_edges_agent_blind.

Files touched

Diff

commit 9d836cfc9d50f2c538f4015ea5b88873fb61144d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 27 08:13:29 2026 -0700

    retire pil-mid-heal + pil-edge-heal: delete generators, promoters, curator; guard snapshot
    
    Steve directive: retire (delete) the band-blur heal pipeline. It applied a 24px
    Gaussian blur across H/2 & V/2 — that band IS the visible 'fuzzy cross' defect and
    breaks 'solid screen-print only'. On 2026-05-26 it put 4,022 blur-cross designs live
    (all pulled 2026-05-27).
    
    Deleted:
      scripts/mid_seam_heal.py        (generator: pil-mid-heal)
      scripts/edge_seam_heal.py       (generator: pil-edge-heal)
      scripts/promote_edge_heal.py    (promoter — dead with the cohort)
      scripts/promote_edge_heals.py   (promoter — dead with the cohort)
      scripts/vision_curate_edgeheals.py (curator — dead with the cohort)
    
    refresh_designs_snapshot.py: hard-exclude both generators (the 'pil-%' prefix would
    otherwise re-admit them) so no leftover heal row can re-enter the catalog. designs.json
    52,127 -> 41,991 entries; published set unchanged. See memory
    feedback_wallco_heal_blur_band_edges_agent_blind.
---
 scripts/edge_seam_heal.py           | 343 ----------------------------------
 scripts/mid_seam_heal.py            | 363 ------------------------------------
 scripts/promote_edge_heal.py        | 122 ------------
 scripts/promote_edge_heals.py       |  87 ---------
 scripts/refresh_designs_snapshot.py |   9 +-
 scripts/vision_curate_edgeheals.py  | 124 ------------
 6 files changed, 8 insertions(+), 1040 deletions(-)

diff --git a/scripts/edge_seam_heal.py b/scripts/edge_seam_heal.py
deleted file mode 100644
index 115d5a1..0000000
--- a/scripts/edge_seam_heal.py
+++ /dev/null
@@ -1,343 +0,0 @@
-#!/usr/bin/env python3
-"""edge_seam_heal — production batch tool for the tile-edge wrap defect.
-
-Defect: pixel discontinuity at the WRAP boundary — row 0 vs row H-1 and/or
-col 0 vs col W-1. The 6-lens edges-agent flags this as `grid=edges_only`
-(midlines clean, edges fail). Usually a tile-prep regression: a center-crop
-or recolor that broke the seamless wrap a source had. mid_seam_heal does NOT
-help these — it blurs the interior midlines, which are already clean here.
-
-Fix (the classic offset-heal): np.roll the image by (H/2, W/2). This moves
-the wrap seam from the edges to the interior midlines, where we can blur a
-band exactly the way mid_seam_heal does. Then np.roll back by -(H/2, W/2).
-np.roll is exactly invertible, so every pixel OUTSIDE the blurred band is
-byte-identical to the source — composition fully preserved, only the wrap
-seam is feathered.
-
-Round-1-sacred pattern (per feedback_round_one_outputs_are_sacred):
-  - Source PNG NEVER modified or deleted
-  - Healed PNG written to data/generated/edgeheal_<src_id>_<ts>.png
-  - New all_designs row INSERTed:
-      generator='pil-edge-heal'
-      parent_design_id=<src_id>
-      is_published=FALSE  (mirrors mid_seam_heal's strict gate — auto-publish
-                          would bypass the edges-agent quality bar. Promotion
-                          requires an explicit edges-agent PASS.)
-      notes='edge_heal of #<src_id>: PIL roll+band-blur BAND=12 SIGMA=4.0'
-  - Source gets notes append: ' | EDGE_HEALED_BY: <new_id>'
-
-Ghost-defective rows are excluded from --all (a BLEED_GHOST verdict can't be
-fixed by an edge feather, so healing them just burns disk). They're left in
-the unpublished pool for the ghost-removal pipeline.
-
-Usage:
-  python3 edge_seam_heal.py --id 4135                # one design
-  python3 edge_seam_heal.py --id 4135 --id 10275     # explicit sample set
-  python3 edge_seam_heal.py --all                    # every edges_only FAIL (non-ghost)
-  python3 edge_seam_heal.py --all --grid both        # also the 'both' cohort
-  python3 edge_seam_heal.py --all --workers 8
-  python3 edge_seam_heal.py --limit 50               # cap for smoke test
-  python3 edge_seam_heal.py --dry-run                # plan only, no writes
-"""
-import argparse
-import multiprocessing as mp
-import subprocess
-import sys
-import tempfile
-import time
-from pathlib import Path
-
-WALLCO_ROOT = Path.home() / 'Projects' / 'wallco-ai'
-OUT_DIR = WALLCO_ROOT / 'data' / 'generated'
-BAND_PX = 12
-SIGMA = 4.0
-
-
-def fetch_targets(grid: str, limit: int | None, ids: list[int] | None):
-    """Return target rows for the edge-wrap heal.
-
-    grid=edges_only → notes LIKE '%EDGES_FAIL: %grid=edges_only%'
-    grid=both       → edges_only OR both (the edge component of catastrophic)
-    """
-    where = []
-    if ids:
-        where = [f"id IN ({','.join(str(int(i)) for i in ids)})"]
-    else:
-        where.append("(notes IS NULL OR notes NOT LIKE '%EDGE_HEALED_BY:%')")
-        where.append("(user_removed IS NULL OR user_removed = FALSE)")
-        where.append("local_path IS NOT NULL")
-        # A ghost feather can't be undone by an edge blur — skip them.
-        where.append("(notes NOT LIKE '%BLEED_GHOST%')")
-        where.append("(settlement_verdict IS NULL OR settlement_verdict NOT LIKE '%GHOST%')")
-        if grid == 'edges_only':
-            where.append("notes LIKE '%EDGES_FAIL:%grid=edges_only%'")
-        elif grid == 'both':
-            where.append("(notes LIKE '%EDGES_FAIL:%grid=edges_only%' "
-                         "OR notes LIKE '%EDGES_FAIL:%grid=both%')")
-        else:
-            raise ValueError(f'bad --grid {grid}')
-
-    sql = (
-        "SELECT id, local_path, dominant_hex, category, kind, "
-        "COALESCE(palette::text, 'null') AS palette_json, "
-        "width_in, height_in, panels "
-        "FROM all_designs WHERE " + ' AND '.join(where)
-        + " ORDER BY id"
-    )
-    if limit:
-        sql += f' LIMIT {int(limit)}'
-    sql += ';'
-
-    r = subprocess.run(
-        ['psql', 'dw_unified', '-At', '-F', chr(31), '-c', sql],
-        check=True, capture_output=True, text=True,
-    )
-    rows = []
-    for line in r.stdout.splitlines():
-        if not line.strip():
-            continue
-        parts = line.split(chr(31))
-        if len(parts) < 9:
-            continue
-        try:
-            rows.append({
-                'id': int(parts[0]),
-                'local_path': parts[1],
-                'dominant_hex': parts[2] if parts[2] != '' else None,
-                'category': parts[3] or 'mixed',
-                'kind': parts[4] or 'seamless_tile',
-                'palette_json': parts[5] if parts[5] not in ('', 'null') else 'null',
-                'width_in': parts[6] if parts[6] != '' else None,
-                'height_in': parts[7] if parts[7] != '' else None,
-                'panels': parts[8] if parts[8] != '' else None,
-            })
-        except ValueError:
-            continue
-    return rows
-
-
-def heal_image(src_path: Path, out_path: Path):
-    """Narrow SHARP cross-fade at the WRAP edges only.
-
-    OLD method did TWO wide 2-D GaussianBlur(SIGMA=4.0) passes over BAND_PX*2=24px
-    bands:
-      1) it blurred the INTERIOR midlines — but interior midlines are NOT a tiling
-         boundary (tiles meet at the EDGES), so this only smeared a visible fuzzy
-         cross through the middle of the displayed tile to satisfy the edges-agent
-         midline lens. Pure metric-gaming (the docstring used to justify it by the
-         midlines scoring 7.5-10.9 ΔE — that's natural pattern content, not a seam).
-      2) it blurred a 24px band at the wrap edges via np.roll, leaving a fuzzy dark
-         frame on every VISIBLE outer edge (corners double-blurred).
-    Both defects were visible on the product page (e.g. #52277).
-
-    NEW method: drop the interior-midline pass entirely (if a design has a real
-    internal-grid seam that's mid_seam_heal's job, or a reject) and heal ONLY the
-    true wrap edges with a ~6px band low-passed along the seam normal (1-D), so
-    motif detail stays crisp and no brightness halo forms. np.roll stays exactly
-    invertible: every pixel outside the thin edge bands is byte-identical to the
-    source, so composition is preserved.
-    """
-    from PIL import Image
-    import numpy as np
-    img = Image.open(src_path).convert('RGB')
-    W, H = img.size
-    hm, wm = H // 2, W // 2
-    band = max(4, min(W, H) // 160)   # ~6px @1024  (was BAND_PX=12 -> 24px band)
-
-    def hsmooth(s):   # horizontal-only low-pass: vertical detail untouched
-        w, h = s.size
-        return s.resize((max(2, w // 3), h), Image.BILINEAR).resize((w, h), Image.BILINEAR)
-
-    def vsmooth(s):   # vertical-only low-pass: horizontal detail untouched
-        w, h = s.size
-        return s.resize((w, max(2, h // 3)), Image.BILINEAR).resize((w, h), Image.BILINEAR)
-
-    def qmask(size, axis):   # opaque at seam center, 0 at band edges
-        w, h = size
-        m = Image.new('L', size, 0); px = m.load()
-        if axis == 'x':
-            for x in range(w):
-                a = int(255 * (1 - abs(x - band) / band) ** 2)
-                for y in range(h): px[x, y] = a
-        else:
-            for y in range(h):
-                a = int(255 * (1 - abs(y - band) / band) ** 2)
-                for x in range(w): px[x, y] = a
-        return m
-
-    # Offset trick: roll by (H/2, W/2) so the wrap edges meet at the interior cross,
-    # heal that cross narrowly + sharply, then roll back so the heal lands on the
-    # (originally) outer edges.
-    rolled = Image.fromarray(np.roll(np.asarray(img), (hm, wm), axis=(0, 1)))
-    vstrip = rolled.crop((wm - band, 0, wm + band, H))
-    rolled.paste(hsmooth(vstrip), (wm - band, 0), qmask(vstrip.size, 'x'))
-    hstrip = rolled.crop((0, hm - band, W, hm + band))
-    rolled.paste(vsmooth(hstrip), (0, hm - band), qmask(hstrip.size, 'y'))
-    healed = np.roll(np.asarray(rolled), (-hm, -wm), axis=(0, 1))
-
-    Image.fromarray(healed).save(out_path, 'PNG', optimize=True)
-    return out_path.stat().st_size
-
-
-def worker(row: dict):
-    src_id = row['id']
-    try:
-        src_path = Path(row['local_path'])
-        if not src_path.exists():
-            return {'id': src_id, 'ok': False, 'error': 'file_missing'}
-        ts = int(time.time() * 1000)
-        out_filename = f'edgeheal_{src_id}_{ts}.png'
-        out_path = OUT_DIR / out_filename
-        bytes_written = heal_image(src_path, out_path)
-        return {
-            'id': src_id, 'ok': True,
-            'out_path': str(out_path), 'out_filename': out_filename,
-            'bytes': bytes_written,
-            'category': row['category'], 'kind': row['kind'],
-            'dominant_hex': row['dominant_hex'], 'palette_json': row['palette_json'],
-            'width_in': row['width_in'], 'height_in': row['height_in'],
-            'panels': row['panels'],
-        }
-    except Exception as exc:
-        return {'id': src_id, 'ok': False, 'error': f'heal_exception: {exc}'}
-
-
-def sql_escape(s: str) -> str:
-    return s.replace("'", "''")
-
-
-def persist(results: list[dict]):
-    ok = [r for r in results if r.get('ok')]
-    if not ok:
-        return 0
-
-    sql_parts = ['BEGIN;']
-    sql_parts.append(
-        'CREATE TEMP TABLE _eheal_in (src_id BIGINT PRIMARY KEY, '
-        'kind TEXT, category TEXT, dominant_hex TEXT, palette JSONB, '
-        'width_in INTEGER, height_in INTEGER, panels INTEGER, '
-        'local_path TEXT, image_url TEXT, prompt TEXT, seed BIGINT, '
-        'notes TEXT) ON COMMIT DROP;'
-    )
-
-    seed_base = int(time.time())
-    rows_for_sql = []
-    for i, r in enumerate(ok):
-        seed = (seed_base + i) & 0x7fffffff
-        kind = sql_escape(r['kind'])
-        cat = sql_escape(r['category'])
-        dh = "NULL" if not r['dominant_hex'] else f"'{sql_escape(r['dominant_hex'])}'"
-        pal_json = r.get('palette_json') or 'null'
-        if pal_json in ('', 'None'):
-            pal_json = 'null'
-        pal = f"'{sql_escape(pal_json)}'::jsonb"
-        wi = r['width_in'] if r['width_in'] not in (None, '') else 'NULL'
-        hi = r['height_in'] if r['height_in'] not in (None, '') else 'NULL'
-        pn = r['panels'] if r['panels'] not in (None, '') else 'NULL'
-        local_path = sql_escape(r['out_path'])
-        img_url = sql_escape(f"/designs/img/{r['out_filename']}")
-        band_px = max(4, 1024 // 160)
-        msg = sql_escape(f"edge_heal of #{r['id']}: PIL narrow sharp 1-D edge cross-fade band~{band_px}px (no interior-midline blur)")
-        rows_for_sql.append(
-            f"({r['id']}, '{kind}', '{cat}', {dh}, {pal}, {wi}, {hi}, {pn}, "
-            f"'{local_path}', '{img_url}', '{msg}', {seed}, '{msg}')"
-        )
-
-    CHUNK = 200
-    for i in range(0, len(rows_for_sql), CHUNK):
-        batch = rows_for_sql[i:i + CHUNK]
-        sql_parts.append(
-            'INSERT INTO _eheal_in (src_id, kind, category, dominant_hex, palette, '
-            'width_in, height_in, panels, local_path, image_url, prompt, seed, notes) VALUES '
-            + ','.join(batch) + ';'
-        )
-
-    sql_parts.append("""
-WITH ins AS (
-  INSERT INTO all_designs
-    (kind, brand, width_in, height_in, panels, generator, prompt, seed,
-     image_url, local_path, dominant_hex, palette, category,
-     is_published, parent_design_id, notes)
-  SELECT
-    h.kind, 'wallco.ai', h.width_in, h.height_in, h.panels, 'pil-edge-heal',
-    h.prompt, h.seed, h.image_url, h.local_path, h.dominant_hex, h.palette,
-    h.category, FALSE, h.src_id, h.notes
-  FROM _eheal_in h
-  RETURNING id AS new_id, parent_design_id AS src_id
-)
-UPDATE all_designs ad
-SET notes = CASE
-  WHEN ad.notes IS NULL OR ad.notes = '' THEN ' | EDGE_HEALED_BY: ' || ins.new_id::text
-  ELSE ad.notes || ' | EDGE_HEALED_BY: ' || ins.new_id::text
-END
-FROM ins
-WHERE ad.id = ins.src_id;
-""")
-
-    sql_parts.append('SELECT count(*) FROM _eheal_in;')
-    sql_parts.append('COMMIT;')
-
-    full_sql = '\n'.join(sql_parts)
-    with tempfile.NamedTemporaryFile(mode='w', suffix='.sql', delete=False) as tf:
-        tf.write(full_sql)
-        sql_path = tf.name
-
-    r = subprocess.run(
-        ['psql', 'dw_unified', '-At', '-q', '-v', 'ON_ERROR_STOP=1', '-f', sql_path],
-        capture_output=True, text=True,
-    )
-    if r.returncode != 0:
-        print(f'PSQL FAILED:\n  stdout: {r.stdout[:500]}\n  stderr: {r.stderr[:500]}',
-              file=sys.stderr)
-        return 0
-    return len(ok)
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    ap.add_argument('--id', type=int, action='append', help='Specific id (repeatable)')
-    ap.add_argument('--all', action='store_true', help='Every matching design')
-    ap.add_argument('--grid', choices=['edges_only', 'both'], default='edges_only')
-    ap.add_argument('--limit', type=int, default=None)
-    ap.add_argument('--workers', type=int, default=8)
-    ap.add_argument('--dry-run', action='store_true', help='List targets, no writes')
-    args = ap.parse_args()
-
-    if not (args.id or args.all or args.limit):
-        ap.error('Pass --id, --all, or --limit')
-
-    targets = fetch_targets(args.grid, args.limit, args.id)
-    print(f'targets: {len(targets)} ({args.grid} cohort)', flush=True)
-    if not targets:
-        print('nothing to do', flush=True)
-        return
-
-    if args.dry_run:
-        print('--dry-run — first 10 ids:', [t['id'] for t in targets[:10]])
-        return
-
-    OUT_DIR.mkdir(parents=True, exist_ok=True)
-    t0 = time.time()
-    results = []
-    counts = {'ok': 0, 'err': 0}
-
-    with mp.Pool(args.workers) as pool:
-        for i, res in enumerate(pool.imap_unordered(worker, targets, chunksize=4), 1):
-            results.append(res)
-            counts['ok' if res.get('ok') else 'err'] += 1
-            if i % 250 == 0 or i == len(targets):
-                rate = i / max(0.001, time.time() - t0)
-                eta = (len(targets) - i) / max(0.001, rate)
-                print(f'  [{i:>5}/{len(targets)}] ok={counts["ok"]} err={counts["err"]} '
-                      f'rate={rate:.1f}/s eta={eta:.0f}s', flush=True)
-
-    pg_written = persist(results)
-    elapsed = time.time() - t0
-    print(f'\nedge-heal done in {elapsed:.1f}s — '
-          f'baked={counts["ok"]} err={counts["err"]} pg_inserted={pg_written}', flush=True)
-    for r in [x for x in results if x.get('ok')][:8]:
-        print(f"  id={r['id']:>6} → {r['out_filename']}  {r['bytes']//1024}KB", flush=True)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/scripts/mid_seam_heal.py b/scripts/mid_seam_heal.py
deleted file mode 100755
index e7f8267..0000000
--- a/scripts/mid_seam_heal.py
+++ /dev/null
@@ -1,363 +0,0 @@
-#!/usr/bin/env python3
-"""mid_seam_heal — production batch tool for the SDXL latent-grid leak.
-
-Defect: pixel discontinuity at row H/2 and col W/2 (the 4x4 latent-block
-stitch line). The 6-lens edges-agent flags this as `grid=mids_only` or
-`grid=both`. Average mids ΔE in flagged designs: 17.10.
-
-Fix: PIL Gaussian-blur of a 24-px band (12px each side) at H/2 and V/2.
-Composition preserved everywhere else. Validated on 20-design sample —
-20/20 improved, 0/20 stayed FAIL, 9/20 reached PASS, 11/20 reached WARN.
-Avg mids ΔE dropped 17.10 → 3.44 (80% reduction).
-
-Round-1-sacred pattern (per feedback_round_one_outputs_are_sacred):
-  - Source PNG NEVER modified or deleted
-  - Healed PNG written to data/generated/midheal_<src_id>_<ts>.png
-  - New spoon_all_designs row INSERTed:
-      generator='pil-mid-heal'
-      parent_design_id=<src_id>
-      is_published=FALSE  (2026-05-26: was TRUE; auto-publish was bypassing
-                          the edges-agent quality bar. 65% of healed outputs
-                          were edges-agent WARN. Now requires explicit
-                          edges-agent PASS before promotion to is_published=true.)
-      notes='mid_heal of #<src_id>: PIL band-blur BAND=12 SIGMA=4.0'
-  - Source kept is_published=FALSE (it already was, from the edges sweep)
-  - Source gets notes append: ' | MID_HEALED_BY: <new_id>'
-
-Reversible via POST /api/fix-decision/<new_id> with verdict='reject' —
-that flips new is_published=FALSE + restores source is_published=TRUE.
-
-Usage:
-  python3 mid_seam_heal.py --id 40689              # one design
-  python3 mid_seam_heal.py --all                   # every mids-only FAIL
-  python3 mid_seam_heal.py --all --workers 8       # parallel
-  python3 mid_seam_heal.py --grid both             # also heal 'both' cohort
-  python3 mid_seam_heal.py --limit 50              # cap for smoke test
-  python3 mid_seam_heal.py --dry-run               # plan only, no writes
-"""
-import argparse
-import json
-import multiprocessing as mp
-import os
-import subprocess
-import sys
-import tempfile
-import time
-from pathlib import Path
-
-WALLCO_ROOT = Path.home() / 'Projects' / 'wallco-ai'
-OUT_DIR = WALLCO_ROOT / 'data' / 'generated'
-BAND_PX = 12
-SIGMA = 4.0
-
-
-def fetch_targets(grid: str, limit: int | None, ids: list[int] | None):
-    """Return list of (id, local_path, dominant_hex, category, kind, palette_json)
-    rows for designs that match the mid-heal target.
-
-    grid=mids_only      → notes LIKE '%EDGES_FAIL: %grid=mids_only%'
-    grid=both           → both 'mids_only' AND 'both'
-    grid=any            → any EDGES_FAIL row (for diagnostic re-heal)
-    """
-    where = []
-    if ids:
-        where = [f"id IN ({','.join(str(int(i)) for i in ids)})"]
-    else:
-        # Skip rows that have already been mid-healed (avoid duplicate work)
-        where.append("(notes IS NULL OR notes NOT LIKE '%MID_HEALED_BY:%')")
-        # Skip user-removed
-        where.append("(user_removed IS NULL OR user_removed = FALSE)")
-        where.append("local_path IS NOT NULL")
-        if grid == 'mids_only':
-            where.append("notes LIKE '%EDGES_FAIL:%grid=mids_only%'")
-        elif grid == 'both':
-            where.append("(notes LIKE '%EDGES_FAIL:%grid=mids_only%' "
-                         "OR notes LIKE '%EDGES_FAIL:%grid=both%')")
-        elif grid == 'any':
-            where.append("notes LIKE '%EDGES_FAIL:%'")
-        else:
-            raise ValueError(f'bad --grid {grid}')
-
-    sql = (
-        "SELECT id, local_path, dominant_hex, category, kind, "
-        "COALESCE(palette::text, 'null') AS palette_json, "
-        "width_in, height_in, panels "
-        "FROM all_designs WHERE " + ' AND '.join(where)
-        + " ORDER BY id"
-    )
-    if limit:
-        sql += f' LIMIT {int(limit)}'
-    sql += ';'
-
-    r = subprocess.run(
-        ['psql', 'dw_unified', '-At', '-F', chr(31), '-c', sql],
-        check=True, capture_output=True, text=True,
-    )
-    rows = []
-    for line in r.stdout.splitlines():
-        if not line.strip():
-            continue
-        parts = line.split(chr(31))
-        if len(parts) < 9:
-            continue
-        try:
-            rows.append({
-                'id': int(parts[0]),
-                'local_path': parts[1],
-                'dominant_hex': parts[2] if parts[2] != '' else None,
-                'category': parts[3] or 'mixed',
-                'kind': parts[4] or 'seamless_tile',
-                'palette_json': parts[5] if parts[5] not in ('', 'null') else 'null',
-                'width_in': parts[6] if parts[6] != '' else None,
-                'height_in': parts[7] if parts[7] != '' else None,
-                'panels': parts[8] if parts[8] != '' else None,
-            })
-        except ValueError:
-            continue
-    return rows
-
-
-def heal_image(src_path: Path, out_path: Path):
-    """Narrow SHARP cross-fade at the interior midlines (the SDXL '2x2 internal
-    grid' seam).
-
-    OLD method blurred a BAND_PX*2 = 24px band at each interior midline with a
-    2-D GaussianBlur(SIGMA=4.0). On the SINGLE tile that wallco displays whole,
-    that smeared a visible fuzzy cross straight through the middle of the product
-    image (and a dark halo where motifs got averaged). Worse, when the midline
-    was flagged on natural pattern content rather than a real internal seam, the
-    blur was pure cosmetic damage.
-
-    NEW method: a ~6px band, low-passed ONLY along the seam normal (1-D), so motif
-    silhouettes stay crisp, no brightness halo forms, and the heal is near-
-    invisible on false-positive rows. A real internal-grid seam is still joined;
-    a quadratic alpha mask keeps the blend opaque at the seam, zero at band edges.
-    """
-    from PIL import Image
-    img = Image.open(src_path).convert('RGB')
-    W, H = img.size
-    hm, wm = H // 2, W // 2
-    band = max(4, min(W, H) // 160)   # ~6px @1024  (was BAND_PX=12 -> 24px band)
-
-    def hsmooth(s):   # horizontal-only low-pass: vertical detail untouched
-        w, h = s.size
-        return s.resize((max(2, w // 3), h), Image.BILINEAR).resize((w, h), Image.BILINEAR)
-
-    def vsmooth(s):   # vertical-only low-pass: horizontal detail untouched
-        w, h = s.size
-        return s.resize((w, max(2, h // 3)), Image.BILINEAR).resize((w, h), Image.BILINEAR)
-
-    def qmask(size, axis):   # opaque at seam center, 0 at band edges
-        w, h = size
-        m = Image.new('L', size, 0); px = m.load()
-        if axis == 'x':
-            for x in range(w):
-                a = int(255 * (1 - abs(x - band) / band) ** 2)
-                for y in range(h): px[x, y] = a
-        else:
-            for y in range(h):
-                a = int(255 * (1 - abs(y - band) / band) ** 2)
-                for x in range(w): px[x, y] = a
-        return m
-
-    # Vertical seam at x=wm (smooth horizontally across it)
-    vstrip = img.crop((wm - band, 0, wm + band, H))
-    img.paste(hsmooth(vstrip), (wm - band, 0), qmask(vstrip.size, 'x'))
-    # Horizontal seam at y=hm (smooth vertically across it)
-    hstrip = img.crop((0, hm - band, W, hm + band))
-    img.paste(vsmooth(hstrip), (0, hm - band), qmask(hstrip.size, 'y'))
-
-    img.save(out_path, 'PNG', optimize=True)
-    return out_path.stat().st_size
-
-
-def worker(row: dict):
-    """Heal one design. Returns dict with success metadata."""
-    src_id = row['id']
-    try:
-        src_path = Path(row['local_path'])
-        if not src_path.exists():
-            return {'id': src_id, 'ok': False, 'error': 'file_missing'}
-
-        ts = int(time.time() * 1000)
-        out_filename = f'midheal_{src_id}_{ts}.png'
-        out_path = OUT_DIR / out_filename
-
-        bytes_written = heal_image(src_path, out_path)
-
-        return {
-            'id': src_id,
-            'ok': True,
-            'out_path': str(out_path),
-            'out_filename': out_filename,
-            'bytes': bytes_written,
-            'category': row['category'],
-            'kind': row['kind'],
-            'dominant_hex': row['dominant_hex'],
-            'palette_json': row['palette_json'],
-            'width_in': row['width_in'],
-            'height_in': row['height_in'],
-            'panels': row['panels'],
-        }
-    except Exception as exc:
-        return {'id': src_id, 'ok': False, 'error': f'heal_exception: {exc}'}
-
-
-def sql_escape(s: str) -> str:
-    return s.replace("'", "''")
-
-
-def persist(results: list[dict]):
-    """Batched PG transaction:
-       INSERT new spoon_all_designs rows + UPDATE source notes for each success."""
-    ok = [r for r in results if r.get('ok')]
-    if not ok:
-        return 0
-
-    sql_parts = ['BEGIN;']
-    # Build the insert statements one at a time so we capture each new id.
-    # We use a CTE per row to also append to the source's notes column in
-    # the same batch. To keep this simple + atomic, we'll INSERT all rows
-    # via a TEMP TABLE join, then UPDATE sources.
-    sql_parts.append(
-        'CREATE TEMP TABLE _heal_in (src_id BIGINT PRIMARY KEY, '
-        'kind TEXT, category TEXT, dominant_hex TEXT, palette JSONB, '
-        'width_in INTEGER, height_in INTEGER, panels INTEGER, '
-        'local_path TEXT, image_url TEXT, prompt TEXT, seed BIGINT, '
-        'notes TEXT) ON COMMIT DROP;'
-    )
-
-    seed_base = int(time.time())
-    rows_for_sql = []
-    for i, r in enumerate(ok):
-        seed = (seed_base + i) & 0x7fffffff
-        kind = sql_escape(r['kind'])
-        cat = sql_escape(r['category'])
-        dh = "NULL" if not r['dominant_hex'] else f"'{sql_escape(r['dominant_hex'])}'"
-        pal_json = r.get('palette_json') or 'null'
-        if pal_json in ('', 'None'):
-            pal_json = 'null'
-        pal = f"'{sql_escape(pal_json)}'::jsonb"
-        wi = r['width_in'] if r['width_in'] not in (None, '') else 'NULL'
-        hi = r['height_in'] if r['height_in'] not in (None, '') else 'NULL'
-        pn = r['panels'] if r['panels'] not in (None, '') else 'NULL'
-        local_path = sql_escape(r['out_path'])
-        img_url = sql_escape(f"/designs/img/{r['out_filename']}")
-        band_px = max(4, 1024 // 160)
-        prompt = sql_escape(f"mid_heal of #{r['id']}: PIL narrow sharp 1-D midline cross-fade band~{band_px}px")
-        notes = sql_escape(f"mid_heal of #{r['id']}: PIL narrow sharp 1-D midline cross-fade band~{band_px}px")
-        rows_for_sql.append(
-            f"({r['id']}, '{kind}', '{cat}', {dh}, {pal}, {wi}, {hi}, {pn}, "
-            f"'{local_path}', '{img_url}', '{prompt}', {seed}, '{notes}')"
-        )
-
-    CHUNK = 200
-    for i in range(0, len(rows_for_sql), CHUNK):
-        batch = rows_for_sql[i:i + CHUNK]
-        sql_parts.append(
-            'INSERT INTO _heal_in (src_id, kind, category, dominant_hex, palette, '
-            'width_in, height_in, panels, local_path, image_url, prompt, seed, notes) VALUES '
-            + ','.join(batch) + ';'
-        )
-
-    # Insert new rows + capture (src_id, new_id) for the source-notes update.
-    sql_parts.append("""
-WITH ins AS (
-  INSERT INTO all_designs
-    (kind, brand, width_in, height_in, panels, generator, prompt, seed,
-     image_url, local_path, dominant_hex, palette, category,
-     is_published, parent_design_id, notes)
-  SELECT
-    h.kind, 'wallco.ai', h.width_in, h.height_in, h.panels, 'pil-mid-heal',
-    h.prompt, h.seed, h.image_url, h.local_path, h.dominant_hex, h.palette,
-    h.category, FALSE, h.src_id, h.notes  -- 2026-05-26: was TRUE; now requires edges-agent PASS before promotion
-  FROM _heal_in h
-  RETURNING id AS new_id, parent_design_id AS src_id
-)
-UPDATE all_designs ad
-SET notes = CASE
-  WHEN ad.notes IS NULL OR ad.notes = '' THEN ' | MID_HEALED_BY: ' || ins.new_id::text
-  ELSE ad.notes || ' | MID_HEALED_BY: ' || ins.new_id::text
-END
-FROM ins
-WHERE ad.id = ins.src_id;
-""")
-
-    sql_parts.append('SELECT count(*) FROM _heal_in;')
-    sql_parts.append('COMMIT;')
-
-    full_sql = '\n'.join(sql_parts)
-    with tempfile.NamedTemporaryFile(mode='w', suffix='.sql', delete=False) as tf:
-        tf.write(full_sql)
-        sql_path = tf.name
-
-    r = subprocess.run(
-        ['psql', 'dw_unified', '-At', '-q', '-v', 'ON_ERROR_STOP=1', '-f', sql_path],
-        capture_output=True, text=True,
-    )
-    if r.returncode != 0:
-        print(f'PSQL FAILED:\n  stdout: {r.stdout[:500]}\n  stderr: {r.stderr[:500]}',
-              file=sys.stderr)
-        return 0
-    return len(ok)
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    ap.add_argument('--id', type=int, action='append', help='Specific id (repeatable)')
-    ap.add_argument('--all', action='store_true', help='Every matching design')
-    ap.add_argument('--grid', choices=['mids_only', 'both', 'any'], default='mids_only')
-    ap.add_argument('--limit', type=int, default=None)
-    ap.add_argument('--workers', type=int, default=8)
-    ap.add_argument('--dry-run', action='store_true', help='List targets, no writes')
-    args = ap.parse_args()
-
-    if not (args.id or args.all or args.limit):
-        ap.error('Pass --id, --all, or --limit')
-
-    targets = fetch_targets(args.grid, args.limit, args.id)
-    print(f'targets: {len(targets)} ({args.grid} cohort)', flush=True)
-    if not targets:
-        print('nothing to do', flush=True)
-        return
-
-    if args.dry_run:
-        print('--dry-run — first 10 ids:', [t['id'] for t in targets[:10]])
-        return
-
-    OUT_DIR.mkdir(parents=True, exist_ok=True)
-    t0 = time.time()
-    results = []
-    counts = {'ok': 0, 'err': 0}
-
-    with mp.Pool(args.workers) as pool:
-        for i, res in enumerate(pool.imap_unordered(worker, targets, chunksize=4), 1):
-            results.append(res)
-            if res.get('ok'):
-                counts['ok'] += 1
-            else:
-                counts['err'] += 1
-            if i % 250 == 0 or i == len(targets):
-                rate = i / max(0.001, time.time() - t0)
-                eta = (len(targets) - i) / max(0.001, rate)
-                print(
-                    f'  [{i:>5}/{len(targets)}] ok={counts["ok"]} err={counts["err"]} '
-                    f'rate={rate:.1f}/s eta={eta:.0f}s',
-                    flush=True,
-                )
-
-    pg_written = persist(results)
-    elapsed = time.time() - t0
-    print('', flush=True)
-    print(f'heal done in {elapsed:.1f}s — '
-          f'baked={counts["ok"]} err={counts["err"]} pg_inserted={pg_written}',
-          flush=True)
-
-    # Sample a few healed rows
-    sample = [r for r in results if r.get('ok')][:5]
-    for r in sample:
-        print(f"  id={r['id']:>6} → {r['out_filename']}  {r['bytes']//1024}KB", flush=True)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/scripts/promote_edge_heal.py b/scripts/promote_edge_heal.py
deleted file mode 100644
index 1291a4e..0000000
--- a/scripts/promote_edge_heal.py
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/env python3
-"""promote_edge_heal — promote edge-heal copies that pass a FRESH scan on BOTH gates.
-
-Safe by design:
-  - DRY-RUN by default. Prints what it WOULD promote. Flips nothing.
-  - --apply is required to actually set is_published=TRUE.
-  - Re-scans every candidate live (does NOT trust a stale verdict). A row is
-    promotable ONLY if it passes BOTH gates, per "only clean designs welcome":
-      1. edges-agent      PASS — no hard ΔE discontinuity at edges/midlines, AND
-      2. fuzzy_seam_scan  PASS — no SMEARED blur band at the midlines.
-    Gate #2 is mandatory because edges-agent measures discontinuity and is BLIND
-    to a blur band (a blur is smooth → low ΔE → it falsely PASSES edges-agent).
-    That false pass is exactly what put 4,022 fuzzy-cross designs live on
-    2026-05-26 (see memory feedback_wallco_heal_blur_band_edges_agent_blind).
-  - Excludes any row whose self OR parent carries a BLEED_GHOST flag (a clean
-    edge feather can't fix a ghost source).
-  - Never deploys. After --apply it PRINTS the two commands Steve runs next
-    (refresh snapshot + deploy-kamatera.sh) — it does not run them.
-
-Reversible: each promoted row can be flipped back via
-  POST /api/fix-decision/<id>  verdict=reject  (or a manual is_published=FALSE).
-
-Usage:
-  python3 scripts/promote_edge_heal.py              # dry-run preview
-  python3 scripts/promote_edge_heal.py --apply       # actually promote PASSes
-  python3 scripts/promote_edge_heal.py --limit 50    # cap (for a staged rollout)
-"""
-import argparse, subprocess, sys
-from pathlib import Path
-
-sys.path.insert(0, str(Path.home() / '.claude/skills/edges-agent/scripts'))
-import scan as edges
-# fuzzy_seam_scan lives alongside this script — the blur-band gate edges-agent can't see
-sys.path.insert(0, str(Path(__file__).resolve().parent))
-import fuzzy_seam_scan as fss
-
-
-def psql(sql):
-    r = subprocess.run(['psql', 'dw_unified', '-At', '-F', '\x1f', '-c', sql],
-                       capture_output=True, text=True, check=True)
-    return [ln.split('\x1f') for ln in r.stdout.splitlines() if ln.strip()]
-
-
-def candidates(limit):
-    sql = (
-        "SELECT a.id, a.local_path, a.parent_design_id "
-        "FROM all_designs a "
-        "WHERE a.generator='pil-edge-heal' "
-        "  AND (a.is_published IS NULL OR a.is_published=FALSE) "
-        "  AND a.local_path IS NOT NULL "
-        "  AND a.notes NOT LIKE '%BLEED_GHOST%' "
-        "  AND (a.settlement_verdict IS NULL OR a.settlement_verdict NOT LIKE '%GHOST%') "
-        "ORDER BY a.id"
-    )
-    if limit:
-        sql += f" LIMIT {int(limit)}"
-    rows = psql(sql + ";")
-    # parent ghost exclusion
-    pids = sorted({int(r[2]) for r in rows if r[2] not in ('', None)})
-    parent_ghost = set()
-    if pids:
-        pg = psql("SELECT id FROM all_designs WHERE id IN (" +
-                  ",".join(str(p) for p in pids) +
-                  ") AND (notes LIKE '%BLEED_GHOST%' OR settlement_verdict LIKE '%GHOST%');")
-        parent_ghost = {int(x[0]) for x in pg}
-    return [(int(r[0]), r[1]) for r in rows
-            if r[2] in ('', None) or int(r[2]) not in parent_ghost]
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    ap.add_argument('--apply', action='store_true', help='actually flip is_published=TRUE')
-    ap.add_argument('--limit', type=int, default=None)
-    args = ap.parse_args()
-
-    cands = candidates(args.limit)
-    print(f"candidates (pil-edge-heal, unpublished, non-ghost): {len(cands)}", flush=True)
-
-    passed = []
-    blocked_fuzzy = 0      # edges-agent PASS but fuzzy_seam_scan caught a blur band
-    for i, (rid, path) in enumerate(cands, 1):
-        try:
-            res = edges.scan_path(Path(path))
-            if res.get('ok') and res['verdict'] == 'PASS':
-                # Gate #2: reject any smeared blur band edges-agent is blind to.
-                fz = fss.scan(path)
-                if fz['verdict'] == 'PASS':
-                    passed.append(rid)
-                else:
-                    blocked_fuzzy += 1
-        except Exception:
-            pass
-        if i % 200 == 0 or i == len(cands):
-            print(f"  re-scanned {i}/{len(cands)}  both-PASS so far={len(passed)}  "
-                  f"blocked-by-fuzzy={blocked_fuzzy}", flush=True)
-
-    print(f"\nblocked by fuzzy-seam gate (edges-agent PASS but blur band present): {blocked_fuzzy}")
-    print(f"fresh-scan PASS on BOTH gates (promotable): {len(passed)}")
-    if not passed:
-        print("nothing to promote"); return
-
-    if not args.apply:
-        print("\nDRY-RUN — nothing changed. Re-run with --apply to promote.")
-        print(f"first 15 ids: {passed[:15]}")
-        return
-
-    # --apply: flip in one transaction
-    idlist = ",".join(str(x) for x in passed)
-    r = subprocess.run(
-        ['psql', 'dw_unified', '-q', '-v', 'ON_ERROR_STOP=1', '-c',
-         f"UPDATE all_designs SET is_published=TRUE WHERE id IN ({idlist});"],
-        capture_output=True, text=True)
-    if r.returncode != 0:
-        print(f"PSQL FAILED: {r.stderr[:400]}", file=sys.stderr); sys.exit(1)
-    print(f"\nPROMOTED {len(passed)} rows to is_published=TRUE.")
-    print("\nNEXT (run these yourself — deploy is Steve-gated):")
-    print("  python3 scripts/refresh_designs_snapshot.py")
-    print("  ./deploy-kamatera.sh")
-
-
-if __name__ == '__main__':
-    main()
diff --git a/scripts/promote_edge_heals.py b/scripts/promote_edge_heals.py
deleted file mode 100644
index c42a5be..0000000
--- a/scripts/promote_edge_heals.py
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/usr/bin/env python3
-"""promote_edge_heals.py — fresh edges-agent re-scan of the pil-edge-heal cohort.
-
-Per the TODO promotion gate: only verdict==PASS rows are eligible to flip
-is_published=TRUE. This script SCANS and REPORTS; pass --apply to actually
-flip the PASS rows (still requires TIFs + snapshot + deploy after).
-
-Usage:
-  python3 scripts/promote_edge_heals.py            # scan + report, write PASS ids
-  python3 scripts/promote_edge_heals.py --apply    # + flip PASS rows is_published=TRUE
-"""
-import os, sys, json, subprocess, importlib.util
-from pathlib import Path
-
-ROOT = Path(__file__).resolve().parents[1]
-APPLY = '--apply' in sys.argv
-
-# import scan_path from the hyphenated edges-scan.py
-spec = importlib.util.spec_from_file_location('edges_scan', ROOT / 'scripts' / 'edges-scan.py')
-es = importlib.util.module_from_spec(spec)
-spec.loader.exec_module(es)
-
-# fuzzy_seam_scan = the blur-band gate edges-agent is BLIND to (a smooth blur scores
-# low ΔE → falsely PASSES edges-agent; that's what put 4,022 fuzzy-cross designs live
-# 2026-05-26, see memory feedback_wallco_heal_blur_band_edges_agent_blind). A row is
-# only promotable if it passes BOTH gates.
-sys.path.insert(0, str(ROOT / 'scripts'))
-import fuzzy_seam_scan as fss
-
-# DB url from .env
-def db_url():
-    for line in (ROOT / '.env').read_text().splitlines():
-        if line.startswith('DATABASE_URL='):
-            return line.split('=', 1)[1].strip().strip('"').strip("'")
-    raise SystemExit('no DATABASE_URL')
-
-import psycopg2
-conn = psycopg2.connect(db_url())
-cur = conn.cursor()
-cur.execute("SELECT id, local_path FROM all_designs WHERE generator='pil-edge-heal' ORDER BY id;")
-rows = cur.fetchall()
-print(f"cohort: {len(rows)} pil-edge-heal rows")
-
-counts = {'PASS': 0, 'WARN': 0, 'FAIL': 0, 'ERROR': 0}
-pass_ids, fail_detail = [], []
-blocked_fuzzy = 0      # edges-agent PASS but fuzzy_seam_scan caught a blur band
-for i, (did, lp) in enumerate(rows):
-    if not lp or not Path(lp).exists():
-        counts['ERROR'] += 1
-        continue
-    try:
-        r = es.scan_path(Path(lp))
-        v = r.get('verdict', 'ERROR')
-        counts[v] = counts.get(v, 0) + 1
-        if v == 'PASS':
-            # Gate #2: reject any smeared blur band edges-agent is blind to.
-            if fss.scan(lp)['verdict'] == 'PASS':
-                pass_ids.append(did)
-            else:
-                blocked_fuzzy += 1
-    except Exception as e:
-        counts['ERROR'] += 1
-    if (i + 1) % 300 == 0:
-        print(f"  ...{i+1}/{len(rows)}  edgesPASS={counts['PASS']} WARN={counts['WARN']} "
-              f"FAIL={counts['FAIL']} blocked-by-fuzzy={blocked_fuzzy}")
-
-print(f"\n=== RESULTS ===")
-print(f"  edges-agent PASS          : {counts['PASS']}")
-print(f"  WARN                      : {counts['WARN']}")
-print(f"  FAIL                      : {counts['FAIL']}")
-print(f"  ERROR                     : {counts['ERROR']}")
-print(f"  blocked by fuzzy-seam gate: {blocked_fuzzy}  (edges PASS but blur band present)")
-print(f"  promotable (BOTH gates)   : {len(pass_ids)}")
-
-(ROOT / 'data' / 'edge-heal-pass-ids.json').write_text(json.dumps(pass_ids))
-print(f"  wrote {len(pass_ids)} PASS ids → data/edge-heal-pass-ids.json")
-
-if APPLY and pass_ids:
-    ids_csv = ','.join(str(i) for i in pass_ids)
-    cur.execute(f"UPDATE all_designs SET is_published=TRUE, "
-                f"notes = coalesce(notes,'') || ' | EDGE_HEAL_PROMOTED (fresh edges-agent + fuzzy-seam BOTH PASS)' "
-                f"WHERE id IN ({ids_csv}) AND generator='pil-edge-heal';")
-    conn.commit()
-    print(f"  APPLIED: flipped {cur.rowcount} PASS rows is_published=TRUE")
-else:
-    print("  (scan-only; re-run with --apply to flip PASS rows)")
-conn.close()
diff --git a/scripts/refresh_designs_snapshot.py b/scripts/refresh_designs_snapshot.py
index 900c2f1..94d1e57 100644
--- a/scripts/refresh_designs_snapshot.py
+++ b/scripts/refresh_designs_snapshot.py
@@ -98,10 +98,17 @@ rows = psql_json(
     # silently dropped from the snapshot until someone added it by hand). Prefix families
     # cover all current + future variants; 'stub' and other non-design generators stay out.
     # Public /api/designs still filters on is_published, so this only widens what's eligible.
+    # 2026-05-27 — RETIRED pil-mid-heal & pil-edge-heal (scripts deleted). They applied
+    # a 24px Gaussian blur band across H/2 & V/2 — that band IS a visible 'fuzzy cross'
+    # defect and breaks 'solid screen-print only'. On 2026-05-26 they put 4,022 blur-cross
+    # designs live (all pulled). Hard-excluded below so no leftover heal row (the prefix
+    # 'pil-%' would otherwise re-admit them) can ever re-enter the catalog. See memory
+    # feedback_wallco_heal_blur_band_edges_agent_blind.
     "FROM spoon_all_designs WHERE ("
     "generator LIKE 'replicate%' OR generator LIKE 'gemini-2.5-flash-image%' "
     "OR generator LIKE 'pil-%' OR generator IN ('comfy','flux')"
-    ") ORDER BY id) t;"
+    ") AND generator NOT IN ('pil-mid-heal','pil-edge-heal') "
+    "ORDER BY id) t;"
 )
 
 import re as _re
diff --git a/scripts/vision_curate_edgeheals.py b/scripts/vision_curate_edgeheals.py
deleted file mode 100644
index 6a1b1f6..0000000
--- a/scripts/vision_curate_edgeheals.py
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/env python3
-"""vision_curate_edgeheals.py — local-vision CLEAN/DEFECT gate for edge-heal designs.
-
-The edges-agent only measures seam ΔE; it passes washed-out/smeared/no-motif
-designs (click-through agent found ~46% of edges-PASS rows are visual defects).
-This adds a Qwen2.5-VL (Mac1 ollama, $0) legibility gate on top.
-
-Usage:
-  python3 scripts/vision_curate_edgeheals.py --validate   # run on the 24 labeled, report accuracy
-  python3 scripts/vision_curate_edgeheals.py --ids a,b,c   # classify specific ids
-  python3 scripts/vision_curate_edgeheals.py --all         # classify every PASS id (data/edge-heal-pass-ids.json)
-"""
-import sys, os, json, base64, io, time, urllib.request
-from pathlib import Path
-from PIL import Image
-import psycopg2
-
-ROOT = Path(__file__).resolve().parents[1]
-OLLAMA = "http://100.94.103.98:11434/api/generate"
-MODEL = "qwen2.5vl:7b"
-
-PROMPT = ("You are grading one AI-generated repeating wallpaper tile for a premium catalog. "
-          "Some tiles came out fine; others were corrupted during processing. Decide which.\n"
-          "Muted, tone-on-tone, subtle or low-contrast color is INTENTIONAL good design — never a "
-          "defect by itself. Judge by whether the MOTIF survived.\n"
-          "Answer CLEAN if a deliberate, recognizable repeating subject is clearly visible (e.g. an "
-          "animal, bird, insect, flower, leaf, damask scroll, geometric figure, stripe, scene) — even "
-          "if subtle or muted.\n"
-          "Answer DEFECT if the subject is wrecked: melted/smeared into streaks, dissolved into "
-          "mottled or marbled blobs, blurred into mush, stippled into noise, or the tile is just a "
-          "formless haze/smudge with no real motif.\n"
-          "If you genuinely cannot recognize what the motif is supposed to be, answer DEFECT.\n"
-          "Reply with EXACTLY ONE WORD: CLEAN or DEFECT.")
-
-ENGINE = 'gemini' if '--gemini' in sys.argv else 'ollama'
-
-def _env(key):
-    for line in (ROOT/'.env').read_text().splitlines():
-        if line.startswith(key+'='):
-            return line.split('=',1)[1].strip().strip('"').strip("'")
-
-def db_url():
-    return _env('DATABASE_URL')
-
-GKEY = _env('GEMINI_API_KEY')
-GMODEL = os.environ.get('GCURATE_MODEL', 'gemini-2.5-flash')
-
-def classify_gemini(path):
-    im = Image.open(path).convert('RGB'); im.thumbnail((640,640))
-    buf = io.BytesIO(); im.save(buf,'JPEG',quality=88)
-    b64 = base64.b64encode(buf.getvalue()).decode()
-    url = f"https://generativelanguage.googleapis.com/v1beta/models/{GMODEL}:generateContent?key={GKEY}"
-    is_pro = 'pro' in GMODEL
-    gencfg = {"temperature":0, "maxOutputTokens": 1200 if is_pro else 20}
-    if not is_pro:
-        gencfg["thinkingConfig"] = {"thinkingBudget":0}  # flash: disable thinking
-    body = json.dumps({"contents":[{"parts":[{"text":PROMPT},
-            {"inline_data":{"mime_type":"image/jpeg","data":b64}}]}],
-            "generationConfig":gencfg}).encode()
-    req = urllib.request.Request(url, data=body, headers={'Content-Type':'application/json'})
-    r = json.loads(urllib.request.urlopen(req, timeout=120).read())
-    cand = r.get('candidates',[{}])[0]
-    parts = cand.get('content',{}).get('parts',[{}])
-    txt = (parts[0].get('text','') if parts else '').strip().upper()
-    return 'DEFECT' if 'DEFECT' in txt else 'CLEAN' if 'CLEAN' in txt else f'?{txt[:20]}'
-
-def classify(path):
-    if ENGINE == 'gemini':
-        return classify_gemini(path)
-    return classify_ollama(path)
-
-def classify_ollama(path):
-    im = Image.open(path).convert('RGB')
-    im.thumbnail((640,640))
-    buf = io.BytesIO(); im.save(buf, 'JPEG', quality=88)
-    b64 = base64.b64encode(buf.getvalue()).decode()
-    body = json.dumps({"model":MODEL,"prompt":PROMPT,"images":[b64],"stream":False,
-                       "options":{"temperature":0,"num_predict":8}}).encode()
-    req = urllib.request.Request(OLLAMA, data=body, headers={'Content-Type':'application/json'})
-    r = json.loads(urllib.request.urlopen(req, timeout=120).read())
-    txt = (r.get('response','') or '').strip().upper()
-    return 'DEFECT' if 'DEFECT' in txt else 'CLEAN' if 'CLEAN' in txt else f'?{txt[:20]}'
-
-def paths_for(ids):
-    conn=psycopg2.connect(db_url()); cur=conn.cursor()
-    cur.execute("SELECT id, local_path FROM all_designs WHERE id = ANY(%s)", (list(ids),))
-    d=dict(cur.fetchall()); conn.close(); return d
-
-if __name__ == '__main__':
-    if '--validate' in sys.argv:
-        CLEAN={50603,50895,51050,51651,51769,51874,52004,52774,52884,53213,53311}
-        DEFECT={50748,51179,51297,51408,51519,52116,52320,52433,52548,52666,53104}
-        ids=sorted(CLEAN|DEFECT)
-        p=paths_for(ids); correct=0; conf={'TP':0,'TN':0,'FP':0,'FN':0}
-        for i in ids:
-            truth='CLEAN' if i in CLEAN else 'DEFECT'
-            pred=classify(p[i])
-            ok = (pred==truth)
-            correct+=ok
-            if truth=='CLEAN' and pred=='CLEAN': conf['TN']+=1   # TN = correctly kept
-            elif truth=='DEFECT' and pred=='DEFECT': conf['TP']+=1 # TP = correctly caught defect
-            elif truth=='CLEAN' and pred=='DEFECT': conf['FP']+=1  # FP = good design wrongly rejected
-            elif truth=='DEFECT' and pred=='CLEAN': conf['FN']+=1  # FN = defect wrongly published (WORST)
-            print(f"  {i} truth={truth:<6} pred={pred:<8} {'OK' if ok else 'XX'}")
-        print(f"\naccuracy {correct}/{len(ids)} = {100*correct/len(ids):.0f}%  | {conf}")
-        print("  FN (defect→published) is the dangerous error; want 0.")
-    else:
-        if '--all' in sys.argv:
-            ids=json.loads((ROOT/'data'/'edge-heal-pass-ids.json').read_text())
-        else:
-            arg=[a for a in sys.argv if a.startswith('--ids')]
-            ids=[int(x) for x in sys.argv[sys.argv.index('--ids')+1].split(',')]
-        p=paths_for(ids); clean=[]; defect=[]; t0=time.time()
-        for n,i in enumerate(ids):
-            if i not in p: continue
-            try: pred=classify(p[i])
-            except Exception as e: pred=f'ERR'
-            (clean if pred=='CLEAN' else defect).append(i)
-            if (n+1)%100==0:
-                el=time.time()-t0
-                print(f"  ...{n+1}/{len(ids)} clean={len(clean)} defect={len(defect)} ({el:.0f}s, {el/(n+1):.2f}s/img)")
-        (ROOT/'data'/'edgeheal-vision-clean.json').write_text(json.dumps(clean))
-        (ROOT/'data'/'edgeheal-vision-defect.json').write_text(json.dumps(defect))
-        print(f"\nCLEAN={len(clean)}  DEFECT={len(defect)}  → data/edgeheal-vision-clean.json")

← 7d932a6 promote gate: require fuzzy_seam_scan PASS in addition to ed  ·  back to Wallco Ai  ·  drunk-animals: allow marijuana imagery (joints/buds/cannabis adae983 →