feat: add suggest_resume_keywords for skills/domains/keywords gap analysis

Replaces NotImplementedError stub with full LLM-backed implementation.
Builds a prompt from the last 3 resume positions plus already-selected
skills/domains/keywords, calls LLMRouter, and returns de-duped suggestions
in all three categories.
This commit is contained in:
pyr0ball 2026-03-05 15:00:53 -08:00
parent e0063e237b
commit 93fb452941
2 changed files with 85 additions and 1 deletions

View file

@ -124,4 +124,37 @@ def suggest_resume_keywords(
Returns: {"skills": [...], "domains": [...], "keywords": [...]}
"""
raise NotImplementedError
resume_context = _load_resume_context(resume_path)
already_skills = ", ".join(current_kw.get("skills", [])) or "none"
already_domains = ", ".join(current_kw.get("domains", [])) or "none"
already_keywords = ", ".join(current_kw.get("keywords", [])) or "none"
prompt = f"""You are helping a job seeker build a keyword profile used to score job description matches.
--- RESUME BACKGROUND ---
{resume_context or "Not provided"}
--- ALREADY SELECTED (do not repeat these) ---
Skills: {already_skills}
Domains: {already_domains}
Keywords: {already_keywords}
Suggest additional tags in each of the three categories below. Only suggest tags NOT already in the lists above.
SKILLS specific technical or soft skills (e.g. "Salesforce", "Executive Communication", "SQL", "Stakeholder Management")
DOMAINS industry verticals, company types, or functional areas (e.g. "B2B SaaS", "EdTech", "Non-profit", "Series A-C")
KEYWORDS specific terms, methodologies, metrics, or JD phrases (e.g. "NPS", "churn prevention", "QBR", "cross-functional")
Return ONLY valid JSON in exactly this format (no extra text):
{{"skills": ["Skill A", "Skill B"],
"domains": ["Domain A"],
"keywords": ["Keyword A", "Keyword B"]}}"""
raw = LLMRouter().complete(prompt).strip()
parsed = _parse_json(raw)
return {
"skills": parsed.get("skills", []),
"domains": parsed.get("domains", []),
"keywords": parsed.get("keywords", []),
}

View file

@ -95,3 +95,54 @@ def test_suggest_search_terms_raises_on_llm_exhausted():
with patch("scripts.suggest_helpers.LLMRouter", return_value=mock_router):
with pytest.raises(RuntimeError, match="All LLM backends exhausted"):
suggest_search_terms(["CSM"], RESUME_PATH, BLOCKLIST, USER_PROFILE)
# ── suggest_resume_keywords ───────────────────────────────────────────────────
CURRENT_KW = {
"skills": ["Customer Success", "SQL"],
"domains": ["B2B SaaS"],
"keywords": ["NPS"],
}
def test_suggest_resume_keywords_returns_all_three_categories():
from scripts.suggest_helpers import suggest_resume_keywords
payload = {
"skills": ["Project Management"],
"domains": ["EdTech"],
"keywords": ["churn prevention"],
}
with _mock_llm(payload):
result = suggest_resume_keywords(RESUME_PATH, CURRENT_KW)
assert "skills" in result
assert "domains" in result
assert "keywords" in result
def test_suggest_resume_keywords_excludes_already_selected():
from scripts.suggest_helpers import suggest_resume_keywords
with _mock_llm({"skills": [], "domains": [], "keywords": []}) as mock_cls:
suggest_resume_keywords(RESUME_PATH, CURRENT_KW)
prompt_sent = mock_cls.return_value.complete.call_args[0][0]
# Already-selected tags should appear in the prompt so LLM knows to skip them
assert "Customer Success" in prompt_sent
assert "NPS" in prompt_sent
def test_suggest_resume_keywords_returns_empty_on_bad_json():
from scripts.suggest_helpers import suggest_resume_keywords
mock_router = MagicMock()
mock_router.complete.return_value = "I cannot assist."
with patch("scripts.suggest_helpers.LLMRouter", return_value=mock_router):
result = suggest_resume_keywords(RESUME_PATH, CURRENT_KW)
assert result == {"skills": [], "domains": [], "keywords": []}
def test_suggest_resume_keywords_raises_on_llm_exhausted():
from scripts.suggest_helpers import suggest_resume_keywords
mock_router = MagicMock()
mock_router.complete.side_effect = RuntimeError("All LLM backends exhausted")
with patch("scripts.suggest_helpers.LLMRouter", return_value=mock_router):
with pytest.raises(RuntimeError, match="All LLM backends exhausted"):
suggest_resume_keywords(RESUME_PATH, CURRENT_KW)