""" Propose furniture lines to drop from phase-1 spreadsheet articles. The sheet articles were copied in verbatim and never trimmed. A subset carries site furniture (interior ad markers, share buttons, newsletter/subscribe blocks, related-article teasers, copyright footers). This proposes ONLY high-confidence furniture lines, from the exact cell text, for human review. Nothing is invented: every proposed drop is a real line from the cell, quoted in full. Canonical split is cell.split("\\n") over ALL lines (blanks included) so drop indices line up exactly with what the trimmer/build will use. Output -> staging/sheet-cuts.json. """ import io import json import re import sys from pathlib import Path import openpyxl sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") BASE = Path(__file__).resolve().parent XLSX = BASE.parent / "KR-reports_analysis_Sofi.xlsx" # Standalone-furniture patterns. A line is furniture only if the token is essentially the # whole line (short line), or it is an unambiguous block marker. Body lines that merely # mention "Telegram"/"WhatsApp" in a sentence are NOT dropped (length guard handles them). STRONG = re.compile(r"^\s*(" r"advertisement|skip advertisement|sponsored|" r"you may (also )?like|related articles?|more from|" r"read more|read next|next article|previous article|" r"follow us|share this|share on|sign up|subscribe|" r"most read|trending|recommended|" r"©|copyright|all rights reserved|terms of service|privacy policy" r")\b", re.I) SHARE = re.compile(r"^\s*(whatsapp|telegram|facebook|twitter|x\b|linkedin|share)" r"[\s|/,·-]*(whatsapp|telegram|facebook|twitter|linkedin|email|share|copy)?\s*$", re.I) NEWS = re.compile(r"(newsletter|sign[- ]up for|subscribe to our|get the latest|" r"delivered (to your inbox|free)|enter your email)", re.I) def is_furniture(line): s = line.strip() if not s: return None if STRONG.search(s) and len(s) < 90: return "marker" if SHARE.match(s) and len(s) < 60: return "share" if NEWS.search(s) and len(s) < 120: return "newsletter" return None def main(): cases = [int(a) for a in sys.argv[1:]] wb = openpyxl.load_workbook(XLSX, data_only=True, read_only=True) rows = list(wb["reported_K&R"].iter_rows(values_only=True)) idx = {h: i for i, h in enumerate(rows[0]) if h is not None} # map case -> (row, col) via the build manifest origins man = json.loads((BASE / "case-files-manifest.json").read_text(encoding="utf-8")) locs = {} for m in man["entries"]: o = m.get("origin", "") if "reported_K&R sheet" in o: rn = int(re.search(r"row (\d+)", o).group(1)) col = re.search(r'column "([^"]+)"', o).group(1) locs.setdefault(m["case"], []).append((rn, col)) out = {} for cid in cases: for rn, col in locs.get(cid, []): cell = str(rows[rn - 1][idx[col]]) lines = cell.split("\n") drops = [(i, is_furniture(l), l) for i, l in enumerate(lines) if is_furniture(l)] key = f"{cid}:{col}" print(f"\n### {key} (row {rn}, {len(lines)} lines) — {len(drops)} proposed drops") for i, kind, l in drops: ctx = " ".join(lines[i].split())[:100] out.setdefault(key, {"row": rn, "col": col, "drop": []}) out[key]["drop"].append(i) print(f" drop [{i}] ({kind}): {ctx}") (BASE / "staging" / "sheet-cuts.json").write_text( json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8") print(f"\nwrote {sum(len(v['drop']) for v in out.values())} proposed drops " f"across {len(out)} articles -> staging/sheet-cuts.json") if __name__ == "__main__": main()