ME commited on
Commit
29708eb
Β·
verified Β·
1 Parent(s): efc4cf7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -2
app.py CHANGED
@@ -1,4 +1,201 @@
1
  import streamlit as st
 
 
 
 
 
 
2
 
3
- x = st.slider('Select a value')
4
- st.write(x, 'squared is', x * x)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import requests
3
+ import json
4
+ import webbrowser
5
+ from io import StringIO
6
+ from groq import Groq
7
+ from bs4 import BeautifulSoup
8
 
9
+ # Initialize session state
10
+ if 'original_resume' not in st.session_state:
11
+ st.session_state['original_resume'] = None
12
+ if 'keywords' not in st.session_state:
13
+ st.session_state['keywords'] = None
14
+ if 'tailored_resume' not in st.session_state:
15
+ st.session_state['tailored_resume'] = None
16
+
17
+ def scrape_website(url):
18
+ response = requests.get(url)
19
+ response.raise_for_status()
20
+ soup = BeautifulSoup(response.text, 'html.parser')
21
+ return soup.get_text()
22
+
23
+ def extract_keywords(job_description, client):
24
+ completion = client.chat.completions.create(
25
+ model="llama-3.1-70b-versatile",
26
+ messages=[
27
+ {
28
+ "role": "system",
29
+ "content": (
30
+ "You are an expert in extracting essential information from job postings for optimal ATS compatibility. "
31
+ "Focus on identifying keywords and skills, prioritized by importance."
32
+ )
33
+ },
34
+ {
35
+ "role": "user",
36
+ "content": (
37
+ f"Extract keywords from this job posting and categorize them by importance. "
38
+ f"Return as JSON with exactly these keys: 'high', 'medium', and 'low' containing arrays of strings.\n\n{job_description}"
39
+ )
40
+ }
41
+ ],
42
+ temperature=1,
43
+ max_tokens=4096,
44
+ response_format={"type": "json_object"}
45
+ )
46
+ return json.loads(completion.choices[0].message.content)
47
+
48
+ def adapt_resume(resume_data, keywords, job_description, client):
49
+ completion = client.chat.completions.create(
50
+ model="llama-3.1-8b-instant",
51
+ messages=[
52
+ {
53
+ "role": "system",
54
+ "content": (
55
+ "You are a CV coach skilled in resume customization and JSON formatting. "
56
+ "Tailor the resume to emphasize relevant keywords while maintaining factual accuracy."
57
+ )
58
+ },
59
+ {
60
+ "role": "user",
61
+ "content": f"Keywords: {json.dumps(keywords)}\nResume: {json.dumps(resume_data)}\nJob Description: {job_description}"
62
+ }
63
+ ],
64
+ temperature=0.9,
65
+ max_tokens=8000,
66
+ response_format={"type": "json_object"}
67
+ )
68
+ return json.loads(completion.choices[0].message.content)
69
+
70
+ def calculate_resume_match(resume_data, keywords):
71
+ """Calculate match score between resume and keywords"""
72
+ resume_text = json.dumps(resume_data).lower()
73
+ total_score = 0
74
+ matches = {'high': [], 'medium': [], 'low': []}
75
+
76
+ # Weight multipliers for different priority levels
77
+ weights = {"high": 3, "medium": 2, "low": 1}
78
+
79
+ # Ensure keywords has the expected structure
80
+ if not all(key in keywords for key in ['high', 'medium', 'low']):
81
+ raise ValueError("Keywords must contain 'high', 'medium', and 'low' arrays")
82
+
83
+ for priority in ['high', 'medium', 'low']:
84
+ priority_score = 0
85
+ priority_matches = []
86
+
87
+ for word in keywords[priority]:
88
+ word = word.lower()
89
+ if word in resume_text:
90
+ priority_score += weights[priority]
91
+ priority_matches.append(word)
92
+
93
+ matches[priority] = priority_matches
94
+ total_score += priority_score
95
+
96
+ # Normalize score to 0-100
97
+ max_possible = sum(len(keywords[p]) * weights[p] for p in ['high', 'medium', 'low'])
98
+ normalized_score = (total_score / max_possible * 100) if max_possible > 0 else 0
99
+
100
+ return normalized_score, matches
101
+
102
+ # Page config
103
+ st.set_page_config(page_title="Resume Tailor", page_icon="πŸ“„", layout="wide")
104
+
105
+ # Header
106
+ st.title("🎯 AI Resume Tailor")
107
+ st.markdown("### Transform your resume for your dream job")
108
+
109
+ # Sidebar with API key
110
+ with st.sidebar:
111
+ api_key = st.text_input(
112
+ "Groq API Key",
113
+ type="password",
114
+ help="Get your API key at https://console.groq.com/keys"
115
+ )
116
+ if not api_key:
117
+ st.markdown("[Get API Key](https://console.groq.com/keys)")
118
+
119
+ # Main input section
120
+ col1, col2 = st.columns(2)
121
+ with col1:
122
+ job_url = st.text_input("Job Posting URL", placeholder="https://...")
123
+ with col2:
124
+ resume_file = st.file_uploader("Upload Resume (JSON)", type="json")
125
+ if resume_file:
126
+ resume_str = StringIO(resume_file.getvalue().decode("utf-8"))
127
+ st.session_state['original_resume'] = json.load(resume_str)
128
+
129
+ # Process button
130
+ if st.button("πŸš€ Tailor Resume", type="primary", use_container_width=True):
131
+ if job_url and api_key and resume_file:
132
+ try:
133
+ with st.status("πŸ”„ Processing...") as status:
134
+ # Initialize client
135
+ client = Groq(api_key=api_key)
136
+
137
+ # Scrape and process
138
+ status.update(label="Analyzing job posting...")
139
+ job_description = scrape_website(job_url)
140
+ keywords = extract_keywords(job_description, client)
141
+ st.session_state['keywords'] = keywords
142
+
143
+ status.update(label="Tailoring resume...")
144
+ tailored_resume = adapt_resume(
145
+ st.session_state['original_resume'],
146
+ keywords,
147
+ job_description,
148
+ client
149
+ )
150
+ st.session_state['tailored_resume'] = tailored_resume
151
+ status.update(label="βœ… Done!", state="complete")
152
+
153
+ # Results section
154
+ st.markdown("---")
155
+ st.markdown("### πŸ“Š Results")
156
+
157
+ # Calculate and display scores
158
+ original_score, original_matches = calculate_resume_match(
159
+ st.session_state['original_resume'],
160
+ st.session_state['keywords']
161
+ )
162
+ tailored_score, tailored_matches = calculate_resume_match(
163
+ st.session_state['tailored_resume'],
164
+ st.session_state['keywords']
165
+ )
166
+
167
+ score_col1, score_col2 = st.columns(2)
168
+ with score_col1:
169
+ st.metric("Original Match", f"{original_score:.1f}%")
170
+ with score_col2:
171
+ st.metric("Tailored Match", f"{tailored_score:.1f}%",
172
+ delta=f"+{tailored_score - original_score:.1f}%")
173
+
174
+ # Keyword matches
175
+ with st.expander("🎯 View Keyword Matches"):
176
+ for priority in ['high', 'medium', 'low']:
177
+ st.subheader(f"{priority.title()} Priority")
178
+ orig_matches = set(original_matches[priority])
179
+ new_matches = set(tailored_matches[priority])
180
+ added = new_matches - orig_matches
181
+
182
+ st.write("βœ“ Original:", ", ".join(orig_matches) if orig_matches else "None")
183
+ if added:
184
+ st.write("✚ Added:", f"<span style='background-color: #d4edda;'>{', '.join(added)}</span>", unsafe_allow_html=True)
185
+
186
+ # Download section
187
+ st.markdown("### πŸ“₯ Download")
188
+ if st.download_button(
189
+ "⬇️ Download Tailored Resume",
190
+ data=json.dumps(st.session_state['tailored_resume'], indent=4),
191
+ file_name="tailored_resume.json",
192
+ mime="application/json",
193
+ use_container_width=True
194
+ ):
195
+ webbrowser.open_new_tab("https://rxresu.me/")
196
+ st.info("πŸ“ Opening Resume Builder in new tab...")
197
+
198
+ except Exception as e:
199
+ st.error(f"An error occurred: {str(e)}")
200
+ else:
201
+ st.error("Please provide all required inputs")