← back to Wallco Ai
seam-wave: build manifest — 3 animal-forward FAIL roots (43199/54385/55272)
04386639c6beb132257268fdc0989b4a08bfc4f3 · 2026-06-11 19:02:41 -0700 · Steve Abrams
Scanned 2448 eligible published drunk-animals roots (not in curator's 85, not
retired) with the canonical both-axes gate verify-edge-seamless.py: 848 PASS /
1586 WARN / 7 distinct FAIL. Animal-forward pre-filter (free local llava) kept
3 (macaw/peacock/elephant), dropped 4 (2 pastoral toile, 1 animal-minority,
1 glassware-dominant). Adds scripts/seam-fail-scan.py (read-only scanner).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
A data/seam-fail-scan.jsonM data/seam-wave-roots.txtA scripts/seam-fail-scan.py
Diff
commit 04386639c6beb132257268fdc0989b4a08bfc4f3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jun 11 19:02:41 2026 -0700
seam-wave: build manifest — 3 animal-forward FAIL roots (43199/54385/55272)
Scanned 2448 eligible published drunk-animals roots (not in curator's 85, not
retired) with the canonical both-axes gate verify-edge-seamless.py: 848 PASS /
1586 WARN / 7 distinct FAIL. Animal-forward pre-filter (free local llava) kept
3 (macaw/peacock/elephant), dropped 4 (2 pastoral toile, 1 animal-minority,
1 glassware-dominant). Adds scripts/seam-fail-scan.py (read-only scanner).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
data/seam-fail-scan.json | 84 +++++++++++++++++++++++++++++
data/seam-wave-roots.txt | 27 ++++++----
scripts/seam-fail-scan.py | 134 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 236 insertions(+), 9 deletions(-)
diff --git a/data/seam-fail-scan.json b/data/seam-fail-scan.json
new file mode 100644
index 0000000..fd0f4d4
--- /dev/null
+++ b/data/seam-fail-scan.json
@@ -0,0 +1,84 @@
+{
+ "eligible_after_skip": 2448,
+ "skipped_in_queue_or_excluded": 85,
+ "tally": {
+ "PASS": 848,
+ "WARN": 1586,
+ "FAIL": 14,
+ "MISSING": 0,
+ "ERROR": 0
+ },
+ "fail_count": 14,
+ "fails": [
+ {
+ "id": 43197,
+ "overall_max": 38.77,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1779805176441_993290578.png"
+ },
+ {
+ "id": 43197,
+ "overall_max": 38.77,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1779805176441_993290578.png"
+ },
+ {
+ "id": 50566,
+ "overall_max": 36.36,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1779811502549_1280451696.png"
+ },
+ {
+ "id": 50566,
+ "overall_max": 36.36,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1779811502549_1280451696.png"
+ },
+ {
+ "id": 50565,
+ "overall_max": 35.01,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1779810576790_159605057.png"
+ },
+ {
+ "id": 50565,
+ "overall_max": 35.01,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1779810576790_159605057.png"
+ },
+ {
+ "id": 43199,
+ "overall_max": 20.13,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1779806462956_732266097.png"
+ },
+ {
+ "id": 43199,
+ "overall_max": 20.13,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1779806462956_732266097.png"
+ },
+ {
+ "id": 55272,
+ "overall_max": 16.14,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1780336621951_1793762070.png"
+ },
+ {
+ "id": 55272,
+ "overall_max": 16.14,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1780336621951_1793762070.png"
+ },
+ {
+ "id": 54686,
+ "overall_max": 16.01,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1780181582926_458477544.png"
+ },
+ {
+ "id": 54686,
+ "overall_max": 16.01,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1780181582926_458477544.png"
+ },
+ {
+ "id": 54385,
+ "overall_max": 13.16,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1780088222096_113927942.png"
+ },
+ {
+ "id": 54385,
+ "overall_max": 13.16,
+ "path": "/Users/stevestudio2/Projects/wallco-ai/data/generated/1780088222096_113927942.png"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/data/seam-wave-roots.txt b/data/seam-wave-roots.txt
index 52ea12a..54a23d2 100644
--- a/data/seam-wave-roots.txt
+++ b/data/seam-wave-roots.txt
@@ -1,13 +1,22 @@
-# seam-wave-roots.txt — the remaining flagged roots to seam-fix, one id per line.
+# seam-wave-roots.txt — the remaining ANIMAL-FORWARD FAIL roots to seam-fix.
# Consumed by scripts/seam-wave.sh in waves of WAVE_SIZE (default 20).
# Lines starting with '#' and blank lines are ignored.
#
-# HOW TO POPULATE (next session, after the 5-root sample is curated + approved):
-# The 85 roots already in the seam-fix curator each have a PASS candidate — they
-# are DONE. This manifest is for the remaining FAIL roots NOT yet processed.
-# Generate the list from a seam scan of published roots, e.g.:
-# python3 scripts/edges-batch-scan.py # or fuzzy_seam_scan.py / seam_break_scan.py
-# then write the FAIL root ids here (one per line), excluding any root that already
-# has a PASS-both-axes candidate in data/seam-fix-queue.jsonl.
+# BUILT 2026-06-11 by scripts/seam-fail-scan.py + the local-llava animal-dominance
+# pre-filter (scripts/vision-contains-local.py). Method:
+# 1. Scanned the 2448 eligible published drunk-animals roots that are NOT already
+# in the seam-fix curator (85 queue roots) and NOT retired (54076/54266) with
+# the canonical both-axes gate scripts/verify-edge-seamless.py.
+# → 848 PASS · 1586 WARN · 14 FAIL rows = 7 DISTINCT FAIL roots.
+# 2. Animal-forward pre-filter (free local llava): "is a single animal the largest,
+# most dominant element?" — drops glassware-dominant / animal-minority roots
+# the ControlNet pipeline can't rescue.
+# KEEP (animal-forward): 43199 macaw · 54385 peacock · 55272 elephant
+# DROP: 43197 / 50565 pastoral toile (no dominant animal); 50566 busy pastoral
+# (animal-minority); 54686 glassware-dominant (mai-tai/hurricane glass).
#
-# Until this file has real ids below, scripts/seam-wave.sh GATE 2 will refuse to run.
+# Result: 3 roots = 1 wave (WAVE_SIZE=20). Most of the original FAIL universe was
+# already swept into the curator's 85; the strict both-axes gate rates the rest WARN.
+43199
+54385
+55272
diff --git a/scripts/seam-fail-scan.py b/scripts/seam-fail-scan.py
new file mode 100644
index 0000000..9a22e6b
--- /dev/null
+++ b/scripts/seam-fail-scan.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+"""seam-fail-scan.py — read-only seam scan over eligible published drunk-animals
+roots, to (re)build the remaining-FAIL universe for the seam-wave manifest.
+
+Mirrors the scan that produced the 392-FAIL / 80-worst-FAIL curator seed
+(commit 0236a2b): the eligible universe is published, parent NULL, has a
+local_path that resolves on disk, not user_removed, needs_fixing IS NULL,
+category=drunk-animals. Each resolved PNG is run through the canonical
+true-toroidal both-axes gate (verify-edge-seamless.verify_path). We tally
+PASS/WARN/FAIL/MISSING and emit the FAIL root ids (with overall_max for ranking).
+
+Excludes (so the output is the REMAINING work):
+ • roots already present in data/seam-fix-queue.jsonl (the ~85 in the curator —
+ DONE per the runbook), and
+ • roots in data/seam-wave-exclude.txt (retired glassware-dominant).
+
+Writes data/seam-fail-scan.json = {tally, fails:[{id,overall_max,path}], ...}.
+DOES NOT touch the DB beyond a single read, never flips is_published, no deploy.
+
+Usage: python3 scripts/seam-fail-scan.py
+"""
+import json, os, sys
+from pathlib import Path
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+GEN = os.path.join(ROOT, 'data', 'generated')
+QUEUE = os.path.join(ROOT, 'data', 'seam-fix-queue.jsonl')
+EXCLUDE = os.path.join(ROOT, 'data', 'seam-wave-exclude.txt')
+OUT = os.path.join(ROOT, 'data', 'seam-fail-scan.json')
+
+sys.path.insert(0, os.path.join(ROOT, 'scripts'))
+from importlib import import_module
+sfv = import_module('seam-fix-variants')
+edgeverify = import_module('verify-edge-seamless')
+
+
+def resolve(lp):
+ if not lp:
+ return None
+ cands = [lp]
+ if not lp.endswith('.png'):
+ cands.append(lp + '.png')
+ base = os.path.basename(lp.rstrip('/'))
+ cands += [os.path.join(GEN, base),
+ os.path.join(GEN, base + '.png'),
+ os.path.join(GEN, base, 'final.png')]
+ return next((c for c in cands if os.path.isfile(c)), None)
+
+
+def queue_roots():
+ s = set()
+ if os.path.exists(QUEUE):
+ for line in open(QUEUE):
+ line = line.strip()
+ if not line:
+ continue
+ try:
+ r = json.loads(line)
+ except Exception:
+ continue
+ if r.get('root_id') is not None:
+ s.add(int(r['root_id']))
+ return s
+
+
+def excluded_roots():
+ s = set()
+ if os.path.exists(EXCLUDE):
+ for line in open(EXCLUDE):
+ line = line.split('#', 1)[0].strip()
+ if line.isdigit():
+ s.add(int(line))
+ return s
+
+
+def main():
+ skip = queue_roots() | excluded_roots()
+ out = sfv.psql('''
+SELECT id, COALESCE(local_path,'')
+FROM all_designs
+WHERE is_published=true
+ AND COALESCE(user_removed,false)=false
+ AND parent_design_id IS NULL
+ AND needs_fixing_at IS NULL
+ AND category='drunk-animals'
+ AND local_path IS NOT NULL AND local_path<>''
+ORDER BY id;
+''')
+ rows = []
+ for line in out.splitlines():
+ if '|' not in line:
+ continue
+ sid, lp = line.split('|', 1)
+ try:
+ rid = int(sid)
+ except ValueError:
+ continue
+ if rid in skip:
+ continue
+ rows.append((rid, lp))
+
+ tally = {'PASS': 0, 'WARN': 0, 'FAIL': 0, 'MISSING': 0, 'ERROR': 0}
+ fails = []
+ total = len(rows)
+ for i, (rid, lp) in enumerate(rows):
+ path = resolve(lp)
+ if not path:
+ tally['MISSING'] += 1
+ continue
+ try:
+ ev = edgeverify.verify_path(Path(path))
+ except Exception:
+ tally['ERROR'] += 1
+ continue
+ v = ev.get('verdict', 'ERROR')
+ tally[v] = tally.get(v, 0) + 1
+ if v == 'FAIL':
+ ax = ev.get('axes', {})
+ hm = (ax.get('horizontal') or {}).get('best', 0) or 0
+ vm = (ax.get('vertical') or {}).get('best', 0) or 0
+ fails.append({'id': rid, 'overall_max': round(max(hm, vm), 2), 'path': path})
+ if (i + 1) % 100 == 0:
+ print(f" scanned {i+1}/{total} tally={tally}", file=sys.stderr)
+
+ fails.sort(key=lambda r: -r['overall_max'])
+ report = {'eligible_after_skip': total, 'skipped_in_queue_or_excluded': len(skip),
+ 'tally': tally, 'fail_count': len(fails), 'fails': fails}
+ with open(OUT, 'w') as f:
+ json.dump(report, f, indent=2)
+ print(json.dumps({'tally': tally, 'fail_count': len(fails), 'out': OUT}, indent=2))
+
+
+if __name__ == '__main__':
+ main()
← f1efeda Add autonomous overnight seam-fix wave runner (DTD-gated, se
·
back to Wallco Ai
·
seam-wave: generalize the retry-loop generator to arbitrary 2fed439 →