← 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)