← back to Wallco Ai
scripts/curate-monterey-batch.py
118 lines
#!/usr/bin/env python3
"""
curate-monterey-batch.py — READ-ONLY selector for the first hi-res Monterey
mural batch. Picks N published Monterey scenes spread across the hue wheel (so
the launch line has visual range, not 12 near-identical colorways) and writes a
manifest the operator feeds to build-mural-master.py.
NO writes to the DB, NO spend. Just a SELECT + a JSON manifest.
Usage:
python3 scripts/curate-monterey-batch.py # top 12 → data/mural-batch-monterey.json
python3 scripts/curate-monterey-batch.py --n 25
"""
import argparse
import importlib.util
import json
from pathlib import Path
from psycopg2.extras import RealDictCursor
ROOT = Path(__file__).resolve().parents[1]
_spec = importlib.util.spec_from_file_location("buildtif", str(ROOT / "scripts" / "build-tif.py"))
_bt = importlib.util.module_from_spec(_spec); _spec.loader.exec_module(_bt)
def hue(hexstr):
import colorsys
s = (hexstr or "").lstrip("#")
if len(s) != 6:
return -1.0
try:
r, g, b = (int(s[i:i+2], 16) / 255 for i in (0, 2, 4))
except ValueError:
return -1.0
mx, mn = max(r, g, b), min(r, g, b)
if mx - mn < 0.06:
return 1000 + (1 - mx) # greys → end, light→dark
return colorsys.rgb_to_hls(r, g, b)[0] * 360
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--n", type=int, default=12)
ap.add_argument("--out", default=str(ROOT / "data" / "mural-batch-monterey.json"))
a = ap.parse_args()
conn = _bt.db()
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("""
SELECT id, dominant_hex, local_path
FROM all_designs
WHERE category ILIKE 'monterey%' AND is_published
AND local_path IS NOT NULL
ORDER BY id DESC
""")
rows = [r for r in cur.fetchall() if Path(r["local_path"]).exists()]
conn.close()
# Exclude designs that ALREADY have a 12x11ft master built (on Henry / the
# masters dir) so a "build more" batch never re-builds what's done.
built = set()
try:
mdir = _bt.ROOT / "data" / "mural-masters"
henry = Path("/Volumes/Henry/wallco-ai-archive/mural-masters")
scan = henry if henry.is_dir() else mdir
for d in scan.iterdir():
if (d / "summary.json").exists():
try: built.add(int(d.name))
except ValueError: pass
except Exception:
pass
before = len(rows)
rows = [r for r in rows if r["id"] not in built]
print(f"excluding {before - len(rows)} already-built masters; {len(rows)} candidates remain")
# Spread across the hue wheel: bucket into N hue slots, take the newest
# (already id-DESC) per slot, then fill any short slots from the remainder.
rows.sort(key=lambda r: hue(r["dominant_hex"]))
picks, seen = [], set()
if rows:
step = max(1, len(rows) // a.n)
for i in range(0, len(rows), step):
if len(picks) >= a.n:
break
r = rows[i]
if r["id"] not in seen:
picks.append(r); seen.add(r["id"])
for r in rows: # backfill if buckets came up short
if len(picks) >= a.n:
break
if r["id"] not in seen:
picks.append(r); seen.add(r["id"])
manifest = {
"category": "monterey-mural",
"n": len(picks),
"spec": {"print_ft": 12, "height_ft": 11, "bleed_in": 6, "panels": [24, 36, 54]},
"selection": "hue-spread across published Monterey (visual range)",
"ids": [p["id"] for p in picks],
"items": [{"id": p["id"], "dominant_hex": p["dominant_hex"],
"local_path": p["local_path"]} for p in picks],
"batch_command": "for id in " + " ".join(str(p["id"]) for p in picks) +
"; do python3 scripts/build-mural-master.py $id; done",
"estimate_command": f"python3 scripts/build-mural-master.py --estimate {len(picks)}",
}
Path(a.out).write_text(json.dumps(manifest, indent=2))
print(f"curated {len(picks)} Monterey murals (hue-spread) → {a.out}")
for p in picks:
print(f" #{p['id']:6} {p['dominant_hex']}")
print(f"\nbatch is ready — review then run:\n {manifest['estimate_command']}\n"
f" {manifest['batch_command']}")
return 0
if __name__ == "__main__":
import sys
sys.exit(main())