feat: add one-command candidate profile build flow
This commit is contained in:
parent
1879de68a8
commit
d27c58014d
@ -1,5 +1,12 @@
|
||||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
|
||||
from job_research.profile.cv_extractor import extract_cv_signals, extract_pdf_text
|
||||
from job_research.profile.merge import build_candidate_profile_output
|
||||
from job_research.profile.profile_parser import parse_profile_markdown
|
||||
from job_research.storage import save_candidate_profile_yaml
|
||||
|
||||
app = typer.Typer(help="Build one canonical candidate profile YAML")
|
||||
|
||||
|
||||
@ -8,9 +15,46 @@ def main_command() -> None:
|
||||
pass
|
||||
|
||||
@app.command("build-profile")
|
||||
def build_profile() -> None:
|
||||
def build_profile(
|
||||
cv: Path = typer.Option(
|
||||
...,
|
||||
"--cv",
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
help="Path to the CV PDF or UTF-8 text file.",
|
||||
),
|
||||
profile: Path = typer.Option(
|
||||
...,
|
||||
"--profile",
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
help="Path to the light-template markdown profile.",
|
||||
),
|
||||
out: Path = typer.Option(
|
||||
Path("data/candidate-profile.yaml"),
|
||||
"--out",
|
||||
dir_okay=False,
|
||||
help="Path to write the canonical YAML profile.",
|
||||
),
|
||||
) -> None:
|
||||
"""Build candidate-profile.yaml from CV and markdown profile."""
|
||||
|
||||
cv_text = extract_pdf_text(cv) if cv.suffix.lower() == ".pdf" else cv.read_text(encoding="utf-8")
|
||||
authored_profile = parse_profile_markdown(profile.read_text(encoding="utf-8"))
|
||||
cv_signals = extract_cv_signals(cv_text)
|
||||
candidate_profile = build_candidate_profile_output(cv_signals, authored_profile)
|
||||
|
||||
save_candidate_profile_yaml(out, candidate_profile)
|
||||
|
||||
typer.echo(f"candidate-profile.yaml written to {out}")
|
||||
warning_count = len(candidate_profile.warnings)
|
||||
if warning_count:
|
||||
typer.echo(f"Warnings included: {warning_count}")
|
||||
else:
|
||||
typer.echo("No warnings included.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
app()
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
from subprocess import run
|
||||
from textwrap import dedent
|
||||
|
||||
from job_research.storage import load_yaml
|
||||
|
||||
|
||||
def test_installed_cli_help_exposes_build_profile_subcommand() -> None:
|
||||
@ -14,3 +17,131 @@ def test_installed_cli_subcommand_help_works() -> None:
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "Usage: job-research build-profile" in result.stdout
|
||||
|
||||
|
||||
def test_build_profile_writes_yaml_from_cv_and_profile(tmp_path) -> None:
|
||||
cv = tmp_path / "cv.txt"
|
||||
cv.write_text(
|
||||
dedent(
|
||||
"""
|
||||
Tonio Example
|
||||
Location: France
|
||||
Languages: French, English
|
||||
Skills: Python, SQL, Terraform
|
||||
Years of experience: 3
|
||||
Data Engineer at Acme
|
||||
Education: Master of Science at Example University
|
||||
"""
|
||||
).strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
profile = tmp_path / "profile.md"
|
||||
profile.write_text(
|
||||
dedent(
|
||||
"""
|
||||
# Candidate Profile
|
||||
|
||||
## 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.
|
||||
- Experience should stay visible.
|
||||
"""
|
||||
).strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
out = tmp_path / "candidate-profile.yaml"
|
||||
|
||||
result = run(
|
||||
["uv", "run", "job-research", "build-profile", "--cv", str(cv), "--profile", str(profile), "--out", str(out)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert result.returncode == 0
|
||||
assert f"candidate-profile.yaml written to {out}" in result.stdout
|
||||
assert "Warnings included: 1" in result.stdout
|
||||
|
||||
payload = load_yaml(out)
|
||||
|
||||
assert payload["name"] == "Tonio Example"
|
||||
assert payload["summary"] == "Junior data engineer focused on Python and GCP."
|
||||
assert payload["target_roles"] == ["Data Engineer"]
|
||||
assert payload["skills"] == ["Python", "SQL", "Terraform", "GCP", "BigQuery"]
|
||||
assert payload["warnings"][0]["field"] == "years_of_experience"
|
||||
|
||||
|
||||
def test_build_profile_reports_when_no_warnings_are_included(tmp_path) -> None:
|
||||
cv = tmp_path / "cv.txt"
|
||||
cv.write_text(
|
||||
dedent(
|
||||
"""
|
||||
Tonio Example
|
||||
Location: France
|
||||
Languages: French, English
|
||||
Skills: Python, SQL
|
||||
Data Engineer at Acme
|
||||
Education: Master of Science at Example University
|
||||
"""
|
||||
).strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
profile = tmp_path / "profile.md"
|
||||
profile.write_text(
|
||||
dedent(
|
||||
"""
|
||||
# Candidate Profile
|
||||
|
||||
## 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.
|
||||
"""
|
||||
).strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
out = tmp_path / "candidate-profile.yaml"
|
||||
|
||||
result = run(
|
||||
["uv", "run", "job-research", "build-profile", "--cv", str(cv), "--profile", str(profile), "--out", str(out)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert result.returncode == 0
|
||||
assert f"candidate-profile.yaml written to {out}" in result.stdout
|
||||
assert "No warnings included." in result.stdout
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user