[object Object]

← back to Wallco Ai

pilot-build-tifs.py: source from design image_url, fall back to by-id

4b22f53c7930aebc5ba2d791e3e1dead9f13b1c4 · 2026-06-02 09:19:22 -0700 · Steve Abrams

The 408 'unbuildable' designs (mostly designer-scenic) were a false negative —
they have images served via a direct /designs/img/<file>.png that the by-id
route can't resolve (no local_path), so by-id 404'd. Now build() tries the
design's own image_url first (passed from designs.json in --all), then by-id.
First 200 wins. Turns the 408 into buildable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 4b22f53c7930aebc5ba2d791e3e1dead9f13b1c4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 2 09:19:22 2026 -0700

    pilot-build-tifs.py: source from design image_url, fall back to by-id
    
    The 408 'unbuildable' designs (mostly designer-scenic) were a false negative —
    they have images served via a direct /designs/img/<file>.png that the by-id
    route can't resolve (no local_path), so by-id 404'd. Now build() tries the
    design's own image_url first (passed from designs.json in --all), then by-id.
    First 200 wins. Turns the 408 into buildable.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 scripts/pilot-build-tifs.py | 64 ++++++++++++++++++++++++++++++---------------
 1 file changed, 43 insertions(+), 21 deletions(-)

diff --git a/scripts/pilot-build-tifs.py b/scripts/pilot-build-tifs.py
index 2c9d788..8752a1f 100644
--- a/scripts/pilot-build-tifs.py
+++ b/scripts/pilot-build-tifs.py
@@ -27,19 +27,35 @@ ROOT = Path(__file__).resolve().parents[1]
 TIF_DIR = ROOT / "data" / "tif"
 
 
-def build(base, design_id, force):
+def _fetch(base, path_or_url):
+    url = path_or_url if path_or_url.startswith("http") else (base + path_or_url)
+    req = urllib.request.Request(url, headers={"User-Agent": "pilot-build-tifs"})
+    with urllib.request.urlopen(req, timeout=30) as r:
+        if r.status != 200:
+            raise RuntimeError(f"HTTP {r.status}")
+        return r.read()
+
+
+def build(base, design_id, force, image_url=None):
     out = TIF_DIR / f"{design_id}.tif"
     if out.exists() and not force:
         return True, f"{design_id}: exists ({out.stat().st_size:,} B) — --force to rebuild"
-    url = f"{base}/designs/img/by-id/{design_id}"
-    try:
-        req = urllib.request.Request(url, headers={"User-Agent": "pilot-build-tifs"})
-        with urllib.request.urlopen(req, timeout=30) as r:
-            if r.status != 200:
-                return False, f"{design_id}: source HTTP {r.status}"
-            data = r.read()
-    except Exception as e:
-        return False, f"{design_id}: fetch fail: {e}"
+    # Source candidates, in order: the design's own image_url (older designs use
+    # a direct /designs/img/<file>.png that the by-id route can't resolve — no
+    # local_path), then the by-id route. First 200 wins.
+    candidates = []
+    if image_url and isinstance(image_url, str) and ("/designs/img/" in image_url or image_url.startswith("http")):
+        candidates.append(image_url)
+    candidates.append(f"/designs/img/by-id/{design_id}")
+    data, errs = None, []
+    for c in candidates:
+        try:
+            data = _fetch(base, c)
+            break
+        except Exception as e:
+            errs.append(f"{c.split('/')[-1]}:{e}")
+    if data is None:
+        return False, f"{design_id}: fetch fail: {' | '.join(errs)}"
     try:
         from PIL import Image
         Image.MAX_IMAGE_PIXELS = None
@@ -73,43 +89,49 @@ def main():
     ap.add_argument("ids", nargs="*")
     a = ap.parse_args()
 
+    # items = list of (id, image_url|None). image_url lets build() source the
+    # design's actual PNG when the by-id route can't resolve it (older designs).
     if a.all or a.all_including_unpublished:
         import json
         p = ROOT / "data" / "designs.json"
         arr = json.loads(p.read_text())
         if isinstance(arr, dict):
             arr = arr.get("designs", [])
-        ids = []
+        seen, items = set(), []
         for d in arr:
             if a.all and d.get("is_published") is False:
                 continue
             try:
-                ids.append(int(d["id"]))
+                did = int(d["id"])
             except (KeyError, ValueError, TypeError):
-                pass
-        ids = sorted(set(ids))
+                continue
+            if did in seen:
+                continue
+            seen.add(did)
+            items.append((did, d.get("image_url")))
+        items.sort(key=lambda t: t[0])
     else:
-        ids = []
+        items = []
         for raw in a.ids:
             try:
-                ids.append(int(raw))
+                items.append((int(raw), None))
             except ValueError:
                 print(f"{raw}: not an int")
     if a.start:
-        ids = ids[a.start:]
+        items = items[a.start:]
     if a.limit:
-        ids = ids[:a.limit]
+        items = items[:a.limit]
 
-    total = len(ids)
+    total = len(items)
     print(f"[pilot-build-tifs] {total} ids to consider · free {free_gb():.1f} GB · base {a.base}", flush=True)
     ok_all = True
     built = skipped = failed = 0
-    for i, did in enumerate(ids, 1):
+    for i, (did, image_url) in enumerate(items, 1):
         if free_gb() < a.min_free_gb:
             print(f"ABORT: free disk {free_gb():.1f} GB < --min-free-gb {a.min_free_gb}. "
                   f"Built {built}, stopped at id {did} (#{i}/{total}).", flush=True)
             return 2
-        ok, msg = build(a.base, did, a.force)
+        ok, msg = build(a.base, did, a.force, image_url)
         if ok:
             built += 1
             if "exists" in msg:

← fe12244 PDP: universal theme customizer — render-a-room + free drag-  ·  back to Wallco Ai  ·  PDP theme switcher: fix dropdown on bare /design/:id — treat 1044ed0 →