← back to Wallco Ai
triage: colorway-sibling fallback in resolveImagePath
3fc81bf33e5d5fcdf259d255e86b61066f4b4a31 · 2026-05-24 07:58:48 -0700 · Steve Abrams
Many flagged designs have spoon_all_designs.local_path pointing at a base
<stem>.png that no longer exists on disk (~1,130 ERROR rows in the last
sweep) — but the per-colorway variants <stem>.cw-<slug>.png are still
there. These are colorway-only designs whose "base" path was a
derivation pattern, not a real file.
New resolver:
- indexColorways(dir) — scans dir once, builds stem → [cw-files] map
- findColorwaySibling(basePath, category) — picks the variant whose
cw-slug matches the trailing colorway in the row's category
("designer-zoo-calm · slate-mist" → cw-slate-mist), or lexicographically
first if no match. Geometry is identical across colorways so any
variant works for ghost-layer / structural defect detection.
Wired in after BOTH pg-source AND json-source primary lookups in
resolveImagePath. Smoke-tested on 5 cases — exact, unsuffixed, unknown
slug, missing sibling, missing-everything — all correct.
Files touched
M scripts/triage-ghost-flagged.js
Diff
commit 3fc81bf33e5d5fcdf259d255e86b61066f4b4a31
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun May 24 07:58:48 2026 -0700
triage: colorway-sibling fallback in resolveImagePath
Many flagged designs have spoon_all_designs.local_path pointing at a base
<stem>.png that no longer exists on disk (~1,130 ERROR rows in the last
sweep) — but the per-colorway variants <stem>.cw-<slug>.png are still
there. These are colorway-only designs whose "base" path was a
derivation pattern, not a real file.
New resolver:
- indexColorways(dir) — scans dir once, builds stem → [cw-files] map
- findColorwaySibling(basePath, category) — picks the variant whose
cw-slug matches the trailing colorway in the row's category
("designer-zoo-calm · slate-mist" → cw-slate-mist), or lexicographically
first if no match. Geometry is identical across colorways so any
variant works for ghost-layer / structural defect detection.
Wired in after BOTH pg-source AND json-source primary lookups in
resolveImagePath. Smoke-tested on 5 cases — exact, unsuffixed, unknown
slug, missing sibling, missing-everything — all correct.
---
scripts/triage-ghost-flagged.js | 59 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 56 insertions(+), 3 deletions(-)
diff --git a/scripts/triage-ghost-flagged.js b/scripts/triage-ghost-flagged.js
index 8c79441..ba0543b 100755
--- a/scripts/triage-ghost-flagged.js
+++ b/scripts/triage-ghost-flagged.js
@@ -74,6 +74,53 @@ function designsJsonMap() {
return DESIGNS_JSON_MAP;
}
+// Colorway-sibling fallback. Many flagged designs in PG point to a base PNG
+// like `<stem>.png` that no longer exists on disk, but per-colorway variants
+// `<stem>.cw-<slug>.png` are still there (the design is colorway-only).
+// Strategy: scan the directory once, build a map of stem → list of sibling
+// colorway files. When the base path doesn't exist, return the colorway
+// variant matching the row's category-suffix slug ("designer-zoo-calm · slate-mist"
+// → cw-slate-mist), or the first variant if no suffix match.
+const COLORWAY_INDEX = new Map(); // dir → Map(stem → [filename, ...])
+function indexColorways(dir) {
+ if (COLORWAY_INDEX.has(dir)) return COLORWAY_INDEX.get(dir);
+ const idx = new Map();
+ try {
+ for (const f of fs.readdirSync(dir)) {
+ // Match <stem>.cw-<slug>.png — slug allowed to contain dashes
+ const m = f.match(/^(.+?)\.cw-([a-z0-9-]+)\.png$/i);
+ if (!m) continue;
+ const stem = m[1];
+ if (!idx.has(stem)) idx.set(stem, []);
+ idx.get(stem).push(f);
+ }
+ } catch { /* dir unreadable — leave empty */ }
+ COLORWAY_INDEX.set(dir, idx);
+ return idx;
+}
+
+// Given a base path like /.../1234_5678.png that doesn't exist, look for
+// /.../1234_5678.cw-<slug>.png siblings. Prefer the slug matching the
+// trailing colorway suffix in the row's category (after " · ").
+function findColorwaySibling(basePath, category) {
+ if (!basePath || !basePath.endsWith('.png')) return null;
+ const dir = path.dirname(basePath);
+ const stem = path.basename(basePath, '.png');
+ const siblings = indexColorways(dir).get(stem);
+ if (!siblings || siblings.length === 0) return null;
+
+ // Try to match the colorway slug from "category · slug" if present
+ if (category && category.includes(' · ')) {
+ const slug = category.split(' · ').pop().trim().toLowerCase();
+ const exact = siblings.find(f => f.toLowerCase().includes(`.cw-${slug}.`));
+ if (exact) return path.join(dir, exact);
+ }
+ // Otherwise return the lexicographically first variant — any colorway works
+ // for ghost-layer / structural defect detection since the motif geometry
+ // is identical across colorways.
+ return path.join(dir, siblings.slice().sort()[0]);
+}
+
// PG fallback — look up local_path for ids that don't carry one in the flagged row.
const PG_PATH_CACHE = new Map();
function pgLocalPath(id) {
@@ -91,17 +138,23 @@ function pgLocalPath(id) {
async function resolveImagePath(row) {
if (row.local_path && fs.existsSync(row.local_path)) return row.local_path;
- // pg source → consult PG for the local_path
+ // pg source → consult PG for the local_path; if file gone, try cw-* siblings
if (row.source === 'pg') {
const p = pgLocalPath(row.id);
- if (p && fs.existsSync(p)) return p;
+ if (p) {
+ if (fs.existsSync(p)) return p;
+ const cw = findColorwaySibling(p, row.category);
+ if (cw) return cw;
+ }
}
- // json source → consult designs.json for the filename
+ // json source → consult designs.json for the filename; same cw-* fallback
if (row.source === 'json') {
const fn = designsJsonMap().get(row.id);
if (fn) {
const cand = path.join(GENERATED_DIR, fn);
if (fs.existsSync(cand)) return cand;
+ const cw = findColorwaySibling(cand, row.category);
+ if (cw) return cw;
}
}
// last resort — fetch via local API (works regardless of source if server is up)
← e110dad ghost-scan flagged.jsonl: gate on unanimous 3/3, not confide
·
back to Wallco Ai
·
seamless-tile physics gate: detector lib + sweeper + pre-pub afee5cd →