← back to Dw Kravet Hires
scripts/verify-batchA-tier2.py
123 lines
#!/usr/bin/env python3
"""TK-12097 Batch A — verify the 99 tier-2 (colorway-unverified) kravet.com heroes.
Tier-2 = a low-res product whose kravet.com hero filename's COLOR matched but whose
PATTERN base differed (legacy Brunschwig/Kravet cross-reference codes). Very likely the
same pattern+color, but unproven. This confirms it the cheap, strong way:
Fetch kravet.com/<naive_slug(mfr)> (follow redirects). A row is VERIFIED iff:
(1) the page returns 200 and did NOT redirect away to /search or a category (the
final URL still resolves to the requested pattern-color slug), AND
(2) the page's isMain hero == the URL phase-2 recorded (same asset), AND
(3) the requested COLOR suffix appears on the product page identity.
Otherwise the row stays UNVERIFIED (dropped — never auto-swapped).
$0 (kravet.com reads only). Writes batchA-tier2-verified.json (safe to add to the map)
and batchA-tier2-still-unverified.json.
"""
import json, re, os, subprocess, sys
from concurrent.futures import ThreadPoolExecutor, as_completed
HERE = os.path.dirname(os.path.abspath(__file__))
PROJ = os.path.dirname(HERE)
D = os.path.join(PROJ, "data/tk12097")
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36"
def naive_slug(mfr):
b = re.sub(r'[./_]', '-', mfr.strip().lower())
b = re.sub(r'-+', '-', b).strip('-')
return re.sub(r'-0$', '', b)
def fetch(url):
# return (final_url, http_code, html)
r = subprocess.run(["curl", "-sL", "-A", UA, "--max-time", "30",
"-w", "\n%{url_effective} %{http_code}", url], capture_output=True)
out = r.stdout
idx = out.rfind(b"\n")
meta = out[idx + 1:].decode("ascii", "replace").strip().split(" ")
return (meta[0] if meta else url), (meta[1] if len(meta) > 1 else "000"), out[:idx].decode("utf-8", "replace")
def extract_main_hero(html):
for m in re.finditer(r'\{"thumb":"(.*?)","img":"(.*?)","full":"(.*?)".*?(?:"isMain":(true|false))?', html):
if m.group(4) == "true":
return m.group(3).replace("\\/", "/")
b = re.findall(r'https://cdn\.brandfolder\.io/[A-Za-z0-9/_.-]+\.(?:jpg|jpeg|png|auto)', html.replace("\\/", "/"))
return b[0] if b else None
def base_of(u):
return re.sub(r'\.(png|jpg|jpeg|auto)$', '', u.split("?", 1)[0], flags=re.I)
def color_suffix(mfr):
# e.g. 8013149.161.0 -> 161 ; 34258.116.0 -> 116
parts = re.sub(r'\.0$', '', mfr).split('.')
return parts[-1] if len(parts) > 1 else ''
def verify(row):
mfr = row["mfr_sku"]
slug = naive_slug(mfr)
url = "https://www.kravet.com/" + slug
rec = {"shopify_id": row["shopify_id"], "vendor": row["vendor"], "mfr_sku": mfr,
"dw_sku": row.get("dw_sku"), "rollback_url": row["rollback_url"],
"recorded_hires_url": row["proposed_hires_url"], "hires_le": row.get("hires_le"),
"slug": slug}
try:
final, code, html = fetch(url)
rec["final_url"] = final
rec["http"] = code
if code != "200":
rec.update(verified=False, reason=f"page {code}")
return rec
# (1) not redirected away to search/category
redirected_away = ("/search" in final.lower()) or (slug not in final.lower().replace("www.kravet.com/", ""))
# (2) page hero base == recorded hero base
hero = extract_main_hero(html)
hero_ok = bool(hero) and (base_of(hero).lower() == base_of(row["proposed_hires_url"]).lower())
# (3) requested color suffix present on the page
csuf = color_suffix(mfr)
color_on_page = bool(csuf) and (csuf in html)
rec.update(redirected_away=redirected_away, hero_matches=hero_ok, color_on_page=color_on_page,
page_hero=hero)
if (not redirected_away) and hero_ok and color_on_page:
rec.update(verified=True, reason="slug resolved to requested product; isMain hero == recorded; color present")
else:
rec.update(verified=False,
reason=f"redirected_away={redirected_away} hero_matches={hero_ok} color_on_page={color_on_page}")
except Exception as e:
rec.update(verified=False, reason="ERR:" + str(e)[:120])
return rec
def main():
tier2 = json.load(open(os.path.join(D, "batchA-tier2-colorway-unverified.json")))["rows"]
print(f"verifying {len(tier2)} tier-2 rows against kravet.com", file=sys.stderr)
out = []
with ThreadPoolExecutor(max_workers=8) as ex:
futs = {ex.submit(verify, r): r for r in tier2}
for i, f in enumerate(as_completed(futs), 1):
out.append(f.result())
if i % 20 == 0:
print(f" {i}/{len(tier2)}", file=sys.stderr)
ver = [r for r in out if r.get("verified")]
unv = [r for r in out if not r.get("verified")]
# verified rows -> apply-hires map schema (ready to fold into batchA-final-map)
map_rows = [{
"shopify_id": r["shopify_id"], "vendor": r["vendor"], "dw_sku": r["dw_sku"], "mfr_sku": r["mfr_sku"],
"cur_width": r.get("cur_width"), "current_400px_url": r["rollback_url"], "rollback_url": r["rollback_url"],
"proposed_hires_url": r["recorded_hires_url"], "hires_le": r["hires_le"],
"hires_source": "kravet_com_brandfolder_hero_tier2_verified", "swappable_from_local_staging": True,
} for r in ver]
json.dump({"ticket": "TK-12097", "batch": "A-tier2", "verified": len(ver), "unverified": len(unv),
"rows": map_rows}, open(os.path.join(D, "batchA-tier2-verified.json"), "w"), indent=1)
json.dump({"rows": unv}, open(os.path.join(D, "batchA-tier2-still-unverified.json"), "w"), indent=1)
print(json.dumps({"tier2_total": len(out), "verified": len(ver), "still_unverified": len(unv)}, indent=2))
if __name__ == "__main__":
main()