← back to Dw Kravet Hires

scripts/preflight-measure.py

114 lines

#!/usr/bin/env python3
"""TK-11658 Cycle 2 pre-flight: MEASURE (do not trust) every recovered hi-res URL
against Shopify media ceilings: HTTP 200 + long-edge <= 5000px + bytes <= 20MB.
Method: ranged GET (first N KB) -> real HTTP status, true total bytes (content-range),
real pixel dims (PIL). ~64KB/row. $0 (network only)."""
import json, io, sys, concurrent.futures as cf
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
from PIL import Image

SRC = "data/kravet-hires-phase2-swap-map.json"
OUT = "data/phase2-preflight-measured.json"
SHOP_PX = 5000            # Shopify long-edge ceiling
SHOP_BYTES = 20*1024*1024 # Shopify 20MB ceiling
UA = "Mozilla/5.0 (dw-preflight TK-11658)"

def fetch_head_bytes(url, nbytes):
    req = Request(url, headers={"User-Agent": UA, "Range": f"bytes=0-{nbytes-1}"})
    with urlopen(req, timeout=45) as r:
        code = r.status
        cr = r.headers.get("Content-Range", "")
        total = None
        if "/" in cr:
            tail = cr.rsplit("/",1)[1].strip()
            if tail.isdigit(): total = int(tail)
        if total is None:
            gcl = r.headers.get("x-goog-stored-content-length")
            if gcl and gcl.isdigit(): total = int(gcl)
        data = r.read()
    return code, total, data

def measure(row):
    url = row.get("proposed_hires_url") or ""
    res = {"mfr_sku": row.get("mfr_sku"), "vendor": row.get("vendor"),
           "shopify_id": row.get("shopify_id"), "url": url,
           "claim_le": row.get("recovered_long_edge"),
           "claim_bytes": row.get("recovered_bytes")}
    if not url:
        res.update(status="NO_URL", ok=False); return res
    try:
        code, total, data = fetch_head_bytes(url, 65536)
        dims = None
        # try progressively larger reads if header truncated
        for rng in (65536, 262144, 1048576):
            try:
                if rng > len(data):
                    code2, total2, data = fetch_head_bytes(url, rng)
                    if total2: total = total2
                im = Image.open(io.BytesIO(data)); im.draft(None,None)
                dims = im.size
                break
            except Exception:
                continue
        res.update(http=code, meas_bytes=total,
                   meas_w=(dims[0] if dims else None),
                   meas_h=(dims[1] if dims else None))
        if code not in (200,206):
            res.update(status=f"HTTP_{code}", ok=False); return res
        if dims is None:
            res.update(status="NO_DIMS", ok=False); return res
        le = max(dims)
        res["meas_le"] = le
        over_px = le > SHOP_PX
        over_bytes = (total is not None and total > SHOP_BYTES)
        unknown_bytes = total is None
        if over_px or over_bytes:
            res.update(status="OVER_LIMIT", ok=False,
                       over_px=over_px, over_bytes=over_bytes)
        elif unknown_bytes:
            res.update(status="BYTES_UNKNOWN", ok=False)  # not-measured != pass
        else:
            res.update(status="PASS", ok=True)
    except HTTPError as e:
        res.update(status=f"HTTP_{e.code}", ok=False)
    except (URLError, Exception) as e:
        res.update(status="ERR", ok=False, err=str(e)[:120])
    return res

def main():
    d = json.load(open(SRC))
    rows = d["rows"]
    results = [None]*len(rows)
    done = 0
    with cf.ThreadPoolExecutor(max_workers=16) as ex:
        futs = {ex.submit(measure, r): i for i,r in enumerate(rows)}
        for f in cf.as_completed(futs):
            i = futs[f]; results[i] = f.result(); done += 1
            if done % 100 == 0: print(f"  measured {done}/{len(rows)}", file=sys.stderr)
    paths = {"clean":[], "flagged":[]}
    for r in results:
        (paths["clean"] if r["ok"] else paths["flagged"]).append(r)
    summary = {}
    for r in results:
        summary[r["status"]] = summary.get(r["status"],0)+1
    out = {"ticket":"TK-11658","measured_at_utc":None,
           "method":"ranged GET (64KB-1MB) content-range total + PIL dims; MEASURED not claimed",
           "ceilings":{"long_edge_px":SHOP_PX,"bytes":SHOP_BYTES},
           "total":len(results),"measured":sum(1 for r in results if r.get("http")),
           "pass":len(paths["clean"]),"flagged":len(paths["flagged"]),
           "status_breakdown":summary,"rows":results}
    import datetime
    out["measured_at_utc"]=datetime.datetime.utcnow().isoformat()+"Z"
    json.dump(out, open(OUT,"w"), indent=2)
    print(json.dumps({"total":out["total"],"measured":out["measured"],
                      "pass":out["pass"],"flagged":out["flagged"],
                      "status_breakdown":summary}, indent=2))
    # max measured dims/bytes
    les=[r["meas_le"] for r in results if r.get("meas_le")]
    bs=[r["meas_bytes"] for r in results if r.get("meas_bytes")]
    if les: print("MAX measured long-edge:",max(les),"px")
    if bs: print("MAX measured bytes:",max(bs),"(",round(max(bs)/1024/1024,2),"MB)")

if __name__=="__main__": main()