Spaces:
Sleeping
Sleeping
File size: 6,300 Bytes
00d3560 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | """Verify the curated policy Markdown (``Contextmd/``) against the source PDFs.
Design note
-----------
One of the source PDFs (``Placement Policy.pdf``) is a *scanned / image-only*
document with no text layer, so automated extractors (pypdf, pymupdf4llm) return
nothing usable for it. The project therefore indexes the hand-curated,
pre-verified Markdown in ``Contextmd/`` as its source of truth instead of a
machine conversion.
This module is the build-time **quality gate** for that curated Markdown. For
every PDF in ``Reference Documents/`` it:
1. Confirms a matching ``Contextmd/<name>.md`` exists and is non-empty.
2. If the PDF has a real text layer, independently re-extracts it with PyMuPDF
and checks that the curated Markdown covers the bulk of the PDF's wording.
3. Checks that a set of critical policy anchors are present.
A hard failure (missing/empty curated file, or coverage below the floor for a
text-layer PDF) exits non-zero so a bad corpus never reaches the index.
Originals in ``Reference Documents/`` are never modified.
"""
from __future__ import annotations
import logging
import re
import sys
from pathlib import Path
import fitz # PyMuPDF (installed via pymupdf4llm)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)-7s | convert_docs | %(message)s",
)
log = logging.getLogger("convert_docs")
# --- Structural paths (not secrets/config) -----------------------------------
REFERENCE_DIR = Path("Reference Documents")
CONTEXTMD_DIR = Path("Contextmd")
# For text-layer PDFs: fraction of PDF word-tokens that must appear in the
# curated Markdown. Boilerplate (form fields, signatures) legitimately drops a
# little, so this is a floor, not a target.
COVERAGE_WARN_THRESHOLD = 0.85
COVERAGE_FAIL_THRESHOLD = 0.50
# Anchors we expect the curated policy corpus to contain; missing ones almost
# always mean a truncated or wrong file.
CRITICAL_ANCHORS = [
"placement",
"eligibility",
"attendance",
"offer",
"dream offer",
"stdc",
"car",
]
_TOKEN_RE = re.compile(r"[a-z0-9₹%×]+")
_SLUG_RE = re.compile(r"[^A-Za-z0-9]+")
def slugify(name: str) -> str:
return _SLUG_RE.sub("-", name).strip("-")
def tokens(text: str) -> list[str]:
return _TOKEN_RE.findall(text.lower())
def pdf_reference_text(pdf_path: Path) -> str:
"""Independent raw-text extraction (empty for scanned/image-only PDFs)."""
parts = []
with fitz.open(pdf_path) as doc:
for page in doc:
parts.append(page.get_text("text"))
return "\n".join(parts)
def find_curated(pdf_stem: str) -> Path | None:
"""Locate the curated Markdown matching a PDF (case-insensitive slug match)."""
if not CONTEXTMD_DIR.is_dir():
return None
slug = slugify(pdf_stem).lower()
for md in sorted(CONTEXTMD_DIR.glob("*.md")):
if md.stem.lower() == slug:
return md
return None
def verify(pdf_path: Path, md_path: Path) -> bool:
"""Verify one curated Markdown file against its source PDF.
Returns True if it passed with no warnings. Raises RuntimeError on a hard
failure (empty curated file or coverage below the hard floor).
"""
markdown = md_path.read_text(encoding="utf-8")
if not markdown.strip():
raise RuntimeError(f"Curated file {md_path} is EMPTY")
md_flat = re.sub(r"\s+", "", markdown.lower())
md_vocab = set(tokens(markdown))
ref_tokens = tokens(pdf_reference_text(pdf_path))
if ref_tokens:
present = sum(1 for w in ref_tokens if w in md_vocab)
coverage = present / len(ref_tokens)
else:
# Scanned PDF: no independent text to cross-check; anchors only.
coverage = None
missing_anchors = [a for a in CRITICAL_ANCHORS if a.replace(" ", "") not in md_flat]
log.info("Verifying %s <- %s", md_path.name, pdf_path.name)
log.info(" curated chars : %d", len(markdown))
if coverage is None:
log.info(" token coverage : n/a (scanned PDF — no text layer to compare)")
else:
log.info(" token coverage : %.1f%% (%d PDF tokens)", coverage * 100, len(ref_tokens))
log.info(
" anchors : %d/%d present%s",
len(CRITICAL_ANCHORS) - len(missing_anchors),
len(CRITICAL_ANCHORS),
"" if not missing_anchors else f" MISSING={missing_anchors}",
)
if coverage is not None and coverage < COVERAGE_FAIL_THRESHOLD:
raise RuntimeError(
f"{md_path.name} failed verification: token coverage {coverage:.1%} "
f"below hard floor {COVERAGE_FAIL_THRESHOLD:.0%}"
)
ok = True
if coverage is not None and coverage < COVERAGE_WARN_THRESHOLD:
log.warning(
"%s: token coverage %.1f%% below expected %.0f%% — review the file.",
md_path.name,
coverage * 100,
COVERAGE_WARN_THRESHOLD * 100,
)
ok = False
if missing_anchors:
log.warning("%s: missing expected anchors %s", md_path.name, missing_anchors)
ok = False
return ok
def main() -> int:
if not REFERENCE_DIR.is_dir():
log.error("Source folder not found: %s", REFERENCE_DIR.resolve())
return 1
if not CONTEXTMD_DIR.is_dir():
log.error("Curated Markdown folder not found: %s", CONTEXTMD_DIR.resolve())
return 1
pdfs = sorted(REFERENCE_DIR.glob("*.pdf"))
if not pdfs:
log.error("No PDF files found in %s", REFERENCE_DIR.resolve())
return 1
log.info(
"Verifying %d curated document(s) in %s against source PDFs in %s",
len(pdfs),
CONTEXTMD_DIR,
REFERENCE_DIR,
)
all_clean = True
for pdf in pdfs:
md_path = find_curated(pdf.stem)
if md_path is None:
log.error(
"No curated Markdown in %s/ matches PDF '%s' (expected stem '%s').",
CONTEXTMD_DIR,
pdf.name,
slugify(pdf.stem),
)
return 1
all_clean = verify(pdf, md_path) and all_clean
log.info(
"Verification %s for %d document(s).",
"clean" if all_clean else "completed WITH WARNINGS (see above)",
len(pdfs),
)
return 0
if __name__ == "__main__":
sys.exit(main())
|