| """
|
| HR-AI Interview Simulation V2 - Full Premium UI
|
| Complete Flask Application with Premium Frontend
|
| Deployed: December 2024
|
| """
|
|
|
| import os
|
| from flask import Flask, request, jsonify, send_from_directory
|
| from flask_cors import CORS
|
| from groq import Groq
|
| import json
|
| import uuid
|
| import re
|
| from datetime import datetime
|
| from PyPDF2 import PdfReader
|
|
|
|
|
| API_KEY = os.getenv('GROQ_API_KEY', '')
|
| GROQ_MODEL = 'llama-3.3-70b-versatile'
|
|
|
|
|
| BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| STATIC_DIR = os.path.join(BASE_DIR, 'static')
|
|
|
| print(f"BASE_DIR: {BASE_DIR}")
|
| print(f"STATIC_DIR: {STATIC_DIR}")
|
| print(f"Static dir exists: {os.path.exists(STATIC_DIR)}")
|
| if os.path.exists(STATIC_DIR):
|
| print(f"Static contents: {os.listdir(STATIC_DIR)}")
|
|
|
| app = Flask(__name__, static_folder=STATIC_DIR, static_url_path='/static')
|
| CORS(app)
|
|
|
| client = None
|
| if API_KEY:
|
| client = Groq(api_key=API_KEY)
|
|
|
| sessions = {}
|
|
|
| def get_or_create_session(session_id):
|
| if session_id not in sessions:
|
| sessions[session_id] = {
|
| 'candidate_profile': None,
|
| 'interview_questions': [],
|
| 'interview_responses': [],
|
| 'interview_start_time': None,
|
| 'interview_end_time': None
|
| }
|
| return sessions[session_id]
|
|
|
| def extract_text_from_pdf(pdf_file):
|
| try:
|
| reader = PdfReader(pdf_file)
|
| return "".join(p.extract_text() or "" for p in reader.pages)
|
| except:
|
| return ""
|
|
|
| def extract_json_from_response(text):
|
| match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', text)
|
| if match:
|
| try: return json.loads(match.group(1))
|
| except: pass
|
| if '{' in text:
|
| try: return json.loads(text[text.find('{'):text.rfind('}')+1])
|
| except: pass
|
| return None
|
|
|
|
|
| def generate_content_with_groq(prompt):
|
| if not client: return None
|
| try:
|
| resp = client.chat.completions.create(
|
| messages=[{"role": "system", "content": "Return only valid JSON."}, {"role": "user", "content": prompt}],
|
| model=GROQ_MODEL, temperature=0.7, max_tokens=4096
|
| )
|
| data = extract_json_from_response(resp.choices[0].message.content)
|
| return json.dumps(data) if data else None
|
| except Exception as e:
|
| print(f"Groq Error: {e}")
|
| return None
|
|
|
|
|
| @app.route('/')
|
| def home():
|
| return send_from_directory(STATIC_DIR, 'signup.html')
|
|
|
|
|
| @app.route('/dashboard')
|
| @app.route('/index.html')
|
| def dashboard():
|
| return send_from_directory(STATIC_DIR, 'index.html')
|
|
|
|
|
| @app.route('/login')
|
| @app.route('/login.html')
|
| def login():
|
| return send_from_directory(STATIC_DIR, 'login.html')
|
|
|
|
|
| @app.route('/signup')
|
| @app.route('/signup.html')
|
| def signup():
|
| return send_from_directory(STATIC_DIR, 'signup.html')
|
|
|
|
|
| @app.route('/assets/<path:filename>')
|
| def serve_assets(filename):
|
| assets_dir = os.path.join(STATIC_DIR, 'assets')
|
| return send_from_directory(assets_dir, filename)
|
|
|
|
|
| @app.route('/<path:filename>')
|
| def serve_static(filename):
|
|
|
| file_path = os.path.join(STATIC_DIR, filename)
|
| if os.path.exists(file_path):
|
| return send_from_directory(STATIC_DIR, filename)
|
|
|
| return send_from_directory(STATIC_DIR, 'index.html')
|
|
|
|
|
| @app.route('/upload_resume', methods=['POST'])
|
| def upload_resume():
|
| session_id = request.headers.get('X-User-Session-Id', str(uuid.uuid4()))
|
| session = get_or_create_session(session_id)
|
|
|
| if 'resume' not in request.files:
|
| return jsonify({'error': 'No resume file'}), 400
|
|
|
| file = request.files['resume']
|
| text = extract_text_from_pdf(file)
|
| if not text.strip():
|
| return jsonify({'error': 'Cannot extract text from PDF'}), 400
|
|
|
| prompt = f'''Analyze this resume and extract information. Return JSON with these exact fields:
|
| - name: candidate's full name (string)
|
| - email: email address (string)
|
| - experience: work experience summary as a simple string like "5 years in software development" or "2 roles (3 months, 2 months)"
|
| - key_skills: array of skill strings like ["Python", "JavaScript", "React"]
|
| - inferred_position: best matching job title (string)
|
|
|
| Return ONLY valid JSON: {{"name":"","email":"","experience":"","key_skills":[],"inferred_position":""}}
|
|
|
| Resume text:
|
| {text[:8000]}'''
|
|
|
| resp = generate_content_with_groq(prompt)
|
| if resp:
|
| profile = json.loads(resp)
|
| if not isinstance(profile.get('key_skills'), list): profile['key_skills'] = []
|
|
|
| if isinstance(profile.get('experience'), dict):
|
| exp = profile['experience']
|
| profile['experience'] = f"{exp.get('years', '')} years" if 'years' in exp else str(exp)
|
| elif isinstance(profile.get('experience'), list):
|
| profile['experience'] = ', '.join(str(e) for e in profile['experience'])
|
| session['candidate_profile'] = profile
|
| return jsonify({'message': 'Success', 'candidate_profile': profile, 'session_id': session_id}), 200
|
| return jsonify({'error': 'AI failed'}), 500
|
|
|
| @app.route('/setup_interview', methods=['POST'])
|
| def setup_interview():
|
| session_id = request.headers.get('X-User-Session-Id')
|
| if not session_id or session_id not in sessions:
|
| return jsonify({'error': 'Invalid session'}), 400
|
|
|
| session = sessions[session_id]
|
| data = request.get_json()
|
| position = data.get('position_role', '')
|
| profile = session.get('candidate_profile')
|
|
|
| if not position or not profile:
|
| return jsonify({'error': 'Position and profile required'}), 400
|
|
|
| skills = ", ".join(profile.get('key_skills', []))
|
| experience = profile.get('experience', 'N/A')
|
|
|
|
|
| prompt = f'''Generate 15 IN-DEPTH interview questions for {profile.get('name','Candidate')} applying for "{position}".
|
|
|
| Candidate Skills: {skills}
|
| Experience: {experience}
|
|
|
| Generate questions that test DEEP understanding of concepts. Questions should be:
|
| - Based on the candidate's actual skills from their resume
|
| - Testing theoretical knowledge, architecture decisions, best practices
|
| - Asking about real-world scenarios and problem-solving approaches
|
| - NO coding/programming challenges - only conceptual questions
|
|
|
| Question Distribution:
|
| - 10 Technical questions (in-depth concepts about their listed skills like {skills})
|
| - 3 Soft Skills questions (teamwork, leadership, conflict resolution)
|
| - 2 Communication questions (explaining technical concepts, stakeholder management)
|
|
|
| Example good technical questions:
|
| - "Explain the difference between SQL and NoSQL databases and when you would choose each"
|
| - "What are the SOLID principles and how have you applied them?"
|
| - "Describe microservices architecture and its trade-offs vs monolithic"
|
| - "How does garbage collection work in [language]?"
|
|
|
| Return JSON: {{"questions":[{{"id":"q1","question":"...","tags":["technical"]}}]}}'''
|
|
|
| resp = generate_content_with_groq(prompt)
|
| if resp:
|
| result = json.loads(resp)
|
| session['interview_questions'] = result.get('questions', [])
|
| session['interview_responses'] = []
|
| session['interview_start_time'] = datetime.now().isoformat()
|
| return jsonify({'message': 'Generated', 'questions': result.get('questions', []), 'is_coding_role': False}), 200
|
| return jsonify({'error': 'Failed'}), 500
|
|
|
| @app.route('/submit_answer', methods=['POST'])
|
| def submit_answer():
|
| session_id = request.headers.get('X-User-Session-Id')
|
| if not session_id or session_id not in sessions:
|
| return jsonify({'error': 'Invalid session'}), 400
|
|
|
| session = sessions[session_id]
|
| data = request.get_json()
|
| qid = data.get('question_id')
|
| answer = data.get('response_text', '')
|
| duration = data.get('duration', '00:00')
|
|
|
| q = next((x for x in session['interview_questions'] if x['id'] == qid), None)
|
| if not q: return jsonify({'error': 'Question not found'}), 404
|
|
|
| prompt = f'''Evaluate strictly:
|
| Q: {q['question']}
|
| A: {answer}
|
| Return: {{"technicalScore":85,"communicationScore":90,"relevanceScore":88,"feedback":"..."}}'''
|
|
|
| resp = generate_content_with_groq(prompt)
|
| if resp:
|
| ev = json.loads(resp)
|
| ev['score'] = round((ev.get('technicalScore',0)+ev.get('communicationScore',0)+ev.get('relevanceScore',0))/3)
|
| session['interview_responses'].append({'question_id':qid,'question':q['question'],'tags':q.get('tags',[]),'response':answer,'duration':duration,'evaluation':ev})
|
| return jsonify({'message': 'Evaluated', 'evaluation': ev}), 200
|
| return jsonify({'error': 'Failed'}), 500
|
|
|
| @app.route('/get_assessment', methods=['GET'])
|
| def get_assessment():
|
| session_id = request.headers.get('X-User-Session-Id')
|
| if not session_id or session_id not in sessions:
|
| return jsonify({'error': 'Invalid session'}), 400
|
|
|
| session = sessions[session_id]
|
| if not session.get('interview_responses'):
|
| return jsonify({'error': 'No responses'}), 400
|
|
|
| profile = session['candidate_profile']
|
| responses = session['interview_responses']
|
| avg = sum(r['evaluation']['score'] for r in responses) / len(responses)
|
|
|
| summary = "\n".join([f"Q: {r['question'][:60]}... Score: {r['evaluation']['score']}%" for r in responses[:5]])
|
| prompt = f'''Assessment for {profile.get('name','Candidate')}. Avg: {avg:.1f}%. Questions: {len(responses)}.
|
| {summary}
|
| Return: {{"overallScore":85,"recommendation":"Recommended","keyStrengths":["..."],"areasForImprovement":["..."],"detailedScores":{{"technicalSkills":85,"communication":80,"softSkills":78}}}}'''
|
|
|
| resp = generate_content_with_groq(prompt)
|
| if resp:
|
| a = json.loads(resp)
|
| a['detailedQuestionAnalysis'] = [{'question':r['question'],'score':r['evaluation']['score'],'technicalScore':r['evaluation'].get('technicalScore',0),'communicationScore':r['evaluation'].get('communicationScore',0),'relevanceScore':r['evaluation'].get('relevanceScore',0)} for r in responses]
|
| return jsonify({'message': 'Generated', 'assessment': a}), 200
|
| return jsonify({'assessment': {'overallScore': round(avg), 'recommendation': 'Recommended' if avg >= 70 else 'Needs Improvement', 'keyStrengths': [], 'areasForImprovement': [], 'detailedScores': {'technicalSkills': round(avg), 'communication': round(avg), 'softSkills': round(avg)}}}), 200
|
|
|
| @app.route('/log_security', methods=['POST'])
|
| def log_security():
|
| return jsonify({'message': 'Logged'}), 200
|
|
|
| if __name__ == '__main__':
|
| port = int(os.environ.get('PORT', 7860))
|
| app.run(host='0.0.0.0', port=port, debug=False)
|
|
|