← back to Maya Width Fix
authoritative_source.py
128 lines
#!/usr/bin/env python3
"""
authoritative_source.py — TK-11029 authoritative-field mapping + SOURCE PROOF.
READ-ONLY. Re-fetches each Maya Romanoff collection page ($0 plain-fetch GET,
no browser/proxy/creds), extracts the spec-table Width value, classifies it with
widthlib, and (if the DB is reachable) proves the already-applied maya_catalog
widths equal the live authoritative source.
Emits: authoritative-source-proof-<ts>.json
Never writes the DB. Network egress = read-only GETs of public product pages.
"""
import urllib.request, re, json, subprocess, sys, datetime, os
from widthlib import classify_width, is_meta_pollution
SLUGS = [
"island-weaves", "craze", "natural-elements", "braided-hemp", "ajiro-burst-of-happiness",
"entwine-inlet-linen", "serigraph-cityscape", "beadazzled-leaf", "entwine-tranquil-linen",
"bouquet", "entwine-serene-silk", "wisping-weaves-montauk", "beadazzled",
"wisping-weaves-cape-may", "cozy-nestle", "cozy-bed-fellow", "coco-chenille",
"beadazzled-geode", "hurly-burly-ii", "ajiro-sunburst",
]
UA = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/120 Safari/537.36")
WIDTH_RX = re.compile(r'>\s*Width\s*</td>\s*<td[^>]*>\s*([^<]{1,120}?)\s*</td>', re.I)
HERE = os.path.dirname(os.path.abspath(__file__))
def fetch_width(slug, attempts=3):
url = f"https://www.mayaromanoff.com/collections/{slug}"
last = None
for i in range(attempts):
try:
req = urllib.request.Request(url, headers={"User-Agent": UA})
with urllib.request.urlopen(req, timeout=30) as r:
html = r.read().decode("utf-8", "ignore")
status = r.status
break
except urllib.error.HTTPError as e:
# 404 is authoritative (discontinued) — do not retry
return {"slug": slug, "http_status": e.code, "width": None, "error": f"HTTP {e.code}"}
except Exception as e:
last = str(e)
import time as _t; _t.sleep(1.5 * (i + 1))
else:
return {"slug": slug, "http_status": None, "width": None, "error": f"transient: {last}"}
m = WIDTH_RX.search(html)
val = re.sub(r"\s+", " ", m.group(1).strip()) if m else None
dec, wi = classify_width(val)
return {"slug": slug, "http_status": status, "width": val,
"decision": dec, "width_inches": wi}
def db_rows():
"""Current maya_catalog state per collection slug (read-only)."""
sql = ("SELECT split_part(product_url,'/collections/',2) AS slug, "
"count(*), "
"count(*) FILTER (WHERE width LIKE '%device-width%'), "
"string_agg(DISTINCT coalesce(width_inches::text,'NULL'), '|') "
"FROM maya_catalog WHERE dw_sku LIKE 'DWMR-8%' GROUP BY slug")
r = subprocess.run(["psql", "-h", "/tmp", "-d", "dw_unified", "-At", "-F", "\t", "-c", sql],
capture_output=True, text=True)
if r.returncode != 0:
return None
out = {}
for line in r.stdout.strip().split("\n"):
if not line:
continue
slug, n, poll, wis = line.split("\t")
out[slug] = {"rows": int(n), "polluted": int(poll), "db_width_inches": wis}
return out
def main():
ts = datetime.datetime.now(datetime.timezone.utc).isoformat()
src = [fetch_width(s) for s in SLUGS]
for s in src:
print(f" {s['slug']:28s} http={str(s.get('http_status')):>4} "
f"{s.get('decision','-'):<14} wi={s.get('width_inches')} {s.get('width')}")
db = db_rows()
proof = []
if db:
for s in src:
d = db.get(s["slug"])
if not d:
continue
match = None
if s.get("decision") == "SINGLE":
# every recovered row for this slug should carry the parsed inch value
match = (d["db_width_inches"] == s["width_inches"])
elif s.get("decision") == "MULTI_WIDTH":
match = (d["db_width_inches"] == "NULL") # ambiguous by design
elif s.get("http_status") == 404:
match = (d["polluted"] > 0) # correctly left flagged
proof.append({"slug": s["slug"], "decision": s.get("decision"),
"http_status": s.get("http_status"),
"src_width_inches": s.get("width_inches"),
"db_width_inches": d["db_width_inches"],
"db_rows": d["rows"], "db_polluted": d["polluted"],
"db_matches_source": match})
single_ok = all(p["db_matches_source"] for p in proof
if p["decision"] == "SINGLE" and p["db_matches_source"] is not None)
out = {
"ticket": "TK-11029", "generated_at": ts, "mode": "READ-ONLY source proof",
"source": "mayaromanoff.com collection spec-table Width cell",
"slugs_fetched": len(src),
"http_200": sum(1 for s in src if s.get("http_status") == 200),
"http_404": sum(1 for s in src if s.get("http_status") == 404),
"single_slugs": sum(1 for s in src if s.get("decision") == "SINGLE"),
"multi_width_slugs": sum(1 for s in src if s.get("decision") == "MULTI_WIDTH"),
"authoritative_source": src,
"db_reconciliation": proof,
"all_single_slugs_match_db": bool(single_ok) if proof else None,
}
path = os.path.join(HERE, f"authoritative-source-proof-{ts.replace(':', '-')}.json")
json.dump(out, open(path, "w"), indent=2)
print(f"\nSOURCE PROOF -> {path}")
print(f" 200s={out['http_200']} 404s={out['http_404']} "
f"single={out['single_slugs']} multi={out['multi_width_slugs']} "
f"all_single_match_db={out['all_single_slugs_match_db']}")
return 0
if __name__ == "__main__":
sys.exit(main())