File size: 4,329 Bytes
f964ea3
 
 
 
 
 
b8b5681
 
14f0075
f964ea3
110cf71
f964ea3
 
 
 
 
 
 
 
 
 
 
 
 
 
110cf71
b8b5681
110cf71
 
 
3086f72
110cf71
 
 
f964ea3
b8b5681
f964ea3
 
 
 
 
 
110cf71
b8b5681
f964ea3
 
b8b5681
f964ea3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3086f72
f964ea3
 
3086f72
 
 
f964ea3
 
b8b5681
 
 
 
 
f964ea3
 
b8b5681
110cf71
 
 
b8b5681
 
110cf71
b8b5681
f964ea3
 
 
 
 
 
 
 
 
110cf71
 
 
 
 
f964ea3
 
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import json
import os
from groq import Groq
from dotenv import load_dotenv

load_dotenv()
api_key = os.environ.get("GROQ_API_KEY", "")
client = Groq(api_key=api_key) if api_key else None
GROQ_MODEL = os.environ.get("GROQ_MODEL", "openai/gpt-oss-20b")


def analyze_interview(session: dict) -> dict:
    messages = session.get("messages", [])
    mode = session.get("mode", "general")
    company = session.get("company", "")
    role = session.get("role", "SDE")

    transcript = ""
    for m in messages:
        if m["role"] == "user":
            transcript += f"CANDIDATE: {m['content']}\n\n"
        elif m["role"] == "assistant":
            transcript += f"INTERVIEWER: {m['content']}\n\n"

    if not transcript.strip():
        return {"error": "No interview answers were recorded."}
    if not client:
        return {"error": "Feedback is unavailable because no LLM provider is configured."}

    rubric = (
        "Evaluate structure, prioritization, business judgment, and communication. Explicitly score structuring ability, MECE decomposition, quantitative rigor, SQL correctness/efficiency, communication, clarity of thought, and comfort with ambiguity or missing information. For case studies and guesstimates, explicitly judge whether the candidate asked clarifying questions when useful, how clearly they articulated assumptions, and how effectively they communicated trade-offs. Return a PM/consulting rubric with each metric 0-100."
        if mode == "consulting_pm"
        else ""
    )

    prompt = f"""You are an exacting but supportive interview coach analyzing a {mode} interview for a {role} role{' at ' + company.capitalize() if company else ''}.

Here is the full interview transcript:
---
{transcript[:6000]}
---

{rubric}
Analyze evidence from the candidate's actual answers. Do not praise generic traits or invent details. Return ONLY valid JSON with EXACTLY this structure:
{{
  "overall_score": <number 0-100>,
  "summary": "<2-3 sentence honest assessment naming the clearest evidence>",
  "strong_points": [
    {{"title": "<strength title>", "detail": "<specific example from interview>"}},
    {{"title": "<strength title>", "detail": "<specific example from interview>"}},
    {{"title": "<strength title>", "detail": "<specific example from interview>"}}
  ],
  "pain_points": [
    {{"title": "<weakness title>", "detail": "<specific example and why it matters>"}},
    {{"title": "<weakness title>", "detail": "<specific example and why it matters>"}},
    {{"title": "<weakness title>", "detail": "<specific example and why it matters>"}}
  ],
  "areas_of_improvement": [
    {{"title": "<area>", "action": "<specific actionable advice>"}},
    {{"title": "<area>", "action": "<specific actionable advice>"}},
    {{"title": "<area>", "action": "<specific actionable advice>"}}
  ],
  "skill_scores": {{
    "technical": <0-100>,
    "communication": <0-100>,
    "clarity_of_thought": <0-100>,
    "problem_solving": <0-100>,
    "confidence": <0-100>,
    "cultural_fit": <0-100>,
    "structured_reasoning": <0-100>,
    "business_judgment": <0-100>
  }},
  "hiring_verdict": "<Strong Hire | Hire | Maybe | No Hire>",
  "verdict_reason": "<one sentence explanation grounded in the transcript>",
  "next_steps": [
    "<a concrete practice exercise for this week>",
    "<a concrete practice exercise for the next interview>"
  ]
}}"""

    options = {
        "model": GROQ_MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": 1200,
    }
    if GROQ_MODEL.startswith("openai/gpt-oss"):
        options["reasoning_effort"] = "low"
    response = client.chat.completions.create(**options)

    raw = response.choices[0].message.content.strip()
    if raw.startswith("```"):
        raw = raw.split("```")[1]
        if raw.startswith("json"):
            raw = raw[4:]
    raw = raw.strip()

    try:
        result = json.loads(raw)
        required = ["overall_score", "summary", "strong_points", "pain_points", "areas_of_improvement", "skill_scores", "hiring_verdict", "verdict_reason", "next_steps"]
        if not all(key in result for key in required):
            return {"error": "Feedback response was incomplete", "raw": raw}
        return result
    except Exception:
        return {"error": "Could not parse analysis", "raw": raw}