Mishal23 commited on
Commit
82e8360
·
verified ·
1 Parent(s): e1adbcc

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +224 -0
app.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from sentence_transformers import SentenceTransformer, util
3
+ import docx
4
+ import os
5
+ from PyPDF2 import PdfReader
6
+ import re
7
+ from datetime import datetime
8
+
9
+ # Load pre-trained model for sentence embedding
10
+ model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
11
+
12
+ # Define maximum number of resumes
13
+ MAX_RESUMES = 10
14
+
15
+ # Function to load job description from file path
16
+ def load_job_description(job_desc_file):
17
+ if not os.path.exists(job_desc_file):
18
+ return "Job description file not found."
19
+ with open(job_desc_file, 'r') as file:
20
+ job_description = file.read()
21
+ if not job_description.strip():
22
+ return "Job description is empty."
23
+ return job_description
24
+
25
+ # Function to load offer letter template
26
+ def load_offer_letter_template(template_file):
27
+ return docx.Document(template_file)
28
+
29
+ # Function to check similarity between resumes and job description
30
+ def check_similarity(job_description, resume_files):
31
+ results = []
32
+ job_emb = model.encode(job_description, convert_to_tensor=True)
33
+
34
+ for resume_file in resume_files:
35
+ resume_text = extract_text_from_resume(resume_file)
36
+ if not resume_text:
37
+ results.append((resume_file.name, 0, "Not Eligible", None))
38
+ continue
39
+ resume_emb = model.encode(resume_text, convert_to_tensor=True)
40
+ similarity_score = util.pytorch_cos_sim(job_emb, resume_emb)[0][0].item()
41
+
42
+ # Set a higher similarity threshold for eligibility
43
+ if similarity_score >= 0.50:
44
+ candidate_name = extract_candidate_name(resume_text)
45
+ results.append((resume_file.name, similarity_score, "Eligible", candidate_name))
46
+ else:
47
+ results.append((resume_file.name, similarity_score, "Not Eligible", None))
48
+
49
+ return results
50
+
51
+ # Extract text from resume (handles .txt, .pdf, .docx)
52
+ def extract_text_from_resume(resume_file):
53
+ file_extension = os.path.splitext(resume_file)[1].lower()
54
+ if file_extension not in ['.txt', '.pdf', '.docx']:
55
+ return "Unsupported file format"
56
+
57
+ if file_extension == '.txt':
58
+ return read_text_file(resume_file)
59
+ elif file_extension == '.pdf':
60
+ return read_pdf_file(resume_file)
61
+ elif file_extension == '.docx':
62
+ return read_docx_file(resume_file)
63
+
64
+ return "Failed to read the resume text."
65
+
66
+ def read_text_file(file_path):
67
+ with open(file_path, 'r') as file:
68
+ return file.read()
69
+
70
+ def read_pdf_file(file_path):
71
+ reader = PdfReader(file_path)
72
+ text = ""
73
+ for page in reader.pages:
74
+ text += page.extract_text()
75
+ return text
76
+
77
+ def read_docx_file(file_path):
78
+ doc = docx.Document(file_path)
79
+ text = ""
80
+ for para in doc.paragraphs:
81
+ text += para.text
82
+ return text
83
+
84
+ # Extract candidate name from resume text
85
+ def extract_candidate_name(resume_text):
86
+ name_pattern = re.compile(r'\b([A-Z][a-z]+ [A-Z][a-z]+)\b')
87
+ matches = name_pattern.findall(resume_text)
88
+ if matches:
89
+ return matches[0] # Returns the first match
90
+ return "Unknown Candidate"
91
+
92
+ # Create an offer letter
93
+ def create_offer_letter(candidate_name, job_title, company_name, joining_date, template_doc):
94
+ new_doc = docx.Document()
95
+ for paragraph in template_doc.paragraphs:
96
+ new_doc.add_paragraph(paragraph.text)
97
+
98
+ # Replace placeholders in the template
99
+ for paragraph in new_doc.paragraphs:
100
+ paragraph.text = paragraph.text.replace('{{ name }}', candidate_name)
101
+ paragraph.text = paragraph.text.replace('{{ role }}', job_title)
102
+ paragraph.text = paragraph.text.replace('{{ joining date }}', joining_date)
103
+
104
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
105
+ output_filename = f"/tmp/{candidate_name}_{timestamp}_Offer_Letter.docx"
106
+ new_doc.save(output_filename)
107
+ return output_filename
108
+
109
+ # Schedule interview with AM/PM format
110
+ def schedule_interview(candidate_name, interview_date, interview_time):
111
+ try:
112
+ interview_datetime = datetime.strptime(f"{interview_date} {interview_time}", '%Y-%m-%d %I:%M %p')
113
+ message = f"Interview scheduled for {candidate_name} on {interview_datetime.strftime('%Y-%m-%d at %I:%M %p')}"
114
+ return message
115
+ except Exception as e:
116
+ return f"Error in scheduling interview: {str(e)}"
117
+
118
+ # Validate date and time with AM/PM
119
+ def validate_date_time(date_str, time_str):
120
+ try:
121
+ date_obj = datetime.strptime(date_str, "%Y-%m-%d")
122
+ time_obj = datetime.strptime(time_str, "%I:%M %p")
123
+ return True, date_obj, time_obj
124
+ except ValueError:
125
+ return False, None, None
126
+
127
+ # Main processing function
128
+ def process_files(job_desc, template, resumes, interview_choice, interview_date, interview_time, generate_offer_choice, role, joining_date, candidate_name):
129
+ try:
130
+ # Check if the number of resumes is within the allowed limit
131
+ if len(resumes) > MAX_RESUMES:
132
+ return "Please upload no more than 10 resumes."
133
+
134
+ # Check if all necessary files are provided
135
+ if not job_desc or not template or not resumes:
136
+ return "Please provide all necessary files."
137
+
138
+ # Load the job description and offer letter template
139
+ job_desc_text = load_job_description(job_desc)
140
+ offer_template_doc = load_offer_letter_template(template)
141
+
142
+ # Check similarity
143
+ results = check_similarity(job_desc_text, resumes)
144
+
145
+ # Initialize lists for the output
146
+ analysis_results = ["Analysis Results:"]
147
+ interview_messages = []
148
+ offer_files = []
149
+
150
+ # Process each resume's similarity
151
+ for idx, (filename, similarity, eligibility, extracted_name) in enumerate(results, start=1):
152
+ candidate_label = f"Candidate {idx}"
153
+ similarity_percentage = similarity * 100
154
+ analysis_results.append(f"{candidate_label}, Similarity Percentage: {similarity_percentage:.2f}%")
155
+
156
+ # If interview is scheduled and "Yes" is selected
157
+ if interview_choice == "Yes" and eligibility == "Eligible" and extracted_name:
158
+ is_valid, date_obj, time_obj = validate_date_time(interview_date, interview_time)
159
+ if is_valid:
160
+ interview_msg = schedule_interview(candidate_label, interview_date, interview_time)
161
+ interview_messages.append(interview_msg)
162
+
163
+ # Ask the user if they want to generate the offer letter
164
+ if generate_offer_choice == "Yes":
165
+ offer_file = create_offer_letter(candidate_name, role, "AI Company", joining_date, offer_template_doc)
166
+ offer_files.append(offer_file)
167
+ else:
168
+ interview_messages.append(f"Offer letter not generated for {candidate_label}.")
169
+ else:
170
+ interview_messages.append(f"Invalid date or time format for {candidate_label}. Use YYYY-MM-DD for date and HH:MM AM/PM for time.")
171
+
172
+ # Prepare interview schedule output
173
+ if interview_messages:
174
+ interview_messages.insert(0, "Interview Schedule:")
175
+ interview_output = "\n".join(interview_messages)
176
+ else:
177
+ interview_output = "No interviews scheduled."
178
+
179
+ # Prepare the offer letters output
180
+ if offer_files:
181
+ analysis_results.append("\nGenerated Offer Letters:")
182
+ for idx, offer_file in enumerate(offer_files, start=1):
183
+ analysis_results.append(f"- Candidate {idx} Offer Letter")
184
+
185
+ # Join and return the results as formatted text
186
+ analysis_output = "\n".join(analysis_results)
187
+ interview_output = "\n".join(interview_messages)
188
+
189
+ return analysis_output, interview_output, offer_files
190
+
191
+ except Exception as e:
192
+ # Return any errors encountered during processing
193
+ return f"Error processing files: {str(e)}", None
194
+
195
+
196
+ # Gradio Interface Components
197
+ job_desc_input = gr.File(label="Upload Job Description (TXT)", type="filepath")
198
+ template_input = gr.File(label="Upload Offer Letter Template (DOCX)", type="filepath")
199
+ resumes_input = gr.Files(label="Upload Resumes (TXT, DOCX, PDF)", type="filepath")
200
+
201
+ interview_choice_input = gr.Radio(["Yes", "No"], label="Schedule Interview?")
202
+ interview_date_input = gr.Textbox(label="Interview Date (YYYY-MM-DD)", placeholder="Enter date in YYYY-MM-DD format")
203
+ interview_time_input = gr.Textbox(label="Interview Time (HH:MM AM/PM)", placeholder="Enter time in HH:MM AM/PM format")
204
+
205
+ generate_offer_choice_input = gr.Radio(["Yes", "No"], label="Generate Offer Letter?")
206
+ role_input = gr.Textbox(label="Enter Role")
207
+ joining_date_input = gr.Textbox(label="Enter Joining Date (YYYY-MM-DD)", placeholder="Enter joining date in YYYY-MM-DD format")
208
+ candidate_name_input = gr.Textbox(label="Enter Candidate Name", placeholder="Enter candidate's name")
209
+
210
+ # Gradio Outputs
211
+ results_output = gr.Markdown(label="Analysis Results")
212
+ interview_output = gr.Markdown(label="Interview Schedule")
213
+ offer_letters_output = gr.Files(label="Generated Offer Letters")
214
+
215
+ # Gradio Interface
216
+ interface = gr.Interface(
217
+ fn=process_files,
218
+ inputs=[job_desc_input, template_input, resumes_input, interview_choice_input, interview_date_input, interview_time_input, generate_offer_choice_input, role_input, joining_date_input, candidate_name_input],
219
+ outputs=[results_output, interview_output, offer_letters_output],
220
+ title="HR Assistant - Resume Screening & Interview Scheduling",
221
+ description="Upload job description, template, and resumes to screen candidates, schedule interviews, and generate offer letters."
222
+ )
223
+
224
+ interface.launch()