Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,67 +3,46 @@ import asyncio
|
|
| 3 |
import nest_asyncio
|
| 4 |
import streamlit as st
|
| 5 |
import time
|
| 6 |
-
from PyPDF2 import PdfReader
|
| 7 |
from pdfminer.high_level import extract_text
|
| 8 |
from docx import Document
|
| 9 |
import os
|
| 10 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
-
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 14 |
-
|
| 15 |
-
prompt = """
|
| 16 |
-
|
| 17 |
-
You are tasked with creating a detailed and specific 10-Day Interview Preparation Guide for a user based on their resume and a provided job description. This guide should be tailored to prepare the user for an interview for the given job role. The guide should cover both technical and soft skills, with a focus on specific topics relevant to the job description. The preparation plan should balance revising concepts already known to the user and addressing any gaps in their knowledge.
|
| 18 |
-
|
| 19 |
-
Instructions:
|
| 20 |
-
|
| 21 |
-
Your responses for Day 1 and Day 10 will be fixed and as follows:
|
| 22 |
-
|
| 23 |
-
Day 1: Research & Initial Study
|
| 24 |
-
|
| 25 |
-
Your response for Day 1 should include -
|
| 26 |
-
|
| 27 |
-
Researching the company, its mission, and its products or services. Also, study the job description thoroughly to understand the key responsibilities and required skills. And finally, note down any unfamiliar terms or technologies for further research.
|
| 28 |
-
|
| 29 |
-
Day 10: Review and Final Preparation
|
| 30 |
-
|
| 31 |
-
Your response for Day 1 should include -
|
| 32 |
-
|
| 33 |
-
A review all the topics covered in the previous days.
|
| 34 |
-
|
| 35 |
-
Focus on the soft skills required for the role.
|
| 36 |
-
|
| 37 |
-
Conducting a final mock interview, including a variety of question types (behavioral, technical, situational, etc.), to ensure thorough preparation.
|
| 38 |
-
|
| 39 |
-
The remaining days (ie, days 2-9) will address specific aspects of the preparation that are most needed for the job and should include :
|
| 40 |
-
|
| 41 |
-
- For each day, provide a detailed plan focusing on specific technical skills, concepts, or tools that are relevant to the job role.
|
| 42 |
-
|
| 43 |
-
- Include concepts that align with both the user's existing knowledge (as indicated by their resume) and new areas that are essential according to the job description.
|
| 44 |
-
|
| 45 |
-
- Ensure there is a balance between reinforcing known skills and addressing knowledge gaps.
|
| 46 |
-
|
| 47 |
-
- Have each day or a group of day focus on a specific aspect or group of skills required for the job
|
| 48 |
-
|
| 49 |
-
Additional Requirements:
|
| 50 |
-
|
| 51 |
-
If there isn't enough content to cover over 10 individual days, combine multiple days to cover a single concepts in order to avoid repetition across different days
|
| 52 |
-
|
| 53 |
-
Within the points for each day avoid further subheadings
|
| 54 |
-
|
| 55 |
-
Speak to the user - "Your Resume..."
|
| 56 |
-
|
| 57 |
-
Simply begin by saying - "Here is your 10 Day Interview Prep Guide:"
|
| 58 |
-
|
| 59 |
-
The guide should be specific, with each day's tasks clearly outlined and explained.
|
| 60 |
-
|
| 61 |
-
The steps for each day should be detailed, with technical depth and practical advice.
|
| 62 |
-
|
| 63 |
-
Ensure the guide is practical and actionable, helping the user prepare effectively for their interview.
|
| 64 |
-
|
| 65 |
-
"""
|
| 66 |
-
|
| 67 |
def extract_text_from_pdf(file):
|
| 68 |
try:
|
| 69 |
# Reset file pointer to the start of the file
|
|
@@ -92,33 +71,105 @@ def extract_text_from_file(file):
|
|
| 92 |
return "Unsupported file type"
|
| 93 |
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
-
|
| 97 |
-
client = OpenAI(api_key =OPENAI_API_KEY)
|
| 98 |
|
| 99 |
-
completion = client.chat.completions.create(
|
| 100 |
-
model="gpt-4o",
|
| 101 |
-
temperature=0.1,
|
| 102 |
-
messages=[
|
| 103 |
-
{"role": "system", "content": prompt},
|
| 104 |
-
{"role": "user", "content": f"Here is the resume text: {resume_text} \n Here is the job description: {job_description}"}
|
| 105 |
-
]
|
| 106 |
-
)
|
| 107 |
-
|
| 108 |
-
return completion.choices[0].message.content
|
| 109 |
|
| 110 |
|
| 111 |
-
def
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
|
| 116 |
st.set_page_config(page_title="Resume Analyzer", layout="centered")
|
| 117 |
-
|
| 118 |
st.title("Resume Analyzer")
|
| 119 |
|
| 120 |
job_description_source = st.radio("Job Description Source", ("Upload File", "Paste Text"))
|
| 121 |
-
|
| 122 |
if job_description_source == "Upload File":
|
| 123 |
job_description_file = st.file_uploader("Upload Job Description", type=['pdf', 'docx', 'txt'])
|
| 124 |
job_description = extract_text_from_file(job_description_file) if job_description_file else None
|
|
@@ -131,22 +182,48 @@ if job_description:
|
|
| 131 |
st.subheader("Job Description Preview")
|
| 132 |
st.text_area("Job Description", job_description, height=200)
|
| 133 |
|
| 134 |
-
resume_text = None
|
| 135 |
if resume_file:
|
| 136 |
resume_text = extract_text_from_file(resume_file)
|
| 137 |
st.subheader("Resume Preview")
|
| 138 |
st.text_area("Resume", resume_text, height=200)
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
if st.button("Analyze Resume"):
|
| 142 |
if job_description and resume_file:
|
| 143 |
start_time = time.time()
|
| 144 |
-
responses_async =
|
| 145 |
end_time = time.time()
|
| 146 |
total_time = end_time - start_time
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
else:
|
| 152 |
-
st.error("Please provide both the job description and resume.")
|
|
|
|
| 3 |
import nest_asyncio
|
| 4 |
import streamlit as st
|
| 5 |
import time
|
|
|
|
| 6 |
from pdfminer.high_level import extract_text
|
| 7 |
from docx import Document
|
| 8 |
import os
|
| 9 |
+
from pdf2image import convert_from_bytes
|
| 10 |
+
import base64
|
| 11 |
+
import io
|
| 12 |
+
import certifi
|
| 13 |
+
import ssl
|
| 14 |
+
# from dotenv import load_dotenv
|
| 15 |
+
import json
|
| 16 |
+
import testing
|
| 17 |
+
from prompts import (
|
| 18 |
+
ANALYSIS_PROMPT,
|
| 19 |
+
ATS_PROMPT,
|
| 20 |
+
OBJECTIVE_ANALYSIS_PROMPT,
|
| 21 |
+
SCORING_PROMPT,
|
| 22 |
+
DEFUALT_PREP_GUIDE,
|
| 23 |
+
LOW_PREP_GUIDE
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# load_dotenv()
|
| 27 |
+
api_key = os.getenv('OPENAI_API_KEY')
|
| 28 |
+
|
| 29 |
+
nest_asyncio.apply()
|
| 30 |
+
|
| 31 |
+
def generate_prompts(num_images):
|
| 32 |
+
return {
|
| 33 |
+
"SCORING": SCORING_PROMPT,
|
| 34 |
+
"ANALYSIS": ANALYSIS_PROMPT,
|
| 35 |
+
"OBJECTIVE_ANALYSIS": OBJECTIVE_ANALYSIS_PROMPT,
|
| 36 |
+
"ATS": ATS_PROMPT.format(pages=num_images),
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def encode_image(image):
|
| 41 |
+
buffered = io.BytesIO()
|
| 42 |
+
image.save(buffered, format="PNG")
|
| 43 |
+
return base64.b64encode(buffered.getvalue()).decode('utf-8')
|
| 44 |
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
def extract_text_from_pdf(file):
|
| 47 |
try:
|
| 48 |
# Reset file pointer to the start of the file
|
|
|
|
| 71 |
return "Unsupported file type"
|
| 72 |
|
| 73 |
|
| 74 |
+
async def generate_completion(message, api_key):
|
| 75 |
+
url = "https://api.openai.com/v1/chat/completions"
|
| 76 |
+
headers = {
|
| 77 |
+
"Content-Type": "application/json",
|
| 78 |
+
"Authorization": f"Bearer {api_key}"
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
payload = {
|
| 82 |
+
"model": "gpt-4o",
|
| 83 |
+
"messages": message,
|
| 84 |
+
"temperature": 0.1,
|
| 85 |
+
"response_format": {"type": "json_object"}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
| 89 |
+
# Use certifi to specify the CA bundle for SSL certificate verification
|
| 90 |
+
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=ssl_context)) as session:
|
| 91 |
+
async with session.post(url, headers=headers, json=payload) as response:
|
| 92 |
+
if response.status != 200:
|
| 93 |
+
print(f"Error: {response.status}")
|
| 94 |
+
return None
|
| 95 |
+
response_json = await response.json()
|
| 96 |
+
return response_json
|
| 97 |
+
|
| 98 |
+
async def main(resume_text, job_description):
|
| 99 |
+
|
| 100 |
+
analysis_types_and_prompts = {
|
| 101 |
+
"SCORING": SCORING_PROMPT,
|
| 102 |
+
"ANALYSIS": ANALYSIS_PROMPT,
|
| 103 |
+
"OBJECTIVE_ANALYSIS": OBJECTIVE_ANALYSIS_PROMPT,
|
| 104 |
+
"ATS": ATS_PROMPT.format(pages=pdf_length)
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
tasks = []
|
| 108 |
+
for analysis, prompt in analysis_types_and_prompts.items():
|
| 109 |
+
messages = construct_message(
|
| 110 |
+
analysis,
|
| 111 |
+
prompt,
|
| 112 |
+
resume_text,
|
| 113 |
+
job_description,
|
| 114 |
+
base_image if analysis == "ATS" else None,
|
| 115 |
+
)
|
| 116 |
+
tasks.append(generate_completion(messages,api_key))
|
| 117 |
+
|
| 118 |
+
scoring_task = asyncio.create_task(tasks[0])
|
| 119 |
+
|
| 120 |
+
other_tasks = tasks[1:]
|
| 121 |
+
other_tasks_futures = [asyncio.create_task(task) for task in other_tasks]
|
| 122 |
+
|
| 123 |
+
result_scoring = await scoring_task
|
| 124 |
+
scoring_json = json.loads(result_scoring['choices'][0]['message']['content'])
|
| 125 |
+
if len(scoring_json) == 1:
|
| 126 |
+
return scoring_json, None # Error dictionary
|
| 127 |
+
|
| 128 |
+
other_results = await asyncio.gather(*other_tasks_futures)
|
| 129 |
+
responses_async = [result_scoring] + other_results
|
| 130 |
+
|
| 131 |
+
if scoring_json['Overall']['Score'] <=50:
|
| 132 |
+
guide_prompt = LOW_PREP_GUIDE
|
| 133 |
+
else:
|
| 134 |
+
guide_prompt = DEFUALT_PREP_GUIDE
|
| 135 |
|
| 136 |
+
return responses_async, guide_prompt
|
|
|
|
| 137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
|
| 140 |
+
def construct_message(analysis, prompt, resume_text, job_description=None, base_image=None):
|
| 141 |
+
if analysis == "ATS":
|
| 142 |
+
messages = [
|
| 143 |
+
{"role": "system", "content": prompt},
|
| 144 |
+
{"role": "user",
|
| 145 |
+
"content": f"Here is the Job Description: {job_description}"},
|
| 146 |
+
{"role": "user", "content": []}
|
| 147 |
+
]
|
| 148 |
+
for image in base_image:
|
| 149 |
+
messages[2]["content"].append({
|
| 150 |
+
"type": "image_url",
|
| 151 |
+
"image_url": {
|
| 152 |
+
"url": f"data:image/png;base64,{image}"
|
| 153 |
+
},
|
| 154 |
+
})
|
| 155 |
+
return messages
|
| 156 |
+
|
| 157 |
+
elif analysis == "OBJECTIVE_ANALYSIS":
|
| 158 |
+
return ([
|
| 159 |
+
{"role": "system", "content": prompt},
|
| 160 |
+
{"role": "user",
|
| 161 |
+
"content": f"Here is the resume text: {resume_text}"}])
|
| 162 |
+
else:
|
| 163 |
+
return ([
|
| 164 |
+
{"role": "system", "content": prompt},
|
| 165 |
+
{"role": "user",
|
| 166 |
+
"content": f"Here is the resume text: {resume_text} \n Here is the job description: {job_description}"}])
|
| 167 |
|
| 168 |
|
| 169 |
st.set_page_config(page_title="Resume Analyzer", layout="centered")
|
|
|
|
| 170 |
st.title("Resume Analyzer")
|
| 171 |
|
| 172 |
job_description_source = st.radio("Job Description Source", ("Upload File", "Paste Text"))
|
|
|
|
| 173 |
if job_description_source == "Upload File":
|
| 174 |
job_description_file = st.file_uploader("Upload Job Description", type=['pdf', 'docx', 'txt'])
|
| 175 |
job_description = extract_text_from_file(job_description_file) if job_description_file else None
|
|
|
|
| 182 |
st.subheader("Job Description Preview")
|
| 183 |
st.text_area("Job Description", job_description, height=200)
|
| 184 |
|
|
|
|
| 185 |
if resume_file:
|
| 186 |
resume_text = extract_text_from_file(resume_file)
|
| 187 |
st.subheader("Resume Preview")
|
| 188 |
st.text_area("Resume", resume_text, height=200)
|
| 189 |
|
| 190 |
+
resume_file.seek(0)
|
| 191 |
+
file_bytes = resume_file.read()
|
| 192 |
+
images = convert_from_bytes(file_bytes)
|
| 193 |
+
pdf_length = len(images)
|
| 194 |
+
base_image = []
|
| 195 |
+
for image in images:
|
| 196 |
+
base_image.append(encode_image(image))
|
| 197 |
|
| 198 |
if st.button("Analyze Resume"):
|
| 199 |
if job_description and resume_file:
|
| 200 |
start_time = time.time()
|
| 201 |
+
responses_async, guide_prompt = asyncio.run(main(resume_text, job_description))
|
| 202 |
end_time = time.time()
|
| 203 |
total_time = end_time - start_time
|
| 204 |
+
|
| 205 |
+
guide_message = construct_message("GUIDE", guide_prompt, resume_text, job_description)
|
| 206 |
+
guide_response = asyncio.run(generate_completion(guide_message, api_key))
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
if isinstance(responses_async, dict) and "Error" in responses_async:
|
| 210 |
+
error_message = f"Improper file entered: {responses_async['Error']}"
|
| 211 |
+
st.write(error_message)
|
| 212 |
+
|
| 213 |
+
else:
|
| 214 |
+
st.subheader("Analysis Result")
|
| 215 |
+
st.text_area("Scoring Analysis", responses_async[0]['choices'][0]['message']['content'], height=400)
|
| 216 |
+
st.text_area("Gap Analysis", responses_async[1]['choices'][0]['message']['content'], height=400)
|
| 217 |
+
st.text_area("Objective Analysis", responses_async[2]['choices'][0]['message']['content'], height=400)
|
| 218 |
+
st.text_area("ATS Analysis", responses_async[3]['choices'][0]['message']['content'], height=400)
|
| 219 |
+
st.text_area("Interview Prep Guide", guide_response['choices'][0]['message']['content'], height=400)
|
| 220 |
+
st.write(f"Image prompt input tokens: {responses_async[2]['usage']['prompt_tokens']}")
|
| 221 |
+
# st.write(f"Time taken for OpenAI response: {response_time:.2f} seconds")
|
| 222 |
+
st.write(f"Total time taken for execution: {total_time:.2f} seconds")
|
| 223 |
+
|
| 224 |
+
#Testing JSON Respones:
|
| 225 |
+
# testing.run_tests(responses_async, guide_response)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
else:
|
| 229 |
+
st.error("Please provide both the job description and resume.")
|