Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions api/app/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,13 +659,15 @@ async def _revise_cv_text(cv: dict, findings: list[dict], candidate: dict, job:
"the candidate data states; no generic claims ('collaborated with cross-functional teams', 'worked on "
"various projects'). 3. SUMMARY: a specific specialism and domain plus one concrete strength; no cliché "
"openers or closing filler, no pronouns. 4. PROJECTS: what it does, how it works, and a real result, framed "
"for this job's domain. 5. ORDER: the most job-relevant bullet comes first in each role."
"for this job's domain. 5. ORDER: the most job-relevant bullet comes first in each role; the most relevant "
"role may carry a third bullet when the data backs one, the others one or two."
)

_LETTER_RUBRIC = (
"The letter follows the candidate's own structure; judge each part against it, never reshape it into another. "
"1. HOOK: opens on a specific challenge from one of the candidate's own projects and what it shows about how "
"they work, tied to the exact role and one concrete fact about this employer; it passes the swap test (another "
"they work, tied to the exact role and one concrete fact about this employer, and says why that fact draws "
"the candidate through their own data (not a bare 'this is relevant'); it passes the swap test (another "
"employer's name couldn't replace theirs); never 'I am writing to apply'. 2. EVIDENCE: two named projects, the "
"first in precise detail with a real number matched to a requirement in the ad, the second a judgement call, "
"joined by the discipline both show. 3. HONEST GAP: one real requirement from the ad the candidate is lighter "
Expand Down Expand Up @@ -1226,8 +1228,10 @@ async def execute_tool(name: str, tool_input: dict) -> dict:
"technical challenge in it and what actually made it hard (the shape 'the hardest challenge wasn't X, "
"but Y'), told only from what that project's data says. Say what it shows about how the candidate works "
"(an instinct or habit), then tie that same instinct to the EXACT role and ONE concrete, specific fact "
"about this employer from the COMPANY RESEARCH or the ad (what they build, or how they work), ending on "
"why this is the place to start their career. NEVER begin with 'I am writing'. It MUST fail the "
"about this employer from the COMPANY RESEARCH or the ad (what they build, or how they work). Then say "
"why that fact draws THIS candidate, through something in their data (a project, a subject they have "
"worked on, a stated goal), never just that it is 'relevant'; end on why this is the place to start "
"their career. NEVER begin with 'I am writing'. It MUST fail the "
"'swap-the-company-name' test: if another employer's name could replace theirs, it is too generic. "
"(2) EVIDENCE ~110 words — start 'Two projects demonstrate the <quality this role needs> I'd bring to "
"your <team/work>.' First, one named project in precise technical detail with a real number from the "
Expand Down Expand Up @@ -1447,8 +1451,11 @@ async def execute_tool(name: str, tool_input: dict) -> dict:
"ATS RULES: use standard industry skill names and SPELL OUT acronyms next to the abbreviation, "
"e.g. 'Search Engine Optimization (SEO)'. Keep every date exactly as the candidate data gives it "
"(MM/YYYY); never reformat one. "
"BULLETS: 1-2 per role (one is right when the role is one story), each one or two lines "
"(~20-35 words) and no more than ~60 words per role in all, beginning with a strong, varied action verb (never 'Responsible for' or 'Helped "
"BULLETS: the ONE role most relevant to this job gets 3 bullets whenever its data holds three or "
"more distinct achievements (up to 3 for the ONE role, fewer only when the data has fewer); every "
"other role gets 1-2 (one is right when the role is one story). Each bullet one or two lines "
"(~20-35 words); no more than ~60 words per role in all (~90 for that most relevant role); "
"each beginning with a strong, varied action verb (never 'Responsible for' or 'Helped "
"with'); follow Action + Task/Tool + quantified Result, and combine closely related achievements "
"into one bullet rather than listing them. Quantify ONLY with metrics actually present in the candidate data; if a bullet has no "
"source number, convey scope/scale with concrete specifics instead — NEVER invent, estimate or "
Expand Down
11 changes: 11 additions & 0 deletions api/tests/test_doc_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ def test_a_revision_in_the_wrong_shape_is_dropped(replies):
assert _generate_cv()["experience"][0]["bullets"] == ["Cut API latency by 35% with Redis caching."]


def test_the_most_relevant_role_keeps_a_third_bullet(replies):
bullets = ["Built a FastAPI service ingesting 40k readings a day into PostgreSQL.",
"Wrote tests for the ingestion service in Python.",
"Documented the service's API for the analytics team."]
model = _model_cv(bullets[0])
model["experience"][0]["bullets"] = bullets
replies.queue = [json.dumps(model)]
assert _generate_cv()["experience"][0]["bullets"] == bullets
assert "up to 3 for the ONE role" in replies.prompts[0]


def test_a_letter_with_a_flagged_word_is_revised(replies):
first = {"salutation": "Dear Hiring Manager,", "signoff": "Yours faithfully,", "headline": "Graduate",
"paragraphs": ["I leverage FastAPI daily.", "Your team of 40 engineers ships daily."]}
Expand Down
7 changes: 7 additions & 0 deletions api/tests/test_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,13 @@ def test_the_letter_ends_on_the_candidates_closing_line(llm, close):
"I'd welcome the chance to talk. Thank you for your time and consideration and I hope to hear from you soon.")


def test_the_hook_says_why_the_employer_draws_the_candidate(llm):
llm.reply = json.dumps({"headline": "Computer Science Graduate", "salutation": "Dear Hiring Manager",
"paragraphs": _letter_paragraphs(), "signoff": "Kind Regards,"})
_cover_letter()
assert "why that fact draws THIS candidate" in llm.prompts[0]


def test_c_plus_plus_stays_on_one_line():
html = doc_render.render_letter_html({"candidate_name": "Sam Okafor", "paragraphs": ["I write C++ daily."]})
assert "C+⁠+ daily" in html # a word joiner between the plus signs
Expand Down
Loading