Upload 2 files
Browse files- APPP.py +135 -0
- requirements.txt +104 -0
APPP.py
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import subprocess
|
3 |
+
from langchain.document_loaders import PyPDFLoader, Docx2txtLoader
|
4 |
+
import google.generativeai as gen_ai
|
5 |
+
import tempfile
|
6 |
+
import os
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
import pdfplumber
|
9 |
+
import time
|
10 |
+
|
11 |
+
# Load environment variables
|
12 |
+
|
13 |
+
|
14 |
+
# Function to extract text from PDF
|
15 |
+
@st.cache_data
|
16 |
+
def extract_text_from_pdf(uploaded_file):
|
17 |
+
st.write(f"Extracting text from {uploaded_file.name}...")
|
18 |
+
with tempfile.NamedTemporaryFile(delete=False, prefix=uploaded_file.name, dir=os.path.dirname(uploaded_file.name)) as temp_file:
|
19 |
+
temp_file.write(uploaded_file.read())
|
20 |
+
|
21 |
+
pdf_file_path = temp_file.name
|
22 |
+
|
23 |
+
text = []
|
24 |
+
loader = PyPDFLoader(pdf_file_path)
|
25 |
+
documents = loader.load()
|
26 |
+
text.extend(documents)
|
27 |
+
|
28 |
+
os.remove(pdf_file_path)
|
29 |
+
|
30 |
+
return text
|
31 |
+
|
32 |
+
# Function to extract information using Generative AI
|
33 |
+
def extract_information(data):
|
34 |
+
gen_ai.configure(api_key=os.getenv('GEMINI'))
|
35 |
+
safety_settings = [
|
36 |
+
{
|
37 |
+
"category": "HARM_CATEGORY_DANGEROUS",
|
38 |
+
"threshold": "BLOCK_NONE",
|
39 |
+
},
|
40 |
+
{
|
41 |
+
"category": "HARM_CATEGORY_HARASSMENT",
|
42 |
+
"threshold": "BLOCK_NONE",
|
43 |
+
},
|
44 |
+
{
|
45 |
+
"category": "HARM_CATEGORY_HATE_SPEECH",
|
46 |
+
"threshold": "BLOCK_NONE",
|
47 |
+
},
|
48 |
+
{
|
49 |
+
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
50 |
+
"threshold": "BLOCK_NONE",
|
51 |
+
},
|
52 |
+
{
|
53 |
+
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
54 |
+
"threshold": "BLOCK_NONE",
|
55 |
+
},
|
56 |
+
]
|
57 |
+
|
58 |
+
|
59 |
+
model = gen_ai.GenerativeModel('gemini-pro',safety_settings=safety_settings)
|
60 |
+
response = model.generate_content(data)
|
61 |
+
return response.text.strip()
|
62 |
+
|
63 |
+
|
64 |
+
def read_pdf(pdf_name, file):
|
65 |
+
with pdfplumber.open(file) as pdf:
|
66 |
+
for i, page in enumerate(pdf.pages):
|
67 |
+
|
68 |
+
image = page.to_image(resolution=600) # Adjust resolution as needed
|
69 |
+
st.image(image._repr_png_(), caption=f"Page {i+1}", use_column_width=True)
|
70 |
+
|
71 |
+
|
72 |
+
def main():
|
73 |
+
# st.title("CareerSync: Harmonizing Resumes with Job Descriptions")
|
74 |
+
st.markdown(
|
75 |
+
"<h1 style='color: orange;'>CareerSync: Harmonizing Resumes with Job Descriptions</h1>",
|
76 |
+
unsafe_allow_html=True
|
77 |
+
)
|
78 |
+
st.header("Welcome to CareerSync!")
|
79 |
+
st.write("This app helps you compare a resume with a job description to find the best fit.")
|
80 |
+
|
81 |
+
# Input job description
|
82 |
+
st.subheader("Step 1: Enter Job Description")
|
83 |
+
job_description = st.text_area("Paste or type the job description here", height=200)
|
84 |
+
|
85 |
+
# Upload resume
|
86 |
+
st.subheader("Step 2: Upload Resume")
|
87 |
+
resume_file = st.file_uploader("Upload your resume (PDF only)", type=['pdf'])
|
88 |
+
|
89 |
+
# Button to compare
|
90 |
+
if st.button("Compare Resumes"):
|
91 |
+
if job_description and resume_file is not None:
|
92 |
+
resume_content = extract_text_from_pdf(resume_file)
|
93 |
+
enhanced_job_description = f"Enhanced Job Description:\n{job_description}"
|
94 |
+
enhanced_job_description = extract_information(enhanced_job_description)
|
95 |
+
|
96 |
+
|
97 |
+
# Displaying extracted content
|
98 |
+
with st.expander("Candidate Resume:"):
|
99 |
+
# st.write(resume_content)
|
100 |
+
pdf_name = resume_file.name
|
101 |
+
read_pdf(pdf_name, resume_file)
|
102 |
+
|
103 |
+
with st.expander("Enhanced Job Description:"):
|
104 |
+
st.write(enhanced_job_description)
|
105 |
+
|
106 |
+
# Generating evaluation prompt
|
107 |
+
prompt_template = f"Is the candidate a good fit? Return a Python list. The first index should be either 'pass' or 'fail', and the second index should have a score from 1 to 10.\n\nHere is the content of the resume:\n{resume_content}\n\nAnd here is the enhanced description of the job:\n{enhanced_job_description}"
|
108 |
+
# prompt_template = f"Assess the candidate's suitability for the position. Provide your evaluation in the form of a Python list with two indices: the first index indicates either 'pass' or 'fail', and the second index denotes a score ranging from 1 to 10. Pay close attention to the alignment between the job's required experience outlined in the job description and the candidate's experience as reflected in the resume, as well as the specific skill set essential for the role.\n\nBelow is the content of the candidate's resume:\n{resume_content}\n\nFurthermore, consider the following enhanced description of the job role:\n{enhanced_job_description}"
|
109 |
+
|
110 |
+
result = extract_information(prompt_template)
|
111 |
+
st.subheader("Evaluation Result:")
|
112 |
+
st.write(result)
|
113 |
+
lst = eval(result)
|
114 |
+
|
115 |
+
# Displaying evaluation results
|
116 |
+
st.subheader("Evaluation Details:")
|
117 |
+
st.write(f"Evaluation: {lst[0]}")
|
118 |
+
st.write(f"Score: {lst[1]}")
|
119 |
+
|
120 |
+
# Running HR app if score is high
|
121 |
+
point = lst[1]
|
122 |
+
if int(point) >= 7:
|
123 |
+
st.success("Congratulations! The candidate is a good fit.")
|
124 |
+
time.sleep(3)
|
125 |
+
st.switch_page("pages/hr.py")
|
126 |
+
else:
|
127 |
+
st.warning("The candidate does not pass the critera. ")
|
128 |
+
|
129 |
+
|
130 |
+
|
131 |
+
else:
|
132 |
+
st.warning("Please enter the job description and upload the resume.")
|
133 |
+
|
134 |
+
if __name__ == "__main__":
|
135 |
+
main()
|
requirements.txt
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
aiohttp==3.9.3
|
2 |
+
aiosignal==1.3.1
|
3 |
+
altair==5.2.0
|
4 |
+
annotated-types==0.6.0
|
5 |
+
anyio==4.3.0
|
6 |
+
async-timeout==4.0.3
|
7 |
+
attrs==23.2.0
|
8 |
+
blinker==1.7.0
|
9 |
+
cachetools==5.3.3
|
10 |
+
certifi==2024.2.2
|
11 |
+
cffi==1.16.0
|
12 |
+
charset-normalizer==3.3.2
|
13 |
+
click==8.1.7
|
14 |
+
colorama==0.4.6
|
15 |
+
cryptography==42.0.5
|
16 |
+
dataclasses-json==0.6.4
|
17 |
+
distro==1.9.0
|
18 |
+
exceptiongroup==1.2.0
|
19 |
+
frozenlist==1.4.1
|
20 |
+
gitdb==4.0.11
|
21 |
+
GitPython==3.1.42
|
22 |
+
google-ai-generativelanguage==0.4.0
|
23 |
+
google-api-core==2.17.1
|
24 |
+
google-auth==2.28.2
|
25 |
+
google-generativeai==0.4.1
|
26 |
+
googleapis-common-protos==1.63.0
|
27 |
+
greenlet==3.0.3
|
28 |
+
grpcio==1.62.1
|
29 |
+
grpcio-status==1.62.1
|
30 |
+
h11==0.14.0
|
31 |
+
httpcore==1.0.4
|
32 |
+
httpx==0.27.0
|
33 |
+
idna==3.6
|
34 |
+
Jinja2==3.1.3
|
35 |
+
joblib==1.3.2
|
36 |
+
jsonpatch==1.33
|
37 |
+
jsonpointer==2.4
|
38 |
+
jsonschema==4.21.1
|
39 |
+
jsonschema-specifications==2023.12.1
|
40 |
+
langchain==0.1.12
|
41 |
+
langchain-community==0.0.28
|
42 |
+
langchain-core==0.1.32
|
43 |
+
langchain-text-splitters==0.0.1
|
44 |
+
langsmith==0.1.26
|
45 |
+
markdown-it-py==3.0.0
|
46 |
+
MarkupSafe==2.1.5
|
47 |
+
marshmallow==3.21.1
|
48 |
+
mdurl==0.1.2
|
49 |
+
multidict==6.0.5
|
50 |
+
mypy-extensions==1.0.0
|
51 |
+
numpy==1.26.4
|
52 |
+
openai==1.14.0
|
53 |
+
orjson==3.9.15
|
54 |
+
packaging==23.2
|
55 |
+
pandas==2.2.1
|
56 |
+
pdf2image==1.17.0
|
57 |
+
pdfminer.six==20231228
|
58 |
+
pdfplumber==0.11.0
|
59 |
+
pillow==10.2.0
|
60 |
+
proto-plus==1.23.0
|
61 |
+
protobuf==4.25.3
|
62 |
+
pyarrow==15.0.1
|
63 |
+
pyasn1==0.5.1
|
64 |
+
pyasn1-modules==0.3.0
|
65 |
+
pycparser==2.21
|
66 |
+
pydantic==2.6.4
|
67 |
+
pydantic_core==2.16.3
|
68 |
+
pydeck==0.8.1b0
|
69 |
+
Pygments==2.17.2
|
70 |
+
pypdf==4.1.0
|
71 |
+
PyPDF2==3.0.1
|
72 |
+
pypdfium2==4.28.0
|
73 |
+
python-dateutil==2.9.0.post0
|
74 |
+
python-dotenv==1.0.1
|
75 |
+
pytz==2024.1
|
76 |
+
PyYAML==6.0.1
|
77 |
+
referencing==0.33.0
|
78 |
+
requests==2.31.0
|
79 |
+
rich==13.7.1
|
80 |
+
rpds-py==0.18.0
|
81 |
+
rsa==4.9
|
82 |
+
scikit-learn==1.4.1.post1
|
83 |
+
scipy==1.12.0
|
84 |
+
six==1.16.0
|
85 |
+
smmap==5.0.1
|
86 |
+
sniffio==1.3.1
|
87 |
+
SQLAlchemy==2.0.28
|
88 |
+
sseclient-py==1.8.0
|
89 |
+
streamlit==1.32.2
|
90 |
+
tabulate==0.9.0
|
91 |
+
tenacity==8.2.3
|
92 |
+
threadpoolctl==3.3.0
|
93 |
+
together==0.2.11
|
94 |
+
toml==0.10.2
|
95 |
+
toolz==0.12.1
|
96 |
+
tornado==6.4
|
97 |
+
tqdm==4.66.2
|
98 |
+
typer==0.9.0
|
99 |
+
typing-inspect==0.9.0
|
100 |
+
typing_extensions==4.10.0
|
101 |
+
tzdata==2024.1
|
102 |
+
urllib3==2.2.1
|
103 |
+
watchdog==4.0.0
|
104 |
+
yarl==1.9.4
|