rohitashva commited on
Commit
8f40e0e
·
verified ·
1 Parent(s): 1a01b1e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -191
app.py CHANGED
@@ -1,22 +1,30 @@
1
- # Importing necessary libraries
2
- from collections import Counter
 
 
 
 
 
3
  import streamlit as st
4
  import nltk
5
  from gensim.models.doc2vec import Doc2Vec, TaggedDocument
6
  from nltk.tokenize import word_tokenize
7
  import PyPDF2
8
  import pandas as pd
9
- from numpy.linalg import matrix_upper_triangular as triu
10
  import re
11
  import matplotlib.pyplot as plt
12
  import seaborn as sns
13
- import subprocess
14
- subprocess.run(["pip", "install", "https://huggingface.co/Priyanka-Balivada/en_Resume_Matching_Keywords/resolve/main/en_Resume_Matching_Keywords-any-py3-none-any.whl"])
15
- import en_Resume_Matching_Keywords # Now import your package
16
 
17
- # Downloading the 'punkt' tokenizer from NLTK
18
  nltk.download('punkt')
19
 
 
 
 
 
 
 
20
  # Function to extract text from a PDF file
21
  def extract_text_from_pdf(pdf_file):
22
  pdf_reader = PyPDF2.PdfReader(pdf_file)
@@ -25,222 +33,182 @@ def extract_text_from_pdf(pdf_file):
25
  text += pdf_reader.pages[page_num].extract_text()
26
  return text
27
 
28
- # Function to extract skills from a text using a list of skill keywords
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def extract_skills(text, skills_keywords):
30
- skills = [skill.lower()
31
- for skill in skills_keywords if re.search(r'\b' + re.escape(skill.lower()) + r'\b', text.lower())]
32
  return skills
33
 
34
- # Function to preprocess text by tokenizing and converting to lowercase
35
  def preprocess_text(text):
36
  return word_tokenize(text.lower())
37
 
38
- # Function to extract mobile numbers from a text
39
- def extract_mobile_numbers(text):
40
- mobile_pattern = r'\b\d{10}\b|\b\d{3}[-.\s]?\d{3}[-.\s]?\d{4}\b'
41
- return re.findall(mobile_pattern, text)
42
-
43
- # Function to extract emails from a text
44
- def extract_emails(text):
45
- email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
46
- return re.findall(email_pattern, text)
47
-
48
- # Function to train a Doc2Vec model on a list of tagged documents
49
  def train_doc2vec_model(documents):
50
  model = Doc2Vec(vector_size=20, min_count=2, epochs=50)
51
  model.build_vocab(documents)
52
- model.train(documents, total_examples=model.corpus_count,
53
- epochs=model.epochs)
54
  return model
55
 
56
- # Function to calculate the cosine similarity between two texts using a trained Doc2Vec model
57
  def calculate_similarity(model, text1, text2):
58
  vector1 = model.infer_vector(preprocess_text(text1))
59
  vector2 = model.infer_vector(preprocess_text(text2))
60
  return model.dv.cosine_similarities(vector1, [vector2])[0]
61
 
62
- # Function to calculate accuracy based on true positives, false positives, and false negatives
63
  def accuracy_calculation(true_positives, false_positives, false_negatives):
64
  total = true_positives + false_positives + false_negatives
65
  accuracy = true_positives / total if total != 0 else 0
66
  return accuracy
67
 
68
- # Function to extract CGPA from a text
69
- def extract_cgpa(resume_text):
70
- # Define a regular expression pattern for CGPA extraction
71
- cgpa_pattern = r'\b(?:CGPA|GPA|C.G.PA|Cumulative GPA)\s*:?[\s-]* ([0-9]+(?:\.[0-9]+)?)\b|\b([0-9]+(?:\.[0-9]+)?)\s*(?:CGPA|GPA)\b'
72
-
73
- # Search for CGPA pattern in the text
74
- match = re.search(cgpa_pattern, resume_text, re.IGNORECASE)
75
-
76
- # Check if a match is found
77
- if match:
78
- cgpa = match.group(1)
79
- if cgpa is not None:
80
- return float(cgpa)
81
- else:
82
- return float(match.group(2))
83
- else:
84
- return None
85
-
86
- # Regular expressions for email and phone number patterns
87
- email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
88
- phone_pattern = r'\b\d{10}\b|\b\d{3}[-.\s]?\d{3}[-.\s]?\d{4}\b'
89
-
90
  # Streamlit Frontend
91
  st.markdown("# Resume Matching Tool 📃📃")
92
  st.markdown("An application to match resumes with a job description.")
93
 
94
  # Sidebar - File Upload for Resumes
95
  st.sidebar.markdown("## Upload Resumes PDF")
96
- resumes_files = st.sidebar.file_uploader(
97
- "Upload Resumes PDF", type=["pdf"], accept_multiple_files=True)
98
 
99
  if resumes_files:
100
  # Sidebar - File Upload for Job Descriptions
101
  st.sidebar.markdown("## Upload Job Description PDF")
102
- job_descriptions_file = st.sidebar.file_uploader(
103
- "Upload Job Description PDF", type=["pdf"])
104
 
105
  if job_descriptions_file:
106
- # Sidebar - Sorting Options
107
- sort_options = ['Weighted Score', 'Similarity Score']
108
- selected_sort_option = st.sidebar.selectbox(
109
- "Sort results by", sort_options)
110
 
111
  # Backend Processing
112
  job_description_text = extract_text_from_pdf(job_descriptions_file)
113
- resumes_texts = [extract_text_from_pdf(
114
- resume_file) for resume_file in resumes_files]
115
-
116
- tagged_resumes = [TaggedDocument(words=preprocess_text(
117
- text), tags=[str(i)]) for i, text in enumerate(resumes_texts)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  model_resumes = train_doc2vec_model(tagged_resumes)
119
 
120
- true_positives_mobile = 0
121
- false_positives_mobile = 0
122
- false_negatives_mobile = 0
123
-
124
- true_positives_email = 0
125
- false_positives_email = 0
126
- false_negatives_email = 0
127
-
128
- results_data = {'Resume': [], 'Similarity Score': [],
129
- 'Weighted Score': [], 'Email': [], 'Contact': [], 'CGPA': []}
130
-
131
- for i, resume_text in enumerate(resumes_texts):
132
- extracted_mobile_numbers = set(extract_mobile_numbers(resume_text))
133
- extracted_emails = set(extract_emails(resume_text))
134
- extracted_cgpa = extract_cgpa(resume_text)
135
-
136
- ground_truth_mobile_numbers = {'1234567890', '9876543210'}
137
- ground_truth_emails = {
138
139
-
140
- true_positives_mobile += len(
141
- extracted_mobile_numbers.intersection(ground_truth_mobile_numbers))
142
- false_positives_mobile += len(
143
- extracted_mobile_numbers.difference(ground_truth_mobile_numbers))
144
- false_negatives_mobile += len(
145
- ground_truth_mobile_numbers.difference(extracted_mobile_numbers))
146
-
147
- true_positives_email += len(
148
- extracted_emails.intersection(ground_truth_emails))
149
- false_positives_email += len(
150
- extracted_emails.difference(ground_truth_emails))
151
- false_negatives_email += len(
152
- ground_truth_emails.difference(extracted_emails))
153
-
154
- similarity_score = calculate_similarity(
155
- model_resumes, resume_text, job_description_text)
156
-
157
- other_criteria_score = 0
158
-
159
- weighted_score = (0.6 * similarity_score) + \
160
- (0.4 * other_criteria_score)
161
-
162
- results_data['Resume'].append(resumes_files[i].name)
163
- results_data['Similarity Score'].append(similarity_score * 100)
164
- results_data['Weighted Score'].append(weighted_score)
165
-
166
- emails = ', '.join(re.findall(email_pattern, resume_text))
167
- contacts = ', '.join(re.findall(phone_pattern, resume_text))
168
- results_data['Email'].append(emails)
169
- results_data['Contact'].append(contacts)
170
- results_data['CGPA'].append(extracted_cgpa)
171
-
172
- results_df = pd.DataFrame(results_data)
173
-
174
- if selected_sort_option == 'Similarity Score':
175
- results_df = results_df.sort_values(
176
- by='Similarity Score', ascending=False)
177
- else:
178
- results_df = results_df.sort_values(
179
- by='Weighted Score', ascending=False)
180
-
181
- st.subheader(f"Results Table (Sorted by {selected_sort_option}):")
182
-
183
- # Define a custom function to highlight maximum values in the specified columns
184
- def highlight_max(data, color='grey'):
185
- is_max = data == data.max()
186
- return [f'background-color: {color}' if val else '' for val in is_max]
187
-
188
- # Apply the custom highlighting function to the DataFrame
189
- st.dataframe(results_df.style.apply(highlight_max, subset=[
190
- 'Similarity Score', 'Weighted Score', 'CGPA']))
191
-
192
-
193
- highest_score_index = results_df['Similarity Score'].idxmax()
194
- highest_score_resume_name = resumes_files[highest_score_index].name
195
-
196
- st.subheader("\nDetails of Highest Similarity Score Resume:")
197
- st.write(f"Resume Name: {highest_score_resume_name}")
198
- st.write(
199
- f"Similarity Score: {results_df.loc[highest_score_index, 'Similarity Score']:.2f}")
200
-
201
- if 'Weighted Score' in results_df.columns:
202
- weighted_score_value = results_df.loc[highest_score_index,
203
- 'Weighted Score']
204
- st.write(f"Weighted Score: {weighted_score_value:.2f}" if pd.notnull(
205
- weighted_score_value) else "Weighted Score: Not Mentioned")
206
- else:
207
- st.write("Weighted Score: Not Mentioned")
208
-
209
- if 'Email' in results_df.columns:
210
- email_value = results_df.loc[highest_score_index, 'Email']
211
- st.write(f"Email: {email_value}" if pd.notnull(
212
- email_value) else "Email: Not Mentioned")
213
- else:
214
- st.write("Email: Not Mentioned")
215
-
216
- if 'Contact' in results_df.columns:
217
- contact_value = results_df.loc[highest_score_index, 'Contact']
218
- st.write(f"Contact: {contact_value}" if pd.notnull(
219
- contact_value) else "Contact: Not Mentioned")
220
- else:
221
- st.write("Contact: Not Mentioned")
222
-
223
- if 'CGPA' in results_df.columns:
224
- cgpa_value = results_df.loc[highest_score_index, 'CGPA']
225
- st.write(f"CGPA: {cgpa_value}" if pd.notnull(
226
- cgpa_value) else "CGPA: Not Mentioned")
227
- else:
228
- st.write("CGPA: Not Mentioned")
229
-
230
- mobile_accuracy = accuracy_calculation(
231
- true_positives_mobile, false_positives_mobile, false_negatives_mobile)
232
- email_accuracy = accuracy_calculation(
233
- true_positives_email, false_positives_email, false_negatives_email)
234
-
235
  st.subheader("\nHeatmap:")
236
- # st.write(f"Mobile Number Accuracy: {mobile_accuracy:.2%}")
237
- # st.write(f"Email Accuracy: {email_accuracy:.2%}")
238
-
239
  # Get skills keywords from user input
240
- skills_keywords_input = st.text_input(
241
- "Enter skills keywords separated by commas (e.g., python, java, machine learning):")
242
- skills_keywords = [skill.strip()
243
- for skill in skills_keywords_input.split(',') if skill.strip()]
244
 
245
  if skills_keywords:
246
  # Calculate the similarity score between each skill keyword and the resume text
@@ -248,20 +216,16 @@ if resumes_files:
248
  for resume_text in resumes_texts:
249
  resume_text_similarity_scores = []
250
  for skill in skills_keywords:
251
- similarity_score = calculate_similarity(
252
- model_resumes, resume_text, skill)
253
  resume_text_similarity_scores.append(similarity_score)
254
  skills_similarity_scores.append(resume_text_similarity_scores)
255
 
256
  # Create a DataFrame with the similarity scores and set the index to the names of the PDFs
257
- skills_similarity_df = pd.DataFrame(
258
- skills_similarity_scores, columns=skills_keywords, index=[resume_file.name for resume_file in resumes_files])
259
 
260
  # Plot the heatmap
261
  fig, ax = plt.subplots(figsize=(12, 8))
262
-
263
- sns.heatmap(skills_similarity_df,
264
- cmap='YlGnBu', annot=True, fmt=".2f", ax=ax)
265
  ax.set_title('Heatmap for Skills Similarity')
266
  ax.set_xlabel('Skills')
267
  ax.set_ylabel('Resumes')
@@ -274,7 +238,6 @@ if resumes_files:
274
  else:
275
  st.write("Please enter at least one skill keyword.")
276
 
277
-
278
  else:
279
  st.warning("Please upload the Job Description PDF to proceed.")
280
  else:
 
1
+
2
+ from numpy.linalg import matrix_upper_triangular as triu
3
+
4
+ import subprocess
5
+ subprocess.run(["pip", "install", "https://huggingface.co/Priyanka-Balivada/en_Resume_Matching_Keywords/resolve/main/en_Resume_Matching_Keywords-any-py3-none-any.whl"])
6
+ import en_Resume_Matching_Keywords # Now import your package
7
+ # Import necessary libraries
8
  import streamlit as st
9
  import nltk
10
  from gensim.models.doc2vec import Doc2Vec, TaggedDocument
11
  from nltk.tokenize import word_tokenize
12
  import PyPDF2
13
  import pandas as pd
 
14
  import re
15
  import matplotlib.pyplot as plt
16
  import seaborn as sns
17
+ import spacy
 
 
18
 
19
+ # Download necessary NLTK data
20
  nltk.download('punkt')
21
 
22
+ # Define regular expressions for pattern matching
23
+ float_regex = re.compile(r'^\d{1,2}(\.\d{1,2})?$')
24
+ email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
25
+ float_digit_regex = re.compile(r'^\d{10}$')
26
+ email_with_phone_regex = re.compile(r'(\d{10}).|.(\d{10})')
27
+
28
  # Function to extract text from a PDF file
29
  def extract_text_from_pdf(pdf_file):
30
  pdf_reader = PyPDF2.PdfReader(pdf_file)
 
33
  text += pdf_reader.pages[page_num].extract_text()
34
  return text
35
 
36
+ # Function to tokenize text using the NLP model
37
+ def tokenize_text(text, nlp_model):
38
+ doc = nlp_model(text, disable=["tagger", "parser"])
39
+ tokens = [(token.text.lower(), token.label_) for token in doc.ents]
40
+ return tokens
41
+
42
+ # Function to extract CGPA from a resume
43
+ def extract_cgpa(resume_text):
44
+ cgpa_pattern = r'\b(?:CGPA|GPA|C\.G\.PA|Cumulative GPA)\s*:?[\s-]([0-9]+(?:\.[0-9]+)?)\b|\b([0-9]+(?:\.[0-9]+)?)\s(?:CGPA|GPA)\b'
45
+ match = re.search(cgpa_pattern, resume_text, re.IGNORECASE)
46
+ if match:
47
+ cgpa = match.group(1) if match.group(1) else match.group(2)
48
+ return float(cgpa)
49
+ else:
50
+ return None
51
+
52
+ # Function to extract skills from a resume
53
  def extract_skills(text, skills_keywords):
54
+ skills = [skill.lower() for skill in skills_keywords if re.search(r'\b' + re.escape(skill.lower()) + r'\b', text.lower())]
 
55
  return skills
56
 
57
+ # Function to preprocess text
58
  def preprocess_text(text):
59
  return word_tokenize(text.lower())
60
 
61
+ # Function to train a Doc2Vec model
 
 
 
 
 
 
 
 
 
 
62
  def train_doc2vec_model(documents):
63
  model = Doc2Vec(vector_size=20, min_count=2, epochs=50)
64
  model.build_vocab(documents)
65
+ model.train(documents, total_examples=model.corpus_count, epochs=model.epochs)
 
66
  return model
67
 
68
+ # Function to calculate similarity between two texts
69
  def calculate_similarity(model, text1, text2):
70
  vector1 = model.infer_vector(preprocess_text(text1))
71
  vector2 = model.infer_vector(preprocess_text(text2))
72
  return model.dv.cosine_similarities(vector1, [vector2])[0]
73
 
74
+ # Function to calculate accuracy
75
  def accuracy_calculation(true_positives, false_positives, false_negatives):
76
  total = true_positives + false_positives + false_negatives
77
  accuracy = true_positives / total if total != 0 else 0
78
  return accuracy
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  # Streamlit Frontend
81
  st.markdown("# Resume Matching Tool 📃📃")
82
  st.markdown("An application to match resumes with a job description.")
83
 
84
  # Sidebar - File Upload for Resumes
85
  st.sidebar.markdown("## Upload Resumes PDF")
86
+ resumes_files = st.sidebar.file_uploader("Upload Resumes PDF", type=["pdf"], accept_multiple_files=True)
 
87
 
88
  if resumes_files:
89
  # Sidebar - File Upload for Job Descriptions
90
  st.sidebar.markdown("## Upload Job Description PDF")
91
+ job_descriptions_file = st.sidebar.file_uploader("Upload Job Description PDF", type=["pdf"])
 
92
 
93
  if job_descriptions_file:
94
+ # Load the pre-trained NLP model
95
+ nlp_model_path = "en_Resume_Matching_Keywords"
96
+ nlp = spacy.load(nlp_model_path)
 
97
 
98
  # Backend Processing
99
  job_description_text = extract_text_from_pdf(job_descriptions_file)
100
+ resumes_texts = [extract_text_from_pdf(resume_file) for resume_file in resumes_files]
101
+ job_description_text = extract_text_from_pdf(job_descriptions_file)
102
+ job_description_tokens = tokenize_text(job_description_text, nlp)
103
+
104
+ # Initialize counters
105
+ overall_skill_matches = 0
106
+ overall_qualification_matches = 0
107
+
108
+ # Create a list to store individual results
109
+ results_list = []
110
+ job_skills = set()
111
+ job_qualifications = set()
112
+
113
+ for job_token, job_label in job_description_tokens:
114
+ if job_label == 'QUALIFICATION':
115
+ job_qualifications.add(job_token.replace('\n', ' '))
116
+ elif job_label == 'SKILLS':
117
+ job_skills.add(job_token.replace('\n', ' '))
118
+
119
+ job_skills_number = len(job_skills)
120
+ job_qualifications_number = len(job_qualifications)
121
+
122
+ # Lists to store counts of matched skills for all resumes
123
+ skills_counts_all_resumes = []
124
+
125
+ # Iterate over all uploaded resumes
126
+ for uploaded_resume in resumes_files:
127
+ resume_text = extract_text_from_pdf(uploaded_resume)
128
+ resume_tokens = tokenize_text(resume_text, nlp)
129
+
130
+ # Initialize counters for individual resume
131
+ skillMatch = 0
132
+ qualificationMatch = 0
133
+ cgpa = ""
134
+
135
+ # Lists to store matched skills and qualifications for each resume
136
+ matched_skills = set()
137
+ matched_qualifications = set()
138
+ email = set()
139
+ phone = set()
140
+ name = set()
141
+
142
+ # Compare the tokens in the resume with the job description
143
+ for resume_token, resume_label in resume_tokens:
144
+ for job_token, job_label in job_description_tokens:
145
+ if resume_token.lower().replace('\n', ' ') == job_token.lower().replace('\n', ' '):
146
+ if resume_label == 'SKILLS':
147
+ matched_skills.add(resume_token.replace('\n', ' '))
148
+ elif resume_label == 'QUALIFICATION':
149
+ matched_qualifications.add(resume_token.replace('\n', ' '))
150
+ elif resume_label == 'PHONE' and bool(float_digit_regex.match(resume_token)):
151
+ phone.add(resume_token)
152
+ elif resume_label == 'QUALIFICATION':
153
+ matched_qualifications.add(resume_token.replace('\n', ' '))
154
+
155
+ skillMatch = len(matched_skills)
156
+ qualificationMatch = len(matched_qualifications)
157
+
158
+ # Convert the list of emails to a set
159
+ email_set = set(re.findall(email_pattern, resume_text.replace('\n', ' ')))
160
+ email.update(email_set)
161
+
162
+ numberphone=""
163
+ for email_str in email:
164
+ numberphone = email_with_phone_regex.search(email_str)
165
+ if numberphone:
166
+ email.remove(email_str)
167
+ val=numberphone.group(1) or numberphone.group(2)
168
+ phone.add(val)
169
+ email.add(email_str.strip(val))
170
+
171
+ # Increment overall counters based on matches
172
+ overall_skill_matches += skillMatch
173
+ overall_qualification_matches += qualificationMatch
174
+
175
+ # Add count of matched skills for this resume to the list
176
+ skills_counts_all_resumes.append([resume_text.count(skill.lower()) for skill in job_skills])
177
+
178
+ # Create a dictionary for the current resume and append to the results list
179
+ result_dict = {
180
+ "Resume": uploaded_resume.name,
181
+ "Similarity Score": (skillMatch/job_skills_number)*100,
182
+ "Skill Matches": skillMatch,
183
+ "Matched Skills": matched_skills,
184
+ "CGPA": extract_cgpa(resume_text),
185
+ "Email": email,
186
+ "Phone": phone,
187
+ "Qualification Matches": qualificationMatch,
188
+ "Matched Qualifications": matched_qualifications
189
+ }
190
+
191
+ results_list.append(result_dict)
192
+
193
+ # Display overall matches
194
+ st.subheader("Overall Matches")
195
+ st.write(f"Total Skill Matches: {overall_skill_matches}")
196
+ st.write(f"Total Qualification Matches: {overall_qualification_matches}")
197
+ st.write(f"Job Qualifications: {job_qualifications}")
198
+ st.write(f"Job Skills: {job_skills}")
199
+
200
+ # Display individual results in a table
201
+ results_df = pd.DataFrame(results_list)
202
+ st.subheader("Individual Results")
203
+ st.dataframe(results_df)
204
+ tagged_resumes = [TaggedDocument(words=preprocess_text(text), tags=[str(i)]) for i, text in enumerate(resumes_texts)]
205
  model_resumes = train_doc2vec_model(tagged_resumes)
206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  st.subheader("\nHeatmap:")
208
+
 
 
209
  # Get skills keywords from user input
210
+ skills_keywords_input = st.text_input("Enter skills keywords separated by commas (e.g., python, java, machine learning):")
211
+ skills_keywords = [skill.strip() for skill in skills_keywords_input.split(',') if skill.strip()]
 
 
212
 
213
  if skills_keywords:
214
  # Calculate the similarity score between each skill keyword and the resume text
 
216
  for resume_text in resumes_texts:
217
  resume_text_similarity_scores = []
218
  for skill in skills_keywords:
219
+ similarity_score = calculate_similarity(model_resumes, resume_text, skill)
 
220
  resume_text_similarity_scores.append(similarity_score)
221
  skills_similarity_scores.append(resume_text_similarity_scores)
222
 
223
  # Create a DataFrame with the similarity scores and set the index to the names of the PDFs
224
+ skills_similarity_df = pd.DataFrame(skills_similarity_scores, columns=skills_keywords, index=[resume_file.name for resume_file in resumes_files])
 
225
 
226
  # Plot the heatmap
227
  fig, ax = plt.subplots(figsize=(12, 8))
228
+ sns.heatmap(skills_similarity_df, cmap='YlGnBu', annot=True, fmt=".2f", ax=ax)
 
 
229
  ax.set_title('Heatmap for Skills Similarity')
230
  ax.set_xlabel('Skills')
231
  ax.set_ylabel('Resumes')
 
238
  else:
239
  st.write("Please enter at least one skill keyword.")
240
 
 
241
  else:
242
  st.warning("Please upload the Job Description PDF to proceed.")
243
  else: