← back to Designer Wallcoverings
gate-readiness.py: read-only Activation-Gate pre-flight over merged Lilycolor records — quantifies publish-readiness per dimension (surfaced 1580 title-less SKUs as the main non-cost blocker)
fedfa292d63c00701405288453af7ffc3cc2acf5 · 2026-07-01 14:46:06 -0700 · Steve
Files touched
A onboarding/sangetsu-lilycolor/scripts/gate-readiness.py
Diff
commit fedfa292d63c00701405288453af7ffc3cc2acf5
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 1 14:46:06 2026 -0700
gate-readiness.py: read-only Activation-Gate pre-flight over merged Lilycolor records — quantifies publish-readiness per dimension (surfaced 1580 title-less SKUs as the main non-cost blocker)
---
.../sangetsu-lilycolor/scripts/gate-readiness.py | 134 +++++++++++++++++++++
1 file changed, 134 insertions(+)
diff --git a/onboarding/sangetsu-lilycolor/scripts/gate-readiness.py b/onboarding/sangetsu-lilycolor/scripts/gate-readiness.py
new file mode 100644
index 00000000..550c5281
--- /dev/null
+++ b/onboarding/sangetsu-lilycolor/scripts/gate-readiness.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+"""
+gate-readiness.py — Activation-Gate PRE-FLIGHT over the merged Lilycolor records.
+
+READ-ONLY. Runs the DW Activation-Gate checks (CLAUDE.md "Never Activate SKU
+Without Specs + Description + ALL Images" + title guards + cost) against every
+merged record and reports, per dimension, how many SKUs pass — so we know exactly
+what publish will look like the moment Steve supplies net cost, and which SKUs are
+blocked for some OTHER reason (missing title, etc.).
+
+This NEVER writes, activates, or touches dw_unified/Shopify. Pure reporting.
+
+Dimensions (per merged record):
+ width global.width present + non-empty (HARD spec req)
+ image >=1 product image (image_count>0 / preview_images)
+ title a pattern title exists (title_ja or title_en) (no Unknown/blank)
+ title_guard title has no banned "Wallpaper" and no "Unknown"
+ description a non-placeholder description (enrichment design.description)
+ cost cost_confirmed == True (Steve-gated blocker)
+
+Bottom line = SKUs that clear EVERYTHING except cost (i.e. would go live the
+instant cost lands) vs SKUs blocked on a non-cost dimension that needs work.
+
+Usage: gate-readiness.py [merged_jsonl]
+ defaults to staging/lilycolor-merged-063026A.jsonl
+"""
+import json
+import os
+import sys
+
+HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+DEFAULT = os.path.join(HERE, "staging", "lilycolor-merged-063026A.jsonl")
+BANNED = "wallpaper"
+BAD_TITLE_TOKENS = ("unknown", "page not found", "404", "not found", "undefined", "null")
+
+
+def has_width(d):
+ return bool(str(d.get("width") or "").strip())
+
+
+def has_image(d):
+ return (d.get("image_count") or 0) > 0 or bool(d.get("preview_images"))
+
+
+def title_text(d):
+ return (d.get("title_en") or d.get("title_ja") or "").strip()
+
+
+def has_title(d):
+ return bool(title_text(d))
+
+
+def title_guard_ok(d):
+ t = title_text(d).lower()
+ if not t:
+ return False
+ if BANNED in t:
+ return False
+ return not any(tok in t for tok in BAD_TITLE_TOKENS)
+
+
+def has_description(d):
+ desc = (d.get("design") or {}).get("description") or ""
+ desc = desc.strip()
+ if not desc:
+ return False
+ return not any(tok in desc.lower() for tok in BAD_TITLE_TOKENS)
+
+
+def cost_ok(d):
+ return d.get("cost_confirmed") is True
+
+
+def main():
+ path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT
+ total = 0
+ counts = {k: 0 for k in ("width", "image", "title", "title_guard", "description", "cost")}
+ ready_but_cost = 0 # clears everything except cost
+ fully_ready = 0 # clears everything incl. cost
+ blocked_noncost = 0 # fails at least one non-cost dimension
+ block_reasons = {"width": 0, "image": 0, "title": 0, "title_guard": 0, "description": 0}
+
+ with open(path) as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+ d = json.loads(line)
+ total += 1
+ checks = {
+ "width": has_width(d),
+ "image": has_image(d),
+ "title": has_title(d),
+ "title_guard": title_guard_ok(d),
+ "description": has_description(d),
+ "cost": cost_ok(d),
+ }
+ for k, v in checks.items():
+ if v:
+ counts[k] += 1
+ noncost_ok = all(checks[k] for k in block_reasons)
+ if noncost_ok:
+ ready_but_cost += 1
+ if checks["cost"]:
+ fully_ready += 1
+ else:
+ blocked_noncost += 1
+ for k in block_reasons:
+ if not checks[k]:
+ block_reasons[k] += 1
+
+ def pct(n):
+ return f"{100*n//total if total else 0}%"
+
+ print(f"Activation-Gate readiness — {os.path.basename(path)} (total SKUs: {total})\n")
+ print(" per-dimension pass counts:")
+ for k in ("width", "image", "title", "title_guard", "description", "cost"):
+ print(f" {k:12} {counts[k]:5} / {total} ({pct(counts[k])})")
+ print()
+ print(f" READY except cost (go-live the instant cost lands): {ready_but_cost} ({pct(ready_but_cost)})")
+ print(f" fully ready (incl. cost): {fully_ready}")
+ print(f" blocked on a NON-cost dimension: {blocked_noncost} ({pct(blocked_noncost)})")
+ print(" non-cost block reasons (a SKU can hit several):")
+ for k, n in sorted(block_reasons.items(), key=lambda x: -x[1]):
+ if n:
+ print(f" {k:12} {n}")
+ print()
+ print(" NOTE: cost is universally unconfirmed (Steve-gated Activation blocker);")
+ print(" description coverage climbs as local enrichment completes.")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
← b7c30130 auto-save: 2026-07-01T14:36:50 (6 files) — DW-Programming/Im
·
back to Designer Wallcoverings
·
gate-readiness.py: check error-tokens only in pattern-name s d76076d1 →