← back to Japan Enrich

enrich_greenland.py

87 lines

#!/usr/bin/env python3
"""
Greenland enricher — per-colorway hex + color_family (Pillow, $0 local).

Greenland rows are PATTERNS holding {colorway_skus:[...], sku_images:{sku:url}}.
Material is already known from the vendor category tree (staging `material`), so
this pass does NOT run vision — it only needs the per-colorway dominant color for
the color-family sort/swatch nav.

DISK-SAFE: Greenland serves full-res (~1MB) even for thumbnails, so we DON'T keep
the originals (3117×1MB ≈ 3GB). Each image is downscaled to a ≤256px swatch on
download (~15KB) into images/greenland/, and hex is computed from that. Total
footprint ~47MB, and the swatches double as local swatches if we localize later.

Emits out/greenland-enriched.jsonl — ONE line per colorway SKU:
  {"mfr_sku":"<sku>", "source":"greenland", "enrich":{"hex":[...], "color_family":..., "material":...}}
so the viewer's ENRICH index (keyed by normSku(mfr_sku)) picks it up per colorway card.
"""
import os, json, io, urllib.request, concurrent.futures as cf
from enrich import hex_palette, dominant_family, norm_sku

IMG_DIR = "images/greenland"
STAGING = "staging/greenland-staging.jsonl"
OUT = "out/greenland-enriched.jsonl"
os.makedirs(IMG_DIR, exist_ok=True)
os.makedirs("out", exist_ok=True)


def fetch_swatch(sku, url):
    """Download → downscale to ≤256px → save small jpg. Returns path or None."""
    from PIL import Image
    dst = os.path.join(IMG_DIR, norm_sku(sku) + ".jpg")
    if os.path.exists(dst) and os.path.getsize(dst) > 0:
        return dst
    try:
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
        data = urllib.request.urlopen(req, timeout=45).read()
        if len(data) < 200:
            return None
        im = Image.open(io.BytesIO(data)).convert("RGB")
        im.thumbnail((256, 256))              # discard full-res; keep only the swatch
        im.save(dst, "JPEG", quality=82)
        return dst
    except Exception:
        return None


def main():
    rows = [json.loads(l) for l in open(STAGING) if l.strip()]
    # (sku, url, material) for every colorway that has an image
    jobs, mat = [], {}
    for r in rows:
        imgs = r.get("sku_images") or {}
        material = r.get("material")
        for sku, url in imgs.items():
            if url:
                jobs.append((sku, url))
                mat[norm_sku(sku)] = material
    print(f"greenland: {len(rows)} patterns, {len(jobs)} colorway swatches to enrich", flush=True)

    done = 0
    with open(OUT, "w") as out, cf.ThreadPoolExecutor(max_workers=8) as ex:
        futs = {ex.submit(fetch_swatch, s, u): s for s, u in jobs}
        for fut in cf.as_completed(futs):
            sku = futs[fut]
            done += 1
            path = fut.result()
            if not path:
                continue
            try:
                hexes = hex_palette(path)
                fam = dominant_family(hexes)
            except Exception:
                continue
            rec = {"mfr_sku": sku, "source": "greenland",
                   "enrich": {"hex": hexes, "color_family": fam,
                              "material": mat.get(norm_sku(sku)), "style": None}}
            out.write(json.dumps(rec, ensure_ascii=False) + "\n")
            out.flush()
            if done % 200 == 0:
                print(f"  {done}/{len(jobs)}", flush=True)
    print(f"greenland: enriched → {OUT}", flush=True)


if __name__ == "__main__":
    main()