Update app.py
Browse files
app.py
CHANGED
@@ -71,13 +71,21 @@ def adapt_resume(resume_data, keywords, job_description, model, max_retries=3):
|
|
71 |
|
72 |
for attempt in range(max_retries):
|
73 |
try:
|
74 |
-
prompt = f"""
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
Keywords: {json.dumps(keywords)}
|
80 |
-
Job: {job_description}"""
|
81 |
|
82 |
response = model.generate_content(prompt)
|
83 |
tailored_resume = json.loads(response.text)
|
@@ -123,16 +131,76 @@ def calculate_resume_match(resume_data, keywords):
|
|
123 |
|
124 |
return normalized_score, matches
|
125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
# Page config
|
127 |
st.set_page_config(page_title="Resume Tailor", page_icon="π", layout="wide")
|
128 |
|
129 |
# Header
|
130 |
-
st.title("
|
131 |
st.markdown("### Transform your resume for your dream job")
|
132 |
# Sidebar with API key
|
133 |
with st.sidebar:
|
134 |
-
st.markdown("###
|
135 |
st.markdown("This tool works with Google's Gemini model, which you can use for free. For more information, visit [Google AI Studio](https://ai.google.dev/aistudio).")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
api_key = st.secrets["google_api_key"]
|
137 |
if not api_key:
|
138 |
st.error("API key not found in secrets. Please add your API key to the secrets.")
|
@@ -148,7 +216,7 @@ with col2:
|
|
148 |
st.session_state['original_resume'] = json.load(resume_str)
|
149 |
|
150 |
# Process button
|
151 |
-
if st.button("
|
152 |
if job_url and api_key and resume_file:
|
153 |
try:
|
154 |
with st.status("π Processing...") as status:
|
@@ -173,7 +241,7 @@ if st.button("π Tailor Resume", type="primary", use_container_width=True):
|
|
173 |
|
174 |
# Results section
|
175 |
st.markdown("---")
|
176 |
-
st.markdown("
|
177 |
|
178 |
# Calculate and display scores
|
179 |
original_score, original_matches = calculate_resume_match(
|
@@ -185,24 +253,13 @@ if st.button("π Tailor Resume", type="primary", use_container_width=True):
|
|
185 |
st.session_state['keywords']
|
186 |
)
|
187 |
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
# Keyword matches
|
196 |
-
with st.expander("π― View Keyword Matches"):
|
197 |
-
for priority in ['high', 'medium', 'low']:
|
198 |
-
st.subheader(f"{priority.title()} Priority")
|
199 |
-
orig_matches = set(original_matches[priority])
|
200 |
-
new_matches = set(tailored_matches[priority])
|
201 |
-
added = new_matches - orig_matches
|
202 |
-
|
203 |
-
st.write("β Original:", ", ".join(orig_matches) if orig_matches else "None")
|
204 |
-
if added:
|
205 |
-
st.write("β Added:", f"<span style='background-color: #d4edda;'>{', '.join(added)}</span>", unsafe_allow_html=True)
|
206 |
|
207 |
# Download section
|
208 |
st.markdown("### π₯ Download")
|
@@ -214,7 +271,7 @@ if st.button("π Tailor Resume", type="primary", use_container_width=True):
|
|
214 |
use_container_width=True
|
215 |
):
|
216 |
webbrowser.open_new_tab("https://rxresu.me/")
|
217 |
-
st.info("π
|
218 |
|
219 |
except Exception as e:
|
220 |
st.error(f"An error occurred: {str(e)}")
|
|
|
71 |
|
72 |
for attempt in range(max_retries):
|
73 |
try:
|
74 |
+
prompt = f"""As a CV expert, optimize the provided resume JSON for the target role.
|
75 |
+
Enhance sections (summary, experience, volunteer, interests, awards, projects, skills) by incorporating provided keywords:
|
76 |
+
- High priority (3x weight)
|
77 |
+
- Medium priority (2x weight)
|
78 |
+
- Low priority (1x weight)
|
79 |
+
|
80 |
+
Rules:
|
81 |
+
- Keep all original facts and information
|
82 |
+
- Maintain exact JSON structure and all existing keys
|
83 |
+
- Use natural language from the keywords list
|
84 |
+
- Do not add fictional content
|
85 |
+
|
86 |
+
Base Schema: {json.dumps(original_schema)}
|
87 |
Keywords: {json.dumps(keywords)}
|
88 |
+
Job Description: {job_description}"""
|
89 |
|
90 |
response = model.generate_content(prompt)
|
91 |
tailored_resume = json.loads(response.text)
|
|
|
131 |
|
132 |
return normalized_score, matches
|
133 |
|
134 |
+
def create_match_visualization(original_score, tailored_score, keywords, original_matches, tailored_matches):
|
135 |
+
"""Create visualization showing resume match comparison"""
|
136 |
+
|
137 |
+
# Overall score comparison
|
138 |
+
st.markdown("### π Resume Match Analysis")
|
139 |
+
|
140 |
+
# Score metrics side by side
|
141 |
+
col1, col2 = st.columns(2)
|
142 |
+
with col1:
|
143 |
+
st.metric(
|
144 |
+
"Original Resume Match Score",
|
145 |
+
f"{original_score:.1f}%"
|
146 |
+
)
|
147 |
+
with col2:
|
148 |
+
st.metric(
|
149 |
+
"Tailored Resume Match Score",
|
150 |
+
f"{tailored_score:.1f}%",
|
151 |
+
delta=f"+{tailored_score - original_score:.1f}%"
|
152 |
+
)
|
153 |
+
|
154 |
+
# Keyword analysis by priority
|
155 |
+
st.markdown("### π― Keyword Matches")
|
156 |
+
tabs = st.tabs(["High Priority π΄", "Medium Priority π‘", "Low Priority π’"])
|
157 |
+
|
158 |
+
for idx, priority in enumerate(['high', 'medium', 'low']):
|
159 |
+
with tabs[idx]:
|
160 |
+
col1, col2 = st.columns(2)
|
161 |
+
|
162 |
+
orig_matches = set(original_matches[priority])
|
163 |
+
new_matches = set(tailored_matches[priority])
|
164 |
+
added = new_matches - orig_matches
|
165 |
+
|
166 |
+
# Original matches
|
167 |
+
with col1:
|
168 |
+
st.markdown("#### Original Matching Keywords")
|
169 |
+
if orig_matches:
|
170 |
+
for keyword in orig_matches:
|
171 |
+
st.markdown(f"β `{keyword}`")
|
172 |
+
else:
|
173 |
+
st.info("No matches found")
|
174 |
+
|
175 |
+
# New matches
|
176 |
+
with col2:
|
177 |
+
st.markdown("#### Added the following Keywords")
|
178 |
+
if added:
|
179 |
+
for keyword in added:
|
180 |
+
st.markdown(f"β `{keyword}`")
|
181 |
+
else:
|
182 |
+
st.info("No new matches")
|
183 |
+
|
184 |
# Page config
|
185 |
st.set_page_config(page_title="Resume Tailor", page_icon="π", layout="wide")
|
186 |
|
187 |
# Header
|
188 |
+
st.title("π Curriculum Customization Tool")
|
189 |
st.markdown("### Transform your resume for your dream job")
|
190 |
# Sidebar with API key
|
191 |
with st.sidebar:
|
192 |
+
st.markdown("### About")
|
193 |
st.markdown("This tool works with Google's Gemini model, which you can use for free. For more information, visit [Google AI Studio](https://ai.google.dev/aistudio).")
|
194 |
+
|
195 |
+
# Disclaimer
|
196 |
+
st.warning("""
|
197 |
+
β οΈ **Disclaimer**
|
198 |
+
|
199 |
+
This tool is for educational purposes only.
|
200 |
+
AI-based tools can produce unexpected or inaccurate results.
|
201 |
+
By using this tool, you accept full responsibility for verifying and using its output.
|
202 |
+
""")
|
203 |
+
|
204 |
api_key = st.secrets["google_api_key"]
|
205 |
if not api_key:
|
206 |
st.error("API key not found in secrets. Please add your API key to the secrets.")
|
|
|
216 |
st.session_state['original_resume'] = json.load(resume_str)
|
217 |
|
218 |
# Process button
|
219 |
+
if st.button("π― Tailor Resume", type="primary", use_container_width=True):
|
220 |
if job_url and api_key and resume_file:
|
221 |
try:
|
222 |
with st.status("π Processing...") as status:
|
|
|
241 |
|
242 |
# Results section
|
243 |
st.markdown("---")
|
244 |
+
st.markdown("## π Results")
|
245 |
|
246 |
# Calculate and display scores
|
247 |
original_score, original_matches = calculate_resume_match(
|
|
|
253 |
st.session_state['keywords']
|
254 |
)
|
255 |
|
256 |
+
create_match_visualization(
|
257 |
+
original_score,
|
258 |
+
tailored_score,
|
259 |
+
st.session_state['keywords'],
|
260 |
+
original_matches,
|
261 |
+
tailored_matches
|
262 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
263 |
|
264 |
# Download section
|
265 |
st.markdown("### π₯ Download")
|
|
|
271 |
use_container_width=True
|
272 |
):
|
273 |
webbrowser.open_new_tab("https://rxresu.me/")
|
274 |
+
st.info("π Resume Builder opened in new tab")
|
275 |
|
276 |
except Exception as e:
|
277 |
st.error(f"An error occurred: {str(e)}")
|