[object Object]

← back to Designer Wallcoverings

hero manifest finalizer: socket-timeout hardening reuse + private-label handle sanitizer + clean .md summary

8bb35f8f99fd7ac292ed34af45749cbc26a9feb5 · 2026-06-23 13:20:56 -0700 · Steve

Files touched

Diff

commit 8bb35f8f99fd7ac292ed34af45749cbc26a9feb5
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jun 23 13:20:56 2026 -0700

    hero manifest finalizer: socket-timeout hardening reuse + private-label handle sanitizer + clean .md summary
---
 shopify/finalize-hero-manifest.py | 102 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 102 insertions(+)

diff --git a/shopify/finalize-hero-manifest.py b/shopify/finalize-hero-manifest.py
new file mode 100644
index 00000000..938f43db
--- /dev/null
+++ b/shopify/finalize-hero-manifest.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+"""
+Post-process the collection-hero manifest:
+  1. Sanitize private-label / true-vendor tokens out of any product-handle text
+     surfaced in the manifest (the hero IMAGE URL is a generic CDN hash and is
+     safe; only the chosen_product_handle can carry an upstream name). We keep
+     the real product id for the apply write, but mask the handle string.
+  2. Emit a clean human-readable .md summary with full counts + flagged buckets.
+
+READ-ONLY w.r.t. Shopify. Rewrites the manifest .json + .md in place.
+"""
+import os, json, re, datetime
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+MANIFEST = os.path.join(HERE, "audit", "collection-hero-manifest.json")
+MANIFEST_MD = os.path.join(HERE, "audit", "collection-hero-manifest.md")
+
+# upstream/private-label names that must NEVER be surfaced in clean output
+LEAK = re.compile(
+    r"wallquest|chesapeake|nextwall|seabrook|brewster|command54|"
+    r"saybrook|sister[-_ ]?parish|desima|carlsten|nicolette[-_ ]?mayer|"
+    r"a-?s-?creation|as[-_]creation",
+    re.I,
+)
+
+
+def mask(s):
+    if not s:
+        return s
+    return LEAK.sub("[private-label]", s)
+
+
+def main():
+    m = json.load(open(MANIFEST))
+    masked = 0
+    for r in m.get("rows", []):
+        for k in ("chosen_product_handle",):
+            v = r.get(k)
+            if v and LEAK.search(v):
+                r[k] = mask(v)
+                masked += 1
+    for f in m.get("flagged", []):
+        for k in ("title",):
+            pass  # collection titles are customer-facing & already clean
+
+    m["sanitized_at"] = datetime.datetime.now().isoformat()
+    m["private_label_handles_masked"] = masked
+    json.dump(m, open(MANIFEST, "w"), indent=2)
+
+    c = m["counts"]
+    fl = m.get("flagged", [])
+    empty = [x for x in fl if x.get("member_image_count") == 0
+             or x.get("reason", "").startswith("no candidate")]
+    failv = [x for x in fl if "vision" in x.get("reason", "")]
+
+    md = []
+    md.append("# Collection Hero Image Manifest — Designer Wallcoverings")
+    md.append(f"\n_generated {m.get('generated_at')} | sanitized {m['sanitized_at']}_")
+    md.append(f"_store {m['store']} · API {m['api']} · vision {m['model']}_\n")
+    md.append("## Counts\n")
+    md.append(f"| bucket | n |")
+    md.append(f"|---|---|")
+    md.append(f"| **RESOLVED** (room-setting hero found inside collection, vision-confirmed → write candidate) | **{c['resolved']}** |")
+    md.append(f"| **ALREADY-GOOD** (existing collection.image already a room-setting → kept, no change) | **{c['already_good']}** |")
+    md.append(f"| **FLAGGED** (no room-setting image inside collection) | **{c['flagged']}** |")
+    md.append(f"|   ↳ empty collection (0 member products) | {len(empty)} |")
+    md.append(f"|   ↳ has products but only flat swatches (no interior shot) | {len(failv)} |")
+    md.append(f"\n**Vision spend:** ${m['vision']['cost_usd']:.4f} over {m['vision']['calls']} Gemini calls.")
+    md.append(f"**Private-label handles masked in this manifest:** {masked}.\n")
+
+    md.append("## FLAGGED — need a generated render or a Steve decision\n")
+    md.append("These collections have NO room-setting image sourced from inside them. "
+              "Per the standing rule we did NOT fill them with a swatch or an outside image. "
+              "Resolution path: generate a render (room-setting-generator / "
+              "commercial-room-setting-generator) for a member product, OR (for empty "
+              "collections) assign products / decide to leave on the gradient fallback.\n")
+    md.append("### Empty collections (0 products — assign products or generate)\n")
+    for x in sorted(empty, key=lambda r: r["handle"]):
+        md.append(f"- `/{x['handle']}` — {x['title']} [{x['kind']}]")
+    md.append("\n### Has products but only swatches (need a room render)\n")
+    for x in sorted(failv, key=lambda r: r["handle"]):
+        md.append(f"- `/{x['handle']}` — {x['title']} [{x['kind']}]")
+
+    md.append("\n## RESOLVED — write candidates (first 60)\n")
+    md.append("| collection | kind | source product (id) | hero image |")
+    md.append("|---|---|---|---|")
+    for r in m["rows"][:60]:
+        img = r["chosen_image_src"].split("/")[-1].split("?")[0]
+        md.append(f"| `/{r['handle']}` | {r['kind']} | {r.get('chosen_product_handle','')[:40]} "
+                  f"(`{r['chosen_product_id']}`) | {img[:48]} |")
+    if len(m["rows"]) > 60:
+        md.append(f"\n_…and {len(m['rows'])-60} more resolved rows in the JSON._")
+
+    open(MANIFEST_MD, "w").write("\n".join(md) + "\n")
+    print(f"sanitized {masked} private-label handle(s)")
+    print(f"counts: {c}")
+    print(f"flagged: {len(empty)} empty + {len(failv)} swatch-only")
+    print(f"wrote {MANIFEST_MD}")
+
+
+if __name__ == "__main__":
+    main()

← 144635ea Add REGEN mode to room generator: replace our 557 rooms at c  ·  back to Designer Wallcoverings  ·  harden hero pipeline: global 45s socket timeout (no hung soc 4f5bbe67 →