Spaces:
Sleeping
Sleeping
EnigmaOfTheWorld
commited on
Commit
·
a101471
1
Parent(s):
5c6c5b0
Upload 7 files
Browse files- app.py +34 -0
- application_dev.db +0 -0
- llm.py +68 -0
- models.py +86 -0
- pages/Careers.py +259 -0
- pages/Employee.py +324 -0
- requirements.txt +6 -0
app.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from sqlalchemy.orm import Session
|
3 |
+
from sqlalchemy import select
|
4 |
+
|
5 |
+
from models import Job, ENGINE
|
6 |
+
|
7 |
+
|
8 |
+
def post_jobs() -> None:
|
9 |
+
with Session(ENGINE) as session:
|
10 |
+
stmt = select(Job)
|
11 |
+
jobs = session.scalars(stmt).all()
|
12 |
+
if jobs:
|
13 |
+
st.markdown("""<h1 style="font-family: Garamond;text-indent: 20px;color:#008DFE;">Submit your job applications today!</h1>""",unsafe_allow_html=True)
|
14 |
+
st.caption(f"""<p style="color:red;">Create your profile and start applying for jobs today. We eagerly anticipate the opportunity to collaborate with you!<p>""",unsafe_allow_html=True)
|
15 |
+
else:
|
16 |
+
st.caption(f"""<p style="color:red;">There are currently no available job vacancies. Please check back later. We are eagerly anticipating the opportunity to begin working together.<p>""",unsafe_allow_html=True)
|
17 |
+
|
18 |
+
for job in jobs:
|
19 |
+
with st.expander(f"{job.job_id}: {job.post_name}"):
|
20 |
+
st.markdown(f"""<p style="font-family:'Garamond'"><b>Description</b>:
|
21 |
+
<br>{job.description}<br><br>
|
22 |
+
<b>Experience</b>:
|
23 |
+
<br>{job.min_experience} - {job.max_experience} year(s)<br><br>
|
24 |
+
<b>Responsibilities</b>:
|
25 |
+
<br>{job.responsibilities}<br><br>
|
26 |
+
<b>Primary Skills</b>:<br>
|
27 |
+
{job.primary_skills}</p>""",unsafe_allow_html=True)
|
28 |
+
if job.secondary_skills:
|
29 |
+
st.markdown(f"""<p style="font-family:'Garamond'"><b>Secondary Skills</b>:
|
30 |
+
<br>{job.secondary_skills}</p>""",unsafe_allow_html=True)
|
31 |
+
st.markdown(f"""<p style="font-family:'Garamond'"><b>Apply before</b>:
|
32 |
+
<br>{job.expires_at}</p>""",unsafe_allow_html=True)
|
33 |
+
|
34 |
+
post_jobs()
|
application_dev.db
ADDED
Binary file (41 kB). View file
|
|
llm.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pathlib
|
2 |
+
import ast
|
3 |
+
import os
|
4 |
+
from pypdf import PdfReader
|
5 |
+
import docx2txt
|
6 |
+
from sqlalchemy import select
|
7 |
+
from sqlalchemy.orm import Session
|
8 |
+
import openai
|
9 |
+
import cohere
|
10 |
+
from models import Job, ENGINE
|
11 |
+
openai.api_key = os.environ["OPEN_API_KEY"]
|
12 |
+
co = cohere.Client(os.environ["COHERE_API_KEY"])
|
13 |
+
def gpt(user_query):
|
14 |
+
response = openai.Completion.create(
|
15 |
+
engine="text-davinci-003",
|
16 |
+
prompt = user_query,
|
17 |
+
max_tokens=1024,
|
18 |
+
n=1,
|
19 |
+
stop=None,
|
20 |
+
temperature=0.5,
|
21 |
+
)
|
22 |
+
return response['choices'][0]['text']
|
23 |
+
|
24 |
+
|
25 |
+
def parse_pdf(file_name):
|
26 |
+
reader = PdfReader(file_name)
|
27 |
+
page = reader.pages[0]
|
28 |
+
resume_text = page.extract_text()
|
29 |
+
|
30 |
+
|
31 |
+
|
32 |
+
return resume_text
|
33 |
+
|
34 |
+
|
35 |
+
def parse_docx(file_name):
|
36 |
+
file_text = docx2txt.process(file_name)
|
37 |
+
return file_text
|
38 |
+
|
39 |
+
# def get_dict(resume_text):
|
40 |
+
# resume_dict = ast.literal_eval(gpt(f"""parse the resume and convert it into a Python string with the headings as "experience," "skills," "certifications," and "education".
|
41 |
+
|
42 |
+
# resume: "{resume_text}"
|
43 |
+
# resume_dict: """).strip())
|
44 |
+
# return resume_dict
|
45 |
+
|
46 |
+
def parse(filename):
|
47 |
+
resume_file = pathlib.Path(filename)
|
48 |
+
text = parse_pdf(resume_file) if resume_file.suffix == ".pdf" else parse_docx(resume_file)
|
49 |
+
print("parse"+"~"*10,text)
|
50 |
+
# dct = get_dict(text)
|
51 |
+
# print(dct)
|
52 |
+
return text
|
53 |
+
|
54 |
+
def rerank(job_id,docs,top_n):
|
55 |
+
with Session(ENGINE) as session:
|
56 |
+
stmt = select(Job).where(Job.job_id == job_id)
|
57 |
+
job = session.scalars(stmt).one()
|
58 |
+
|
59 |
+
post = job.post_name
|
60 |
+
|
61 |
+
response = co.rerank(
|
62 |
+
model = 'rerank-english-v2.0',
|
63 |
+
query = f'Which profile suits most for the role of {post}?',
|
64 |
+
documents = docs,
|
65 |
+
top_n = top_n,
|
66 |
+
)
|
67 |
+
print(response)
|
68 |
+
return response
|
models.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import List
|
2 |
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
3 |
+
from sqlalchemy import ForeignKey, create_engine
|
4 |
+
|
5 |
+
|
6 |
+
ENGINE = create_engine('sqlite:///application_dev.db')
|
7 |
+
|
8 |
+
class Base(DeclarativeBase):
|
9 |
+
pass
|
10 |
+
|
11 |
+
class Employee(Base):
|
12 |
+
__tablename__ = 'employees'
|
13 |
+
employee_id: Mapped[str] = mapped_column(primary_key = True)
|
14 |
+
email_id: Mapped[str] = mapped_column(unique = True,nullable = False)
|
15 |
+
first_name: Mapped[str] = mapped_column(nullable = True)
|
16 |
+
last_name: Mapped[str] = mapped_column(nullable = True)
|
17 |
+
password: Mapped[str]
|
18 |
+
department: Mapped[str]
|
19 |
+
# jobs_posted: Mapped[list["Job"]] = relationship(back_populates="jobs")
|
20 |
+
|
21 |
+
def __repr__(self):
|
22 |
+
return f'Employee({self.employee_id!r},{self.email_id!r},{self.first_name!r},{self.department!r})'
|
23 |
+
|
24 |
+
@property
|
25 |
+
def full_name(self):
|
26 |
+
return f'{self.first_name.title()} {self.last_name.title()}'
|
27 |
+
|
28 |
+
|
29 |
+
class Job(Base):
|
30 |
+
__tablename__ = 'jobs'
|
31 |
+
job_id :Mapped[str] = mapped_column(primary_key = True)
|
32 |
+
employee_id :Mapped[str] #= mapped_column(ForeignKey('employees.employee_id'))
|
33 |
+
post_name: Mapped[str] = mapped_column(nullable = False)
|
34 |
+
description: Mapped[str] = mapped_column(nullable= False)
|
35 |
+
responsibilities: Mapped[str]
|
36 |
+
min_experience: Mapped[int] = mapped_column(nullable = False)
|
37 |
+
max_experience: Mapped[int] = mapped_column(nullable = False)
|
38 |
+
primary_skills: Mapped[str] = mapped_column(nullable = False)
|
39 |
+
secondary_skills: Mapped[str] = mapped_column(nullable = True)
|
40 |
+
vacancies: Mapped[int] = mapped_column(nullable=False)
|
41 |
+
# employee: Mapped["Employee"] = relationship(back_populates="employee")
|
42 |
+
# users_applied: Mapped[list["User"]] = relationship(back_populates="users")
|
43 |
+
created_at: Mapped[str] = mapped_column(nullable=False)
|
44 |
+
expires_at: Mapped[str] = mapped_column(nullable = False)
|
45 |
+
|
46 |
+
def __repr__(self):
|
47 |
+
return f"Job({self.job_id!r},{self.post_name!r},{self.min_experience},{self.max_experience})"
|
48 |
+
|
49 |
+
|
50 |
+
class User(Base):
|
51 |
+
__tablename__ = "users"
|
52 |
+
email_id: Mapped[str] = mapped_column(primary_key=True)
|
53 |
+
password: Mapped[str] = mapped_column(nullable=False)
|
54 |
+
first_name: Mapped[str] = mapped_column(nullable = False)
|
55 |
+
last_name: Mapped[str] = mapped_column(nullable = False)
|
56 |
+
|
57 |
+
def __repr__(self):
|
58 |
+
return f'User({self.email_id!r},{self.first_name!r},{self.last_name!r})'
|
59 |
+
|
60 |
+
@property
|
61 |
+
def full_name(self):
|
62 |
+
return f'{self.first_name.title()} {self.last_name.title()}'
|
63 |
+
|
64 |
+
|
65 |
+
|
66 |
+
class JobsApplied(Base):
|
67 |
+
__tablename__ = 'jobs_applied'
|
68 |
+
email_id: Mapped[str] = mapped_column(primary_key=True)
|
69 |
+
job_id: Mapped[str] = mapped_column(ForeignKey('jobs.job_id'))
|
70 |
+
rank: Mapped[int] = mapped_column(nullable=False)
|
71 |
+
experience: Mapped[int] = mapped_column(nullable=False)
|
72 |
+
round_number: Mapped[int] = mapped_column(nullable=False)
|
73 |
+
primary_skills: Mapped[int]
|
74 |
+
secondary_skills: Mapped[int]
|
75 |
+
|
76 |
+
|
77 |
+
def __repr__(self):
|
78 |
+
return f'JobsApplied({self.email_id!r},{self.job_id!r},{self.rank},{self.experience},{self.round_number})'
|
79 |
+
|
80 |
+
|
81 |
+
|
82 |
+
|
83 |
+
def __create_tables():
|
84 |
+
Base.metadata.create_all(bind = ENGINE)
|
85 |
+
|
86 |
+
|
pages/Careers.py
ADDED
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pathlib
|
2 |
+
import re
|
3 |
+
from typing import Union
|
4 |
+
import os
|
5 |
+
import ast
|
6 |
+
|
7 |
+
import streamlit as st
|
8 |
+
import sqlalchemy
|
9 |
+
from sqlalchemy.orm import Session
|
10 |
+
from sqlalchemy import select
|
11 |
+
import openai
|
12 |
+
|
13 |
+
from models import User, Job, JobsApplied, ENGINE
|
14 |
+
from llm import gpt
|
15 |
+
|
16 |
+
openai.api_key = os.environ['OPEN_API_KEY']
|
17 |
+
def clear_cache():
|
18 |
+
for k in st.session_state:
|
19 |
+
del st.session_state[k]
|
20 |
+
|
21 |
+
def add_user_to_db():
|
22 |
+
col1, col2 = st.columns(2)
|
23 |
+
with col1:
|
24 |
+
first_name = st.text_input(label = "First Name", placeholder="First Name").title().strip()
|
25 |
+
with col2:
|
26 |
+
last_name = st.text_input(label = "Last Name", placeholder="last Name").title().strip()
|
27 |
+
email_id = st.text_input(label = "Email ID", placeholder = "Enter Email ID").strip()
|
28 |
+
new_password = st.text_input(label = "New Password",placeholder="New Password", type = "password").strip()
|
29 |
+
confirm_password = st.text_input(label = "Password",placeholder="New Password", type = "password").strip()
|
30 |
+
col1, col2, col3 = st.columns(3)
|
31 |
+
with col3:
|
32 |
+
if st.button("Back", key="Mistake"):
|
33 |
+
clear_cache()
|
34 |
+
with col1:
|
35 |
+
|
36 |
+
if st.button("Create Profile"):
|
37 |
+
if new_password != confirm_password:
|
38 |
+
st.error("Passwords do not match")
|
39 |
+
return
|
40 |
+
|
41 |
+
if not(first_name and last_name and email_id and new_password):
|
42 |
+
st.error("No text fields must be blank!")
|
43 |
+
return
|
44 |
+
|
45 |
+
|
46 |
+
|
47 |
+
if re.search('^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$',email_id) is None:
|
48 |
+
st.error("Invalid Email ID")
|
49 |
+
return
|
50 |
+
|
51 |
+
with Session(ENGINE) as session:
|
52 |
+
stmt = select(User).where(User.email_id == email_id)
|
53 |
+
result = session.scalars(stmt).one_or_none()
|
54 |
+
if result is not None:
|
55 |
+
st.error("User already exists!")
|
56 |
+
else:
|
57 |
+
user_object = User(email_id = email_id, password = new_password,
|
58 |
+
first_name = first_name, last_name = last_name)
|
59 |
+
session.add_all([user_object])
|
60 |
+
session.commit()
|
61 |
+
st.success("Successfully Created!")
|
62 |
+
clear_cache()
|
63 |
+
return
|
64 |
+
|
65 |
+
|
66 |
+
|
67 |
+
|
68 |
+
|
69 |
+
|
70 |
+
def login() -> None:
|
71 |
+
|
72 |
+
login_container = st.empty()
|
73 |
+
with login_container.container():
|
74 |
+
email_id = st.text_input(label=":email: Email ID", placeholder = "Email ID").lower().strip()
|
75 |
+
password = st.text_input(label = ":lock: Password", placeholder = "Password", type = "password")
|
76 |
+
col1, col2,col3,col4, = st.columns(4)
|
77 |
+
with col4:
|
78 |
+
if st.button("Sign Up"):
|
79 |
+
st.session_state['sign_up_clicked'] = True
|
80 |
+
|
81 |
+
with col1:
|
82 |
+
sign_in = st.button("Sign In")
|
83 |
+
if sign_in:
|
84 |
+
|
85 |
+
with Session(ENGINE) as session:
|
86 |
+
stmt = select(User).where(User.email_id == email_id).where(User.password == password)
|
87 |
+
user = session.scalars(stmt).one_or_none()
|
88 |
+
if user is None:
|
89 |
+
st.error("Invalid email ID/password")
|
90 |
+
else:
|
91 |
+
st.session_state['user_logged'] = True
|
92 |
+
st.session_state['user'] = user
|
93 |
+
|
94 |
+
if st.session_state['sign_up_clicked']:
|
95 |
+
login_container.empty()
|
96 |
+
add_user_to_db()
|
97 |
+
return
|
98 |
+
|
99 |
+
|
100 |
+
if st.session_state['user'] is not None:
|
101 |
+
login_container.empty()
|
102 |
+
|
103 |
+
|
104 |
+
def add_to_db(**job_applied):
|
105 |
+
with Session(ENGINE) as session:
|
106 |
+
obj = JobsApplied(**job_applied)
|
107 |
+
session.add_all([obj])
|
108 |
+
|
109 |
+
session.commit()
|
110 |
+
|
111 |
+
def apply_for_jobs():
|
112 |
+
|
113 |
+
with Session(ENGINE) as session:
|
114 |
+
stmt = select(Job)
|
115 |
+
jobs = session.scalars(stmt).all()
|
116 |
+
job_container = st.empty()
|
117 |
+
with job_container.container():
|
118 |
+
col1, col2 = st.columns(2)
|
119 |
+
with col1:
|
120 |
+
job_id = st.selectbox(label="Job ID", options = [job.job_id for job in jobs])
|
121 |
+
with col2:
|
122 |
+
post_name = st.text_input(label="Post",value = [job.post_name for job in jobs if job.job_id == job_id][-1], disabled=True)
|
123 |
+
experience = st.number_input(label = 'Expereince',min_value=0)
|
124 |
+
col1, col2 = st.columns(2)
|
125 |
+
with col1:
|
126 |
+
primary_skills = st.text_input(label="Primary Skills",placeholder="Primary Skills", help="Input your skills delimited by a comma").lower().strip()
|
127 |
+
with col2:
|
128 |
+
secondary_skills = st.text_input(label="Secondary Skills",placeholder="Secondary Skills", help="Input your skills delimited by a comma").lower().strip()
|
129 |
+
|
130 |
+
uploded_file = st.file_uploader(label = "Resume Upload", type = ["pdf","docx"])
|
131 |
+
|
132 |
+
col1, col2 = st.columns(2)
|
133 |
+
with col2:
|
134 |
+
|
135 |
+
if st.button("Apply"):
|
136 |
+
st.session_state['applied_for_job'] = True
|
137 |
+
|
138 |
+
if st.session_state['applied_for_job']:
|
139 |
+
if uploded_file is None:
|
140 |
+
st.error("No Resume Uploaded")
|
141 |
+
return False
|
142 |
+
|
143 |
+
with Session(ENGINE) as session:
|
144 |
+
stmt = select(JobsApplied).where(JobsApplied.email_id == st.session_state['user'].email_id)
|
145 |
+
res = session.scalars(stmt).one_or_none()
|
146 |
+
if res is not None:
|
147 |
+
st.info("You have already applied for job")
|
148 |
+
return False
|
149 |
+
stmt = select(Job).where(Job.job_id == job_id)
|
150 |
+
selected_job = session.scalars(stmt).one()
|
151 |
+
|
152 |
+
if st.session_state['applied_for_job'] and not(selected_job.min_experience <= experience <= selected_job.max_experience):
|
153 |
+
return False
|
154 |
+
|
155 |
+
|
156 |
+
resumes_dir = pathlib.Path(f'resumes/{job_id}')
|
157 |
+
resumes_dir.mkdir(exist_ok = True)
|
158 |
+
resume = resumes_dir / f"{st.session_state['user'].email_id} - {st.session_state['user'].full_name}"
|
159 |
+
resume.touch()
|
160 |
+
resume.write_bytes(uploded_file.read())
|
161 |
+
|
162 |
+
|
163 |
+
add_to_db(**{"email_id": st.session_state["user"].email_id,
|
164 |
+
"job_id": job_id, 'experience': experience,
|
165 |
+
'primary_skills': primary_skills,
|
166 |
+
'secondary_skills':secondary_skills,
|
167 |
+
'round_number': 0, 'rank': -1})
|
168 |
+
return True
|
169 |
+
|
170 |
+
|
171 |
+
|
172 |
+
def jobs_applied():
|
173 |
+
with Session(ENGINE) as session:
|
174 |
+
stmt = select(JobsApplied).where(JobsApplied.email_id == st.session_state['user'].email_id)
|
175 |
+
jobs_applied = session.scalars(stmt).all()
|
176 |
+
|
177 |
+
if jobs_applied:
|
178 |
+
for job_applied in jobs_applied:
|
179 |
+
with st.expander(f'Job ID: {job_applied.job_id} Round Number: {job_applied.round_number}'):
|
180 |
+
if job_applied.round_number > 0:
|
181 |
+
temporary = st.empty()
|
182 |
+
with temporary.container():
|
183 |
+
get_interview_questions(job_applied.job_id)
|
184 |
+
temporary.empty()
|
185 |
+
st.markdown(f'<p style="font-family:Garamond">You will be notified about the result</p>',unsafe_allow_html=True)
|
186 |
+
|
187 |
+
if not(st.session_state['rank']) >= 7:
|
188 |
+
with Session(ENGINE) as session:
|
189 |
+
session.delete(job_applied)
|
190 |
+
|
191 |
+
|
192 |
+
|
193 |
+
|
194 |
+
|
195 |
+
|
196 |
+
def get_interview_questions(job_id):
|
197 |
+
with Session(ENGINE) as session:
|
198 |
+
stmt = select(Job).where(Job.job_id == job_id.strip())
|
199 |
+
job = session.scalars(stmt).one_or_none()
|
200 |
+
|
201 |
+
if job is not None:
|
202 |
+
lst = gpt(f"Generate a set of 10 multiple-choice questions (MCQs) based on the subject of {job.post_name} with experience between {job.min_experience} - {job.max_experience} years.Return it as a list of dictionaries with question as key and optionaand correct answer as kets")
|
203 |
+
st.write(f"{lst!r}")
|
204 |
+
mcqs = ast.literal_eval(lst.strip())
|
205 |
+
rank = 0
|
206 |
+
for index, mcq in enumerate(mcqs,start=1):
|
207 |
+
question = mcq["question"] if 'question' in mcq.keys() else mcq["Question"]
|
208 |
+
options = mcq["options"] if 'options' in mcq.keys() else mcq["Options"]
|
209 |
+
correct_answer = mcq["correct_answer"] if 'correct_answer' in mcq.keys() else mcq["Correct Answer"]
|
210 |
+
answer = st.radio(f'Q{index} {question}',key=mcq,options=answer)
|
211 |
+
if answer.strip() == correct_answer.strip():
|
212 |
+
rank += 1
|
213 |
+
if st.button('Submit'):
|
214 |
+
st.session_state['rank'] = rank
|
215 |
+
return st.session_state['rank']
|
216 |
+
|
217 |
+
|
218 |
+
|
219 |
+
|
220 |
+
|
221 |
+
|
222 |
+
|
223 |
+
|
224 |
+
|
225 |
+
|
226 |
+
|
227 |
+
|
228 |
+
|
229 |
+
|
230 |
+
|
231 |
+
|
232 |
+
|
233 |
+
|
234 |
+
|
235 |
+
|
236 |
+
if 'sign_up_clicked' not in st.session_state:
|
237 |
+
st.session_state['sign_up_clicked'] = False
|
238 |
+
if 'user_logged' not in st.session_state:
|
239 |
+
st.session_state['user_logged'] = False
|
240 |
+
if 'user' not in st.session_state:
|
241 |
+
st.session_state['user'] = None
|
242 |
+
if 'applied_for_job' not in st.session_state:
|
243 |
+
st.session_state['applied_for_job'] = False
|
244 |
+
|
245 |
+
if 'rank' not in st.session_state:
|
246 |
+
st.session_state['rank'] = False
|
247 |
+
|
248 |
+
|
249 |
+
|
250 |
+
login()
|
251 |
+
if st.session_state['user_logged']:
|
252 |
+
col1, col2 = st.columns(2)
|
253 |
+
with col2:
|
254 |
+
st.button('Log Out',on_click=clear_cache)
|
255 |
+
tab1, tab2= st.tabs(["Apply Jobs","Place Holder"])
|
256 |
+
with tab1:
|
257 |
+
apply_for_jobs()
|
258 |
+
with tab2:
|
259 |
+
jobs_applied()
|
pages/Employee.py
ADDED
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import random
|
3 |
+
from datetime import datetime, timedelta
|
4 |
+
from typing import Union
|
5 |
+
import pathlib
|
6 |
+
import smtplib
|
7 |
+
from email.message import EmailMessage
|
8 |
+
import streamlit as st
|
9 |
+
from sqlalchemy.orm import Session
|
10 |
+
from sqlalchemy import select
|
11 |
+
|
12 |
+
from models import Employee, Job , JobsApplied,User, ENGINE
|
13 |
+
from llm import parse, rerank
|
14 |
+
|
15 |
+
|
16 |
+
|
17 |
+
st.markdown("""-
|
18 |
+
<style>
|
19 |
+
|
20 |
+
.stButton > button{
|
21 |
+
border: black;
|
22 |
+
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
|
23 |
+
}
|
24 |
+
</style>""",unsafe_allow_html=True)
|
25 |
+
|
26 |
+
def clear_cache():
|
27 |
+
for k in st.session_state:
|
28 |
+
del st.session_state[k]
|
29 |
+
|
30 |
+
def login() -> Union[Employee,None]:
|
31 |
+
login_container = st.empty()
|
32 |
+
with login_container.container():
|
33 |
+
employee_id = st.text_input(label=":email: User ID", placeholder = "User ID").upper().strip()
|
34 |
+
password = st.text_input(label = ":lock: Password", placeholder = "Password", type = "password")
|
35 |
+
button = st.button('Login', type = "secondary")
|
36 |
+
|
37 |
+
if button:
|
38 |
+
if employee_id and password:
|
39 |
+
with Session(ENGINE) as session:
|
40 |
+
stmt = select(Employee).where(Employee.employee_id == employee_id).\
|
41 |
+
where(Employee.password == password)
|
42 |
+
employee = session.scalars(stmt).one_or_none()
|
43 |
+
print(employee)
|
44 |
+
|
45 |
+
if employee is None:
|
46 |
+
st.error('Invalid UserID/password')
|
47 |
+
return
|
48 |
+
|
49 |
+
st.session_state['employee_logged'] = True
|
50 |
+
st.session_state['employee'] = employee
|
51 |
+
login_container.empty()
|
52 |
+
|
53 |
+
return employee
|
54 |
+
else:
|
55 |
+
st.error('Empty UserID/Password')
|
56 |
+
|
57 |
+
|
58 |
+
def add_to_db(**job) -> None:
|
59 |
+
|
60 |
+
job_id = ''.join(chr(random.randint(65,90)) for i in range(5))+f'{random.randint(100,1000)}'
|
61 |
+
job['job_id'] = job_id
|
62 |
+
job['employee_id'] = st.session_state["employee"].employee_id
|
63 |
+
job['created_at'] = datetime.now().strftime("%d-%m-%Y %H:%M:%S")
|
64 |
+
job['primary_skills'] = ', '.join(job['primary_skills'])
|
65 |
+
job['secondary_skills'] = ', '.join(job['secondary_skills'])
|
66 |
+
|
67 |
+
|
68 |
+
with Session(ENGINE) as session:
|
69 |
+
job_object = Job(**job)
|
70 |
+
session.add_all([job_object])
|
71 |
+
session.commit()
|
72 |
+
st.success(f"Successfully posted job {job['post_name']}!")
|
73 |
+
|
74 |
+
|
75 |
+
def add_job() -> None:
|
76 |
+
post_name = st.text_input(label = 'Post Name').strip().title()
|
77 |
+
description = st.text_area(label = 'Job Description').strip()
|
78 |
+
responsibilities = st.text_area(label = "Responsibility").strip()
|
79 |
+
vacancies = st.number_input(label="Number Vacancies",min_value=1)
|
80 |
+
col1, col2 = st.columns(2)
|
81 |
+
with col1:
|
82 |
+
min_experience = st.number_input(label = "Minimum Experience",min_value = 0, )
|
83 |
+
with col2:
|
84 |
+
max_experience = st.number_input(label = "Maximum Experience",min_value = 1, )
|
85 |
+
number_of_primary_skills = st.number_input(label = "How many primary skills?",min_value = 1, )
|
86 |
+
primary_skills = [st.text_input(label = f'Primary Skill {i}',key=f'Primary Skill {i}').strip() for i in range(1,number_of_primary_skills+1)]
|
87 |
+
number_of_secondary_skills = st.number_input(label = "How many secondary skills?",min_value = 0, )
|
88 |
+
secondary_skills = [st.text_input(label = f'Secondary Skill {i}',key=f'Secondary Skill {i}').strip() for i in range(1,number_of_secondary_skills+1)]
|
89 |
+
st.markdown("Expires at")
|
90 |
+
col1, col2 = st.columns(2)
|
91 |
+
with col1:
|
92 |
+
expires_date = st.date_input(label = "expries date",value =datetime.now() + timedelta(days = 1), min_value = datetime.now() + timedelta(days = 1), max_value=None, label_visibility="collapsed")
|
93 |
+
print(expires_date )
|
94 |
+
with col2:
|
95 |
+
expires_time = st.time_input(label = "Time Input", label_visibility="collapsed")
|
96 |
+
print(expires_time)
|
97 |
+
expires_at = f'{expires_date.strftime("%d-%m-%Y")} {expires_time.strftime("%H:%M:%S")}'
|
98 |
+
st.markdown("<p style='color: red'>⚠️ Check before you submit once submitted you cannot make changes</p>", unsafe_allow_html=True)
|
99 |
+
if st.button("Post"):
|
100 |
+
if (post_name and description and responsibilities and primary_skills):
|
101 |
+
if min_experience <= max_experience:
|
102 |
+
job = {
|
103 |
+
"post_name" : post_name, 'description': description,'min_experience': min_experience,'max_experience': max_experience,
|
104 |
+
'primary_skills': primary_skills, 'secondary_skills': secondary_skills,'responsibilities':responsibilities,
|
105 |
+
'expires_at': expires_at, 'vacancies': vacancies
|
106 |
+
|
107 |
+
}
|
108 |
+
|
109 |
+
|
110 |
+
add_to_db(**job)
|
111 |
+
return True
|
112 |
+
st.error("Minimum Experience must be less than maximum experience")
|
113 |
+
return
|
114 |
+
st.error("Post Name/Description/Responsibility/Primary SKills are required")
|
115 |
+
|
116 |
+
|
117 |
+
|
118 |
+
def post_job():
|
119 |
+
st.session_state['employee']
|
120 |
+
st.session_state['employee_logged']
|
121 |
+
if st.session_state['employee_logged']:
|
122 |
+
col1, col2 = st.columns([0.001, 0.01])
|
123 |
+
with col2:
|
124 |
+
st.markdown(f'Would you like to include a job vacancy listing. {st.session_state["employee"].full_name}?')
|
125 |
+
with col1:
|
126 |
+
if st.button(':heavy_plus_sign:'):
|
127 |
+
st.session_state['add_job'] = True
|
128 |
+
|
129 |
+
if st.session_state['add_job']:
|
130 |
+
add_job_container = st.empty()
|
131 |
+
with add_job_container.container():
|
132 |
+
is_job_posted = add_job()
|
133 |
+
if is_job_posted:
|
134 |
+
add_job_container.empty()
|
135 |
+
|
136 |
+
|
137 |
+
def get_jobs_posted() -> None:
|
138 |
+
with Session(ENGINE) as session:
|
139 |
+
stmt = select(Job).where(Job.employee_id == st.session_state["employee"].employee_id)
|
140 |
+
jobs = session.scalars(stmt).all()
|
141 |
+
print(jobs)
|
142 |
+
for job in jobs:
|
143 |
+
with st.expander(f"{job.job_id}: {job.post_name} posted at: {job.created_at}"):
|
144 |
+
pass
|
145 |
+
|
146 |
+
|
147 |
+
|
148 |
+
def get_users_who_applied_for_jobs():
|
149 |
+
|
150 |
+
#Get Jobs Posted
|
151 |
+
with Session(ENGINE) as session:
|
152 |
+
stmt = select(Job).where(Job.employee_id == st.session_state["employee"].employee_id)
|
153 |
+
jobs = session.scalars(stmt).all()
|
154 |
+
|
155 |
+
if not jobs:
|
156 |
+
st.info("No jobs posted!")
|
157 |
+
return
|
158 |
+
|
159 |
+
job_id = st.selectbox("Filter by job_id",options=[job.job_id for job in jobs])
|
160 |
+
|
161 |
+
|
162 |
+
|
163 |
+
with Session(ENGINE) as session:
|
164 |
+
stmt = select(JobsApplied).where(JobsApplied.job_id == job_id)
|
165 |
+
users_applied_for_jobs = session.scalars(stmt).all()
|
166 |
+
|
167 |
+
if not users_applied_for_jobs:
|
168 |
+
st.info('No users have applied for job')
|
169 |
+
return False
|
170 |
+
|
171 |
+
re_rank_resumes_container = st.empty()
|
172 |
+
with re_rank_resumes_container.container():
|
173 |
+
st.markdown("""<p style="font-family:Garamond;text-indent: 45vh;font-size:25px">Jobs Application Details</p>""",unsafe_allow_html=True)
|
174 |
+
col1, col2 = st.columns([0.001, 0.01])
|
175 |
+
with col1:
|
176 |
+
round_number = users_applied_for_jobs[0].round_number
|
177 |
+
st.markdown(f"""<p style="font-size:20px"><span style = "font-family:Garamond;">Round</span><span style="font-family:monospace;padding: 10px 20px">{round_number}</span></p>""", unsafe_allow_html=True)
|
178 |
+
with col2:
|
179 |
+
num_docs = st.number_input(label="Top Resumes",min_value=1, max_value=len(users_applied_for_jobs))
|
180 |
+
for jobs_application in users_applied_for_jobs:
|
181 |
+
st.markdown(f"""<p style="border: 2px solid black;padding: 10px 20px; box-shadow: 10px 10px 5px #aaaaaa"><span style="font-family:Garamond;">User Email ID</span>: <span style="font-family:monospace">{jobs_application.email_id}</span><br><span style="font-family:Garamond">Current Rank: <span style="font-family:monospace">{jobs_application.rank}</span></p>""",unsafe_allow_html=True)
|
182 |
+
col1,col2, col3 = st.columns(3)
|
183 |
+
with col2:
|
184 |
+
if st.button("Rank Resumes"):
|
185 |
+
st.session_state['rank_resumes'] = True
|
186 |
+
|
187 |
+
#Ranking Resumes
|
188 |
+
if st.session_state['rank_resumes']:
|
189 |
+
re_rank_resumes_container.empty()
|
190 |
+
docs = get_docs(users_applied_for_jobs,job_id)
|
191 |
+
re_ranked = rerank(job_id,docs,num_docs)
|
192 |
+
candidates = [r.document['text'].split('-'*50)[0].strip() for r in re_ranked.results]
|
193 |
+
st.markdown(f"<p style='font-family:Garamond;text-indent: 45vh;font-size:25px'>Resumes re-ranked:</p>",unsafe_allow_html=True)
|
194 |
+
for candidate in candidates:
|
195 |
+
st.markdown(f"""<p style="border: 2px solid black;padding: 10px 20px; box-shadow: 10px 10px 5px #aaaaaa"><span style="font-family:Garamond;">User Email ID</span>: <span style="font-family:monospace">{candidate}</span></p>""",unsafe_allow_html=True)
|
196 |
+
col1,col2, col3 = st.columns(3)
|
197 |
+
with col2:
|
198 |
+
if st.button("Send Emails"):
|
199 |
+
st.session_state['send_resumes'] = True
|
200 |
+
if st.session_state['send_resumes']:
|
201 |
+
add_job_aplication_details(job_id,round_number,candidates)
|
202 |
+
|
203 |
+
|
204 |
+
def add_job_aplication_details(job_id,round_number,candidates):
|
205 |
+
def update(job_application):
|
206 |
+
print('----------------------------------------4')
|
207 |
+
job_application.round_number += 1
|
208 |
+
job_application.rank = job_application.round_number
|
209 |
+
return job_application
|
210 |
+
|
211 |
+
|
212 |
+
|
213 |
+
with Session(ENGINE) as session:
|
214 |
+
stmt = select(JobsApplied).where(JobsApplied.job_id == job_id).where(JobsApplied.email_id.not_in(candidates))
|
215 |
+
jobs = session.scalars(stmt).all()
|
216 |
+
for job in jobs:
|
217 |
+
session.delete(job)
|
218 |
+
session.commit()
|
219 |
+
print('deleted')
|
220 |
+
with Session(ENGINE) as session:
|
221 |
+
stmt = select(JobsApplied).where(JobsApplied.job_id == job_id).where(JobsApplied.email_id.in_(candidates))
|
222 |
+
jobs = session.scalars(stmt).all()
|
223 |
+
for job in jobs:
|
224 |
+
update(job)
|
225 |
+
session.commit()
|
226 |
+
print('updated')
|
227 |
+
sending_emails(job_id, candidates)
|
228 |
+
|
229 |
+
def sending_emails(job_id, candidates):
|
230 |
+
email_message = EmailMessage()
|
231 |
+
email_message['From'] = st.session_state['employee'].email_id
|
232 |
+
email_message['To'] = candidates
|
233 |
+
email_message['Subject'] = f'You are selected for Job ID: {job_id}.'
|
234 |
+
email_message.set_content('Hello,\nYou are selected to attend the interview. Please login to your profile and attend.')
|
235 |
+
with smtplib.SMTP_SSL('smtp.gmail.com',465) as smtp:
|
236 |
+
# st.write(os.environ['EMAIL'])
|
237 |
+
smtp.login(st.session_state['employee'].email_id, os.environ['EMAIL'])
|
238 |
+
smtp.send_message(email_message)
|
239 |
+
st.success(f'⚠️Email Sent SUccessfully')
|
240 |
+
|
241 |
+
|
242 |
+
|
243 |
+
|
244 |
+
def get_docs(jobs_applications,job_id):
|
245 |
+
docs = []
|
246 |
+
print(f'resumes/{job_id}')
|
247 |
+
resumes_dir = pathlib.Path(f'resumes/{job_id}')
|
248 |
+
|
249 |
+
|
250 |
+
for resume in resumes_dir.iterdir():
|
251 |
+
print(resume)
|
252 |
+
parsed_text = parse(resume)
|
253 |
+
print(parsed_text)
|
254 |
+
print('---------------------------------------------------------------------------')
|
255 |
+
email_id = resume.stem.split('-')[0]
|
256 |
+
text = f"{email_id}"+'-'*50+parsed_text
|
257 |
+
docs.append(text)
|
258 |
+
return docs
|
259 |
+
|
260 |
+
|
261 |
+
|
262 |
+
|
263 |
+
|
264 |
+
|
265 |
+
|
266 |
+
|
267 |
+
|
268 |
+
|
269 |
+
print('Before settings')
|
270 |
+
|
271 |
+
# Setting Session states
|
272 |
+
if 'employee_logged' not in st.session_state:
|
273 |
+
st.session_state['employee_logged'] = False
|
274 |
+
if 'employee' not in st.session_state:
|
275 |
+
st.session_state['employee'] = None
|
276 |
+
if 'add_job' not in st.session_state:
|
277 |
+
st.session_state['add_job'] = False
|
278 |
+
|
279 |
+
if 'add_new_skill' not in st.session_state:
|
280 |
+
st.session_state['add_new_skill'] = False
|
281 |
+
|
282 |
+
if 'rank_resumes' not in st.session_state:
|
283 |
+
st.session_state['rank_resumes'] = False
|
284 |
+
|
285 |
+
if 'rank_resumes' not in st.session_state:
|
286 |
+
st.session_state['rank_resumes'] = False
|
287 |
+
|
288 |
+
if 'send_resumes' not in st.session_state:
|
289 |
+
st.session_state['send_resumes'] = False
|
290 |
+
|
291 |
+
|
292 |
+
|
293 |
+
|
294 |
+
|
295 |
+
|
296 |
+
|
297 |
+
|
298 |
+
|
299 |
+
|
300 |
+
|
301 |
+
print('script start')
|
302 |
+
|
303 |
+
#Login
|
304 |
+
|
305 |
+
|
306 |
+
if not st.session_state['employee_logged']:
|
307 |
+
employee = login()
|
308 |
+
|
309 |
+
|
310 |
+
if st.session_state['employee_logged']:
|
311 |
+
col1, col2 = st.columns(2)
|
312 |
+
with col2:
|
313 |
+
st.button('Log Out',on_click=clear_cache)
|
314 |
+
|
315 |
+
|
316 |
+
tab1, tab2, tab3 = st.tabs(["Post Jobs","Jobs Posted","Get Job Applied Details"])
|
317 |
+
with tab1:
|
318 |
+
post_job()
|
319 |
+
with tab2:
|
320 |
+
get_jobs_posted()
|
321 |
+
with tab3:
|
322 |
+
get_users_who_applied_for_jobs()
|
323 |
+
|
324 |
+
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit==1.25.0
|
2 |
+
sqlalchemy==2.0.19
|
3 |
+
cohere
|
4 |
+
openai
|
5 |
+
pypdf
|
6 |
+
docx2txt
|