Update .app
Browse files
.app
CHANGED
|
@@ -7,17 +7,28 @@ from sklearn.metrics.pairwise import cosine_similarity
|
|
| 7 |
import gradio as gr
|
| 8 |
import os
|
| 9 |
import subprocess
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
# Download spaCy model if not already present
|
| 12 |
try:
|
|
|
|
| 13 |
spacy.load("en_core_web_sm")
|
| 14 |
except OSError:
|
|
|
|
| 15 |
subprocess.run(["python", "-m", "spacy", "download", "en_core_web_sm"])
|
|
|
|
| 16 |
|
| 17 |
class ATSValidator:
|
| 18 |
def __init__(self):
|
|
|
|
| 19 |
self.nlp = spacy.load("en_core_web_sm")
|
|
|
|
| 20 |
self.bert_model = SentenceTransformer('all-MiniLM-L6-v2')
|
|
|
|
| 21 |
self.section_weights = {
|
| 22 |
'experience': 0.35,
|
| 23 |
'education': 0.25,
|
|
@@ -25,7 +36,6 @@ class ATSValidator:
|
|
| 25 |
'projects': 0.15,
|
| 26 |
'certifications': 0.05
|
| 27 |
}
|
| 28 |
-
# Common technical skills
|
| 29 |
self.key_skills = {
|
| 30 |
'python', 'javascript', 'java', 'sql', 'aws', 'docker', 'react',
|
| 31 |
'nodejs', 'machine learning', 'data analysis', 'git', 'html', 'css'
|
|
@@ -33,11 +43,13 @@ class ATSValidator:
|
|
| 33 |
|
| 34 |
def extract_text(self, pdf_path):
|
| 35 |
if not os.path.exists(pdf_path):
|
|
|
|
| 36 |
return ""
|
| 37 |
try:
|
| 38 |
with fitz.open(pdf_path) as doc:
|
| 39 |
return " ".join(page.get_text("text") for page in doc)[:100000]
|
| 40 |
-
except:
|
|
|
|
| 41 |
return ""
|
| 42 |
|
| 43 |
def preprocess_text(self, text):
|
|
@@ -62,11 +74,12 @@ class ATSValidator:
|
|
| 62 |
embeddings = self.bert_model.encode([resume_text[:1024], jd_text[:1024]])
|
| 63 |
similarity = cosine_similarity([embeddings[0]], [embeddings[1]])
|
| 64 |
return round(max(0, similarity.item() * 100), 2)
|
| 65 |
-
except:
|
|
|
|
| 66 |
return 0
|
| 67 |
|
| 68 |
def analyze(self, resume_file, job_description):
|
| 69 |
-
|
| 70 |
resume_text = self.extract_text(resume_file)
|
| 71 |
if not resume_text:
|
| 72 |
return "Error: Could not read resume file", "", "", ""
|
|
@@ -74,16 +87,13 @@ class ATSValidator:
|
|
| 74 |
cleaned_resume = self.preprocess_text(resume_text)
|
| 75 |
cleaned_jd = self.preprocess_text(job_description)
|
| 76 |
|
| 77 |
-
# Calculate scores
|
| 78 |
semantic_score = self.calculate_semantic_fit(cleaned_resume, cleaned_jd)
|
| 79 |
section_scores = self.analyze_sections(resume_text)
|
| 80 |
section_score = sum(self.section_weights[sec] * score * 100
|
| 81 |
for sec, score in section_scores.items())
|
| 82 |
|
| 83 |
-
# Final ATS score
|
| 84 |
ats_score = np.clip(0.7 * semantic_score + 0.3 * section_score, 0, 100)
|
| 85 |
|
| 86 |
-
# Skills analysis
|
| 87 |
resume_skills = self.extract_skills(cleaned_resume)
|
| 88 |
jd_skills = self.extract_skills(cleaned_jd)
|
| 89 |
matched_skills = resume_skills.intersection(jd_skills)
|
|
@@ -92,7 +102,6 @@ class ATSValidator:
|
|
| 92 |
skills_match = f"Matched Skills: {', '.join(matched_skills) if matched_skills else 'None'}\n" \
|
| 93 |
f"Missing Skills: {', '.join(missing_skills) if missing_skills else 'None'}"
|
| 94 |
|
| 95 |
-
# Improvement suggestions
|
| 96 |
improvements = []
|
| 97 |
if semantic_score < 60:
|
| 98 |
improvements.append("Increase alignment with job description keywords and phrases")
|
|
@@ -105,13 +114,14 @@ class ATSValidator:
|
|
| 105 |
|
| 106 |
improvement_text = "\n".join(improvements) if improvements else "Looks good!"
|
| 107 |
|
| 108 |
-
# Score breakdown
|
| 109 |
breakdown = f"Semantic Match: {semantic_score:.1f}%\nSection Score: {section_score:.1f}%"
|
| 110 |
|
|
|
|
| 111 |
return f"ATS Score: {ats_score:.1f}%", skills_match, improvement_text, breakdown
|
| 112 |
|
| 113 |
# Gradio Interface
|
| 114 |
def process_resume(resume_file, job_description):
|
|
|
|
| 115 |
validator = ATSValidator()
|
| 116 |
ats_score, skills_match, improvements, breakdown = validator.analyze(resume_file.name, job_description)
|
| 117 |
return ats_score, skills_match, improvements, breakdown
|
|
@@ -135,4 +145,5 @@ interface = gr.Interface(
|
|
| 135 |
|
| 136 |
# Launch the app
|
| 137 |
if __name__ == "__main__":
|
|
|
|
| 138 |
interface.launch()
|
|
|
|
| 7 |
import gradio as gr
|
| 8 |
import os
|
| 9 |
import subprocess
|
| 10 |
+
import logging
|
| 11 |
+
|
| 12 |
+
# Configure logging
|
| 13 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 14 |
+
logger = logging.getLogger(__name__)
|
| 15 |
|
| 16 |
# Download spaCy model if not already present
|
| 17 |
try:
|
| 18 |
+
logger.info("Loading spaCy model...")
|
| 19 |
spacy.load("en_core_web_sm")
|
| 20 |
except OSError:
|
| 21 |
+
logger.info("Downloading spaCy model 'en_core_web_sm'...")
|
| 22 |
subprocess.run(["python", "-m", "spacy", "download", "en_core_web_sm"])
|
| 23 |
+
logger.info("spaCy model downloaded successfully.")
|
| 24 |
|
| 25 |
class ATSValidator:
|
| 26 |
def __init__(self):
|
| 27 |
+
logger.info("Initializing ATSValidator...")
|
| 28 |
self.nlp = spacy.load("en_core_web_sm")
|
| 29 |
+
logger.info("spaCy model loaded.")
|
| 30 |
self.bert_model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 31 |
+
logger.info("SentenceTransformer model loaded.")
|
| 32 |
self.section_weights = {
|
| 33 |
'experience': 0.35,
|
| 34 |
'education': 0.25,
|
|
|
|
| 36 |
'projects': 0.15,
|
| 37 |
'certifications': 0.05
|
| 38 |
}
|
|
|
|
| 39 |
self.key_skills = {
|
| 40 |
'python', 'javascript', 'java', 'sql', 'aws', 'docker', 'react',
|
| 41 |
'nodejs', 'machine learning', 'data analysis', 'git', 'html', 'css'
|
|
|
|
| 43 |
|
| 44 |
def extract_text(self, pdf_path):
|
| 45 |
if not os.path.exists(pdf_path):
|
| 46 |
+
logger.error(f"PDF file not found: {pdf_path}")
|
| 47 |
return ""
|
| 48 |
try:
|
| 49 |
with fitz.open(pdf_path) as doc:
|
| 50 |
return " ".join(page.get_text("text") for page in doc)[:100000]
|
| 51 |
+
except Exception as e:
|
| 52 |
+
logger.error(f"Error extracting text from PDF: {str(e)}")
|
| 53 |
return ""
|
| 54 |
|
| 55 |
def preprocess_text(self, text):
|
|
|
|
| 74 |
embeddings = self.bert_model.encode([resume_text[:1024], jd_text[:1024]])
|
| 75 |
similarity = cosine_similarity([embeddings[0]], [embeddings[1]])
|
| 76 |
return round(max(0, similarity.item() * 100), 2)
|
| 77 |
+
except Exception as e:
|
| 78 |
+
logger.error(f"Error calculating semantic fit: {str(e)}")
|
| 79 |
return 0
|
| 80 |
|
| 81 |
def analyze(self, resume_file, job_description):
|
| 82 |
+
logger.info("Starting resume analysis...")
|
| 83 |
resume_text = self.extract_text(resume_file)
|
| 84 |
if not resume_text:
|
| 85 |
return "Error: Could not read resume file", "", "", ""
|
|
|
|
| 87 |
cleaned_resume = self.preprocess_text(resume_text)
|
| 88 |
cleaned_jd = self.preprocess_text(job_description)
|
| 89 |
|
|
|
|
| 90 |
semantic_score = self.calculate_semantic_fit(cleaned_resume, cleaned_jd)
|
| 91 |
section_scores = self.analyze_sections(resume_text)
|
| 92 |
section_score = sum(self.section_weights[sec] * score * 100
|
| 93 |
for sec, score in section_scores.items())
|
| 94 |
|
|
|
|
| 95 |
ats_score = np.clip(0.7 * semantic_score + 0.3 * section_score, 0, 100)
|
| 96 |
|
|
|
|
| 97 |
resume_skills = self.extract_skills(cleaned_resume)
|
| 98 |
jd_skills = self.extract_skills(cleaned_jd)
|
| 99 |
matched_skills = resume_skills.intersection(jd_skills)
|
|
|
|
| 102 |
skills_match = f"Matched Skills: {', '.join(matched_skills) if matched_skills else 'None'}\n" \
|
| 103 |
f"Missing Skills: {', '.join(missing_skills) if missing_skills else 'None'}"
|
| 104 |
|
|
|
|
| 105 |
improvements = []
|
| 106 |
if semantic_score < 60:
|
| 107 |
improvements.append("Increase alignment with job description keywords and phrases")
|
|
|
|
| 114 |
|
| 115 |
improvement_text = "\n".join(improvements) if improvements else "Looks good!"
|
| 116 |
|
|
|
|
| 117 |
breakdown = f"Semantic Match: {semantic_score:.1f}%\nSection Score: {section_score:.1f}%"
|
| 118 |
|
| 119 |
+
logger.info("Analysis completed successfully.")
|
| 120 |
return f"ATS Score: {ats_score:.1f}%", skills_match, improvement_text, breakdown
|
| 121 |
|
| 122 |
# Gradio Interface
|
| 123 |
def process_resume(resume_file, job_description):
|
| 124 |
+
logger.info("Processing resume via Gradio interface...")
|
| 125 |
validator = ATSValidator()
|
| 126 |
ats_score, skills_match, improvements, breakdown = validator.analyze(resume_file.name, job_description)
|
| 127 |
return ats_score, skills_match, improvements, breakdown
|
|
|
|
| 145 |
|
| 146 |
# Launch the app
|
| 147 |
if __name__ == "__main__":
|
| 148 |
+
logger.info("Launching Gradio app...")
|
| 149 |
interface.launch()
|