Spaces:
Sleeping
Sleeping
| """Core resume analysis logic""" | |
| import streamlit as st | |
| import re | |
| from models.llm_client import LLMClient | |
| from agents.resume_extractor import ResumeExtractor | |
| from agents.jd_summarizer import JobDescriptionSummarizer | |
| from agents.matcher import ResumeJDMatcher | |
| from agents.shortlister import Shortlister | |
| from db.database import ResumeMatchDB | |
| def extract_candidate_info(resume_text): | |
| """Extract candidate name and email from resume text""" | |
| email_pattern = r'[\w\.-]+@[\w\.-]+\.\w+' | |
| email = re.search(email_pattern, resume_text) | |
| email = email.group(0) if email else "Not found" | |
| lines = resume_text.split('\n') | |
| name = "Not found" | |
| name_patterns = [ | |
| r'^[A-Z][a-z]+\s+[A-Z][a-z]+$', | |
| r'^[A-Z][a-z]+\s+[A-Z]\.\s+[A-Z][a-z]+$', | |
| r'^[A-Z][a-z]+\s+[A-Z][a-z]+\s+[A-Z][a-z]+$' | |
| ] | |
| for line in lines: | |
| line = line.strip() | |
| if line and '@' not in line: | |
| for pattern in name_patterns: | |
| if re.match(pattern, line): | |
| name = line | |
| break | |
| if name != "Not found": | |
| break | |
| return name, email | |
| def analyze_resumes(uploaded_files, job_descriptions, api_key, model_name, base_url): | |
| """Analyze resumes and store results in session state""" | |
| progress_bar = st.progress(0) | |
| status_text = st.empty() | |
| with st.spinner("Analyzing resumes..."): | |
| results = [] | |
| total_steps = len(uploaded_files) * len(job_descriptions) | |
| current_step = 0 | |
| db = ResumeMatchDB() | |
| for uploaded_file in uploaded_files: | |
| status_text.text(f"π Processing {uploaded_file.name}...") | |
| extractor = ResumeExtractor(uploaded_file) | |
| resume_text = extractor.get_resume_text() | |
| if not resume_text or len(resume_text.strip()) < 10: | |
| st.error(f"β Could not extract text from {uploaded_file.name}. File may be corrupted or empty.") | |
| continue | |
| candidate_name, candidate_email = extract_candidate_info(resume_text) | |
| candidate_id = db.insert_candidate( | |
| name=candidate_name, | |
| email=candidate_email, | |
| resume_path=uploaded_file.name | |
| ) | |
| resume_results = [] | |
| for jd in job_descriptions: | |
| current_step += 1 | |
| progress = current_step / total_steps | |
| progress_bar.progress(progress) | |
| status_text.text(f"π Matching with {jd['title']}...") | |
| jd_agent = JobDescriptionSummarizer(jd['content']) | |
| jd_summary = jd_agent.get_summary() | |
| if not jd_summary or len(jd_summary.strip()) < 10: | |
| st.error(f"β Could not process job description: {jd['title']}") | |
| continue | |
| llm = LLMClient(api_key=api_key, model_name=model_name, base_url=base_url) | |
| matcher = ResumeJDMatcher(llm) | |
| shortlister = Shortlister(threshold=70.0) | |
| match_result = matcher.match_resume_to_job(resume_text, jd_summary) | |
| match_percent = shortlister.compute_final_score(match_result) | |
| is_shortlisted = shortlister.is_shortlisted(match_percent) | |
| job_id = db.insert_job_description( | |
| title=jd['title'], | |
| description=jd['content'] | |
| ) | |
| match_data = { | |
| 'match_score': match_percent, | |
| 'skills_match': match_result['skills_match'], | |
| 'experience_match': match_result['experience_match'], | |
| 'education_match': match_result['education_match'], | |
| 'certifications_match': match_result['certifications_match'], | |
| 'summary': match_result['summary'], | |
| 'is_shortlisted': is_shortlisted | |
| } | |
| db.insert_match_result(candidate_id, job_id, match_data) | |
| resume_results.append({ | |
| "job_title": jd['title'], | |
| "match_score": match_percent, | |
| "is_shortlisted": is_shortlisted, | |
| "details": match_result, | |
| "job_id": job_id | |
| }) | |
| if resume_results: | |
| best_match = max(resume_results, key=lambda x: x['match_score']) | |
| results.append({ | |
| "candidate_name": candidate_name, | |
| "candidate_email": candidate_email, | |
| "resume_name": uploaded_file.name, | |
| "best_match": best_match, | |
| "candidate_id": candidate_id | |
| }) | |
| progress_bar.empty() | |
| status_text.empty() | |
| st.session_state.results = results | |