from __future__ import annotations
import re
import pymupdf4llm
import spectre_rs
def norm(s: str) -> str:
s = re.sub(r"[*_`]", "", s or "")
s = re.sub(r"\s+", " ", s).strip().lower()
return re.sub(r"[\.\:\,\;\!\?\—\-]+$", "", s)
def headings(md: str) -> set[str]:
out: set[str] = set()
for m in re.finditer(r"^#+\s+(.+?)$", md, re.M):
h = norm(m.group(1))
if h and len(h) > 1:
out.add(h)
return out
def main() -> None:
path = "/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/largedoc/i1040gi.pdf"
sd = spectre_rs.Document(open(path, "rb").read())
spec_heads = headings(sd.markdown())
pmu_md = pymupdf4llm.to_markdown(path)
pmu_heads = headings(pmu_md)
toc = [norm(t.title) for t in sd.toc() if t.title]
toc_set = {t for t in toc if t and len(t) > 2}
classes = {
"both_hit": [],
"spec_only": [],
"pmu_only": [],
"both_miss": [],
}
for t in toc_set:
s = t in spec_heads
p = t in pmu_heads
if s and p:
classes["both_hit"].append(t)
elif s and not p:
classes["spec_only"].append(t)
elif p and not s:
classes["pmu_only"].append(t)
else:
classes["both_miss"].append(t)
print(f"TOC entries: {len(toc_set)}")
print(f" both_hit: {len(classes['both_hit'])} (TP for both)")
print(f" spec_only: {len(classes['spec_only'])} (spec catches, pmu misses)")
print(f" pmu_only: {len(classes['pmu_only'])} (pmu catches, spec misses) ← load-bearing")
print(f" both_miss: {len(classes['both_miss'])} (neither catches)")
print()
spec_fp = spec_heads - toc_set
pmu_fp = pmu_heads - toc_set
print(f"False positives (detections not in TOC):")
print(f" spec: {len(spec_fp)}")
print(f" pmu: {len(pmu_fp)}")
print()
print("Sample pmu_only headings (pmu catches, spec misses — fix-targets):")
for h in sorted(classes["pmu_only"])[:20]:
print(f" {h!r}")
print()
print("Sample spec false positives (over-detections):")
for h in sorted(spec_fp)[:20]:
print(f" {h!r}")
if __name__ == "__main__":
main()