OptiHire / app.py
AdithyaSNair's picture
Update app.py
0b0fa7c verified
raw
history blame
20 kB
# app.py
import streamlit as st
from streamlit_option_menu import option_menu
from langchain_groq import ChatGroq
from langchain_core.prompts import PromptTemplate
import fitz # PyMuPDF
import requests
from bs4 import BeautifulSoup
import uuid
import plotly.express as px
import re
import pandas as pd
import json
# Initialize the LLM with your Groq API key from Streamlit secrets
llm = ChatGroq(
temperature=0,
groq_api_key=st.secrets["groq_api_key"],
model_name="llama-3.1-70b-versatile"
)
def extract_text_from_pdf(pdf_file):
"""
Extracts text from an uploaded PDF file.
"""
text = ""
try:
with fitz.open(stream=pdf_file.read(), filetype="pdf") as doc:
for page in doc:
text += page.get_text()
return text
except Exception as e:
st.error(f"Error extracting text from resume: {e}")
return ""
def extract_job_description(job_link):
"""
Fetches and extracts job description text from a given URL.
"""
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
response = requests.get(job_link, headers=headers)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
# Adjust selectors based on the website's structure for better extraction
job_description = soup.get_text(separator='\n')
return job_description.strip()
except Exception as e:
st.error(f"Error fetching job description: {e}")
return ""
def extract_requirements(job_description):
"""
Uses Groq to extract job requirements from the job description.
"""
prompt_text = f"""
The following is a job description:
{job_description}
Extract the list of job requirements, qualifications, and skills from the job description. Provide them as a numbered list.
Requirements:
"""
prompt = PromptTemplate.from_template(prompt_text)
chain = prompt | llm
response = chain.invoke({})
requirements = response.content.strip()
return requirements
def generate_email(job_description, requirements, resume_text):
"""
Generates a personalized cold email using Groq based on the job description, requirements, and resume.
"""
prompt_text = f"""
You are Adithya S Nair, a recent Computer Science graduate specializing in Artificial Intelligence and Machine Learning. Craft a concise and professional cold email to a potential employer based on the following information:
**Job Description:**
{job_description}
**Extracted Requirements:**
{requirements}
**Your Resume:**
{resume_text}
**Email Requirements:**
- **Introduction:** Briefly introduce yourself and mention the specific job you are applying for.
- **Body:** Highlight your relevant skills, projects, internships, and leadership experiences that align with the job requirements.
- **Value Proposition:** Explain how your fresh perspective and recent academic knowledge can add value to the company.
- **Closing:** Express enthusiasm for the opportunity, mention your willingness for an interview, and thank the recipient for their time.
**Email:**
"""
prompt = PromptTemplate.from_template(prompt_text)
chain = prompt | llm
response = chain.invoke({})
email_text = response.content.strip()
return email_text
def generate_cover_letter(job_description, requirements, resume_text):
"""
Generates a personalized cover letter using Groq based on the job description, requirements, and resume.
"""
prompt_text = f"""
You are Adithya S Nair, a recent Computer Science graduate specializing in Artificial Intelligence and Machine Learning. Compose a personalized and professional cover letter based on the following information:
**Job Description:**
{job_description}
**Extracted Requirements:**
{requirements}
**Your Resume:**
{resume_text}
**Cover Letter Requirements:**
1. **Greeting:** Address the hiring manager by name if available; otherwise, use a generic greeting such as "Dear Hiring Manager."
2. **Introduction:** Begin with an engaging opening that mentions the specific position you are applying for and conveys your enthusiasm.
3. **Body:**
- **Skills and Experiences:** Highlight relevant technical skills, projects, internships, and leadership roles that align with the job requirements.
- **Alignment:** Demonstrate how your academic background and hands-on experiences make you a suitable candidate for the role.
4. **Value Proposition:** Explain how your fresh perspective, recent academic knowledge, and eagerness to learn can contribute to the company's success.
5. **Conclusion:** End with a strong closing statement expressing your interest in an interview, your availability, and gratitude for the hiring manager’s time and consideration.
6. **Professional Tone:** Maintain a respectful and professional tone throughout the letter.
**Cover Letter:**
"""
prompt = PromptTemplate.from_template(prompt_text)
chain = prompt | llm
response = chain.invoke({})
cover_letter = response.content.strip()
return cover_letter
def extract_skills(text):
"""
Extracts a list of skills from the resume text using Groq.
"""
prompt_text = f"""
Extract a comprehensive list of technical and soft skills from the following resume text. Provide the skills as a comma-separated list.
Resume Text:
{text}
Skills:
"""
prompt = PromptTemplate.from_template(prompt_text)
chain = prompt | llm
response = chain.invoke({})
skills = response.content.strip()
# Clean and split the skills
skills_list = [skill.strip() for skill in re.split(',|\\n', skills) if skill.strip()]
return skills_list
def suggest_keywords(resume_text, job_description=None):
"""
Suggests additional relevant keywords to enhance resume compatibility with ATS.
"""
prompt_text = f"""
Analyze the following resume text and suggest additional relevant keywords that can enhance its compatibility with Applicant Tracking Systems (ATS). If a job description is provided, tailor the keywords to align with the job requirements.
Resume Text:
{resume_text}
Job Description:
{job_description if job_description else "N/A"}
Suggested Keywords:
"""
prompt = PromptTemplate.from_template(prompt_text)
chain = prompt | llm
response = chain.invoke({})
keywords = response.content.strip()
keywords_list = [keyword.strip() for keyword in re.split(',|\\n', keywords) if keyword.strip()]
return keywords_list
def get_job_recommendations(resume_text, location="India"):
"""
Fetches job recommendations using the JSearch API based on the user's skills.
"""
# Extract skills from resume
skills = extract_skills(resume_text)
query = " ".join(skills) if skills else "Software Engineer"
url = "https://jsearch.p.rapidapi.com/search"
headers = {
"X-RapidAPI-Key": st.secrets["rapidapi_key"], # Accessing RapidAPI key securely
"X-RapidAPI-Host": "jsearch.p.rapidapi.com"
}
params = {
"query": query,
"page": "1",
"num_pages": "1",
"size": "20",
"remote_filter": "false",
"location": location,
"sort": "relevance",
"salary_min": "0",
"salary_max": "0",
"salary_currency": "INR",
"radius": "0",
"company_type": "",
"job_type": "",
"degree_level": "",
"career_level": "",
"include_remote": "false"
}
try:
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
data = response.json()
jobs = data.get("data", [])
job_list = []
for job in jobs:
job_info = {
"title": job.get("job_title"),
"company": job.get("employer", {}).get("name"),
"link": job.get("job_apply_link") or job.get("job_listing_url")
}
job_list.append(job_info)
return job_list
except Exception as e:
st.error(f"Error fetching job recommendations: {e}")
return []
def create_skill_distribution_chart(skills):
"""
Creates a bar chart showing the distribution of skills.
"""
skill_counts = {}
for skill in skills:
skill_counts[skill] = skill_counts.get(skill, 0) + 1
df = pd.DataFrame(list(skill_counts.items()), columns=['Skill', 'Count'])
fig = px.bar(df, x='Skill', y='Count', title='Skill Distribution')
return fig
def create_experience_timeline(resume_text):
"""
Creates an experience timeline from the resume text.
"""
# Extract work experience details using Groq
prompt_text = f"""
From the following resume text, extract the job titles, companies, and durations of employment. Provide the information in a table format with columns: Job Title, Company, Duration (in years).
Resume Text:
{resume_text}
Table:
"""
prompt = PromptTemplate.from_template(prompt_text)
chain = prompt | llm
response = chain.invoke({})
table_text = response.content.strip()
# Parse the table_text to create a DataFrame
data = []
for line in table_text.split('\n'):
if line.strip() and not line.lower().startswith("job title"):
parts = line.split('|')
if len(parts) == 3:
job_title = parts[0].strip()
company = parts[1].strip()
duration = parts[2].strip()
# Convert duration to a float representing years
duration_years = parse_duration(duration)
data.append({"Job Title": job_title, "Company": company, "Duration (years)": duration_years})
df = pd.DataFrame(data)
if not df.empty:
# Create a cumulative duration for timeline
df['Start Year'] = df['Duration (years)'].cumsum() - df['Duration (years)']
df['End Year'] = df['Duration (years)'].cumsum()
fig = px.timeline(df, x_start="Start Year", x_end="End Year", y="Job Title", color="Company", title="Experience Timeline")
fig.update_yaxes(categoryorder="total ascending")
return fig
else:
return None
def parse_duration(duration_str):
"""
Parses duration strings like '2 years' or '6 months' into float years.
"""
try:
if 'year' in duration_str.lower():
years = float(re.findall(r'\d+\.?\d*', duration_str)[0])
return years
elif 'month' in duration_str.lower():
months = float(re.findall(r'\d+\.?\d*', duration_str)[0])
return months / 12
else:
return 0
except:
return 0
# -------------------------------
# Page Functions
# -------------------------------
def email_generator_page():
st.header("Automated Email Generator")
st.write("""
This application generates a personalized cold email based on a job posting and your resume.
""")
# Input fields
job_link = st.text_input("Enter the job link:")
uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf")
if st.button("Generate Email"):
if not job_link:
st.error("Please enter a job link.")
return
if not uploaded_file:
st.error("Please upload your resume.")
return
with st.spinner("Processing..."):
# Extract job description
job_description = extract_job_description(job_link)
if not job_description:
st.error("Failed to extract job description.")
return
# Extract requirements
requirements = extract_requirements(job_description)
if not requirements:
st.error("Failed to extract requirements.")
return
# Extract resume text
resume_text = extract_text_from_pdf(uploaded_file)
if not resume_text:
st.error("Failed to extract text from resume.")
return
# Generate email
email_text = generate_email(job_description, requirements, resume_text)
if email_text:
st.subheader("Generated Email:")
st.write(email_text)
# Provide download option
st.download_button(
label="Download Email",
data=email_text,
file_name="generated_email.txt",
mime="text/plain"
)
else:
st.error("Failed to generate email.")
def cover_letter_generator_page():
st.header("Automated Cover Letter Generator")
st.write("""
This application generates a personalized cover letter based on a job posting and your resume.
""")
# Input fields
job_link = st.text_input("Enter the job link:")
uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf")
if st.button("Generate Cover Letter"):
if not job_link:
st.error("Please enter a job link.")
return
if not uploaded_file:
st.error("Please upload your resume.")
return
with st.spinner("Processing..."):
# Extract job description
job_description = extract_job_description(job_link)
if not job_description:
st.error("Failed to extract job description.")
return
# Extract requirements
requirements = extract_requirements(job_description)
if not requirements:
st.error("Failed to extract requirements.")
return
# Extract resume text
resume_text = extract_text_from_pdf(uploaded_file)
if not resume_text:
st.error("Failed to extract text from resume.")
return
# Generate cover letter
cover_letter = generate_cover_letter(job_description, requirements, resume_text)
if cover_letter:
st.subheader("Generated Cover Letter:")
st.write(cover_letter)
# Provide download option
st.download_button(
label="Download Cover Letter",
data=cover_letter,
file_name="generated_cover_letter.txt",
mime="text/plain"
)
else:
st.error("Failed to generate cover letter.")
def resume_analysis_page():
import pandas as pd # Importing here to prevent unnecessary imports if not used
st.header("Resume Analysis and Optimization")
uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf")
if uploaded_file:
resume_text = extract_text_from_pdf(uploaded_file)
if resume_text:
st.success("Resume uploaded successfully!")
# Perform analysis
st.subheader("Extracted Information")
# Extracted skills
skills = extract_skills(resume_text)
st.write("**Skills:**", ', '.join(skills))
# Extract keywords
keywords = suggest_keywords(resume_text)
st.write("**Suggested Keywords for ATS Optimization:**", ', '.join(keywords))
# Provide optimization suggestions
st.subheader("Optimization Suggestions")
st.write("- **Keyword Optimization:** Incorporate the suggested keywords to improve ATS compatibility.")
st.write("- **Formatting:** Ensure consistent formatting for headings and bullet points to enhance readability.")
st.write("- **Experience Details:** Provide specific achievements and quantify your accomplishments where possible.")
# Visual Resume Analytics
st.subheader("Visual Resume Analytics")
# Skill Distribution Chart
if skills:
st.write("**Skill Distribution:**")
fig_skills = create_skill_distribution_chart(skills)
st.plotly_chart(fig_skills)
# Experience Timeline (if applicable)
fig_experience = create_experience_timeline(resume_text)
if fig_experience:
st.write("**Experience Timeline:**")
st.plotly_chart(fig_experience)
else:
st.write("**Experience Timeline:** Not enough data to generate a timeline.")
else:
st.error("Failed to extract text from resume.")
def job_recommendations_page():
st.header("Job Recommendations")
uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf")
if uploaded_file:
resume_text = extract_text_from_pdf(uploaded_file)
if resume_text:
st.success("Resume uploaded successfully!")
# Fetch job recommendations
st.subheader("Recommended Jobs")
jobs = get_job_recommendations(resume_text)
if jobs:
for job in jobs:
st.write(f"**{job['title']}** at {job['company']}")
st.markdown(f"[Apply Here]({job['link']})")
else:
st.write("No job recommendations found based on your skills.")
else:
st.error("Failed to extract text from resume.")
def skill_matching_page():
st.header("Skill Matching and Gap Analysis")
job_description_input = st.text_area("Paste the job description here:")
uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf")
if st.button("Analyze Skills"):
if not job_description_input:
st.error("Please paste the job description.")
return
if not uploaded_file:
st.error("Please upload your resume.")
return
with st.spinner("Analyzing..."):
# Extract resume text
resume_text = extract_text_from_pdf(uploaded_file)
if not resume_text:
st.error("Failed to extract text from resume.")
return
# Extract skills
resume_skills = extract_skills(resume_text)
job_skills = extract_skills(job_description_input)
# Find matches and gaps
matching_skills = set(resume_skills).intersection(set(job_skills))
missing_skills = set(job_skills) - set(resume_skills)
# Display results
st.subheader("Matching Skills")
st.write(', '.join(matching_skills) if matching_skills else "No matching skills found.")
st.subheader("Missing Skills")
st.write(', '.join(missing_skills) if missing_skills else "No missing skills.")
# -------------------------------
# Main App with Sidebar Navigation
# -------------------------------
def main():
st.set_page_config(page_title="Job Application Assistant", layout="wide")
with st.sidebar:
selected = option_menu(
"Main Menu",
["Email Generator", "Cover Letter Generator", "Resume Analysis", "Job Recommendations", "Skill Matching"],
icons=["envelope", "file-earmark-text", "file-person", "briefcase", "bar-chart"],
menu_icon="cast",
default_index=0,
)
if selected == "Email Generator":
email_generator_page()
elif selected == "Cover Letter Generator":
cover_letter_generator_page()
elif selected == "Resume Analysis":
resume_analysis_page()
elif selected == "Job Recommendations":
job_recommendations_page()
elif selected == "Skill Matching":
skill_matching_page()
if __name__ == "__main__":
main()