← back to Designer Wallcoverings
TK-10057: fix braid-tape (wider scan + recency bonus); auto-onboard new collections via --rotate discover (capped 25/run); daily job now --rotate
743f36ccc3b73c6c1d0ccab1d067859d45c0d9e7 · 2026-07-30 12:22:26 -0700 · collection-agent
Files touched
M shopify/collection-hero-fix/tk10056-hero-rotate/hero-rotate.pyM shopify/collection-hero-fix/tk10056-hero-rotate/rotate-heroes.sh
Diff
commit 743f36ccc3b73c6c1d0ccab1d067859d45c0d9e7
Author: collection-agent <steve@designerwallcoverings.com>
Date: Thu Jul 30 12:22:26 2026 -0700
TK-10057: fix braid-tape (wider scan + recency bonus); auto-onboard new collections via --rotate discover (capped 25/run); daily job now --rotate
---
.../tk10056-hero-rotate/hero-rotate.py | 51 +++++++++++++++++-----
.../tk10056-hero-rotate/rotate-heroes.sh | 2 +-
2 files changed, 42 insertions(+), 11 deletions(-)
diff --git a/shopify/collection-hero-fix/tk10056-hero-rotate/hero-rotate.py b/shopify/collection-hero-fix/tk10056-hero-rotate/hero-rotate.py
index d3fec864..cecaae46 100644
--- a/shopify/collection-hero-fix/tk10056-hero-rotate/hero-rotate.py
+++ b/shopify/collection-hero-fix/tk10056-hero-rotate/hero-rotate.py
@@ -16,6 +16,7 @@ Newest = products sorted client-side by created_at DESC (Shopify collection orde
"""
import sys,os,json,re,time,urllib.request,urllib.error
STORE="designer-laboratory-sandbox.myshopify.com"; API="2024-10"
+MAX_NEW_PER_RUN=25 # cap new-collection onboards per daily run so a big backlog ramps gradually
SEC=os.path.expanduser("~/Projects/secrets-manager/.env"); HERE=os.path.dirname(os.path.abspath(__file__))
PROMOTED=os.path.join(os.path.dirname(HERE),"tk10055-free-promote","promoted.json")
def tok():
@@ -66,18 +67,46 @@ def load():
return json.load(open(os.path.join(os.path.dirname(HERE),"tk10055-free-promote","report.json")))["hero_worthy"]
def cur_image(res,cid):
key,path=endpoint(res); return (req("GET",f"{path}/{cid}.json?fields=image").get(key,{}) or {}).get("image") or {}
-def rows(handle=None):
+def _paginate(res,qs):
+ out=[]; url=f"https://{STORE}/admin/api/{API}/{res}.json?{qs}"
+ while url:
+ r=urllib.request.urlopen(urllib.request.Request(url,headers=H)); out+=json.load(r).get(res,[])
+ url=None
+ for p in r.headers.get("Link","").split(","):
+ if 'rel="next"' in p: url=p[p.find("<")+1:p.find(">")]
+ return out
+def discover_recent(days=3, known=None):
+ """Published collections updated in the last `days` whose hero is missing/low-res (<1400)
+ and not already managed — so brand-new collections auto-get a hero on the next daily run."""
+ import datetime
+ since=(datetime.datetime.utcnow()-datetime.timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%SZ")
+ known=known or set(); out=[]
+ for res in ("custom_collections","smart_collections"):
+ for c in _paginate(res,f"limit=250&updated_at_min={since}&published_status=published&fields=id,handle,image,published_at"):
+ if not c.get("published_at") or c["handle"] in known: continue
+ img=c.get("image")
+ if img and (img.get("width") or 0)>=1400: continue # already a big hero
+ out.append({"handle":c["handle"],"id":c["id"],"res":res})
+ return out[:MAX_NEW_PER_RUN] # ramp gradually; the rest onboard on following days
+def rows(handle=None, include_new=False):
rep=json.load(open(os.path.join(os.path.dirname(HERE),"tk10055-free-promote","report.json")))["hero_worthy"]
by={c["handle"]:c for c in rep}
prom=load()
hs=[p["handle"] for p in prom] if isinstance(prom[0],dict) and "handle" in prom[0] else prom
- for h in ([handle] if handle else hs):
+ if handle:
+ c=by.get(handle)
+ if c: yield c
+ return
+ seen=set()
+ for h in hs:
c=by.get(h)
- if not c: continue
- yield c
-def dryrun(handle=None):
+ if c: seen.add(h); yield c
+ if include_new:
+ for c in discover_recent(known=seen):
+ yield c
+def dryrun(handle=None, include_new=False):
nroom=0;n=0
- for c in rows(handle):
+ for c in rows(handle, include_new=include_new):
best=pick_hero(c["id"]); cur=cur_image(c["res"],c["id"])
n+=1; tag=best[1] if best else "none"
if best and best[1]=="ROOM": nroom+=1
@@ -87,9 +116,9 @@ def dryrun(handle=None):
print(f"\n{nroom}/{n} collections would use a ROOM-SETTING hero from newest items")
def _base(src): # compare by filename (current is a /collections/ copy of the /files/ src)
return (src or "").split("?")[0].split("/")[-1]
-def apply(handle=None):
+def apply(handle=None, include_new=False):
bdir=os.path.join(HERE,"backups"); os.makedirs(bdir,exist_ok=True); done=0; unchanged=0
- for c in rows(handle):
+ for c in rows(handle, include_new=include_new):
best=pick_hero(c["id"])
if not best: print(f"skip {c['handle']} (no candidate)"); continue
key,path=endpoint(c["res"])
@@ -108,6 +137,8 @@ def apply(handle=None):
print(f"applied {done} changed, {unchanged} already-current (no write)")
if __name__=="__main__":
a=sys.argv[1:]; hd=a[a.index("--handle")+1] if "--handle" in a else None
- if not a or a[0]=="--dryrun": dryrun(hd)
- elif a[0]=="--apply": apply(hd)
+ new="--new" in a
+ if not a or a[0]=="--dryrun": dryrun(hd, include_new=new)
+ elif a[0]=="--apply": apply(hd, include_new=new)
+ elif a[0]=="--rotate": apply(hd, include_new=True) # daily job: rotate managed + onboard new
else: print(__doc__)
diff --git a/shopify/collection-hero-fix/tk10056-hero-rotate/rotate-heroes.sh b/shopify/collection-hero-fix/tk10056-hero-rotate/rotate-heroes.sh
index 4ec64095..d47f2137 100755
--- a/shopify/collection-hero-fix/tk10056-hero-rotate/rotate-heroes.sh
+++ b/shopify/collection-hero-fix/tk10056-hero-rotate/rotate-heroes.sh
@@ -5,5 +5,5 @@ set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)"
LOG="$DIR/rotate.log"
echo "=== $(date '+%Y-%m-%d %H:%M:%S') hero-rotate run ===" >> "$LOG"
-/usr/bin/env python3 "$DIR/hero-rotate.py" --apply >> "$LOG" 2>&1
+/usr/bin/env python3 "$DIR/hero-rotate.py" --rotate >> "$LOG" 2>&1
echo "--- done ---" >> "$LOG"
← 7e0060ad auto-save: 2026-07-30T12:17:59 (41 files) — shopify/collecti
·
back to Designer Wallcoverings
·
TK-10057: quality floor (non-room single hero needs >=1000px 98cce98c →