sundeep-news-scan/scratch_py/_regress_masthead.py

140 lines
5.8 KiB
Python

"""Regression harness for drop_masthead_regions.
drop_masthead_regions runs inside main() BEFORE political_result.json is saved, so
the saved fixtures never capture its effect. This harness replays it directly over
every cached page's regions.json, mirroring the real call
(drop_masthead_regions(regions, paper, page_num, page_img_path)).
The cached fixtures carry no meta.json, so the paper is recovered from the page-1
image height (verified against the printed masthead nameplate):
7053/7055 -> sakshi 6833/6549 -> andhra_jyothi 6422 -> namaste_telangana
Two views:
• SHIPPED (gated): what the function actually drops in production. Expect the
Namaste Telangana page-1 masthead to be dropped (incl. a doc_title nameplate,
the mis-group seed) and NOTHING dropped on inner pages or other papers.
• UNGATED PROBE: the same geometric rule with the paper/page gate removed, run on
every page-1. Confirms the gate is the only thing keeping the rule off other
papers AND that the rule would never strip a genuine page-1 headline.
"""
import glob
import json
import sys
from collections import Counter
from pathlib import Path
from PIL import Image
import smart_extractor
from smart_extractor import drop_masthead_regions
OUT = Path("output")
def paper_of(run_dir):
png = run_dir / "pages" / "page_001.png"
if png.exists():
try:
h = Image.open(png).height
if 7040 <= h <= 7060:
return "sakshi"
if h in (6833, 6549):
return "andhra_jyothi"
if h == 6422:
return "namaste_telangana"
except Exception:
pass
return "unknown"
def page_num_of(reg_path):
# page_001.regions.json -> 1
stem = reg_path.name.split(".")[0] # page_001
return int(stem.split("_")[1])
def types_of(regions, ids):
m = {r["id"]: r.get("type") for r in regions}
return Counter(m.get(i) for i in ids)
def run():
dirs = sorted([d for d in OUT.iterdir() if d.is_dir()])
shipped = [] # rows: (paper, page, run, dropped_ids, dropped_types)
probe = [] # ungated, page-1 only
fired = Counter() # paper -> #page-1 runs where shipped drop fired
p1_total = Counter() # paper -> #page-1 runs
false_pos = [] # shipped drops on non-NT or page>1 (should be empty)
for d in dirs:
pages_dir = d / "pages"
if not pages_dir.exists():
continue
paper = paper_of(d)
for reg_path in sorted(pages_dir.glob("page_*.regions.json")):
page_num = page_num_of(reg_path)
png = pages_dir / reg_path.name.replace(".regions.json", ".png")
regions = json.loads(reg_path.read_text())["regions"]
# ---- SHIPPED (gated) ----
kept = drop_masthead_regions(list(regions), paper, page_num, str(png))
kept_ids = {r["id"] for r in kept}
dropped = sorted(r["id"] for r in regions if r["id"] not in kept_ids)
if dropped:
shipped.append((paper, page_num, d.name, dropped, types_of(regions, dropped)))
if not (paper == "namaste_telangana" and page_num == 1):
false_pos.append((paper, page_num, d.name, dropped))
if page_num == 1:
p1_total[paper] += 1
if paper == "namaste_telangana" and dropped:
fired[paper] += 1
# ---- UNGATED PROBE (force the rule to run) ----
probe_kept = drop_masthead_regions(list(regions), "namaste_telangana", 1, str(png))
probe_kept_ids = {r["id"] for r in probe_kept}
pdropped = sorted(r["id"] for r in regions if r["id"] not in probe_kept_ids)
if pdropped:
probe.append((paper, d.name, pdropped, types_of(regions, pdropped)))
return shipped, probe, fired, p1_total, false_pos
def report(shipped, probe, fired, p1_total, false_pos):
print("\n=== SHIPPED (gated) — Namaste Telangana page-1 masthead drop ===")
nt = [s for s in shipped if s[0] == "namaste_telangana" and s[1] == 1]
print(f"NT page-1 runs: {p1_total['namaste_telangana']} | masthead fired in: {fired['namaste_telangana']}")
seedless = []
for paper, page, run, dropped, types in sorted(nt, key=lambda x: x[2]):
has_title = types.get("doc_title", 0) > 0
flag = "" if has_title else " ⚠️ no doc_title dropped (seed not removed?)"
if not has_title:
seedless.append(run)
print(f" {run}: dropped {dropped} types={dict(types)}{flag}")
misses = p1_total['namaste_telangana'] - fired['namaste_telangana']
print(f"\n NT page-1 misses (nothing dropped): {misses}")
print(f" NT page-1 drops WITHOUT a doc_title nameplate seed: {len(seedless)} {seedless}")
print("\n=== FALSE POSITIVES (shipped drops on non-NT, or page>1) ===")
if false_pos:
for paper, page, run, dropped in false_pos:
print(f" ⚠️ {paper} p{page} {run}: dropped {dropped}")
else:
print(" none — gate held: 0 drops on inner pages and on Sakshi/Andhra Jyothi.")
print("\n=== UNGATED PROBE — would the bare geometric rule strip a real headline? ===")
by_paper = Counter(p[0] for p in probe)
print(f"page-1 runs where the ungated rule drops something, by paper: {dict(by_paper)}")
for paper in ("sakshi", "andhra_jyothi"):
hits = [p for p in probe if p[0] == paper]
print(f"\n --- {paper}: {len(hits)} page-1 run(s) would drop something ungated ---")
for _, run, dropped, types in hits[:12]:
print(f" {run}: {dropped} types={dict(types)}")
if not hits:
print(" none — rule is inert on this paper's page-1 even ungated (safe).")
if __name__ == "__main__":
report(*run())