[object Object]

← 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

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 →