File size: 3,267 Bytes
aeacc04
a0f1a88
aeacc04
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a0f1a88
aeacc04
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
from groq import AsyncGroq
from ..config import get_settings, get_revolving_groq_key


SYSTEM_PROMPT = """You are a senior technical recruiter with deep engineering knowledge.
You analyze candidate profiles against job descriptions and provide concise, honest assessments.
Always ground your analysis in the specific data provided — never hallucinate skills or experience.
Structure your response in exactly two parts:
1. FIT ANALYSIS (2-3 sentences on why the candidate is or isn't a good match)
2. KEY GAPS (1-2 sentences addressing specific missing requirements)"""


def _build_prompt(jd: dict, candidate: dict, gaps: list[dict]) -> str:
    gaps_text = ""
    if gaps:
        gap_lines = []
        for g in gaps:
            gap_lines.append(f"- {g['type'].replace('_', ' ').title()}: {g['detail']}")
        gaps_text = "Pre-computed gaps:\n" + "\n".join(gap_lines)

    skills_text = ", ".join(
        (candidate.get("programming_languages") or [])
        + (candidate.get("backend_frameworks") or [])
        + (candidate.get("frontend_technologies") or [])
    )

    return f"""JOB DESCRIPTION:
Title: {jd.get('title', 'Unknown')}
Required Skills: {', '.join(jd.get('required_skills') or [])}
Min Experience: {jd.get('min_yoe', 'Not specified')} years
Engineer Type: {jd.get('engineer_type', 'Not specified')}
Location: {jd.get('location', 'Not specified')}

CANDIDATE PROFILE:
Summary: {candidate.get('parsed_summary') or 'Not provided'}
Skills: {candidate.get('parsed_skills') or skills_text or 'Not provided'}
Experience: {candidate.get('years_of_experience', 'Unknown')} years
Company: {candidate.get('most_recent_company') or 'Not provided'}
Growth Velocity Score: {candidate.get('growth_velocity', 0.5):.2f} / 1.0

{gaps_text}

Provide your assessment:"""


async def generate_explanation(jd: dict, candidate: dict, gaps: list[dict]) -> str:
    settings = get_settings()
    client = AsyncGroq(api_key=get_revolving_groq_key())

    try:
        response = await client.chat.completions.create(
            model=settings.groq_model,
            messages=[
                {"role": "system", "content": SYSTEM_PROMPT},
                {"role": "user", "content": _build_prompt(jd, candidate, gaps)},
            ],
            temperature=0.3,
            max_tokens=400,
        )
        return response.choices[0].message.content.strip()
    except Exception:
        return _fallback_explanation(jd, candidate, gaps)


def _fallback_explanation(jd: dict, candidate: dict, gaps: list[dict]) -> str:
    skill_gaps = [g["detail"] for g in gaps if g["type"] == "missing_skill"]
    yoe_gaps = [g["detail"] for g in gaps if g["type"] == "yoe_gap"]

    parts = []
    skills_text = ", ".join(
        (candidate.get("programming_languages") or [])[:5]
    )
    if skills_text:
        parts.append(f"FIT ANALYSIS: Candidate brings experience in {skills_text}")
    else:
        parts.append("FIT ANALYSIS: Candidate profile available for review.")

    if skill_gaps or yoe_gaps:
        gap_list = (skill_gaps[:3] + yoe_gaps)[:3]
        parts.append(f"KEY GAPS: Missing requirements include: {', '.join(gap_list)}.")
    else:
        parts.append("KEY GAPS: No critical gaps identified from structured data.")

    return "\n".join(parts)