feat: add deterministic CV extraction
This commit is contained in:
parent
ddd3c9d78a
commit
df140c3c77
55
src/job_research/profile/cv_extractor.py
Normal file
55
src/job_research/profile/cv_extractor.py
Normal file
@ -0,0 +1,55 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from pypdf import PdfReader
|
||||
|
||||
|
||||
def extract_pdf_text(path: Path) -> str:
|
||||
reader = PdfReader(str(path))
|
||||
return "\n".join(page.extract_text() or "" for page in reader.pages)
|
||||
|
||||
|
||||
def extract_cv_signals(text: str) -> dict[str, object]:
|
||||
lines = [line.strip() for line in text.splitlines()]
|
||||
non_empty_lines = [line for line in lines if line]
|
||||
|
||||
name = non_empty_lines[0] if non_empty_lines else None
|
||||
location = None
|
||||
languages: list[str] = []
|
||||
skills: list[str] = []
|
||||
experience_entries: list[dict[str, str]] = []
|
||||
|
||||
for line in non_empty_lines[1:]:
|
||||
lowered = line.lower()
|
||||
if lowered.startswith("location:"):
|
||||
location = line.split(":", 1)[1].strip() or None
|
||||
continue
|
||||
if lowered.startswith("languages:"):
|
||||
languages = _parse_csv_field(line)
|
||||
continue
|
||||
if lowered.startswith("skills:"):
|
||||
skills = _parse_csv_field(line)
|
||||
continue
|
||||
if " at " in line:
|
||||
title, company = line.split(" at ", 1)
|
||||
experience_entries.append(
|
||||
{
|
||||
"title": title.strip(),
|
||||
"company": company.strip(),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"location": location,
|
||||
"languages": languages,
|
||||
"skills": skills,
|
||||
"experience_entries": experience_entries,
|
||||
"education_entries": [],
|
||||
}
|
||||
|
||||
|
||||
def _parse_csv_field(line: str) -> list[str]:
|
||||
_, value = line.split(":", 1)
|
||||
return [item.strip() for item in value.split(",") if item.strip()]
|
||||
25
tests/profile/test_cv_extractor.py
Normal file
25
tests/profile/test_cv_extractor.py
Normal file
@ -0,0 +1,25 @@
|
||||
from textwrap import dedent
|
||||
|
||||
from job_research.profile.cv_extractor import extract_cv_signals
|
||||
|
||||
|
||||
def test_extract_cv_signals_reads_basic_fields_from_text() -> None:
|
||||
text = dedent(
|
||||
"""
|
||||
Tonio
|
||||
Location: France
|
||||
Languages: French, English
|
||||
Skills: Python, SQL, Terraform, GCP, BigQuery
|
||||
Data Engineer at Company A
|
||||
Analytics Engineer at Company B
|
||||
"""
|
||||
).strip()
|
||||
|
||||
extracted = extract_cv_signals(text)
|
||||
|
||||
assert extracted["name"] == "Tonio"
|
||||
assert extracted["location"] == "France"
|
||||
assert extracted["languages"] == ["French", "English"]
|
||||
assert extracted["skills"] == ["Python", "SQL", "Terraform", "GCP", "BigQuery"]
|
||||
assert extracted["experience_entries"][0]["title"] == "Data Engineer"
|
||||
assert len(extracted["experience_entries"]) == 2
|
||||
Loading…
x
Reference in New Issue
Block a user