feat: add canonical candidate profile output model

This commit is contained in:
Antoine 2026-05-28 13:16:21 +02:00
parent a0522c316e
commit 1b4e901afe
3 changed files with 84 additions and 0 deletions

View File

@ -0,0 +1,37 @@
from pydantic import BaseModel, Field
class ExperienceEntry(BaseModel):
company: str
title: str
start: str | None = None
end: str | None = None
highlights: list[str] = Field(default_factory=list)
class EducationEntry(BaseModel):
institution: str
credential: str
start: str | None = None
end: str | None = None
class WarningItem(BaseModel):
field: str
message: str
class CandidateProfileOutput(BaseModel):
name: str | None = None
summary: str | None = None
target_roles: list[str] = Field(default_factory=list)
strengths: list[str] = Field(default_factory=list)
skills_to_emphasize: list[str] = Field(default_factory=list)
constraints: list[str] = Field(default_factory=list)
notes: list[str] = Field(default_factory=list)
location: str | None = None
languages: list[str] = Field(default_factory=list)
skills: list[str] = Field(default_factory=list)
experience_entries: list[ExperienceEntry] = Field(default_factory=list)
education_entries: list[EducationEntry] = Field(default_factory=list)
warnings: list[WarningItem] = Field(default_factory=list)

View File

@ -0,0 +1,18 @@
from __future__ import annotations
from pathlib import Path
from typing import Any
import yaml
from job_research.models import CandidateProfileOutput
def save_candidate_profile_yaml(path: Path, profile: CandidateProfileOutput) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
payload = profile.model_dump(mode="json")
path.write_text(yaml.safe_dump(payload, sort_keys=False, allow_unicode=True), encoding="utf-8")
def load_yaml(path: Path) -> dict[str, Any]:
return yaml.safe_load(path.read_text(encoding="utf-8")) or {}

29
tests/test_storage.py Normal file
View File

@ -0,0 +1,29 @@
from job_research.models import CandidateProfileOutput, ExperienceEntry, WarningItem
from job_research.storage import save_candidate_profile_yaml, load_yaml
def test_save_candidate_profile_yaml_round_trips_readable_output(tmp_path) -> None:
profile = CandidateProfileOutput(
name="Tonio",
summary="Junior data engineer focused on Python and GCP.",
target_roles=["Data Engineer"],
strengths=["Python", "SQL"],
skills_to_emphasize=["GCP", "BigQuery"],
constraints=["CDI only", "France only"],
notes=["Slight preference for French listings."],
location="France",
languages=["French", "English"],
skills=["Python", "SQL", "Terraform", "GCP", "BigQuery"],
experience_entries=[ExperienceEntry(company="A", title="Data Engineer")],
education_entries=[],
warnings=[WarningItem(field="years_of_experience", message="CV and profile disagree.")],
)
out = tmp_path / "candidate-profile.yaml"
save_candidate_profile_yaml(out, profile)
payload = load_yaml(out)
assert payload["name"] == "Tonio"
assert payload["constraints"] == ["CDI only", "France only"]
assert payload["warnings"][0]["field"] == "years_of_experience"