← back to Fentucci Naturals

scripts/build-drafts.py

94 lines

#!/usr/bin/env python3
"""Build data/shopify-drafts.jsonl from enriched dw_unified.tokiwa_catalog.

STRUCTURE DECISION: matches the existing live Fentucci Naturals family (148
DWNAT products) = ONE PRODUCT PER COLORWAY (504 drafts), option "Size" with
Sample + Per Yard variants. Quote-only posture per the RIGO precedent (Yard
$0.00 + quotes/Needs-Price tags, Sample $4.25 sku {DW_SKU}-Sample).

Title = "<Pattern> <Color> | Fentucci Naturals" (family convention; the word
"Wallpaper" is banned house-wide — "Wallcovering" only).

Rows with settlement_status != 'clear' are EXCLUDED and listed on stderr.
FILES ONLY — no API calls, no shopify_products writes.
"""
import json, os, re, subprocess, sys

HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
OUT = os.path.join(ROOT, "data", "shopify-drafts.jsonl")
CONN = "host=/tmp dbname=dw_unified"

def psql(sql):
    return subprocess.run(["psql", CONN, "-Atc", sql], capture_output=True, text=True, check=True).stdout.strip()

rows = json.loads(psql(
    "select coalesce(json_agg(row_to_json(t))::text,'[]') from (select * from tokiwa_catalog "
    "where enriched_at is not null order by dw_sku) t"))

slug = lambda s: re.sub(r"-+", "-", re.sub(r"[^a-z0-9]+", "-", s.lower())).strip("-")
excluded, written = [], 0

with open(OUT, "w") as f:
    for r in rows:
        if r["settlement_status"] != "clear":
            excluded.append((r["dw_sku"], r["pattern_name"], r["color_name"], r["settlement_status"]))
            continue
        title = f"{r['pattern_name']} {r['color_name']} | Fentucci Naturals"
        body = (
            f"<p>{r['description']}</p>"
            f"<table>"
            f"<tr><td><strong>Pattern</strong></td><td>{r['pattern_name']}</td></tr>"
            f"<tr><td><strong>Color</strong></td><td>{r['color_name']}</td></tr>"
            f"<tr><td><strong>Collection</strong></td><td>Fentucci Naturals</td></tr>"
            f"<tr><td><strong>Material</strong></td><td>{r['material']}</td></tr>"
            f"<tr><td><strong>Type</strong></td><td>Sidewall</td></tr>"
            f"<tr><td><strong>Pricing</strong></td><td>By quote — order a memo sample to see the material in hand</td></tr>"
            f"</table>")
        rec = {
            "mfr_sku": r["mfr_sku"],
            "sku": r["dw_sku"],
            "local_image_path": os.path.join(ROOT, r["image_local"]),
            "product": {
                "title": title,
                "handle": f"{slug(r['pattern_name'])}-{slug(r['color_name'])}-fentucci-naturals",
                "vendor": "Fentucci Naturals",
                "product_type": "Wallcovering",
                "status": "draft",
                "tags": ", ".join(r["tags"]),
                "body_html": body,
                "options": [{"name": "Size"}],
                "variants": [
                    {"sku": f"{r['dw_sku']}-Sample", "price": "4.25", "option1": "Sample",
                     "taxable": True, "requires_shipping": True, "inventory_management": None},
                    # inventory_policy deny: hard stop on the $0.00 quote-only yard
                    # variant ever being purchasable if a tag edit strips `quotes`
                    {"sku": r["dw_sku"], "price": "0.00", "option1": "Per Yard",
                     "taxable": True, "requires_shipping": True, "inventory_management": None,
                     "inventory_policy": "deny"},
                ],
            },
            "metafields": [
                {"namespace": "custom", "key": "manufacturer_sku", "type": "single_line_text_field", "value": r["mfr_sku"]},
                {"namespace": "global", "key": "Brand", "type": "single_line_text_field", "value": "Fentucci Naturals"},
                {"namespace": "custom", "key": "material", "type": "multi_line_text_field", "value": r["material"]},
                {"namespace": "custom", "key": "color_family", "type": "single_line_text_field", "value": r["specs"]["color_family"]},
                # explicit metafield gates, independent of tags: the rotation
                # activator exempts `quotes`-TAGGED products from the price>0
                # gate, so a bulk tag edit could otherwise flip $0.00 Per Yard
                # variants live ($4.25-on-GMC risk). These survive tag edits.
                {"namespace": "custom", "key": "price_mode", "type": "single_line_text_field", "value": "quote_only"},
                {"namespace": "custom", "key": "needs_width", "type": "boolean", "value": "true"},
            ],
        }
        # 5-field + image gate: refuse to write an incomplete draft
        assert r["description"] and len(r["tags"]) >= 2 and os.path.exists(rec["local_image_path"]), r["dw_sku"]
        f.write(json.dumps(rec, ensure_ascii=False) + "\n")
        written += 1

print(f"wrote {written} draft payloads -> {OUT}")
if excluded:
    print(f"EXCLUDED {len(excluded)} (settlement review):", file=sys.stderr)
    for e in excluded:
        print("  " + " | ".join(map(str, e)), file=sys.stderr)