Spaces:
Sleeping
Sleeping
File size: 2,048 Bytes
c8f4a46 | 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 | import re
from pathlib import Path
import fitz # PyMuPDF
from app.core.logging import get_logger
logger = get_logger(__name__)
# If extracted text is fewer than this many characters it's almost certainly
# a scanned / image-only PDF that we cannot handle in MVP.
SCANNED_THRESHOLD = 100
class ExtractionError(Exception):
"""Raised when the PDF cannot be meaningfully extracted."""
def extract_text(path: Path) -> tuple[str, int]:
"""
Extract and clean text from a digital PDF.
Returns
-------
text : str
Cleaned, whitespace-normalised body text.
page_count : int
Number of pages in the document.
Raises
------
ExtractionError
If the file is unreadable or appears to be a scanned/image PDF.
"""
try:
doc = fitz.open(str(path))
except Exception as exc:
logger.error("Could not open PDF %s: %s", path, exc)
raise ExtractionError("Could not read this PDF.") from exc
page_count = len(doc)
raw_pages: list[str] = []
for page in doc:
raw_pages.append(page.get_text("text")) # type: ignore[arg-type]
doc.close()
full_text = "\n".join(raw_pages)
cleaned = _clean(full_text)
if len(cleaned) < SCANNED_THRESHOLD:
logger.warning(
"PDF %s yielded only %d chars — likely scanned", path, len(cleaned)
)
raise ExtractionError(
"This looks like a scanned resume; MVP supports digital text PDFs only."
)
logger.info("Extracted %d chars from %d-page PDF %s", len(cleaned), page_count, path.name)
return cleaned, page_count
def _clean(raw: str) -> str:
"""Normalise whitespace and remove common PDF artifacts."""
# Collapse runs of whitespace that aren't newlines
text = re.sub(r"[ \t]+", " ", raw)
# Collapse 3+ consecutive blank lines into 2
text = re.sub(r"\n{3,}", "\n\n", text)
# Strip leading/trailing whitespace per line
lines = [line.strip() for line in text.splitlines()]
return "\n".join(lines).strip()
|