diff --git a/docs/profile-template.md b/docs/profile-template.md new file mode 100644 index 0000000..e363456 --- /dev/null +++ b/docs/profile-template.md @@ -0,0 +1,22 @@ +# Candidate Profile + +## Summary +One short paragraph describing your target profile. + +## Target Roles +- Data Engineer + +## Strengths +- Python +- SQL + +## Skills To Emphasize +- GCP +- BigQuery + +## Constraints +- CDI only +- France only + +## Notes +- Anything the CV parser might miss but future ranking should understand. diff --git a/src/job_research/profile/__init__.py b/src/job_research/profile/__init__.py new file mode 100644 index 0000000..e3742ea --- /dev/null +++ b/src/job_research/profile/__init__.py @@ -0,0 +1 @@ +__all__ = ["profile_parser"] diff --git a/src/job_research/profile/profile_parser.py b/src/job_research/profile/profile_parser.py new file mode 100644 index 0000000..364030a --- /dev/null +++ b/src/job_research/profile/profile_parser.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from collections import defaultdict +from dataclasses import dataclass, field + + +@dataclass +class AuthoredProfile: + 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) + + +REQUIRED_SECTIONS = { + "summary", + "target roles", + "strengths", + "skills to emphasize", + "constraints", + "notes", +} + + +def parse_profile_markdown(markdown: str) -> AuthoredProfile: + sections: dict[str, list[str]] = defaultdict(list) + current_section: str | None = None + + for raw_line in markdown.splitlines(): + line = raw_line.strip() + if line.startswith("## "): + current_section = line[3:].strip().lower() + continue + if not line or current_section is None: + continue + sections[current_section].append(line) + + missing = REQUIRED_SECTIONS - set(sections) + if missing: + missing_text = ", ".join(sorted(missing)) + raise ValueError(f"Missing required markdown sections: {missing_text}") + + return AuthoredProfile( + summary=" ".join(sections["summary"]), + target_roles=[ + line[2:].strip() + for line in sections["target roles"] + if line.startswith("- ") + ], + strengths=[ + line[2:].strip() + for line in sections["strengths"] + if line.startswith("- ") + ], + skills_to_emphasize=[ + line[2:].strip() + for line in sections["skills to emphasize"] + if line.startswith("- ") + ], + constraints=[ + line[2:].strip() + for line in sections["constraints"] + if line.startswith("- ") + ], + notes=[line[2:].strip() if line.startswith("- ") else line for line in sections["notes"]], + ) diff --git a/tests/profile/test_profile_parser.py b/tests/profile/test_profile_parser.py new file mode 100644 index 0000000..52cac41 --- /dev/null +++ b/tests/profile/test_profile_parser.py @@ -0,0 +1,42 @@ +from textwrap import dedent + +from job_research.profile.profile_parser import parse_profile_markdown + + +def test_parse_profile_markdown_reads_light_required_template() -> None: + markdown = dedent( + """ + # Candidate Profile + + ## Summary + Junior data engineer focused on Python and GCP. + + ## Target Roles + - Data Engineer + - Analytics Engineer + + ## Strengths + - Python + - SQL + + ## Skills To Emphasize + - BigQuery + - Terraform + + ## Constraints + - CDI only + - France only + + ## Notes + - Slight preference for French listings. + """ + ).strip() + + profile = parse_profile_markdown(markdown) + + assert profile.summary == "Junior data engineer focused on Python and GCP." + assert profile.target_roles == ["Data Engineer", "Analytics Engineer"] + assert profile.strengths == ["Python", "SQL"] + assert profile.skills_to_emphasize == ["BigQuery", "Terraform"] + assert profile.constraints == ["CDI only", "France only"] + assert profile.notes == ["Slight preference for French listings."]