Skip to content

Commit 37765b6

Browse files
committed
done
1 parent 9368fe0 commit 37765b6

7 files changed

Lines changed: 916 additions & 12 deletions

File tree

apps/api/routers/interview_v2.py

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ async def fetch_jd_content(jd_id: str) -> str:
109109

110110

111111
async def extract_text_from_file(file: UploadFile) -> str:
112-
"""Extract text from uploaded file (PDF, DOCX, TXT)"""
112+
"""Extract text from uploaded file (PDF, DOCX, TXT) with robust error handling"""
113113
content = await file.read()
114114
filename = file.filename.lower()
115115

@@ -118,15 +118,54 @@ async def extract_text_from_file(file: UploadFile) -> str:
118118
if filename.endswith('.txt'):
119119
return content.decode('utf-8', errors='ignore')
120120

121-
# PDF files
121+
# PDF files - with multiple fallback strategies
122122
elif filename.endswith('.pdf'):
123-
from pypdf import PdfReader
124123
import io
125124
pdf_file = io.BytesIO(content)
126-
reader = PdfReader(pdf_file)
127125
text = ""
128-
for page in reader.pages:
129-
text += page.extract_text() + "\n"
126+
127+
# Try pypdf first
128+
try:
129+
from pypdf import PdfReader
130+
reader = PdfReader(pdf_file, strict=False)
131+
for page_num, page in enumerate(reader.pages):
132+
try:
133+
page_text = page.extract_text()
134+
if page_text:
135+
text += page_text + "\n"
136+
except Exception as page_error:
137+
print(f"Warning: Could not extract page {page_num}: {page_error}")
138+
continue
139+
140+
if text.strip():
141+
return text.strip()
142+
except Exception as pypdf_error:
143+
print(f"PyPDF extraction failed: {pypdf_error}")
144+
145+
# Fallback: Try pdfplumber
146+
try:
147+
import pdfplumber
148+
pdf_file.seek(0)
149+
with pdfplumber.open(pdf_file) as pdf:
150+
for page in pdf.pages:
151+
try:
152+
page_text = page.extract_text()
153+
if page_text:
154+
text += page_text + "\n"
155+
except:
156+
continue
157+
if text.strip():
158+
return text.strip()
159+
except Exception as plumber_error:
160+
print(f"pdfplumber extraction failed: {plumber_error}")
161+
162+
# If all methods failed
163+
if not text.strip():
164+
raise HTTPException(
165+
status_code=400,
166+
detail=f"Could not extract text from PDF '{file.filename}'. The file may be corrupted, image-based (scanned), or password-protected. Please try: 1) Re-saving the PDF, 2) Using a text-based PDF, or 3) Converting to TXT/DOCX format."
167+
)
168+
130169
return text.strip()
131170

132171
# Word documents
@@ -136,6 +175,13 @@ async def extract_text_from_file(file: UploadFile) -> str:
136175
doc_file = io.BytesIO(content)
137176
doc = Document(doc_file)
138177
text = "\n".join([paragraph.text for paragraph in doc.paragraphs])
178+
179+
if not text.strip():
180+
raise HTTPException(
181+
status_code=400,
182+
detail=f"No text content found in '{file.filename}'. The document may be empty or contain only images."
183+
)
184+
139185
return text.strip()
140186

141187
else:

docs/COMPLETION_SUMMARY.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# 🎯 Interview Completion - Quick Reference
2+
3+
## The Problem
4+
❌ Interview said "thank you for your time" but didn't actually end
5+
❌ User didn't know when to call `/complete` endpoint
6+
7+
## The Solution
8+
**Auto-completion detection** - 3 ways interviews now end automatically
9+
**Clear status signals** - `"status": "completed"` in response
10+
**Comprehensive docs** - Full guide on completion flow
11+
12+
---
13+
14+
## How It Works Now
15+
16+
### When Interview Auto-Completes
17+
18+
The system detects completion when:
19+
20+
1. **Candidate says:** "I want to end the interview"
21+
2. **AI says:** "Thank you for your time" / "We'll be in touch"
22+
3. **Max questions:** Reaches 10 questions (safety limit)
23+
24+
### Response Changes
25+
26+
**Before (OLD):**
27+
```json
28+
{
29+
"status": "active",
30+
"question": "Thank you for your time!" // ❌ Confusing!
31+
}
32+
```
33+
34+
**After (NEW):**
35+
```json
36+
{
37+
"status": "completed", // ✅ Clear signal!
38+
"completion_reason": "interviewer_concluded",
39+
"message": "Interview completed. Call /complete endpoint to get final evaluation."
40+
}
41+
```
42+
43+
---
44+
45+
## Quick Usage
46+
47+
### Basic Flow
48+
49+
```bash
50+
# 1. Start
51+
SESSION=$(curl -s -X POST ".../start" -F "role=Engineer" ... | jq -r '.session_id')
52+
53+
# 2. Answer questions
54+
curl -X POST ".../answer" -F "answer=My answer"
55+
# Keep answering until status becomes "completed"
56+
57+
# 3. When completed, get report
58+
curl -X POST ".../$SESSION/complete" -H "Content-Type: application/json" -d '{}'
59+
```
60+
61+
### Smart Loop (Handles Auto-Completion)
62+
63+
```bash
64+
while true; do
65+
# Submit answer
66+
RESP=$(curl -s -X POST ".../answer" -F "answer=$ANSWER")
67+
68+
# Check if completed
69+
if [ "$(echo $RESP | jq -r '.status')" == "completed" ]; then
70+
echo "Interview ended: $(echo $RESP | jq -r '.completion_reason')"
71+
break
72+
fi
73+
74+
# Show next question
75+
echo "Next: $(echo $RESP | jq -r '.question')"
76+
read -p "Answer: " ANSWER
77+
done
78+
79+
# Get final report
80+
curl -X POST ".../$SESSION/complete" -H "Content-Type: application/json" -d '{}'
81+
```
82+
83+
---
84+
85+
## Files Updated
86+
87+
| File | Changes |
88+
|------|---------|
89+
| `interview/gemini_interviewer.py` | ✅ Added `_should_complete_interview()` method |
90+
| | ✅ Auto-detection in `submit_answer()` |
91+
| | ✅ Session tracks `status` and `completion_reason` |
92+
| `interview/voice_analyzer.py` | ✅ Added pydub for audio format conversion |
93+
| | ✅ Fixed "Format not recognised" error |
94+
| `docs/INTERVIEW_COMPLETION_GUIDE.md` |**NEW** - Complete guide (650+ lines) |
95+
| `docs/V2_INTERVIEW_API_COMPLETE.md` | ✅ Updated with auto-completion info |
96+
| `requirements.txt` | ✅ Added pydub for audio conversion |
97+
98+
---
99+
100+
## Testing
101+
102+
### Test Auto-Completion
103+
104+
```bash
105+
# Start interview
106+
SESSION=$(curl -s -X POST "http://127.0.0.1:8000/v2/interview/start" \
107+
-F "role=Engineer" -F "company=Test" \
108+
-F "resume_text=Test" -F "jd_text=Test" | jq -r '.session_id')
109+
110+
# Try to end
111+
curl -X POST "http://127.0.0.1:8000/v2/interview/$SESSION/answer" \
112+
-F "answer=I want to end the interview please." | jq .
113+
114+
# Should see:
115+
# {
116+
# "status": "completed",
117+
# "completion_reason": "candidate_requested_end",
118+
# "message": "Interview completed. Call /complete endpoint..."
119+
# }
120+
121+
# Get final report
122+
curl -X POST "http://127.0.0.1:8000/v2/interview/$SESSION/complete" \
123+
-H "Content-Type: application/json" -d '{}' | jq '.evaluation.overall_score'
124+
```
125+
126+
---
127+
128+
## Key Improvements
129+
130+
### 1. Auto-Detection Keywords
131+
132+
**Candidate ending phrases:**
133+
- "end the interview"
134+
- "stop the interview"
135+
- "don't want to continue"
136+
- "I want to stop"
137+
- "that's all"
138+
139+
**Interviewer conclusion phrases:**
140+
- "thank you for your time"
141+
- "thanks for joining"
142+
- "that concludes"
143+
- "we'll be in touch"
144+
- "we'll get back to you"
145+
146+
### 2. Safety Limits
147+
- Max 10 questions per interview
148+
- Prevents infinite loops
149+
- Auto-completes with reason: `"max_questions_reached"`
150+
151+
### 3. Clear State Tracking
152+
- `status`: `"active"` or `"completed"`
153+
- `completion_reason`: Why it ended
154+
- `message`: What to do next
155+
156+
---
157+
158+
## Documentation
159+
160+
📖 **Main API Docs:** `docs/V2_INTERVIEW_API_COMPLETE.md`
161+
📖 **Completion Guide:** `docs/INTERVIEW_COMPLETION_GUIDE.md`
162+
163+
The completion guide includes:
164+
- Full explanation of all 3 completion methods
165+
- End-to-end bash examples
166+
- React/TypeScript integration example
167+
- Testing instructions
168+
- Best practices & common mistakes
169+
170+
---
171+
172+
## Next Steps for Frontend
173+
174+
Update your UI to:
175+
176+
1. **Check `status` after each answer:**
177+
```typescript
178+
const response = await submitAnswer(answer);
179+
if (response.status === 'completed') {
180+
showCompletionScreen(response.completion_reason);
181+
fetchFinalReport();
182+
}
183+
```
184+
185+
2. **Show completion reason:**
186+
- "You ended the interview"
187+
- "Interview concluded by interviewer"
188+
- "Maximum questions reached"
189+
190+
3. **Auto-fetch report:**
191+
- When `status === 'completed'`
192+
- Call `/complete` endpoint
193+
- Display evaluation to user
194+
195+
---
196+
197+
## Summary
198+
199+
**Problem solved:** Interviews now auto-detect completion
200+
**Clear signals:** Response status shows when to get report
201+
**Well documented:** 650+ lines of guides and examples
202+
**Audio fixed:** Voice analysis works with all formats
203+
**Production ready:** Safety limits and error handling

0 commit comments

Comments
 (0)