From 59b3490bb0d34f5f0ffca81645f58b2232c5a80f Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Fri, 7 Aug 2026 11:30:01 +0530 Subject: [PATCH 1/5] docs: update pipeline architecture and database schema references --- docs/architecture/AI_PROCESSING.md | 200 +++++++++++++++++++ docs/architecture/DB_DESIGN.md | 152 +++++++++++--- docs/tools_research/PDF_PARSING_LIBRARIES.md | 12 -- 3 files changed, 322 insertions(+), 42 deletions(-) diff --git a/docs/architecture/AI_PROCESSING.md b/docs/architecture/AI_PROCESSING.md index 483ff10..87f6415 100644 --- a/docs/architecture/AI_PROCESSING.md +++ b/docs/architecture/AI_PROCESSING.md @@ -13,6 +13,7 @@ 2. [AI Prompt Engineering](#2-ai-prompt-engineering) 3. [Matching Score Formula](#3-matching-score-formula) 4. [Document Parsing](#4-document-parsing) +5. [AI Interview Screening Pipeline](#5-ai-interview-screening-pipeline) > **Related**: For full prompt evaluation results, JD/Resume test profiles, and all 8 parsed JSON match outputs, see [PROMPT_EVALUATION_REPORT.md](../PROMPT_EVALUATION_REPORT.md). @@ -333,3 +334,202 @@ Supported input formats: **PDF, DOCX**. Files are validated by MIME type and mag --- +## 5. AI Interview Screening Pipeline + +This section outlines the flow, prompts, and scoring formulas used for the automated video screening interviews. + +### 5.1 End-to-End Pipeline Sequence Diagram +This diagram visualizes the flow of data through the AI models and the Attendee.dev voice platform. + +```mermaid +sequenceDiagram + participant DB as PostgreSQL DB + participant LLM as AI Model + participant AttendeeDev as Attendee.dev Platform + participant Candidate as Candidate + + %% Step 1: Generation + rect rgb(220, 240, 220) + Note over DB, LLM: Step 1: Question Generation + DB->>LLM: Supply Parsed JD, Parsed Resume & match_result JSON + LLM-->>DB: Generate Categorized Questions + Note right of DB: Saved to interview_session.generated_questions (JSON) + end + + %% Step 2: Interview Loop + rect rgb(250, 230, 200) + Note over DB, AttendeeDev: Step 2: The Interview Loop (Repeats per Question) + DB->>AttendeeDev: Send Question Text + AttendeeDev->>Candidate: Speak Question (Audio) + Candidate->>AttendeeDev: Speak Answer (Audio) + AttendeeDev-->>DB: Return Transcribed Text + end + + %% Step 3: Evaluation + rect rgb(240, 210, 240) + Note over DB, LLM: Step 3: Hybrid Evaluation + DB->>LLM: Send Transcribed Answer + LLM-->>DB: Evaluate (Score, Keywords, Decision) + Note right of DB: Save Q&A to question_answer array + Note right of DB: Append evaluation to analysis_result array + end + + %% Step 4: Follow-Up Loop + rect rgb(255, 220, 220) + Note over DB, AttendeeDev: Step 4: Follow-Up Loop (If Decision == ASK_FOLLOW_UP) + LLM->>DB: Suggest Follow-up Question + DB->>AttendeeDev: Send Follow-up Text + AttendeeDev->>Candidate: Speak Follow-up (Audio) + Candidate->>AttendeeDev: Speak Follow-up Answer (Audio) + AttendeeDev-->>DB: Return Transcribed Text + DB->>LLM: Evaluate Follow-up Answer + Note right of DB: Append follow_up to analysis_result array + end + + %% Step 5: Finalization + rect rgb(210, 240, 240) + Note over DB, DB: Step 5: Final Summary & Storage + DB->>DB: Calculate final_summary (60% Pass Threshold) + Note right of DB: Append final_summary to analysis_result + end +``` + +### 5.2 Question Generation Logic & Prompts + +**Question Category Distribution:** +The pipeline currently generates a baseline of **15 questions** per candidate, though this total number may vary based on system configuration. For a standard 15-question session, the distribution is strictly allocated as follows: + +| Category | Description | Question Count | +| :--- | :--- | :--- | +| `must_have_matched` | Verifies depth on skills the candidate claims to possess. | 7 - 8 | +| `lacking_skill` | Basic awareness checks for required skills missing from the resume. | 3 - 4 | +| `good_to_have` | Checks for bonus skills requested by the JD. | 2 - 3 | +| `experience_domain` | Practical, scenario-based questions based on responsibilities. | ~ 2 | + +**Question Generation Prompt:** +*(Note: The prompt below assumes a 15-question limit. The total number and category distribution are dynamically injected based on the interview time limit).* +```text +You are an expert technical AI preparing questions for an AUTOMATED VIDEO SCREENING interview. The candidate will be recording 1-2 minute video answers. The goal of this round is only to verify whether the candidate genuinely knows the required skills — not to run a full-depth technical L1/L2 interview. + +═══ JOB CONTEXT ═══ +Role: {title} at {company} +JD Required Experience: {experience_required} +JD Must-Have Skills: {must_have_skills} +JD Good-to-Have Skills: {good_to_have_skills} +JD Responsibilities: {responsibilities} + +═══ CANDIDATE CONTEXT ═══ +Years of Experience: {years} +Candidate Domain Expertise: {domain} + +═══ FULL MATCH ANALYSIS JSON — use this to decide question focus ═══ +{match_json} + +How to use the match analysis above: +- matched_skills.must_have → candidate HAS these → generate depth-verification questions +- missing_skills.must_have → candidate MISSING these → generate basic awareness questions +- score_breakdown.must_have_skills_score → if low (< 20/40), add more "lacking_skill" questions +- score_breakdown.good_to_have_skills_score → if 0, ask only basic "what is X" awareness +- reasoning → use the gap analysis directly to frame targeted questions +- experience_match: false → frame experience_domain questions as awareness checks +- qualification_match: false → do not expect academic-level depth in answers + +═══ SCREENING DIFFICULTY RULES ═══ +Apply the following difficulty guidance based on the JD requiring {min_y} years of experience: +- If 0-2 years (EASY): Ask basic knowledge-verification questions only. +- If 3-5 years (MEDIUM): Ask single-concept questions that verify genuine hands-on knowledge. +- If 5+ years (HARD): Ask high-level "when to use what" questions. + +Generate EXACTLY 15 questions in total across the following categories: +1. CATEGORY "must_have_matched" — from matched_skills.must_have (7–8 questions) +2. CATEGORY "lacking_skill" — from missing_skills.must_have (3–4 questions) +3. CATEGORY "good_to_have" — from JD good-to-have skills (2–3 questions) +4. CATEGORY "experience_domain" — from JD responsibilities (~2 purely technical questions) + +═══ OUTPUT FORMAT ═══ +Return a JSON array only. No markdown, no commentary. +[ + { + "id": 1, + "category": "must_have_matched | lacking_skill | good_to_have | experience_domain", + "skill_focus": "the specific skill or topic", + "question": "the interview question", + "expected_keywords": ["3 to 5 keywords a correct answer must touch"], + "answer_depth": "one sentence describing what a passing answer should cover at this screening level" + } +] +``` + +### 5.3 Answer Evaluation Prompts + +**Standard Answer Evaluation Prompt:** +```text +You are evaluating a candidate's answer in a FIRST SCREENING interview. + +QUESTION: {question} +CANDIDATE ANSWER: {answer} +EXPECTED KEYWORDS (answer should address most of these): {keywords} +EXPECTED DEPTH FOR PASSING: {depth} + +SCORING RULES: +- Score 0–10. Coverage 0–100%. +- Score 7–10: correct and clear, even if brief. +- Score 5–6: partially correct, key concept there but something important missing. +- Score 0–4: wrong, confused, or vague with no real understanding shown. +- Do NOT penalize for informal phrasing or brevity if the concept is correct. +- DO penalize for factually wrong statements or restating the question without substance. + +DECISION: +- "NEXT_QUESTION" if score >= 6 AND coverage_percent >= 50 (candidate understood it well enough for screening). +- "ASK_FOLLOW_UP" if score < 6 OR coverage_percent < 50 (answer was too shallow or missed key concepts). + +Return STRICT JSON only. No markdown: +{ + "score": <0-10>, + "coverage_percent": <0-100>, + "keywords_found": ["..."], + "keywords_missing": ["..."], + "is_sufficient": , + "decision": "NEXT_QUESTION | ASK_FOLLOW_UP", + "feedback": "2-3 sentences: what was good, what was missing, pass/fail on this topic for screening", + "suggested_follow_up": "If decision is ASK_FOLLOW_UP and this is NOT a follow-up evaluation itself, write a specific, conversational follow-up question here to probe what they missed based on the missing keywords. Otherwise null." +} +``` + +> **Note on Evaluation Output Calculation:** +> * The **`score` (0-10)** is determined subjectively by the LLM based on conceptual understanding and the phrasing of the candidate's answer. +> * The **`coverage_percent`** is strictly and mathematically calculated by the Python backend using the array output: `( len(keywords_found) / len(expected_keywords) ) * 100`. The LLM's generated `coverage_percent` acts merely as a fallback. + +**Follow-up Answer Evaluation Prompt:** +The prompt is **identical** to the standard Answer Evaluation Prompt above, except this exact string is injected at the very top of the context: +```text +NOTE: This is a FOLLOW-UP evaluation. The candidate had an insufficient primary answer. +``` +*(The LLM uses this to understand that the candidate is attempting to recover from a previously missed keyword).* + +### 5.4 Final Summary Calculation Engine + +Once all generated questions (and any necessary follow-up loops) are complete, the pipeline's Python layer calculates the final summary. + +**Topic Score Resolution:** +If a question resulted in a follow-up loop, the final score for that topic is the **average** of the primary score and the follow-up score: +```python +if "follow_up_score" in eval_obj: + topic_score = (primary_score + follow_up_score) / 2 +else: + topic_score = primary_score +``` + +**Aggregate Scoring:** +* **Total Score:** The sum of all resolved `topic_score` values. +* **Max Possible Score:** `{total_questions} * 10` points (e.g., 150 for a 15-question set). +* **Final Percentage:** `(total_score / max_possible_score) * 100` + +**Recommendation Threshold:** +The system enforces a strict, deterministic pass/fail threshold at **60%**: +```python +if final_percentage >= 60.0: + final_recommendation = "shortlist_for_l1" +else: + final_recommendation = "reject" +``` diff --git a/docs/architecture/DB_DESIGN.md b/docs/architecture/DB_DESIGN.md index 18106f3..e459ab0 100644 --- a/docs/architecture/DB_DESIGN.md +++ b/docs/architecture/DB_DESIGN.md @@ -138,7 +138,7 @@ | `status` | interview_status | | See [Interview Status Enum](#interview-status) | | `interview_metadata` | jsonb | nullable | Flexible metadata (links, tokens, etc.) | | `comment` | varchar | nullable | Internal notes | -| `generated_questions` | jsonb | nullable | AI-generated question set for the session | +| `generated_questions` | jsonb | nullable | See [generated_questions Schema](#interview-session-generated_questions) | | `scheduled_at` | timestamp | nullable | Confirmed session time | | `completed_at` | timestamp | nullable | | | `created_at` | timestamp | | | @@ -152,8 +152,8 @@ | `id` | uuid | PK | | | `interview_session_id` | uuid | FK → interview_session, NOT NULL, UNIQUE | One-to-one with session | | `application_id` | uuid | FK → applications, NOT NULL | | -| `analysis_result` | jsonb | nullable | See [analysis_result Schema](#interview_analysis-analysis_result) | -| `question_answer` | jsonb | nullable | Array of `{question, answer, score, analysis}` | +| `analysis_result` | jsonb | nullable | See [analysis_result Schema](#interview-analysis-analysis_result) | +| `question_answer` | jsonb | nullable | See [question_answer Schema](#interview-analysis-question_answer) | | `recording_url` | text | nullable | URL / S3 path for session recording | | `interview_type` | interview_type | | | | `created_at` | timestamp | | | @@ -331,12 +331,12 @@ erDiagram | Status | Description | |--------|-------------| -| `scheduled` | Session confirmed and upcoming | -| `rescheduled` | Session rescheduled | -| `completed` | Session successfully completed | +| `scheduled` | Interview confirmed and upcoming | +| `rescheduled` | Interview rescheduled | +| `completed` | Interview completed | | `no_show` | Candidate did not join | -| `cancelled` | Session cancelled | -| `failed` | Session failed due to a technical error | +| `cancelled` | Interview cancelled | +| `failed` | Interview failed due to a technical error | --- @@ -345,23 +345,23 @@ erDiagram ### JD `parsed_jd` ```json -{{ +{ "title": null, "company": null, "company_description": null, - "experience_required": {{ + "experience_required": { "min_years": null, "max_years": null - }}, - "skills": {{ + }, + "skills": { "must_have": ["skill1", "skill2"], "good_to_have": ["skill1", "skill2"] - }}, + }, "qualifications": ["degree1", "degree2"], "responsibilities": ["resp1", "resp2"], "location": null, "employment_type": "Full-time" -}} +} ``` @@ -372,32 +372,32 @@ All fields return `null` if not explicitly found in the JD. The LLM prompt const ### Application `parsed_resume` ```json -{{ +{ "primary_skills": ["string"], "secondary_skills": ["string"], "domain_expertise": ["string"], - "relevant_experience": {{ + "relevant_experience": { "total_years": "number or null", "roles": [ - {{ + { "title": "string or null", "company": "string or null", "start_date": "string or null", "end_date": "string or null", "years": "number or null", "highlights": ["string"] - }} + } ] - }}, + }, "education_certificates": [ - {{ + { "name": "string", "issuer": "string or null", "year": "string or null", "type": "degree or certification" - }} + } ] -}} +} ``` --- @@ -405,31 +405,123 @@ All fields return `null` if not explicitly found in the JD. The LLM prompt const ### Application `matching_result` ```json -{{ - "score_breakdown": {{ +{ + "score_breakdown": { "must_have_skills_score": 32.0, "experience_score": 30.0, "good_to_have_skills_score": 15.0, "qualifications_score": 10.0 - }}, + }, "match_score": 8.7, "reasoning": [ "point 1", "point 2", "point 3" ], - "matched_skills": {{ + "matched_skills": { "must_have": ["..."], "good_to_have": ["..."] - }}, - "missing_skills": {{ + }, + "missing_skills": { "must_have": ["..."], "good_to_have": ["..."] - }}, + }, "qualification_match": true, "experience_match": true -}} +} +``` +--- + +### Interview Session `generated_questions` + +```json +[ + { + "id": 1, + "category": "must_have_matched", + "skill_focus": "Core Java", + "question": "Can you explain the difference between checked and unchecked exceptions?", + "expected_keywords": ["RuntimeException", "compile-time", "try-catch", "throws"], + "answer_depth": "The candidate should clearly state that checked exceptions are verified at compile-time while unchecked exceptions happen at runtime." + } +] +``` +This dynamic list of questions is tailored to the candidate and generated by the LLM based on the interview time limit. + +--- + +### Interview Analysis `question_answer` + +```json +[ + { + "question_id": 1, + "question": "Can you explain the difference between a HashMap and a ConcurrentHashMap?", + "candidate_answer": "So, a HashMap isn't thread-safe, while a ConcurrentHashMap is designed for multi-threaded environments.", + "follow_up_question": "You mentioned that ConcurrentHashMap handles synchronization for you; can you explain how it does that differently?", + "follow_up_answer": "Right, so instead of locking the entire map, ConcurrentHashMap uses a technique called lock stripping." + }, + { + "question_id": 2, + "question": "When would you use a JOIN versus a Subquery to retrieve data?", + "candidate_answer": "I generally use joins when I need to combine columns from multiple tables, while subqueries are better for filtering." + } +] +``` +This stores the raw Q&A combinations generated during the session BEFORE the final LLM evaluation. + +--- + +### Interview Analysis `analysis_result` + +```json +{ + "evaluations": [ + { + "question_id": 1, + "question": "Can you explain the difference between checked and unchecked exceptions?", + "candidate_answer": "Checked exceptions are checked at compile time, and you have to use a try catch block. Unchecked exceptions extend RuntimeException.", + "score": 8, + "coverage_percent": 75.0, + "keywords_found": ["compile-time", "try-catch", "RuntimeException"], + "keywords_missing": ["throws"], + "is_sufficient": true, + "decision": "NEXT_QUESTION", + "feedback": "The candidate provided a solid and accurate definition of the exceptions.", + "suggested_follow_up": null + }, + { + "question_id": 2, + "question": "Can you explain the difference between a HashMap and a ConcurrentHashMap?", + "candidate_answer": "A HashMap isn't thread-safe, while a ConcurrentHashMap is designed for multi-threaded environments.", + "score": 5, + "coverage_percent": 66.6, + "keywords_found": ["thread-safety", "multi-threading"], + "keywords_missing": ["segment locking"], + "is_sufficient": false, + "decision": "ASK_FOLLOW_UP", + "feedback": "The candidate correctly identified the basic difference regarding thread-safety, but failed to explain *how* it achieves this.", + "suggested_follow_up": "You mentioned it handles synchronization; can you explain how it does that differently than a global lock to maintain performance?", + "follow_up_answer": "It uses a technique called lock stripping or bucket-level locking to allow multiple threads.", + "follow_up_score": 9, + "follow_up_coverage": 100.0, + "follow_up_keywords_found": ["segment locking", "thread-safety", "multi-threading"], + "follow_up_keywords_missing": [], + "follow_up_is_sufficient": true, + "follow_up_decision": "NEXT_QUESTION", + "follow_up_feedback": "The candidate correctly identified lock stripping/bucket-level locking as the mechanism to reduce contention." + } + ], + "final_summary": { + "total_score": 114.0, + "max_possible_score": 150, + "final_percentage": 76.0, + "final_recommendation": "shortlist_for_l1" + } +} ``` +*(Note: The `max_possible_score` and `final_percentage` are mathematically calculated based on the dynamic number of questions).* + --- ### Denormalised Columns on `applications` diff --git a/docs/tools_research/PDF_PARSING_LIBRARIES.md b/docs/tools_research/PDF_PARSING_LIBRARIES.md index c63e1d9..185028d 100644 --- a/docs/tools_research/PDF_PARSING_LIBRARIES.md +++ b/docs/tools_research/PDF_PARSING_LIBRARIES.md @@ -72,18 +72,6 @@ --- -## Library Score Summary - -| Library | Graphical CV | Formal CV (Tables) | Two-Column CV | Simple CV | Compact CV | JD | **Total / 30** | -|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| **Docling** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | **27** | -| **PyMuPDF4LLM** | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | **20** | -| **LlamaParse** | ⭐⭐⭐ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | **19** | -| **OpenDataLoader** | ⭐⭐ | ⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | **17** | -| **MarkItDown** | ⭐ | ⭐⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | **13** | - ---- - ## Decision: Why Docling - **Consistent across all layout types** — the only library that scored 4+ stars on every resume format. From 0aedc8b6c0ab6fe0bee7f79bd3fe68f8c6228d0b Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Mon, 10 Aug 2026 15:01:12 +0530 Subject: [PATCH 2/5] docs: update anlayst_result json for follow_up --- docs/architecture/DB_DESIGN.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/architecture/DB_DESIGN.md b/docs/architecture/DB_DESIGN.md index e459ab0..80564a9 100644 --- a/docs/architecture/DB_DESIGN.md +++ b/docs/architecture/DB_DESIGN.md @@ -488,7 +488,7 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin "is_sufficient": true, "decision": "NEXT_QUESTION", "feedback": "The candidate provided a solid and accurate definition of the exceptions.", - "suggested_follow_up": null + "follow_ups": [] }, { "question_id": 2, @@ -501,15 +501,19 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin "is_sufficient": false, "decision": "ASK_FOLLOW_UP", "feedback": "The candidate correctly identified the basic difference regarding thread-safety, but failed to explain *how* it achieves this.", - "suggested_follow_up": "You mentioned it handles synchronization; can you explain how it does that differently than a global lock to maintain performance?", - "follow_up_answer": "It uses a technique called lock stripping or bucket-level locking to allow multiple threads.", - "follow_up_score": 9, - "follow_up_coverage": 100.0, - "follow_up_keywords_found": ["segment locking", "thread-safety", "multi-threading"], - "follow_up_keywords_missing": [], - "follow_up_is_sufficient": true, - "follow_up_decision": "NEXT_QUESTION", - "follow_up_feedback": "The candidate correctly identified lock stripping/bucket-level locking as the mechanism to reduce contention." + "follow_ups": [ + { + "follow_up_question": "You mentioned it handles synchronization; can you explain how it does that differently than a global lock to maintain performance?", + "follow_up_answer": "It uses a technique called lock stripping or bucket-level locking to allow multiple threads.", + "score": 9, + "coverage_percent": 100.0, + "keywords_found": ["segment locking", "thread-safety", "multi-threading"], + "keywords_missing": [], + "is_sufficient": true, + "decision": "NEXT_QUESTION", + "feedback": "The candidate correctly identified lock stripping/bucket-level locking as the mechanism to reduce contention." + }, + ] } ], "final_summary": { @@ -520,7 +524,7 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin } } ``` -*(Note: The `max_possible_score` and `final_percentage` are mathematically calculated based on the dynamic number of questions).* +*(Note: The `max_possible_score` and `final_percentage` are mathematically calculated based on the dynamic number of questions. The `follow_ups` array is empty `[]` when `decision` is `NEXT_QUESTION`, and contains one or more objects when `decision` is `ASK_FOLLOW_UP`.)* --- From ad378491e15f3f354cd701f3cc13fe80ed7c6190 Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Mon, 10 Aug 2026 15:13:45 +0530 Subject: [PATCH 3/5] docs: remove empty follow_ups field from DB_DESIGN.md schema documentation --- docs/architecture/DB_DESIGN.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/architecture/DB_DESIGN.md b/docs/architecture/DB_DESIGN.md index 80564a9..6811470 100644 --- a/docs/architecture/DB_DESIGN.md +++ b/docs/architecture/DB_DESIGN.md @@ -487,8 +487,8 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin "keywords_missing": ["throws"], "is_sufficient": true, "decision": "NEXT_QUESTION", - "feedback": "The candidate provided a solid and accurate definition of the exceptions.", - "follow_ups": [] + "feedback": "The candidate provided a solid and accurate definition of the exceptions." + }, { "question_id": 2, @@ -512,7 +512,7 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin "is_sufficient": true, "decision": "NEXT_QUESTION", "feedback": "The candidate correctly identified lock stripping/bucket-level locking as the mechanism to reduce contention." - }, + } ] } ], From 8bee46efeab725750a6f723a417f512cc4152dd1 Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Mon, 10 Aug 2026 19:05:31 +0530 Subject: [PATCH 4/5] docs: update AI strictness definitions --- docs/architecture/AI_PROCESSING.md | 41 +++++++++++++++++++++--------- docs/architecture/DB_DESIGN.md | 16 +++++++----- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/docs/architecture/AI_PROCESSING.md b/docs/architecture/AI_PROCESSING.md index 87f6415..a407473 100644 --- a/docs/architecture/AI_PROCESSING.md +++ b/docs/architecture/AI_PROCESSING.md @@ -446,6 +446,17 @@ Generate EXACTLY 15 questions in total across the following categories: 3. CATEGORY "good_to_have" — from JD good-to-have skills (2–3 questions) 4. CATEGORY "experience_domain" — from JD responsibilities (~2 purely technical questions) +═══ ANSWER DEPTH LEVELS ═══ +Each question must include an `answer_depth` level ("aware", "partial_depth", or "full_depth") based on how strictly the answer should be evaluated: +- "aware" → awareness only: any reasonable attempt at a response is acceptable. +- "partial_depth" → partial coverage: the answer must touch some of the expected keywords with a basic explanation. +- "full_depth" → full depth: the answer must cover most expected keywords with a clear, accurate explanation. + +Assign depth dynamically based on the candidate's {years} years of experience AND the question category: +- Use "aware" for "lacking_skill" and "good_to_have" categories, OR if the candidate has < 2 years experience (awareness check only). +- Use "partial_depth" for "must_have_matched" questions when the candidate has 2-4 years experience, OR if experience_match is false. +- Use "full_depth" for "must_have_matched" and "experience_domain" questions only when the candidate has 5+ years experience and clearly possesses the skill. + ═══ OUTPUT FORMAT ═══ Return a JSON array only. No markdown, no commentary. [ @@ -455,7 +466,7 @@ Return a JSON array only. No markdown, no commentary. "skill_focus": "the specific skill or topic", "question": "the interview question", "expected_keywords": ["3 to 5 keywords a correct answer must touch"], - "answer_depth": "one sentence describing what a passing answer should cover at this screening level" + "answer_depth": "aware | partial_depth | full_depth" } ] ``` @@ -469,15 +480,21 @@ You are evaluating a candidate's answer in a FIRST SCREENING interview. QUESTION: {question} CANDIDATE ANSWER: {answer} EXPECTED KEYWORDS (answer should address most of these): {keywords} -EXPECTED DEPTH FOR PASSING: {depth} +EVALUATION STRICTNESS LEVEL: {depth} + +STRICTNESS DEFINITIONS: +- "aware": Accept the answer as-is. Any reasonable attempt at a response is sufficient. Do not penalize for missing keywords. +- "partial_depth": Accept if the answer covers some of the expected keywords with a basic explanation. Partial understanding is acceptable. +- "full_depth": Accept only if the answer covers most of the expected keywords with a clear and accurate explanation. Vague or incomplete answers are not sufficient. SCORING RULES: -- Score 0–10. Coverage 0–100%. -- Score 7–10: correct and clear, even if brief. -- Score 5–6: partially correct, key concept there but something important missing. -- Score 0–4: wrong, confused, or vague with no real understanding shown. -- Do NOT penalize for informal phrasing or brevity if the concept is correct. -- DO penalize for factually wrong statements or restating the question without substance. +- Score 0–10. Your score MUST reflect BOTH keyword coverage AND the EVALUATION STRICTNESS LEVEL above. +- If STRICTNESS LEVEL is "aware": Score generously (7-10) if they show basic understanding, even if missing keywords. +- If STRICTNESS LEVEL is "partial_depth": Score 7-10 only if they hit some keywords and explain the basic concept. +- If STRICTNESS LEVEL is "full_depth": Score strictly. Score 7-10 ONLY if they hit most keywords with a clear, accurate explanation. +- Score 5–6: Candidate fell short of the required strictness level or missed key concepts. +- Score 0–4: Wrong, confused, or vague with no real understanding shown. +- Do NOT penalize for informal phrasing if the technical concept is correct. DECISION: - "NEXT_QUESTION" if score >= 6 AND coverage_percent >= 50 (candidate understood it well enough for screening). @@ -492,7 +509,7 @@ Return STRICT JSON only. No markdown: "is_sufficient": , "decision": "NEXT_QUESTION | ASK_FOLLOW_UP", "feedback": "2-3 sentences: what was good, what was missing, pass/fail on this topic for screening", - "suggested_follow_up": "If decision is ASK_FOLLOW_UP and this is NOT a follow-up evaluation itself, write a specific, conversational follow-up question here to probe what they missed based on the missing keywords. Otherwise null." + "suggested_follow_up": "If decision is ASK_FOLLOW_UP and this is NOT a follow-up evaluation itself, write a specific, conversational follow-up question here to probe what they missed based on the missing keywords. Otherwise omit this field entirely." } ``` @@ -523,12 +540,12 @@ else: **Aggregate Scoring:** * **Total Score:** The sum of all resolved `topic_score` values. * **Max Possible Score:** `{total_questions} * 10` points (e.g., 150 for a 15-question set). -* **Final Percentage:** `(total_score / max_possible_score) * 100` +* **Overall Score:** `(total_score / max_possible_score) * 10` **Recommendation Threshold:** -The system enforces a strict, deterministic pass/fail threshold at **60%**: +The system enforces a strict, deterministic pass/fail threshold at **6.0 / 10**: ```python -if final_percentage >= 60.0: +if overall_score >= 6.0: final_recommendation = "shortlist_for_l1" else: final_recommendation = "reject" diff --git a/docs/architecture/DB_DESIGN.md b/docs/architecture/DB_DESIGN.md index 6811470..5029ce5 100644 --- a/docs/architecture/DB_DESIGN.md +++ b/docs/architecture/DB_DESIGN.md @@ -442,7 +442,7 @@ All fields return `null` if not explicitly found in the JD. The LLM prompt const "skill_focus": "Core Java", "question": "Can you explain the difference between checked and unchecked exceptions?", "expected_keywords": ["RuntimeException", "compile-time", "try-catch", "throws"], - "answer_depth": "The candidate should clearly state that checked exceptions are verified at compile-time while unchecked exceptions happen at runtime." + "answer_depth": "partial_depth" } ] ``` @@ -458,8 +458,12 @@ This dynamic list of questions is tailored to the candidate and generated by the "question_id": 1, "question": "Can you explain the difference between a HashMap and a ConcurrentHashMap?", "candidate_answer": "So, a HashMap isn't thread-safe, while a ConcurrentHashMap is designed for multi-threaded environments.", - "follow_up_question": "You mentioned that ConcurrentHashMap handles synchronization for you; can you explain how it does that differently?", - "follow_up_answer": "Right, so instead of locking the entire map, ConcurrentHashMap uses a technique called lock stripping." + "follow_ups": [ + { + "follow_up_question": "You mentioned that ConcurrentHashMap handles synchronization for you; can you explain how it does that differently?", + "follow_up_answer": "Right, so instead of locking the entire map, ConcurrentHashMap uses a technique called lock stripping." + } + ] }, { "question_id": 2, @@ -519,12 +523,12 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin "final_summary": { "total_score": 114.0, "max_possible_score": 150, - "final_percentage": 76.0, + "overall_score": 7.6, "final_recommendation": "shortlist_for_l1" } } ``` -*(Note: The `max_possible_score` and `final_percentage` are mathematically calculated based on the dynamic number of questions. The `follow_ups` array is empty `[]` when `decision` is `NEXT_QUESTION`, and contains one or more objects when `decision` is `ASK_FOLLOW_UP`.)* +*(Note: The `max_possible_score` and `overall_score` are mathematically calculated based on the dynamic number of questions. The `follow_ups` array is entirely omitted from an evaluation block when `decision` is `NEXT_QUESTION`, and initialized and populated when `decision` is `ASK_FOLLOW_UP`.)* --- @@ -575,7 +579,7 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin | Record Type | Retention Period | |-------------|-----------------| -| Rejected applications | 180 days | +| Rejected applications | 6 month | | Shortlisted / hired candidate records | 7 years | | Closed job descriptions | 5 years | | Interview session recordings | 2 years (or until deletion requested) | From cc40ee537e62fa4b12e43b128d82ab414dd535a9 Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Tue, 11 Aug 2026 16:35:55 +0530 Subject: [PATCH 5/5] refactor: rename matching_result field to job_fit_analysis --- USER_STORIES.md | 2 +- docs/architecture/API_SPEC.md | 4 ++-- docs/architecture/DB_DESIGN.md | 12 ++++++------ docs/architecture/SYSTEM_DESIGN.md | 6 +++--- docs/architecture/openapi.yaml | 4 ++-- docs/mvp/MVP.md | 2 +- docs/requirements/AI_SCREENING_REQUIREMENTS.md | 12 ++++++------ services/ai-screening/README.md | 4 ++-- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/USER_STORIES.md b/USER_STORIES.md index a635fdc..4dec948 100644 --- a/USER_STORIES.md +++ b/USER_STORIES.md @@ -141,7 +141,7 @@ This document serves as the single source of truth for developer user stories on - Compares `parsed_resume` JSONB against the JD `parsed_jd` JSONB. - Calculates a 0.0 to 10.0 overall matching score using: `(skills * 0.40 + experience * 0.35 + education * 0.25) * 10`. - Generates a fit recommendation (`strong_fit`, `moderate_fit`, `weak_fit`, `not_suitable`), list of matched skills, missing skills, strengths, and concerns. - - Writes the matching result to the `matching_result` JSONB and denormalizes `resume_score` and `candidate_yoe` for fast sorting. + - Writes the matching result to the `job_fit_analysis` JSONB and denormalizes `resume_score` and `candidate_yoe` for fast sorting. - Transitions application status to `resume_screened` (or error state on failure). * **Scope / Phase:** MVP (Phase 3: Application Pipeline) diff --git a/docs/architecture/API_SPEC.md b/docs/architecture/API_SPEC.md index 69559e7..bee991f 100644 --- a/docs/architecture/API_SPEC.md +++ b/docs/architecture/API_SPEC.md @@ -800,7 +800,7 @@ Response 200: "email": "john.doe@example.com", "skills": ["Java", "Spring Boot", "PostgreSQL"] }, - "matching_result": { + "job_fit_analysis": { "overall_score": 85, "matched_skills": ["Java", "Spring Boot"] } @@ -1110,7 +1110,7 @@ Request: Response 200: { "resume_score": 85.0, - "matching_result": {} + "job_fit_analysis": {} } ``` diff --git a/docs/architecture/DB_DESIGN.md b/docs/architecture/DB_DESIGN.md index 5029ce5..f097cc3 100644 --- a/docs/architecture/DB_DESIGN.md +++ b/docs/architecture/DB_DESIGN.md @@ -20,7 +20,7 @@ ## 1. Design Principles -1. **JSONB for AI-Extracted Data**: `parsed_jd` (job_descriptions), `parsed_resume` (applications), `matching_result` (applications), `analysis_result` & `question_answer` (interview_analysis) use PostgreSQL JSONB — allows schema evolution without migrations and efficient querying. +1. **JSONB for AI-Extracted Data**: `parsed_jd` (job_descriptions), `parsed_resume` (applications), `job_fit_analysis` (applications), `analysis_result` & `question_answer` (interview_analysis) use PostgreSQL JSONB — allows schema evolution without migrations and efficient querying. 2. **Status-Driven Workflows**: State machines for JD lifecycle (`draft → published → closed`) and application lifecycle (`applied → interview_scheduled → shortlist_for_l1 / rejected`). All status transitions are validated at the application layer. @@ -117,7 +117,7 @@ | `parsed_resume` | jsonb | nullable | See [parsed_resume Schema](#application-parsed_resume) | | `candidate_yoe` | float | nullable | Denormalised years of experience | | `resume_score` | decimal | nullable | AI match score (0–100) | -| `matching_result` | jsonb | nullable | See [matching_result Schema](#application-matching_result) | +| `job_fit_analysis` | jsonb | nullable | See [job_fit_analysis Schema](#application-job_fit_analysis) | | `status` | application_status | | See [Application Status Enum](#application-status) | | `applied_at` | timestamp | nullable | | | `created_at` | timestamp | | | @@ -225,7 +225,7 @@ erDiagram jsonb parsed_resume "candidate_name, email, phone, summary, skills, experience_years, education, certifications, projects" float candidate_yoe "nullable - denormalised" decimal resume_score "nullable - AI match score 0-100" - jsonb matching_result "score_breakdown, matched_skills, missing_skills, experience_match, education_match, reasoning" + jsonb job_fit_analysis "score_breakdown, matched_skills, missing_skills, experience_match, education_match, reasoning" varchar status "enum: applied, interview_scheduled, interview_completed, shortlist_for_l1, rejected" timestamp applied_at "nullable" timestamp created_at @@ -402,7 +402,7 @@ All fields return `null` if not explicitly found in the JD. The LLM prompt const --- -### Application `matching_result` +### Application `job_fit_analysis` ```json { @@ -536,7 +536,7 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin | Column | Type | Source | Purpose | |--------|------|--------|---------| -| `resume_score` | decimal (0–100) | `matching_result.score_breakdown.overall_score` | Fast `ORDER BY` sorting | +| `resume_score` | decimal (0–100) | `job_fit_analysis.score_breakdown.overall_score` | Fast `ORDER BY` sorting | | `candidate_yoe` | float | `parsed_resume.experience_years` | Fast filter/sort by experience | --- @@ -568,7 +568,7 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin - **N+1 queries** — always eager-load associated records in a single query - **Full table scans on large tables** — all `WHERE` clauses on `applications` and `job_descriptions` must use indexed columns -- **Sorting unindexed JSONB fields** — sort on the denormalised `resume_score` decimal column, not on `matching_result->>'overall_score'` +- **Sorting unindexed JSONB fields** — sort on the denormalised `resume_score` decimal column, not on `job_fit_analysis->>'overall_score'` - **Unbounded queries** — all list endpoints must enforce a `LIMIT` and use cursor or offset pagination --- diff --git a/docs/architecture/SYSTEM_DESIGN.md b/docs/architecture/SYSTEM_DESIGN.md index 81a33f7..bc67f51 100644 --- a/docs/architecture/SYSTEM_DESIGN.md +++ b/docs/architecture/SYSTEM_DESIGN.md @@ -99,7 +99,7 @@ The platform is designed as a **Modular Monolith inside a Monorepo**. This guara * System gatekeeper handling Authentication, Multi-tenant Organization Scoping (`organization_id`), User Provisioning, Public Job Board API, Interview Scheduling, and Webhook routing. 2. **Parsing & Matching Engine (`services/parsing-matching`)**: - * Pure document parsing and matching engine. Extracts structured `parsed_jd` requirements, parses candidate resumes (`parsed_resume`), and computes candidate-JD matching scores (`matching_result`). + * Pure document parsing and matching engine. Extracts structured `parsed_jd` requirements, parses candidate resumes (`parsed_resume`), and computes candidate-JD matching scores (`job_fit_analysis`). * *Standalone Capability*: Can be packaged and deployed independently as a "Resume & JD Parsing API". 3. **AI Screening Microservice (`services/ai-screening`)**: @@ -187,13 +187,13 @@ The following requirements constrain the option space regardless of preference: | `organizations` | Multi-tenant organization records (`name`, `domain`, `logo_url`) | MVP | | `users` | All user accounts (`super_admin`, `organization_admin`, `hr`, `candidate`) | MVP | | `job_descriptions` | JD records with `parsed_jd` JSONB for AI-extracted requirements | MVP | -| `applications` | Candidate applications with `parsed_resume` & `matching_result` JSONB | MVP | +| `applications` | Candidate applications with `parsed_resume` & `job_fit_analysis` JSONB | MVP | | `interview_session` | Session-based AI interview scheduling & static question sets (`generated_questions`) | MVP | | `interview_analysis` | AI screening report, Q&A transcript analysis (`question_answer`), and call recording URL | MVP | ### Key Design Decisions -1. **JSONB for Flexible AI Data**: `parsed_jd`, `parsed_resume`, `matching_result`, `generated_questions`, `analysis_result`, and `question_answer` use PostgreSQL's JSONB type +1. **JSONB for Flexible AI Data**: `parsed_jd`, `parsed_resume`, `job_fit_analysis`, `generated_questions`, `analysis_result`, and `question_answer` use PostgreSQL's JSONB type - Allows AI extraction schema evolution without complex SQL migrations - Enables efficient querying with GIN indexes - Perfect for storing rich AI feedback, score breakdowns, and dual-channel transcripts diff --git a/docs/architecture/openapi.yaml b/docs/architecture/openapi.yaml index 1939505..448ebfd 100644 --- a/docs/architecture/openapi.yaml +++ b/docs/architecture/openapi.yaml @@ -997,7 +997,7 @@ paths: properties: resume_score: type: number - matching_result: + job_fit_analysis: type: object /internal/v1/screening/questions/generate: @@ -1196,7 +1196,7 @@ components: example: 85.0 parsed_resume: type: object - matching_result: + job_fit_analysis: type: object InterviewSessionResponse: diff --git a/docs/mvp/MVP.md b/docs/mvp/MVP.md index 217da68..dc66ad1 100644 --- a/docs/mvp/MVP.md +++ b/docs/mvp/MVP.md @@ -397,7 +397,7 @@ These are the MVP-relevant acceptance criteria from the Requirements Document: | AC-003 | Publishing gate: required_skills required, else 422 | Phase 2 | | AC-004 | Guest application → 201 in <500ms → confirmation email sent | Phase 3 | | AC-005 | Duplicate application → 422 with clear error | Phase 3 | -| AC-006 | Resume scoring → status = screened, score 0-10, matching_result populated | Phase 3 | +| AC-006 | Resume scoring → status = screened, score 0-10, job_fit_analysis populated | Phase 3 | | AC-007 | Ranked list: 10+ applicants sorted by score, shows name, score, skills bar, experience, status | Phase 4 | | AC-008 | Direct interview invite → email within 2 min with meeting link and time | Phase 4 | diff --git a/docs/requirements/AI_SCREENING_REQUIREMENTS.md b/docs/requirements/AI_SCREENING_REQUIREMENTS.md index 64a0d28..2615cd1 100644 --- a/docs/requirements/AI_SCREENING_REQUIREMENTS.md +++ b/docs/requirements/AI_SCREENING_REQUIREMENTS.md @@ -31,7 +31,7 @@ This document specifies the requirements for the **automated AI assessment and i 1. **Direct JD Parsing & Publishing**: HR uploads a JD file (PDF/DOCX) or pastes text. The system parses it in-memory via `POST /api/v1/jobs/parse`, displays extracted requirements directly on the UI form for HR verification/editing, and saves it to PostgreSQL via `POST /api/v1/jobs`. -2. **Resume Parsing & Candidate Matching**: A candidate applies to a job via the subdomain portal (`{org}.ezscreen.io`) with their resume. The system extracts structured resume data (`parsed_resume`), evaluates it against `parsed_jd` using the matching algorithm, and saves `matching_result` JSONB + `resume_score` (0–100). +2. **Resume Parsing & Candidate Matching**: A candidate applies to a job via the subdomain portal (`{org}.ezscreen.io`) with their resume. The system extracts structured resume data (`parsed_resume`), evaluates it against `parsed_jd` using the matching algorithm, and saves `job_fit_analysis` JSONB + `resume_score` (0–100). 3. **Automated AI Interview Sessions & Screening Analysis**: HR schedules an AI screening interview (`interview_session`). The system auto-generates static questions (`generated_questions`). At call time, the Attendee.dev meeting bot is dispatched (`POST /api/v1/interview-sessions/{id}/dispatch-bot`) to join Google Meet. After call completion, the transcript is evaluated via `gemma4:31b` to populate `interview_analysis` (`analysis_result` + `question_answer` transcript scoring + `recording_url`). @@ -82,7 +82,7 @@ flowchart TD * **US-101**: As an HR user, I want to upload or paste a JD document so the system parses requirements in-memory and populates the UI form directly without requiring pre-signed storage URLs. * **US-102**: As an HR user, I want to review and edit AI-extracted JD fields before publishing so I maintain full control over requirement criteria. * **US-103**: As an HR user, I want a single unified update endpoint (`PUT /api/v1/jobs/{id}`) to modify job details and change job status (`draft`, `published`, `closed`). -* **US-104**: As an HR user, I want to see candidate applications ranked by `resume_score` DESC with detailed score breakdowns (`matching_result`). +* **US-104**: As an HR user, I want to see candidate applications ranked by `resume_score` DESC with detailed score breakdowns (`job_fit_analysis`). * **US-105**: As an HR user, I want to schedule an AI screening interview session that automatically generates candidate-specific static questions (`generated_questions`). * **US-106**: As an HR user, I want to dispatch an Attendee.dev meeting bot to Google Meet to conduct the interview, record dual-channel audio, and generate transcript analysis (`interview_analysis`). @@ -139,7 +139,7 @@ sequenceDiagram B->>P: POST /internal/v1/parse/resume P-->>B: Return parsed_resume JSON B->>P: POST /internal/v1/match/resume-jd - P-->>B: Return matching_result & resume_score + P-->>B: Return job_fit_analysis & resume_score Note over B: Create Application record (status = applied) B-->>F: 201 Application Submitted ``` @@ -157,7 +157,7 @@ sequenceDiagram ### Functional Specifications * **FR-301 (Ranked Applicant List)**: `GET /api/v1/jobs/{id}/applicants` returns all candidate applications for a posting sorted by `resume_score` DESC by default. -* **FR-302 (Match Matrix Breakdown)**: `GET /api/v1/applications/{id}` provides full candidate details, including `parsed_resume` and `matching_result` (matched skills, missing skills, score breakdown, and fit reasoning). +* **FR-302 (Match Matrix Breakdown)**: `GET /api/v1/applications/{id}` provides full candidate details, including `parsed_resume` and `job_fit_analysis` (matched skills, missing skills, score breakdown, and fit reasoning). * **FR-303 (Application Status Lifecycle)**: HR updates candidate application status via `PATCH /api/v1/applications/{id}/status` (`applied` $\rightarrow$ `interview_scheduled` $\rightarrow$ `interview_completed` $\rightarrow$ `shortlist_for_l1` / `rejected`). --- @@ -221,7 +221,7 @@ sequenceDiagram | **Organizations** | `organizations` | Multi-tenant organization records (`id`, `name`, `domain`, `logo_url`, `is_active`). | | **Users** | `users` | All user accounts (`super_admin`, `organization_admin`, `hr`, `candidate`). | | **Job Descriptions** | `job_descriptions` | JD records with `parsed_jd` JSONB for AI-extracted requirement criteria. | -| **Applications** | `applications` | Applications with `parsed_resume` JSONB & `matching_result` JSONB. | +| **Applications** | `applications` | Applications with `parsed_resume` JSONB & `job_fit_analysis` JSONB. | | **Interview Sessions** | `interview_session` | Session scheduling metadata & static question set (`generated_questions` JSONB). | | **Interview Analysis** | `interview_analysis` | AI screening evaluation (`analysis_result` JSONB, `question_answer` JSONB, `recording_url`). | @@ -250,7 +250,7 @@ interview_status: scheduled | rescheduled | completed | no_show | cancelled | fa ### AC-002: Candidate Application & Scoring * **Given** a published job posting on `{org}.ezscreen.io`, * **When** a candidate submits their resume, -* **Then** an `applications` record is created, `parsed_resume` & `matching_result` JSONB are populated, and `resume_score` (0–100) is stored for instant sorting. +* **Then** an `applications` record is created, `parsed_resume` & `job_fit_analysis` JSONB are populated, and `resume_score` (0–100) is stored for instant sorting. ### AC-003: AI Interview Bot Scheduling & Analysis * **Given** a candidate application with `status = interview_scheduled`, diff --git a/services/ai-screening/README.md b/services/ai-screening/README.md index f9132a4..3f5776a 100644 --- a/services/ai-screening/README.md +++ b/services/ai-screening/README.md @@ -6,7 +6,7 @@ The **AI Screening Service** is an internal microservice responsible for AI proc - **Job Description (JD) Parsing**: Extracting structured requirement criteria (`parsed_jd` JSONB) from raw text or uploaded PDF/DOCX documents. - **Resume Parsing**: Extracting candidate skills, experience, and education (`parsed_resume` JSONB) from uploaded resumes. -- **Resume & JD Matching**: Evaluating candidate fit against job criteria to calculate `resume_score` (0–100) and `matching_result` JSONB. +- **Resume & JD Matching**: Evaluating candidate fit against job criteria to calculate `resume_score` (0–100) and `job_fit_analysis` JSONB. - **Question Generation**: Generating candidate-tailored static interview questions (`generated_questions` JSONB containing `id`, `question`, `expected_keywords`, `example_depth`, `follow_up`) based on matching results and skill gaps. - **Bot-Based Screening Interviews**: Orchestrating Attendee.dev meeting bots to join Google Meet calls, record dual-channel audio, and run real-time STT $\rightarrow$ LLM $\rightarrow$ TTS conversation pipelines. - **Candidate Interview Analysis**: Evaluating complete call transcripts post-interview using `gemma4:31b` to generate structured `interview_analysis` records (`analysis_result` JSONB + `question_answer` transcript scoring + recording URL). @@ -22,7 +22,7 @@ The **AI Screening Service** is an internal microservice responsible for AI proc - `POST /internal/v1/parse/jd` — Parse raw JD text/file into structured `parsed_jd` JSON. - `POST /internal/v1/parse/resume` — Parse candidate resume binary into `parsed_resume` JSON. -- `POST /internal/v1/match/resume-jd` — Score candidate resume against JD (`resume_score` & `matching_result`). +- `POST /internal/v1/match/resume-jd` — Score candidate resume against JD (`resume_score` & `job_fit_analysis`). - `POST /internal/v1/screening/questions/generate` — Generate static interview questions (`generated_questions`). - `POST /internal/v1/screening/bot/dispatch` — Dispatch Attendee.dev meeting bot to Google Meet URL. - `POST /internal/v1/screening/analysis/evaluate` — Evaluate call transcript to generate `interview_analysis`.