Spaces:
No application file
No application file
| #!/usr/bin/env python3 | |
| """ | |
| IQKiller Enhanced Interview Orchestrator | |
| Main analysis engine that coordinates all components for 30-60 second analysis | |
| Enterprise-grade with 93%+ accuracy as mentioned in the documentation | |
| """ | |
| import asyncio | |
| import logging | |
| import time | |
| from typing import Dict, Any, Optional, List, Tuple | |
| from dataclasses import dataclass | |
| from enum import Enum | |
| # Local imports | |
| from config import get_config | |
| from llm_client import get_llm_client, generate_interview_content, generate_analysis_content | |
| from micro.scrape import get_scraper, ScrapeResult | |
| from salary_negotiation_simulator import get_simulator, get_random_scenario | |
| # Simple text extraction function | |
| def extract_text_from_content(content: str) -> str: | |
| """Extract text from content (placeholder for file processing)""" | |
| return content | |
| # Setup logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class AnalysisType(Enum): | |
| """Types of analysis available""" | |
| QUICK = "quick" | |
| FULL = "full" | |
| COMPREHENSIVE = "comprehensive" | |
| class ResumeData: | |
| """Parsed resume information""" | |
| raw_text: str | |
| skills: List[str] | |
| experience_years: int | |
| education: List[str] | |
| previous_roles: List[str] | |
| key_achievements: List[str] | |
| contact_info: Dict[str, str] | |
| class JobData: | |
| """Parsed job posting information""" | |
| raw_text: str | |
| company: str | |
| role: str | |
| location: str | |
| required_skills: List[str] | |
| preferred_skills: List[str] | |
| experience_level: str | |
| salary_range: Optional[str] | |
| benefits: List[str] | |
| responsibilities: List[str] | |
| class AnalysisResult: | |
| """Complete analysis result""" | |
| match_score: float # 0-100 | |
| strengths: List[str] | |
| gaps: List[str] | |
| interview_questions: List[Dict[str, str]] | |
| preparation_advice: List[str] | |
| salary_insights: Dict[str, Any] | |
| negotiation_points: List[str] | |
| processing_time: float | |
| confidence_level: str | |
| action_items: List[str] | |
| class EnhancedInterviewOrchestrator: | |
| """Main orchestrator for interview analysis and preparation""" | |
| def __init__(self, config=None): | |
| """Initialize orchestrator with all components""" | |
| self.config = config or get_config() | |
| self.llm_client = get_llm_client() | |
| self.scraper = get_scraper() | |
| self.negotiation_simulator = get_simulator() | |
| async def parse_resume(self, resume_content: str, is_file_content: bool = False) -> ResumeData: | |
| """Parse resume content and extract structured data""" | |
| start_time = time.time() | |
| logger.info("π Parsing resume content...") | |
| # Extract text if it's a file | |
| if is_file_content: | |
| text_content = extract_text_from_content(resume_content) | |
| else: | |
| text_content = resume_content | |
| # Use LLM to extract structured data | |
| extraction_prompt = f""" | |
| Analyze this resume and extract structured information in JSON format: | |
| RESUME CONTENT: | |
| {text_content[:3000]} # Limit for token efficiency | |
| Extract the following information: | |
| {{ | |
| "skills": ["list of technical and soft skills"], | |
| "experience_years": number, | |
| "education": ["degree, school, year"], | |
| "previous_roles": ["job title at company"], | |
| "key_achievements": ["quantified achievements"], | |
| "contact_info": {{"email": "", "phone": "", "linkedin": "", "location": ""}} | |
| }} | |
| Be precise and only include information explicitly mentioned in the resume. | |
| """ | |
| system_prompt = """You are an expert resume parser. Extract information accurately and return valid JSON only.""" | |
| try: | |
| response = await generate_analysis_content(extraction_prompt, system_prompt) | |
| # Parse JSON response (with error handling) | |
| import json | |
| try: | |
| parsed_data = json.loads(response) | |
| except json.JSONDecodeError: | |
| # Fallback parsing if JSON is malformed | |
| logger.warning("LLM returned malformed JSON, using fallback parsing") | |
| parsed_data = self._fallback_resume_parse(text_content) | |
| processing_time = time.time() - start_time | |
| logger.info(f"β Resume parsed in {processing_time:.2f}s") | |
| return ResumeData( | |
| raw_text=text_content, | |
| skills=parsed_data.get("skills", []) if isinstance(parsed_data, dict) else [], | |
| experience_years=parsed_data.get("experience_years", 0) if isinstance(parsed_data, dict) else 0, | |
| education=parsed_data.get("education", []) if isinstance(parsed_data, dict) else [], | |
| previous_roles=parsed_data.get("previous_roles", []) if isinstance(parsed_data, dict) else [], | |
| key_achievements=parsed_data.get("key_achievements", []) if isinstance(parsed_data, dict) else [], | |
| contact_info=parsed_data.get("contact_info", {}) if isinstance(parsed_data, dict) else {} | |
| ) | |
| except Exception as e: | |
| logger.error(f"β Resume parsing failed: {e}") | |
| # Return basic parsed data | |
| return self._fallback_resume_parse(text_content) | |
| async def parse_job_posting(self, job_input: str) -> JobData: | |
| """Parse job posting (URL or text) and extract structured data""" | |
| start_time = time.time() | |
| logger.info("π Processing job posting...") | |
| # Determine if input is URL or text | |
| if job_input.startswith(('http://', 'https://', 'www.')): | |
| # It's a URL - scrape it | |
| logger.info(f"π Scraping job URL: {job_input}") | |
| scrape_result = await self.scraper.scrape_job_posting(job_input) | |
| if scrape_result.success: | |
| job_text = scrape_result.content | |
| logger.info(f"β Scraped {len(job_text)} characters") | |
| else: | |
| logger.warning(f"β οΈ Scraping failed: {scrape_result.error}") | |
| return self._fallback_job_parse(job_input, f"Failed to scrape: {scrape_result.error}") | |
| else: | |
| # It's direct text | |
| job_text = job_input | |
| logger.info(f"π Using direct job text: {len(job_text)} characters") | |
| # Use LLM to extract structured job data | |
| extraction_prompt = f""" | |
| Analyze this job posting and extract structured information in JSON format: | |
| JOB POSTING: | |
| {job_text[:3000]} # Limit for token efficiency | |
| Extract the following information: | |
| {{ | |
| "company": "company name", | |
| "role": "job title/role", | |
| "location": "location (city, state/country or remote)", | |
| "required_skills": ["must-have skills"], | |
| "preferred_skills": ["nice-to-have skills"], | |
| "experience_level": "entry/mid/senior level", | |
| "salary_range": "salary range if mentioned", | |
| "benefits": ["benefits listed"], | |
| "responsibilities": ["key responsibilities"] | |
| }} | |
| Be precise and only include information explicitly mentioned. | |
| """ | |
| system_prompt = """You are an expert job posting analyzer. Extract information accurately and return valid JSON only.""" | |
| try: | |
| response = await generate_analysis_content(extraction_prompt, system_prompt) | |
| # Parse JSON response | |
| import json | |
| try: | |
| parsed_data = json.loads(response) | |
| except json.JSONDecodeError: | |
| logger.warning("LLM returned malformed JSON for job parsing") | |
| parsed_data = self._fallback_job_parse_data(job_text) | |
| processing_time = time.time() - start_time | |
| logger.info(f"β Job posting parsed in {processing_time:.2f}s") | |
| return JobData( | |
| raw_text=job_text, | |
| company=parsed_data.get("company", "Unknown Company"), | |
| role=parsed_data.get("role", "Unknown Role"), | |
| location=parsed_data.get("location", "Location not specified"), | |
| required_skills=parsed_data.get("required_skills", []), | |
| preferred_skills=parsed_data.get("preferred_skills", []), | |
| experience_level=parsed_data.get("experience_level", "Not specified"), | |
| salary_range=parsed_data.get("salary_range"), | |
| benefits=parsed_data.get("benefits", []), | |
| responsibilities=parsed_data.get("responsibilities", []) | |
| ) | |
| except Exception as e: | |
| logger.error(f"β Job parsing failed: {e}") | |
| return self._fallback_job_parse(job_text, str(e)) | |
| async def analyze_compatibility(self, resume_data: ResumeData, job_data: JobData) -> AnalysisResult: | |
| """Perform comprehensive compatibility analysis""" | |
| start_time = time.time() | |
| logger.info("π Analyzing resume-job compatibility...") | |
| # Generate comprehensive analysis | |
| analysis_prompt = f""" | |
| Perform a detailed compatibility analysis between this resume and job posting: | |
| RESUME SUMMARY: | |
| - Experience: {resume_data.experience_years} years | |
| - Skills: {', '.join(resume_data.skills[:10])} | |
| - Previous Roles: {', '.join(resume_data.previous_roles[:3])} | |
| - Key Achievements: {', '.join(resume_data.key_achievements[:3])} | |
| JOB REQUIREMENTS: | |
| - Company: {job_data.company} | |
| - Role: {job_data.role} | |
| - Location: {job_data.location} | |
| - Required Skills: {', '.join(job_data.required_skills[:10])} | |
| - Preferred Skills: {', '.join(job_data.preferred_skills[:10])} | |
| - Experience Level: {job_data.experience_level} | |
| - Key Responsibilities: {', '.join(job_data.responsibilities[:5])} | |
| Provide analysis in this JSON format: | |
| {{ | |
| "match_score": 85, | |
| "strengths": ["specific strengths with evidence"], | |
| "gaps": ["specific gaps and how to address them"], | |
| "interview_questions": [ | |
| {{"question": "Tell me about...", "category": "technical", "difficulty": "medium"}}, | |
| {{"question": "How would you...", "category": "behavioral", "difficulty": "easy"}} | |
| ], | |
| "preparation_advice": ["specific actionable advice"], | |
| "salary_insights": {{ | |
| "market_range": "$X - $Y", | |
| "recommendation": "negotiate for $Z based on...", | |
| "factors": ["experience", "skills", "market demand"] | |
| }}, | |
| "negotiation_points": ["specific points to emphasize"], | |
| "confidence_level": "high/medium/low", | |
| "action_items": ["immediate next steps"] | |
| }} | |
| Be specific, actionable, and realistic. Focus on match score accuracy. | |
| """ | |
| system_prompt = """You are an expert career advisor and interview coach. Provide detailed, actionable analysis with accurate scoring based on resume-job fit.""" | |
| try: | |
| response = await generate_analysis_content(analysis_prompt, system_prompt) | |
| # Parse analysis results | |
| import json | |
| try: | |
| analysis_data = json.loads(response) | |
| except json.JSONDecodeError: | |
| logger.warning("LLM returned malformed JSON for analysis") | |
| analysis_data = self._fallback_analysis_data(resume_data, job_data) | |
| processing_time = time.time() - start_time | |
| logger.info(f"β Analysis completed in {processing_time:.2f}s") | |
| return AnalysisResult( | |
| match_score=min(100, max(0, analysis_data.get("match_score", 75))), | |
| strengths=analysis_data.get("strengths", []), | |
| gaps=analysis_data.get("gaps", []), | |
| interview_questions=analysis_data.get("interview_questions", []), | |
| preparation_advice=analysis_data.get("preparation_advice", []), | |
| salary_insights=analysis_data.get("salary_insights", {}), | |
| negotiation_points=analysis_data.get("negotiation_points", []), | |
| processing_time=processing_time, | |
| confidence_level=analysis_data.get("confidence_level", "medium"), | |
| action_items=analysis_data.get("action_items", []) | |
| ) | |
| except Exception as e: | |
| logger.error(f"β Analysis failed: {e}") | |
| return self._fallback_analysis(resume_data, job_data, str(e)) | |
| async def full_analysis_pipeline(self, resume_content: str, job_input: str, | |
| analysis_type: AnalysisType = AnalysisType.FULL) -> Dict[str, Any]: | |
| """Complete analysis pipeline from raw inputs to final results""" | |
| pipeline_start = time.time() | |
| logger.info(f"π Starting {analysis_type.value} analysis pipeline...") | |
| try: | |
| # Step 1: Parse resume (parallel with job parsing) | |
| resume_task = asyncio.create_task( | |
| self.parse_resume(resume_content, is_file_content=False) | |
| ) | |
| # Step 2: Parse job posting (parallel with resume parsing) | |
| job_task = asyncio.create_task( | |
| self.parse_job_posting(job_input) | |
| ) | |
| # Wait for both parsing tasks to complete | |
| resume_data, job_data = await asyncio.gather(resume_task, job_task) | |
| # Step 3: Perform compatibility analysis | |
| analysis_result = await self.analyze_compatibility(resume_data, job_data) | |
| # Step 4: Get salary negotiation scenario (if full analysis) | |
| negotiation_scenario = None | |
| if analysis_type in [AnalysisType.FULL, AnalysisType.COMPREHENSIVE]: | |
| negotiation_scenario = get_random_scenario() | |
| total_time = time.time() - pipeline_start | |
| logger.info(f"β Pipeline completed in {total_time:.2f}s") | |
| # Compile final results | |
| results = { | |
| "success": True, | |
| "analysis_type": analysis_type.value, | |
| "processing_time": round(total_time, 2), | |
| "resume_data": { | |
| "skills_count": len(resume_data.skills), | |
| "experience_years": resume_data.experience_years, | |
| "previous_roles_count": len(resume_data.previous_roles), | |
| "achievements_count": len(resume_data.key_achievements) | |
| }, | |
| "job_data": { | |
| "company": job_data.company, | |
| "role": job_data.role, | |
| "location": job_data.location, | |
| "required_skills_count": len(job_data.required_skills), | |
| "experience_level": job_data.experience_level | |
| }, | |
| "analysis": { | |
| "match_score": analysis_result.match_score, | |
| "strengths": analysis_result.strengths, | |
| "gaps": analysis_result.gaps, | |
| "interview_questions": analysis_result.interview_questions, | |
| "preparation_advice": analysis_result.preparation_advice, | |
| "salary_insights": analysis_result.salary_insights, | |
| "negotiation_points": analysis_result.negotiation_points, | |
| "confidence_level": analysis_result.confidence_level, | |
| "action_items": analysis_result.action_items | |
| }, | |
| "metadata": { | |
| "timestamp": time.time(), | |
| "version": "2.0", | |
| "llm_provider": self.llm_client.get_status()["primary_provider"], | |
| "scraping_method": "auto-detected" | |
| } | |
| } | |
| # Add negotiation scenario if included | |
| if negotiation_scenario: | |
| results["negotiation_scenario"] = { | |
| "id": negotiation_scenario.id, | |
| "title": negotiation_scenario.title, | |
| "situation": negotiation_scenario.situation, | |
| "question": negotiation_scenario.question, | |
| "options": negotiation_scenario.options, | |
| "difficulty": negotiation_scenario.difficulty, | |
| "type": negotiation_scenario.type.value | |
| } | |
| return results | |
| except Exception as e: | |
| logger.error(f"β Pipeline failed: {e}") | |
| return { | |
| "success": False, | |
| "error": str(e), | |
| "processing_time": time.time() - pipeline_start, | |
| "fallback_message": "Analysis failed. Please try again or contact support." | |
| } | |
| def _fallback_resume_parse(self, text: str) -> ResumeData: | |
| """Fallback resume parsing when LLM fails""" | |
| # Basic keyword extraction | |
| import re | |
| skills = [] | |
| for skill in ["Python", "JavaScript", "SQL", "Excel", "Communication", "Leadership"]: | |
| if skill.lower() in text.lower(): | |
| skills.append(skill) | |
| # Basic experience extraction | |
| experience_match = re.search(r'(\d+)[\s\+]*years?\s+(?:of\s+)?experience', text, re.IGNORECASE) | |
| experience_years = int(experience_match.group(1)) if experience_match else 2 | |
| return ResumeData( | |
| raw_text=text, | |
| skills=skills, | |
| experience_years=experience_years, | |
| education=["Education not parsed"], | |
| previous_roles=["Previous roles not parsed"], | |
| key_achievements=["Achievements not parsed"], | |
| contact_info={} | |
| ) | |
| def _fallback_job_parse(self, text: str, error: str) -> JobData: | |
| """Fallback job parsing when scraping/LLM fails""" | |
| return JobData( | |
| raw_text=text, | |
| company="Company not detected", | |
| role="Role not detected", | |
| location="Location not specified", | |
| required_skills=["Skills not parsed"], | |
| preferred_skills=[], | |
| experience_level="Not specified", | |
| salary_range=None, | |
| benefits=[], | |
| responsibilities=["Responsibilities not parsed"] | |
| ) | |
| def _fallback_job_parse_data(self, text: str) -> Dict[str, Any]: | |
| """Fallback structured job data parsing""" | |
| return { | |
| "company": "Unknown Company", | |
| "role": "Unknown Role", | |
| "location": "Location not specified", | |
| "required_skills": ["Skills not parsed"], | |
| "preferred_skills": [], | |
| "experience_level": "Not specified", | |
| "salary_range": None, | |
| "benefits": [], | |
| "responsibilities": ["Responsibilities not parsed"] | |
| } | |
| def _fallback_analysis_data(self, resume_data: ResumeData, job_data: JobData) -> Dict[str, Any]: | |
| """Fallback analysis data when LLM fails""" | |
| return { | |
| "match_score": 65, | |
| "strengths": ["Experience in relevant field", "Skills alignment"], | |
| "gaps": ["Analysis incomplete due to technical issues"], | |
| "interview_questions": [ | |
| {"question": "Tell me about your experience", "category": "general", "difficulty": "easy"} | |
| ], | |
| "preparation_advice": ["Review your experience", "Practice common interview questions"], | |
| "salary_insights": { | |
| "market_range": "Market data unavailable", | |
| "recommendation": "Research salary ranges for this role", | |
| "factors": ["experience", "skills", "location"] | |
| }, | |
| "negotiation_points": ["Highlight your experience"], | |
| "confidence_level": "medium", | |
| "action_items": ["Prepare for interview", "Research company"] | |
| } | |
| def _fallback_analysis(self, resume_data: ResumeData, job_data: JobData, error: str) -> AnalysisResult: | |
| """Fallback analysis result when LLM fails""" | |
| return AnalysisResult( | |
| match_score=65.0, | |
| strengths=["Experience in relevant field"], | |
| gaps=["Analysis incomplete due to technical issues"], | |
| interview_questions=[{"question": "Tell me about yourself", "category": "general", "difficulty": "easy"}], | |
| preparation_advice=["Review the job description", "Prepare STAR method examples"], | |
| salary_insights={"recommendation": "Research market rates"}, | |
| negotiation_points=["Highlight your experience"], | |
| processing_time=1.0, | |
| confidence_level="low", | |
| action_items=["Retry analysis", "Check your inputs"] | |
| ) | |
| def get_status(self) -> Dict[str, Any]: | |
| """Get orchestrator status and component health""" | |
| return { | |
| "llm_status": self.llm_client.get_status(), | |
| "scraper_status": self.scraper.get_status(), | |
| "config": { | |
| "analysis_timeout": self.config.max_analysis_time, | |
| "retry_attempts": self.config.retry_attempts | |
| }, | |
| "version": "2.0", | |
| "features": { | |
| "firecrawl_scraping": self.scraper.get_status()["firecrawl_available"], | |
| "multi_llm_fallback": self.llm_client.get_status()["fallback_available"], | |
| "salary_negotiation": True | |
| } | |
| } | |
| # Global orchestrator instance | |
| _orchestrator: Optional[EnhancedInterviewOrchestrator] = None | |
| def get_orchestrator() -> EnhancedInterviewOrchestrator: | |
| """Get global orchestrator instance""" | |
| global _orchestrator | |
| if _orchestrator is None: | |
| _orchestrator = EnhancedInterviewOrchestrator() | |
| return _orchestrator | |
| async def quick_analysis(resume_content: str, job_input: str) -> Dict[str, Any]: | |
| """Convenience function for quick analysis""" | |
| orchestrator = get_orchestrator() | |
| return await orchestrator.full_analysis_pipeline(resume_content, job_input, AnalysisType.QUICK) | |
| async def full_analysis(resume_content: str, job_input: str) -> Dict[str, Any]: | |
| """Convenience function for full analysis""" | |
| orchestrator = get_orchestrator() | |
| return await orchestrator.full_analysis_pipeline(resume_content, job_input, AnalysisType.FULL) | |
| if __name__ == "__main__": | |
| async def test_orchestrator(): | |
| """Test the orchestrator functionality""" | |
| orchestrator = EnhancedInterviewOrchestrator() | |
| print("π§ͺ Testing Enhanced Interview Orchestrator") | |
| print("=" * 60) | |
| # Print status | |
| status = orchestrator.get_status() | |
| print("π Orchestrator Status:") | |
| for key, value in status.items(): | |
| print(f" {key}: {value}") | |
| # Test with sample data | |
| sample_resume = """ | |
| John Doe | |
| Software Engineer | |
| 5 years experience in Python, JavaScript, SQL | |
| Led team of 3 developers at TechCorp | |
| Built scalable web applications serving 10k+ users | |
| """ | |
| sample_job = """ | |
| Senior Software Engineer at InnovateTech | |
| Requirements: 3+ years Python, JavaScript, team leadership | |
| Responsibilities: Lead development team, architect solutions | |
| Location: San Francisco, CA | |
| Salary: $120,000 - $150,000 | |
| """ | |
| print(f"\nπ Testing analysis pipeline...") | |
| try: | |
| result = await orchestrator.full_analysis_pipeline(sample_resume, sample_job) | |
| if result["success"]: | |
| print(f"β Analysis completed in {result['processing_time']}s") | |
| print(f"π― Match Score: {result['analysis']['match_score']}%") | |
| print(f"πͺ Strengths: {len(result['analysis']['strengths'])}") | |
| print(f"π Interview Questions: {len(result['analysis']['interview_questions'])}") | |
| print(f"π Confidence: {result['analysis']['confidence_level']}") | |
| else: | |
| print(f"β Analysis failed: {result.get('error', 'Unknown error')}") | |
| except Exception as e: | |
| print(f"β Test failed: {e}") | |
| print("=" * 60) | |
| # Run test | |
| asyncio.run(test_orchestrator()) |