← back to Dw Five Field Step0
add set-lec-min5.py — 5-yard minimum for all active LEC (Le Embossed Croc) products, both metafield levels
f6bbd13ece66fb74b00670f0fd13947ad6926810 · 2026-07-02 08:50:47 -0700 · Steve Abrams
Root cause: min=5 sat on the legacy no-s product key the theme ignores; variant metafields absent entirely. Upserts theme key global.v_prods_quantity_order_min=5 + variant custom keys (Sample stays 1/1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Files touched
A scripts/set-lec-min5.py
Diff
commit f6bbd13ece66fb74b00670f0fd13947ad6926810
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 2 08:50:47 2026 -0700
add set-lec-min5.py — 5-yard minimum for all active LEC (Le Embossed Croc) products, both metafield levels
Root cause: min=5 sat on the legacy no-s product key the theme ignores; variant metafields absent entirely. Upserts theme key global.v_prods_quantity_order_min=5 + variant custom keys (Sample stays 1/1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
scripts/set-lec-min5.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/scripts/set-lec-min5.py b/scripts/set-lec-min5.py
new file mode 100644
index 0000000..d249c4b
--- /dev/null
+++ b/scripts/set-lec-min5.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+"""Set 5-yard minimum on all ACTIVE LEC- (Le Embossed Croc) products.
+
+Steve 2026-07-02: "update all lec- patterns to 5 yard minimum".
+Root cause found on LEC-6000: min=5 was set on the LEGACY no-s product key
+(global.v_prod_quantity_order_min) which the live theme IGNORES, and variant
+metafields were absent entirely — so the storefront never enforced the minimum.
+
+Per the update-product-min skill (verified against theme 2026-06-30), BOTH
+levels must be set:
+ product : global.v_prods_quantity_order_min = 5 (with-s — the theme key)
+ global.v_prods_quantity_order_units = 1 (only if missing)
+ variant : non-Sample -> custom.v_prod_quantity_order_min = 5
+ custom.v_prods_quantity_order_units = 1
+ Sample -> 1 / 1 (skill hard rule)
+
+Idempotent (upsert: PUT existing metafield id, else POST). REST rate-limited
+at ~2/s via 0.55s sleeps. Audit JSON written next to this script's out/.
+"""
+import json, subprocess, time, urllib.request
+
+TOKEN = "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"
+API = "https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-01"
+OUT = "/Users/stevestudio2/Projects/dw-five-field-step0/out/lec-min5-result.json"
+MIN_YD, STEP = "5", "1"
+
+def req(method, path, body=None):
+ r = urllib.request.Request(API + path, method=method,
+ headers={"X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json"},
+ data=json.dumps(body).encode() if body else None)
+ time.sleep(0.55)
+ with urllib.request.urlopen(r, timeout=30) as resp:
+ return json.loads(resp.read())
+
+def upsert(owner_path, namespace, key, value, existing):
+ cur = next((m for m in existing if m["namespace"] == namespace and m["key"] == key), None)
+ if cur:
+ if str(cur["value"]) == value:
+ return "ok"
+ req("PUT", f"/metafields/{cur['id']}.json",
+ {"metafield": {"id": cur["id"], "value": value, "type": "single_line_text_field"}})
+ return f"updated {cur['value']}->{value}"
+ req("POST", owner_path + "/metafields.json",
+ {"metafield": {"namespace": namespace, "key": key, "value": value,
+ "type": "single_line_text_field"}})
+ return f"created ={value}"
+
+ids = subprocess.run(["psql", "-d", "dw_unified", "-tAc",
+ "select replace(shopify_id,'gid://shopify/Product/','') from shopify_products "
+ "where (sku ilike 'LEC%' or handle ilike 'lec-%') and status='ACTIVE' order by sku"],
+ capture_output=True, text=True).stdout.split()
+
+print(f"{len(ids)} active LEC products")
+audit = []
+for i, pid in enumerate(ids, 1):
+ try:
+ p = req("GET", f"/products/{pid}.json")["product"]
+ pmf = req("GET", f"/products/{pid}/metafields.json?limit=250")["metafields"]
+ entry = {"product_id": pid, "title": p["title"], "actions": {}}
+ entry["actions"]["product.min"] = upsert(f"/products/{pid}", "global",
+ "v_prods_quantity_order_min", MIN_YD, pmf)
+ # units: only fill if missing — don't clobber an existing step
+ if not any(m["namespace"] == "global" and m["key"] == "v_prods_quantity_order_units" for m in pmf):
+ entry["actions"]["product.units"] = upsert(f"/products/{pid}", "global",
+ "v_prods_quantity_order_units", STEP, pmf)
+ for v in p["variants"]:
+ vmf = req("GET", f"/variants/{v['id']}/metafields.json?limit=250")["metafields"]
+ is_sample = "sample" in (v["title"] or "").lower() or (v["sku"] or "").lower().endswith("-sample")
+ mn, st = ("1", "1") if is_sample else (MIN_YD, STEP)
+ tag = "sample" if is_sample else "yard"
+ entry["actions"][f"variant.{v['sku']}.min({tag})"] = upsert(
+ f"/variants/{v['id']}", "custom", "v_prod_quantity_order_min", mn, vmf)
+ entry["actions"][f"variant.{v['sku']}.units({tag})"] = upsert(
+ f"/variants/{v['id']}", "custom", "v_prods_quantity_order_units", st, vmf)
+ entry["status"] = "done"
+ except Exception as e:
+ entry = {"product_id": pid, "status": "error", "error": str(e)[:200]}
+ audit.append(entry)
+ print(f"[{i}/{len(ids)}] {pid} {entry.get('title','')[:50]} -> {entry['status']}")
+
+with open(OUT, "w") as f:
+ json.dump(audit, f, indent=1)
+done = sum(1 for a in audit if a["status"] == "done")
+print(f"\nDONE {done}/{len(audit)} — audit: {OUT}")
← c2e408c drain: raise late-slot request to 150 (21/22/23h) so the end
·
back to Dw Five Field Step0
·
audit: LEC 5-yard minimum applied to all 70 active products 014512f →