DreamStream-1 commited on
Commit
a2eab18
·
verified ·
1 Parent(s): aecaa76

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +200 -0
app.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import pandas as pd
4
+ from sentence_transformers import SentenceTransformer
5
+ from fuzzywuzzy import fuzz
6
+ from typing import List, Dict, Any
7
+ import fitz # PyMuPDF for PDF extraction
8
+ import docx
9
+ import re
10
+ from pathlib import Path
11
+
12
+ # Configuration class for constants
13
+ class Config:
14
+ MAX_RESUMES = 10
15
+ MAX_LEADERSHIP_EXP = 10
16
+ MAX_MANAGEMENT_EXP = 10
17
+ MODEL_NAME = 'paraphrase-MiniLM-L6-v2'
18
+
19
+ class ResumeAnalyzer:
20
+ def __init__(self):
21
+ self.config = Config() # Initialize configuration
22
+ self._initialize_models()
23
+ self.required_skills = self._load_required_skills()
24
+ self.role_hierarchy = self._load_role_hierarchy()
25
+
26
+ def _initialize_models(self):
27
+ """Initialize the sentence transformer model."""
28
+ self.sentence_model = SentenceTransformer(self.config.MODEL_NAME)
29
+
30
+ def _load_required_skills(self) -> List[str]:
31
+ """Load the list of required skills for leadership and management roles."""
32
+ return [
33
+ "strategic planning", "team management", "project management",
34
+ "decision making", "communication", "leadership",
35
+ "conflict resolution", "delegation", "performance management",
36
+ "budget management", "resource allocation", "staff development",
37
+ "change management", "risk management", "problem solving",
38
+ "negotiation", "executive leadership", "organizational skills",
39
+ "business development", "stakeholder management", "collaboration",
40
+ "emotional intelligence", "coaching", "mentoring",
41
+ "time management", "cross-functional team leadership", "innovation",
42
+ "organizational culture", "team motivation", "employee engagement",
43
+ "organizational design", "continuous improvement",
44
+ "decision-making under pressure", "adaptability", "accountability",
45
+ "team building", "succession planning", "strategic partnerships",
46
+ "executive presence", "influencing", "visionary leadership"
47
+ ]
48
+
49
+ def _load_role_hierarchy(self) -> Dict[str, int]:
50
+ """Load role hierarchy to calculate seniority scores."""
51
+ return {
52
+ "CEO": 5, "CIO": 5, "CFO": 5, "COO": 5,
53
+ "Director": 4, "VP": 4, "Head": 4,
54
+ "Manager": 3, "Senior": 3,
55
+ "Team Lead": 2, "Lead": 2,
56
+ "Junior": 1, "Associate": 1
57
+ }
58
+
59
+ def extract_text_from_file(self, file_path: str) -> str:
60
+ """Extract text from different file formats (PDF, DOCX, TXT)."""
61
+ file_path = Path(file_path)
62
+ if not file_path.exists():
63
+ raise FileNotFoundError(f"File not found: {file_path}")
64
+
65
+ ext = file_path.suffix.lower()
66
+ if ext == ".txt":
67
+ return file_path.read_text(encoding='utf-8')
68
+ elif ext == ".pdf":
69
+ return self._extract_text_from_pdf(file_path)
70
+ elif ext == ".docx":
71
+ return self._extract_text_from_docx(file_path)
72
+ else:
73
+ raise ValueError(f"Unsupported file format: {ext}")
74
+
75
+ def _extract_text_from_pdf(self, file_path: Path) -> str:
76
+ """Extract text from a PDF using PyMuPDF."""
77
+ doc = fitz.open(file_path)
78
+ text = ""
79
+ for page in doc:
80
+ text += page.get_text("text")
81
+ return text
82
+
83
+ def _extract_text_from_docx(self, file_path: Path) -> str:
84
+ """Extract text from a DOCX file."""
85
+ doc = docx.Document(file_path)
86
+ text = ""
87
+ for para in doc.paragraphs:
88
+ text += para.text + "\n"
89
+ return text
90
+
91
+ def analyze_with_gemini(self, resume_text: str, job_desc: str) -> str:
92
+ """Simulated analysis with Gemini model (or other model)."""
93
+ # In a real-world scenario, this method would send data to an external model/API.
94
+ # Here, we'll simply return a placeholder analysis (mock-up for now).
95
+ return f"Candidate Name: John Doe\nEmail Address: john.doe@example.com\nContact Number: 123-456-7890\n" \
96
+ f"Skills: leadership, project management, team building\n" \
97
+ f"Team Leadership Experience (years): 5\nManagement Experience (years): 3\nManagement Skills: leadership, management, team building"
98
+
99
+ def extract_management_details(self, gemini_response: str) -> tuple:
100
+ """Extract leadership and management details from the analysis."""
101
+ patterns = {
102
+ 'leadership': r"Team Leadership Experience \(years\):\s*(\d+)",
103
+ 'management': r"Management Experience \(years\):\s*(\d+)",
104
+ 'skills': r"Management Skills\s*[:\-]?\s*(.*?)(?=\n|$)"
105
+ }
106
+
107
+ matches = {
108
+ key: re.search(pattern, gemini_response)
109
+ for key, pattern in patterns.items()
110
+ }
111
+
112
+ leadership_years = int(matches['leadership'].group(1)) if matches['leadership'] else 0
113
+ management_years = int(matches['management'].group(1)) if matches['management'] else 0
114
+ skills = matches['skills'].group(1) if matches['skills'] else ""
115
+
116
+ return leadership_years, management_years, skills
117
+
118
+ def calculate_role_score(self, role_keywords: str) -> float:
119
+ """Calculate seniority score based on role keywords."""
120
+ seniority_score = 0
121
+ for keyword, score in self.role_hierarchy.items():
122
+ if fuzz.partial_ratio(keyword.lower(), role_keywords.lower()) > 80:
123
+ seniority_score = max(seniority_score, score)
124
+ return seniority_score
125
+
126
+ def calculate_advanced_match(self, leadership_years, management_years, skills, role_keywords) -> float:
127
+ """Calculate overall match percentage using weighted criteria."""
128
+ weights = {
129
+ 'leadership': 0.35,
130
+ 'management': 0.35,
131
+ 'skills': 0.20,
132
+ 'role': 0.10
133
+ }
134
+
135
+ leadership_score = min(leadership_years / self.config.MAX_LEADERSHIP_EXP, 1.0) * 100
136
+ management_score = min(management_years / self.config.MAX_MANAGEMENT_EXP, 1.0) * 100
137
+
138
+ role_score = self.calculate_role_score(role_keywords) * 20 # Scale to 100
139
+
140
+ skills_matched = sum(1 for skill in self.required_skills
141
+ if fuzz.partial_ratio(skill.lower(), skills.lower()) > 80)
142
+ skill_match_score = (skills_matched / len(self.required_skills)) * 100
143
+
144
+ overall_match = sum([
145
+ leadership_score * weights['leadership'],
146
+ management_score * weights['management'],
147
+ skill_match_score * weights['skills'],
148
+ role_score * weights['role']
149
+ ])
150
+
151
+ return round(overall_match, 2)
152
+
153
+ def process_resume(self, resume, job_desc) -> Dict[str, Any]:
154
+ """Process a single resume and return analysis results."""
155
+ resume_text = self.extract_text_from_file(resume.name)
156
+ gemini_analysis = self.analyze_with_gemini(resume_text, job_desc)
157
+ leadership_years, management_years, skills = self.extract_management_details(gemini_analysis)
158
+ overall_match = self.calculate_advanced_match(leadership_years, management_years, skills, job_desc)
159
+
160
+ return {
161
+ "Resume": resume.name,
162
+ "Leadership Experience": leadership_years,
163
+ "Management Experience": management_years,
164
+ "Skills": skills,
165
+ "Match Percentage": f"{overall_match}%" # Match percentage formatted as a string with "%"
166
+ }
167
+
168
+ # Gradio Interface function to handle multiple resumes and job description
169
+ def process_uploaded_resumes(resume_files: list, job_desc: str):
170
+ """Process multiple uploaded resumes and compare them against a job description."""
171
+ results = []
172
+ for resume in resume_files:
173
+ result = analyzer.process_resume(resume, job_desc)
174
+ results.append(result)
175
+ return pd.DataFrame(results)
176
+
177
+ # Create the Gradio interface
178
+ def create_gradio_interface():
179
+ """Creates and launches a Gradio interface for the ResumeAnalyzer."""
180
+ resume_input = gr.inputs.File(label="Upload Resumes (PDF, DOCX, TXT)", type="file", multiple=True)
181
+ job_desc_input = gr.inputs.Textbox(label="Enter Job Description", lines=6)
182
+ output = gr.outputs.Dataframe(label="Resume Analysis Results")
183
+
184
+ interface = gr.Interface(
185
+ fn=process_uploaded_resumes,
186
+ inputs=[resume_input, job_desc_input],
187
+ outputs=[output],
188
+ title="Resume Match Analysis",
189
+ description="Upload resumes and provide a job description to see how well the resumes match the required skills, experience, and role.",
190
+ allow_flagging="never", # Disable flagging (can be enabled if needed)
191
+ )
192
+
193
+ return interface
194
+
195
+ # Initialize the ResumeAnalyzer and Gradio interface
196
+ analyzer = ResumeAnalyzer() # Initialize the ResumeAnalyzer
197
+ gradio_interface = create_gradio_interface() # Create Gradio interface
198
+
199
+ # Launch the Gradio interface
200
+ gradio_interface.launch(share=True) # share=True for generating a public URL to share