feat: add canonical profile merger with warnings
This commit is contained in:
parent
042feab4fd
commit
3c331ef687
56
src/job_research/profile/merge.py
Normal file
56
src/job_research/profile/merge.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from job_research.models import (
|
||||||
|
CandidateProfileOutput,
|
||||||
|
EducationEntry,
|
||||||
|
ExperienceEntry,
|
||||||
|
WarningItem,
|
||||||
|
)
|
||||||
|
from job_research.profile.profile_parser import AuthoredProfile
|
||||||
|
|
||||||
|
|
||||||
|
def build_candidate_profile_output(
|
||||||
|
cv_signals: dict[str, object], authored: AuthoredProfile
|
||||||
|
) -> CandidateProfileOutput:
|
||||||
|
warnings: list[WarningItem] = []
|
||||||
|
|
||||||
|
if "years_of_experience" in cv_signals and any(
|
||||||
|
"experience" in note.lower() for note in authored.notes
|
||||||
|
):
|
||||||
|
warnings.append(
|
||||||
|
WarningItem(
|
||||||
|
field="years_of_experience",
|
||||||
|
message=(
|
||||||
|
"CV-derived experience may differ from markdown interpretation. "
|
||||||
|
"Review manually."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
merged_skills: list[str] = []
|
||||||
|
for skill in [
|
||||||
|
*(cv_signals.get("skills") or []),
|
||||||
|
*authored.strengths,
|
||||||
|
*authored.skills_to_emphasize,
|
||||||
|
]:
|
||||||
|
if skill not in merged_skills:
|
||||||
|
merged_skills.append(skill)
|
||||||
|
|
||||||
|
experience_entries = cv_signals.get("experience_entries") or []
|
||||||
|
education_entries = cv_signals.get("education_entries") or []
|
||||||
|
|
||||||
|
return CandidateProfileOutput(
|
||||||
|
name=cv_signals.get("name"),
|
||||||
|
summary=authored.summary,
|
||||||
|
target_roles=authored.target_roles,
|
||||||
|
strengths=authored.strengths,
|
||||||
|
skills_to_emphasize=authored.skills_to_emphasize,
|
||||||
|
constraints=authored.constraints,
|
||||||
|
notes=authored.notes,
|
||||||
|
location=cv_signals.get("location"),
|
||||||
|
languages=cv_signals.get("languages") or [],
|
||||||
|
skills=merged_skills,
|
||||||
|
experience_entries=[ExperienceEntry.model_validate(item) for item in experience_entries],
|
||||||
|
education_entries=[EducationEntry.model_validate(item) for item in education_entries],
|
||||||
|
warnings=warnings,
|
||||||
|
)
|
||||||
28
tests/profile/test_merge.py
Normal file
28
tests/profile/test_merge.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from job_research.profile.merge import build_candidate_profile_output
|
||||||
|
from job_research.profile.profile_parser import AuthoredProfile
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_candidate_profile_output_writes_warning_when_facts_conflict() -> None:
|
||||||
|
cv_signals = {
|
||||||
|
"name": "Tonio",
|
||||||
|
"location": "France",
|
||||||
|
"languages": ["French", "English"],
|
||||||
|
"skills": ["Python", "SQL"],
|
||||||
|
"experience_entries": [{"title": "Data Engineer", "company": "A"}],
|
||||||
|
"education_entries": [],
|
||||||
|
"years_of_experience": 2,
|
||||||
|
}
|
||||||
|
authored = AuthoredProfile(
|
||||||
|
summary="Junior data engineer focused on GCP.",
|
||||||
|
target_roles=["Data Engineer"],
|
||||||
|
strengths=["Python"],
|
||||||
|
skills_to_emphasize=["BigQuery", "GCP"],
|
||||||
|
constraints=["CDI only"],
|
||||||
|
notes=["Years of experience feels closer to 3."],
|
||||||
|
)
|
||||||
|
|
||||||
|
output = build_candidate_profile_output(cv_signals, authored)
|
||||||
|
|
||||||
|
assert output.summary == "Junior data engineer focused on GCP."
|
||||||
|
assert output.constraints == ["CDI only"]
|
||||||
|
assert output.warnings[0].field == "years_of_experience"
|
||||||
Loading…
x
Reference in New Issue
Block a user