fix: tighten cv extraction heuristics
This commit is contained in:
parent
df140c3c77
commit
544b5892fa
@ -5,9 +5,32 @@ from pathlib import Path
|
||||
from pypdf import PdfReader
|
||||
|
||||
|
||||
EXPERIENCE_TITLE_ENDINGS = {
|
||||
"administrator",
|
||||
"analyst",
|
||||
"architect",
|
||||
"consultant",
|
||||
"data engineer",
|
||||
"data scientist",
|
||||
"developer",
|
||||
"director",
|
||||
"engineer",
|
||||
"lead",
|
||||
"manager",
|
||||
"researcher",
|
||||
"scientist",
|
||||
"specialist",
|
||||
}
|
||||
|
||||
|
||||
def extract_pdf_text(path: Path) -> str:
|
||||
reader = PdfReader(str(path))
|
||||
return "\n".join(page.extract_text() or "" for page in reader.pages)
|
||||
page_texts = [
|
||||
text.strip()
|
||||
for text in (page.extract_text() or "" for page in reader.pages)
|
||||
if text.strip()
|
||||
]
|
||||
return "\n".join(page_texts)
|
||||
|
||||
|
||||
def extract_cv_signals(text: str) -> dict[str, object]:
|
||||
@ -31,7 +54,7 @@ def extract_cv_signals(text: str) -> dict[str, object]:
|
||||
if lowered.startswith("skills:"):
|
||||
skills = _parse_csv_field(line)
|
||||
continue
|
||||
if " at " in line:
|
||||
if _looks_like_experience_line(line):
|
||||
title, company = line.split(" at ", 1)
|
||||
experience_entries.append(
|
||||
{
|
||||
@ -53,3 +76,21 @@ def extract_cv_signals(text: str) -> dict[str, object]:
|
||||
def _parse_csv_field(line: str) -> list[str]:
|
||||
_, value = line.split(":", 1)
|
||||
return [item.strip() for item in value.split(",") if item.strip()]
|
||||
|
||||
|
||||
def _looks_like_experience_line(line: str) -> bool:
|
||||
if line.count(" at ") != 1:
|
||||
return False
|
||||
|
||||
title, company = (part.strip() for part in line.split(" at ", 1))
|
||||
if not title or not company:
|
||||
return False
|
||||
|
||||
title_words = title.split()
|
||||
if len(title_words) < 2:
|
||||
return False
|
||||
|
||||
if title_words[-1].lower() not in EXPERIENCE_TITLE_ENDINGS:
|
||||
return False
|
||||
|
||||
return any(character.isupper() for character in company)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
from textwrap import dedent
|
||||
from pathlib import Path
|
||||
|
||||
from job_research.profile.cv_extractor import extract_cv_signals
|
||||
from job_research.profile.cv_extractor import extract_pdf_text
|
||||
|
||||
|
||||
def test_extract_cv_signals_reads_basic_fields_from_text() -> None:
|
||||
@ -23,3 +25,49 @@ def test_extract_cv_signals_reads_basic_fields_from_text() -> None:
|
||||
assert extracted["skills"] == ["Python", "SQL", "Terraform", "GCP", "BigQuery"]
|
||||
assert extracted["experience_entries"][0]["title"] == "Data Engineer"
|
||||
assert len(extracted["experience_entries"]) == 2
|
||||
|
||||
|
||||
def test_extract_cv_signals_ignores_prose_that_mentions_at() -> None:
|
||||
text = dedent(
|
||||
"""
|
||||
Tonio
|
||||
Location: France
|
||||
Worked at a startup before joining the team.
|
||||
"""
|
||||
).strip()
|
||||
|
||||
extracted = extract_cv_signals(text)
|
||||
|
||||
assert extracted["experience_entries"] == []
|
||||
|
||||
|
||||
def test_extract_pdf_text_skips_blank_pages(monkeypatch) -> None:
|
||||
class FakePage:
|
||||
def __init__(self, text: str | None) -> None:
|
||||
self._text = text
|
||||
|
||||
def extract_text(self) -> str | None:
|
||||
return self._text
|
||||
|
||||
class FakePdfReader:
|
||||
def __init__(self, path: str) -> None:
|
||||
self.path = path
|
||||
self.pages = [
|
||||
FakePage(None),
|
||||
FakePage("Tonio"),
|
||||
FakePage(""),
|
||||
FakePage("Data Engineer"),
|
||||
]
|
||||
|
||||
seen_paths: list[str] = []
|
||||
|
||||
def fake_pdf_reader(path: str) -> FakePdfReader:
|
||||
seen_paths.append(path)
|
||||
return FakePdfReader(path)
|
||||
|
||||
monkeypatch.setattr("job_research.profile.cv_extractor.PdfReader", fake_pdf_reader)
|
||||
|
||||
extracted = extract_pdf_text(Path("cv.pdf"))
|
||||
|
||||
assert extracted == "Tonio\nData Engineer"
|
||||
assert seen_paths == ["cv.pdf"]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user