← back to Dw Color Image Audit
scripts/classify_survivors.py
109 lines
#!/usr/bin/env python3
"""
Classify the 29 vision-gate SURVIVORS into 3 dispositions per DTD Q2=a:
DROP_NAME_LEAK : the vision-detected color is literally present in the declared
name -> audit-side parser leak, NOT a real defect (false positive).
HOLD_BUSY : busy multi-color / botanical / commercial pattern where the
declared color plausibly names one ink -> HUMAN REVIEW, never
auto-remap (the memo's hard rule).
RECRAWL : solid / textured / neutral-ground product whose image is plainly
a different color than the name -> real wrong-image suspect ->
re-crawl source full-page to find the correct colorway->image pairing.
Pure-local logic, $0. Writes out/survivor_dispositions.{jsonl,csv}.
"""
import json, os, re
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
IN = os.path.join(ROOT, "out", "vision_gate_results.jsonl")
BASIC = ["red", "orange", "yellow", "green", "blue", "teal", "purple", "violet",
"pink", "brown", "beige", "tan", "gold", "magenta", "navy", "burgundy",
"white", "black", "gray", "grey", "cream", "lavender", "turquoise",
"crimson", "coral", "terracotta", "olive", "khaki", "chestnut"]
# busy / multi-color pattern signals: doubled motif words + botanical/commercial lines
BUSY_RE = re.compile(
r"\b(flowers?\s+flowers?|trees?\s+trees?|leaves?\s+leaves?|grasses?[- ]leaves?|"
r"landscapes?\s+landscapes?|waterscapes?\s+waterscapes?|floral|botanical|"
r"flower|tree|leaf|leaves|grass|vine|garden|meadow|reef|safari|woodland|"
r"damask|toile|paisley|medley|pinwheel|splash|sunset|coleus|petrouchka)\b", re.I)
# solid / texture lines that SHOULD be one near-solid color (strong wrong-image signal)
SOLID_RE = re.compile(
r"\b(paintable|faux suede|suede|faux linen|linen|grasscloth|grass cloth|"
r"raffia|vinyl|texture|weave|woven|silk|solid|plain)\b", re.I)
def tokens(s):
return set(re.findall(r"[a-z]+", (s or "").lower()))
def main():
surv = [json.loads(l) for l in open(IN)
if json.loads(l).get("gate") == "SURVIVOR"]
out = []
for r in surv:
declared = (r["declared_color"] or "")
dl = declared.lower()
vis = (r.get("vision_dominant") or "").lower()
vis_tokens = [t for t in re.findall(r"[a-z]+", vis) if t in BASIC]
name_leak = any(t in dl for t in vis_tokens)
is_busy = bool(BUSY_RE.search(declared))
is_solid = bool(SOLID_RE.search(declared))
# vision reporting >1 color => inherently multi-ink => can't be a wrong-solid
# defect; ambiguous busy pattern -> HOLD (a real wrong-image shows ONE color)
multi_vis = bool(re.search(r"\band\b|multi|gradient|/", vis))
conf = r.get("vision_confidence") or 0
if name_leak:
disp = "DROP_NAME_LEAK"
why = f"declared name already contains '{[t for t in vis_tokens if t in dl][0]}'"
elif multi_vis:
disp = "HOLD_BUSY"
why = "vision sees multiple colors -> multi-ink/busy -> human review"
elif is_solid and not is_busy:
disp = "RECRAWL"
why = "solid/textured line expected near-solid; image is clearly off-color"
elif is_busy:
disp = "HOLD_BUSY"
why = "busy/botanical pattern; declared may name one ink -> human review"
elif conf >= 0.95:
disp = "RECRAWL"
why = "non-busy ground, high-confidence color contradiction"
else:
disp = "HOLD_BUSY"
why = "ambiguous; conservative hold"
r2 = dict(r)
r2["disposition"] = disp
r2["disposition_reason"] = why
out.append(r2)
out.sort(key=lambda r: (r["disposition"], r["vendor"], r["dw_sku"]))
with open(os.path.join(ROOT, "out", "survivor_dispositions.jsonl"), "w") as f:
for r in out:
f.write(json.dumps(r) + "\n")
import csv
fields = ["disposition", "vendor", "dw_sku", "mfr_sku", "declared_color",
"vision_dominant", "vision_confidence", "disposition_reason",
"image_url", "handle"]
with open(os.path.join(ROOT, "out", "survivor_dispositions.csv"), "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
w.writeheader()
for r in out:
w.writerow(r)
from collections import Counter
c = Counter(r["disposition"] for r in out)
print("DISPOSITIONS:", dict(c))
for disp in ["RECRAWL", "HOLD_BUSY", "DROP_NAME_LEAK"]:
print(f"\n=== {disp} ===")
for r in out:
if r["disposition"] == disp:
print(f" {r['vendor'][:16]:16} {r['dw_sku']:14} {r['declared_color'][:40]:40} "
f"vis={r.get('vision_dominant'):20} ({r['disposition_reason']})")
if __name__ == "__main__":
main()