translators-will commited on
Commit
e889191
·
verified ·
1 Parent(s): ceabd12

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +253 -0
app.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Imports
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
+ import pandas as pd
5
+ from time import sleep
6
+ import random
7
+ from datetime import datetime
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ import re
12
+ from docx import Document
13
+ import logging
14
+ from typing import List, Dict, Any
15
+ from openai import OpenAI
16
+ import tiktoken
17
+ from dotenv import load_dotenv
18
+ import streamlit as st
19
+
20
+ # OpenAI model
21
+ model = "gpt-4o-mini"
22
+
23
+ class LLMJobAssistant:
24
+ def __init__(self):
25
+ self.headers = {
26
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
27
+ }
28
+ self.jobs = []
29
+ self.setup_logging()
30
+ self.load_config()
31
+ self.client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
32
+
33
+ with open('templates/base_resume.txt', 'r') as f:
34
+ self.resume_text = f.read()
35
+
36
+ def analyze_job_posting(self, job_description: str) -> Dict[str, Any]:
37
+ """Use LLM to analyze job posting and extract key information"""
38
+ prompt = f""" \
39
+ Analyze this job posting and extract key information: \
40
+ {job_description} \
41
+
42
+ Return a JSON object with:
43
+ 1. Required skills
44
+ 2. Required experience
45
+ 3. Estimated salary range
46
+ 4. Key responsibilities
47
+ 5. Match score (0-100) with this resume:
48
+ {self.resume_text} \
49
+
50
+ Also include a boolean 'should_apply' based on match score > 70% \
51
+ """
52
+
53
+ response = self.client.chat.completions.create(
54
+ model=model,
55
+ messages=[
56
+ {"role": "user", "content": prompt}
57
+ ],
58
+ response_format={"type": "json_object"}
59
+ )
60
+
61
+ return json.loads(response.choices[0].message.content)
62
+
63
+ def generate_custom_cover_letter(self, job_info: Dict[str, Any], company_name: str) -> str:
64
+ """Generate a customized cover letter using an LLM"""
65
+ prompt = f""" \
66
+ Write a professional cover letter for a {job_info['title']} position at {company_name} \
67
+ Use these details from my resume:
68
+
69
+ {self.resume_text} \
70
+
71
+ And these job requirements:
72
+ {json.dumps(job_info['requirements'], indent=2)} \
73
+
74
+ Focus on:
75
+ 1. Specific matching experiences
76
+ 2. Relevant projects and achievements
77
+ 3. Why I'm interested in this role and companhy
78
+ 4. My background in languages and AI/ML
79
+
80
+ Tone should be professional but conversational.
81
+ """
82
+
83
+ response = self.client.chat.completions.create(
84
+ model=model,
85
+ messages= [
86
+ {"role": "user", "content": prompt}
87
+ ]
88
+ )
89
+
90
+ return response.choices[0].message.content
91
+
92
+ def tailor_resume(self, job_info: Dict[str, Any]) -> str:
93
+ """Use LLMs to suggest resume tailoring for a specific job"""
94
+ prompt = f""" \
95
+ Suggest specific modifications to this resume for a {job_info['title']} position. \
96
+
97
+ Current resume:
98
+ {self.resume_text} \
99
+
100
+ Job requirements: \
101
+ {json.dumps(job_info['requirements'], indent=2)} \
102
+
103
+ Return specific suggestions for:
104
+ 1. Skills to emphasize
105
+ 2. Experiences to highlight
106
+ 3. Projects to feature
107
+ 4. Keywords to add
108
+ """
109
+
110
+ response = self.client.chat.completions.create(
111
+ model=model,
112
+ messages= [
113
+ {"role": "user", "content": prompt}
114
+ ]
115
+ )
116
+
117
+ return response.choices[0].message.content
118
+
119
+ def scrape_job_description(self, url: str) -> str:
120
+ """Scrape full job description from posting URL"""
121
+ try:
122
+ response = requests.get(url, headers=self.headers)
123
+ soup = BeautifulSoup(response.text, "html.parser")
124
+
125
+ # Detect job board from URL
126
+ if 'linkedin.com' in url:
127
+ description = soup.find('div', class_='description__text')
128
+ elif 'indeed.com' in url:
129
+ description = soup.find('div', id='jobDescriptionText')
130
+ elif 'glassdoor.com' in url:
131
+ description = soup.find('div', class_='jobDescriptionContent')
132
+ else:
133
+ # Default fallback - look for common job description containers
134
+ description = (
135
+ soup.find('div', class_='job-description') or
136
+ soup.find('div', class_='job_description') or
137
+ soup.find('div', {'class': lambda x: x and 'description' in x.lower()})
138
+ )
139
+ return description.text.strip() if description else ""
140
+ except Exception as e:
141
+ logging.error(f"Error scraping job description: {str(e)}")
142
+ return ""
143
+
144
+ def process_job_posting(self, job: Dict[str, Any]):
145
+ """Process a single job posting with LLM analysis"""
146
+ # Scrape full job description
147
+ full_description = self.scrape_job_description(job['url'])
148
+
149
+ if not full_description:
150
+ return None
151
+
152
+ # Analyze job posting
153
+ analysis = self.analyze_job_posting(full_description)
154
+
155
+ # If there is a good match, generate materials
156
+ if analysis.get('should_apply', False):
157
+ cover_letter = self.generate_custom_cover_letter(analysis, job['company'])
158
+ resume_suggestions = self.tailor_resume(analysis)
159
+
160
+ return {
161
+ **job,
162
+ 'analysis': analysis,
163
+ 'cover_letter': cover_letter,
164
+ 'resume_suggestions': resume_suggestions,
165
+ 'full_description': full_description
166
+ }
167
+
168
+ return None
169
+
170
+ def run_enhanced_job_search(self):
171
+ """Run job search with LLM enhancements"""
172
+ # First run basic job search
173
+ jobs_df = self.run_job_search()
174
+
175
+ # Process each job with the LLM
176
+ enhanced_jobs = []
177
+ for _, job in jobs_df.iterrows():
178
+ processed_job = self.process_job_posting(job.to_dict())
179
+ if processed_job:
180
+ enhanced_jobs.append(processed_job)
181
+ sleep(random.uniform(1, 2)) # Rate limiting
182
+
183
+ # Convert to DataFrame
184
+ enhanced_df = pd.DataFrame(enhanced_jobs)
185
+
186
+ # Save detailed results
187
+ enhanced_df.to_pickle('enhanced_jobs.pkl') # Save full data
188
+
189
+ return enhanced_df
190
+
191
+ def generate_application_strategy(self, job_data: Dict[str, Any]) -> str:
192
+ """Generate application strategy using an LLM"""
193
+ prompt = f""" \
194
+ Create an application strategy for this job:
195
+
196
+ Job Title: {job_data['title']} \
197
+ Company: {job_data['company']} \
198
+ Match Score: {job_data['analysis']['match_score']} \
199
+
200
+ Include:
201
+ 1. Best approach for application (direct, referral, etc.)
202
+ 2. Key points to emphasize in interview
203
+ 3. Potential questions to ask
204
+ 4. Company research suggestions
205
+ 5. Follow-up strategy
206
+ """
207
+
208
+ response = self.client.chat.completions.create(
209
+ model=model,
210
+ messages = [
211
+ {"role": "user", "content": prompt}
212
+ ]
213
+ )
214
+
215
+ return response.choices[0].message.content
216
+
217
+
218
+
219
+ def main():
220
+ # Load environment variables
221
+ load_dotenv()
222
+
223
+ # Initialize assistant
224
+ assistant = LLMJobAssistant()
225
+
226
+ st.title("LLM-Enhanced Job Application Assistant")
227
+ st.spinner("Running enhanced job search...")
228
+ jobs_df = assistant.run_enhanced_job_search()
229
+
230
+ st.write("Job Search Summary:")
231
+ st.write(f"Total matching jobs found: {len(jobs_df)}")
232
+ st.write("Top matching positions:")
233
+ top_matches = jobs_df.nlargest(5, 'analysis.match_score')
234
+ for _, job in top_matches.iterrows():
235
+ st.write(f"\n{job['title']} at {job['company']}")
236
+ st.write(f"Match Score: {job['analysis']['match_score']}")
237
+ st.write(f"Estimated Salary: {job['analysis']['estimated_salary_range']}")
238
+
239
+ # Generate application strategies for top matches
240
+ st.write("Generating application strategies for top matches...")
241
+ for _, job in top_matches.iterrows():
242
+ strategy = assistant.generate_application_strategy(job.to_dict())
243
+
244
+ # Save strategy to file
245
+ filename = f"strategies/{job['company']}_{job['title']}.txt".replace(' ', '_')
246
+ os.makedirs('strategies', exist_ok=True)
247
+ with open(filename, 'w') as f:
248
+ f.write(strategy)
249
+
250
+ st.dataframe(jobs_df)
251
+
252
+ if __name__ == 'main':
253
+ main()