Spaces:
Sleeping
Sleeping
datasciencedojo
commited on
Commit
•
cc6dcbe
1
Parent(s):
be9d639
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from utils.utilss import *
|
3 |
+
from prompts import cover_letter_prompts
|
4 |
+
import pyperclip
|
5 |
+
from dotenv import load_dotenv
|
6 |
+
import pandas as pd
|
7 |
+
import hashlib
|
8 |
+
|
9 |
+
load_dotenv()
|
10 |
+
def copytoclipboard():
|
11 |
+
if st.button("Copy to Clipboard"):
|
12 |
+
pyperclip.copy(st.session_state.analysis)
|
13 |
+
st.success("Cover letter copied to clipboard!")
|
14 |
+
|
15 |
+
def hash_inputs(resume_text, job_title, must_have, job_pref):
|
16 |
+
# Generate a hash based on the inputs
|
17 |
+
input_str = resume_text + job_title + must_have + job_pref
|
18 |
+
return hashlib.md5(input_str.encode()).hexdigest()
|
19 |
+
|
20 |
+
def hash_sel_inputs(resume_text, job_title, must_have, job_pref):
|
21 |
+
# Generate a hash based on the inputs
|
22 |
+
input_str = str(resume_text) + job_title + must_have + job_pref
|
23 |
+
return hashlib.md5(input_str.encode()).hexdigest()
|
24 |
+
def table_resp(lists):
|
25 |
+
d={}
|
26 |
+
sc=[[],[],[],[],[],[],[]]
|
27 |
+
for i in lists:
|
28 |
+
sc[0].append(i['candidate_name'])
|
29 |
+
sc[1].append(str(i['overall_match_score'])+'%')
|
30 |
+
sc[2].append(str(i['skills_keywords_score'])+'%')
|
31 |
+
sc[3].append(str(i['experience_score'])+'%')
|
32 |
+
sc[4].append(str(i['education_certifications_score'])+'%')
|
33 |
+
sc[5].append(str(i['preferred_qualifications_score'])+'%')
|
34 |
+
sc[6].append(i['score_interpretation'])
|
35 |
+
cols=['Candidate Name','Match Score','Skills & Keywords (40%)','Experience & Responsibilities (30%)','Education & Certifications (20%)','Preferred Qualifications (10%)','Score Interpretation']
|
36 |
+
for i in range(len(sc)):
|
37 |
+
d[cols[i]]=sc[i]
|
38 |
+
df = pd.DataFrame(d)
|
39 |
+
return df
|
40 |
+
|
41 |
+
def table_resp_exp(lists):
|
42 |
+
d={}
|
43 |
+
sc=[[],[],[],[],[],[],[]]
|
44 |
+
for i in lists:
|
45 |
+
sc[0].append(i['candidate_name'])
|
46 |
+
sc[1].append(i['overall_match_score'])
|
47 |
+
sc[2].append('('+str(i['skills_keywords_score'])+' out of 40) '+i['skills_keywords_explanation'])
|
48 |
+
sc[3].append('('+str(i['experience_score'])+' out of 30) '+i['experience_explanation'])
|
49 |
+
sc[4].append('('+str(i['education_certifications_score'])+' out of 20) '+i['education_certifications_explanation'])
|
50 |
+
sc[5].append('('+str(i['preferred_qualifications_score'])+' out of 10) '+i['preferred_qualifications_explanation'])
|
51 |
+
sc[6].append(i['score_interpretation'])
|
52 |
+
cols=['Candidate Name','Match Score','Skills & Keywords (40%)','Experience & Responsibilities (30%)','Education & Certifications (20%)','Preferred Qualifications (10%)','Score Interpretation']
|
53 |
+
for i in range(len(sc)):
|
54 |
+
d[cols[i]]=sc[i]
|
55 |
+
df = pd.DataFrame(d)
|
56 |
+
return df
|
57 |
+
|
58 |
+
def expand(ext_res):
|
59 |
+
formatted_resp=f"""
|
60 |
+
**Candidate:** {ext_res['candidate_name']}
|
61 |
+
|
62 |
+
**Match Score**: {ext_res['overall_match_score']}%
|
63 |
+
|
64 |
+
**Skills & Keywords** ({ext_res['skills_keywords_score']}% out of 40%):
|
65 |
+
{ext_res['skills_keywords_explanation']}
|
66 |
+
|
67 |
+
**Experience & Responsibilities** ({ext_res['experience_score']}% out of 30%):
|
68 |
+
{ext_res['experience_explanation']}
|
69 |
+
|
70 |
+
**Education & Certifications** ({ext_res['education_certifications_score']}% out of 20%):
|
71 |
+
{ext_res['education_certifications_explanation']}
|
72 |
+
|
73 |
+
**Preferred Qualifications** ({ext_res['preferred_qualifications_score']}% out of 10%):
|
74 |
+
{ext_res['preferred_qualifications_explanation']}
|
75 |
+
|
76 |
+
**Score Interpretation**: {ext_res['score_interpretation']}
|
77 |
+
"""
|
78 |
+
return formatted_resp
|
79 |
+
|
80 |
+
def concise_resp(ext_res):
|
81 |
+
formatted_resp=f"""
|
82 |
+
**Candidate:** {ext_res['candidate_name']}
|
83 |
+
|
84 |
+
**Match Score**: {ext_res['overall_match_score']}%
|
85 |
+
|
86 |
+
**Skills & Keywords**: {ext_res['skills_keywords_score']}% out of 40%
|
87 |
+
|
88 |
+
**Experience**: {ext_res['experience_score']}% out of 30%
|
89 |
+
|
90 |
+
**Education & Certifications**: {ext_res['education_certifications_score']}% out of 20%
|
91 |
+
|
92 |
+
**Preferred Qualifications**: {ext_res['preferred_qualifications_score']}% out of 10%
|
93 |
+
|
94 |
+
**Score Interpretation**: {ext_res['score_interpretation']}
|
95 |
+
"""
|
96 |
+
|
97 |
+
return formatted_resp
|
98 |
+
def filecheck(resume_file):
|
99 |
+
if len(resume_file)==1 and resume_file is not None:
|
100 |
+
resume_text = parse_resume(resume_file[0])
|
101 |
+
return resume_text
|
102 |
+
def filecheck_error(resume_file):
|
103 |
+
if len(resume_file)==0:
|
104 |
+
st.warning("Please upload a Resume.")
|
105 |
+
else:
|
106 |
+
st.warning("Please upload only one Resume.")
|
107 |
+
|
108 |
+
def filescheck(resume_file):
|
109 |
+
if len(resume_file)>1 and resume_file is not None:
|
110 |
+
resume_text = parse_resumes(resume_file)
|
111 |
+
return resume_text
|
112 |
+
|
113 |
+
|
114 |
+
def filescheck_error(resume_file):
|
115 |
+
if len(resume_file)==0:
|
116 |
+
st.warning("Please upload Resumes.")
|
117 |
+
else:
|
118 |
+
st.warning("Please upload more than 1 Resume for selection.")
|
119 |
+
|
120 |
+
def main():
|
121 |
+
if 'analysis' not in st.session_state:
|
122 |
+
st.session_state.analysis = None
|
123 |
+
if 'jobadv' not in st.session_state:
|
124 |
+
st.session_state.jobadv = None
|
125 |
+
if 'analysis_mc' not in st.session_state:
|
126 |
+
st.session_state.analysis_mc = None
|
127 |
+
if 'analysis' not in st.session_state:
|
128 |
+
st.session_state.analysis = None
|
129 |
+
if 'input_hash' not in st.session_state:
|
130 |
+
st.session_state.input_hash = None
|
131 |
+
if 'analysis_mc_s' not in st.session_state:
|
132 |
+
st.session_state.analysis_mc_s = None
|
133 |
+
if 'analysis_s' not in st.session_state:
|
134 |
+
st.session_state.analysis_s = None
|
135 |
+
if 'input_hash_sel' not in st.session_state:
|
136 |
+
st.session_state.input_hash_sel = None
|
137 |
+
if 'analysis_mc_s_exp' not in st.session_state:
|
138 |
+
st.session_state.analysis_mc_s_exp = None
|
139 |
+
|
140 |
+
st.title("Job Hiring Assistant")
|
141 |
+
|
142 |
+
|
143 |
+
# Select Cover Letter Style
|
144 |
+
st.sidebar.header("Select Task")
|
145 |
+
selection = st.sidebar.radio("Select option", ("Generate Job Adverstisment", "Resume Analysis","Resume Selection"))
|
146 |
+
|
147 |
+
# Generate Cover Letter
|
148 |
+
if selection == "Generate Job Adverstisment":
|
149 |
+
|
150 |
+
st.header("Job Details")
|
151 |
+
st.subheader('Job Title')
|
152 |
+
job_title_text = st.text_input("Enter job title here",max_chars=30)
|
153 |
+
st.subheader('Job Requirement')
|
154 |
+
job_requirement = st.text_area("Enter job requirement here")
|
155 |
+
if st.button("Generate Job Adverstisment"):
|
156 |
+
if job_requirement is not None:
|
157 |
+
prompt_template = cover_letter_prompts.prompt_template_classic
|
158 |
+
jobadv = generate_adv(job_requirement,job_title_text, prompt_template)
|
159 |
+
st.subheader("Job Adverstisment:")
|
160 |
+
st.markdown(jobadv)
|
161 |
+
st.session_state.jobadv = jobadv
|
162 |
+
#copytoclipboard()
|
163 |
+
else:
|
164 |
+
st.warning("Please provide a job requirement.")
|
165 |
+
else:
|
166 |
+
st.sidebar.header("Resume Analysis Criteria")
|
167 |
+
scoretext='''**80-100**: Good match
|
168 |
+
|
169 |
+
**50-79**: Medium match
|
170 |
+
|
171 |
+
**0-49**: Poor match '''
|
172 |
+
criteriatext='''**40%**: Skills and Keywords
|
173 |
+
|
174 |
+
**30%**: Experience & Responsibilities
|
175 |
+
|
176 |
+
**20%**: Education & Certifications
|
177 |
+
|
178 |
+
**10%**: Preferred Qualifications '''
|
179 |
+
#st.session_state.dropdown_open= False # Close the dropdown on click
|
180 |
+
|
181 |
+
#Match Score Range
|
182 |
+
# st.sidebar.button("Match Score Range",icon=":material/arrow_drop_down:")
|
183 |
+
#st.session_state.dropdown_open= False
|
184 |
+
#st.sidebar.button("Match Score Range",icon=":material/arrow_drop_up:")
|
185 |
+
st.sidebar.subheader("Match Score Range")
|
186 |
+
scorecontainer=st.sidebar.container(height=130)
|
187 |
+
scorecontainer.markdown(scoretext)
|
188 |
+
|
189 |
+
#Criteria weight
|
190 |
+
st.sidebar.subheader("Criteria weight")
|
191 |
+
criteriacontainer=st.sidebar.container(height=130)
|
192 |
+
criteriacontainer.markdown(criteriatext)
|
193 |
+
|
194 |
+
st.subheader("Upload Resume")
|
195 |
+
resume_file = st.file_uploader("Choose a file or drag and drop", type=["pdf"],accept_multiple_files=True)
|
196 |
+
|
197 |
+
#st.header("Job Details")
|
198 |
+
st.subheader('Job Title')
|
199 |
+
job_title_text = st.text_input("Enter job title here", "",max_chars=30)
|
200 |
+
st.subheader('Job Requirements')
|
201 |
+
must_have = st.text_area("Enter job must-have requirements here", "")
|
202 |
+
st.subheader('Preferred Qualification')
|
203 |
+
job_pref = st.text_area("Enter any preferred skills or qualifications here", "")
|
204 |
+
resume_text = None
|
205 |
+
|
206 |
+
if selection == "Resume Analysis":
|
207 |
+
btn1=st.button("Generate Resume Analysis")
|
208 |
+
if btn1:
|
209 |
+
#Only show Scores
|
210 |
+
#if st.button("Match Score"):
|
211 |
+
resume_text=filecheck(resume_file)
|
212 |
+
if resume_text is not None:
|
213 |
+
if job_pref is not None and must_have is not None :
|
214 |
+
current_input_hash = hash_inputs(resume_text, job_title_text, must_have, job_pref)
|
215 |
+
|
216 |
+
# Check if the inputs have changed
|
217 |
+
if st.session_state.input_hash != current_input_hash:
|
218 |
+
# Inputs have changed, generate new analysis
|
219 |
+
st.session_state.input_hash = current_input_hash
|
220 |
+
prompt_template = cover_letter_prompts.prompt_template_modern
|
221 |
+
response = generate_analysis(resume_text, job_pref, job_title_text, must_have, prompt_template)
|
222 |
+
|
223 |
+
# Cache the result
|
224 |
+
st.session_state.analysis = expand(response)
|
225 |
+
st.session_state.analysis_mc = concise_resp(response)
|
226 |
+
|
227 |
+
# Display the cached response based on the button clicked
|
228 |
+
# Match Score button clicked
|
229 |
+
st.subheader("Resume Analysis (Match Score)")
|
230 |
+
st.markdown(st.session_state.analysis_mc)
|
231 |
+
with st.expander("Detailed Analysis"):
|
232 |
+
st.markdown(st.session_state.analysis)
|
233 |
+
|
234 |
+
else:
|
235 |
+
st.warning("Please provide all job details.")
|
236 |
+
else:
|
237 |
+
filecheck_error(resume_file)
|
238 |
+
|
239 |
+
|
240 |
+
|
241 |
+
else:
|
242 |
+
btn1=st.button("Generate Match Score")
|
243 |
+
btn2=st.button("Generate Analysis")
|
244 |
+
if btn1 or btn2:
|
245 |
+
#Only show Scores
|
246 |
+
#if st.button("Match Score"):
|
247 |
+
resume_text=filescheck(resume_file)
|
248 |
+
if resume_text is not None:
|
249 |
+
if job_pref is not None and must_have is not None :
|
250 |
+
current_input_hash = hash_sel_inputs(resume_text, job_title_text, must_have, job_pref)
|
251 |
+
|
252 |
+
# Check if the inputs have changed
|
253 |
+
if st.session_state.input_hash_sel != current_input_hash:
|
254 |
+
# Inputs have changed, generate new analysis
|
255 |
+
st.session_state.input_hash_sel = current_input_hash
|
256 |
+
prompt_template = cover_letter_prompts.prompt_template_resumes_
|
257 |
+
response = generate_sel_analysis(resume_text, job_pref, job_title_text, must_have, prompt_template)
|
258 |
+
print('response:',response)
|
259 |
+
response_anal=max(response, key=lambda x: x['overall_match_score'])
|
260 |
+
# Cache the result
|
261 |
+
st.session_state.analysis_s = expand(response_anal)
|
262 |
+
st.session_state.analysis_mc_s = table_resp(response)
|
263 |
+
st.session_state.analysis_mc_s_exp = table_resp_exp(response)
|
264 |
+
|
265 |
+
# Display the cached response based on the button clicked
|
266 |
+
if btn1:
|
267 |
+
# Match Score button clicked
|
268 |
+
st.subheader("Match Scores")
|
269 |
+
st.dataframe(st.session_state.analysis_mc_s,hide_index=True)
|
270 |
+
elif btn2:
|
271 |
+
# Generate Analysis button clicked
|
272 |
+
print("expands")
|
273 |
+
st.subheader("Resume Analysis (Top Scored)")
|
274 |
+
# Candidate selection
|
275 |
+
st.markdown(st.session_state.analysis_s)
|
276 |
+
with st.expander("Detailed Analysis - All Candidates"):
|
277 |
+
st.dataframe(st.session_state.analysis_mc_s_exp,hide_index=True)
|
278 |
+
|
279 |
+
else:
|
280 |
+
st.warning("Please provide all job details.")
|
281 |
+
else:
|
282 |
+
filescheck_error(resume_file)
|
283 |
+
|
284 |
+
|
285 |
+
|
286 |
+
if __name__ == "__main__":
|
287 |
+
main()
|