Update app.py
Browse files
app.py
CHANGED
|
@@ -46,10 +46,8 @@ predefined_skills_list.update([
|
|
| 46 |
"data engineer", "software engineer", "full stack", "frontend", "backend"
|
| 47 |
])
|
| 48 |
|
| 49 |
-
|
| 50 |
# --- Text Extraction Functions (Adapted for file paths in Gradio's File component) ---
|
| 51 |
# Gradio's gr.File component provides a file path to the temporary uploaded file.
|
| 52 |
-
|
| 53 |
def extract_text_from_pdf(pdf_path):
|
| 54 |
"""
|
| 55 |
Extracts text from a PDF file given its path.
|
|
@@ -259,7 +257,6 @@ def create_top_keywords_plot(cv_keywords, jd_keywords):
|
|
| 259 |
plt.tight_layout()
|
| 260 |
return fig
|
| 261 |
|
| 262 |
-
|
| 263 |
# --- Main Gradio Interface Function ---
|
| 264 |
def analyze_cv_match(cv_file_obj, cv_text_input, jd_text_input):
|
| 265 |
"""
|
|
@@ -272,17 +269,14 @@ def analyze_cv_match(cv_file_obj, cv_text_input, jd_text_input):
|
|
| 272 |
cv_content = get_file_content(cv_file_obj)
|
| 273 |
elif cv_text_input:
|
| 274 |
cv_content = cv_text_input
|
| 275 |
-
|
| 276 |
if not cv_content:
|
| 277 |
return (f"<h4><p style='color:red;'>π¨ Error: Please upload a CV file or paste your CV text.</p></h4>",
|
| 278 |
None, None, None, "Analysis Failed")
|
| 279 |
if not jd_text_input:
|
| 280 |
return (f"<h4><p style='color:red;'>π¨ Error: Please paste the Job Description text.</p></h4>",
|
| 281 |
None, None, None, "Analysis Failed")
|
| 282 |
-
|
| 283 |
try:
|
| 284 |
analysis_results = perform_cv_job_analysis(cv_content, jd_text_input)
|
| 285 |
-
|
| 286 |
# Generate HTML output for KPIs and detailed breakdown
|
| 287 |
html_output = f"""
|
| 288 |
<h2 style='text-align: center;'>π‘ Analysis Results Summary π‘</h2>
|
|
@@ -306,30 +300,24 @@ def analyze_cv_match(cv_file_obj, cv_text_input, jd_text_input):
|
|
| 306 |
<p><strong>β
Matched Skills:</strong> {', '.join(analysis_results['matched_skills']) if analysis_results['matched_skills'] else 'None found matching job description.'}</p>
|
| 307 |
<p><strong>β Missing Skills (from Job Description):</strong> {', '.join(analysis_results['missing_skills']) if analysis_results['missing_skills'] else 'π₯³ None! Your CV has all specified skills.'}</p>
|
| 308 |
<p><strong>π‘ Extra Skills in CV (not in Job Description):</strong> {', '.join(analysis_results['extra_skills_in_cv']) if analysis_results['extra_skills_in_cv'] else 'None. (This is often fine, showing broader capability.)'}</p>
|
| 309 |
-
|
| 310 |
<h4>Keyword Relevance (Top TF-IDF Terms)</h4>
|
| 311 |
<p><strong>π€ Top Common Keywords:</strong> {', '.join(analysis_results['common_keywords']) if analysis_results['common_keywords'] else 'No significant common keywords beyond skills.'}</p>
|
| 312 |
<p><strong>π Top Keywords in Your CV:**</strong> {', '.join(analysis_results['top_cv_keywords']) if analysis_results['top_cv_keywords'] else 'N/A'}</p>
|
| 313 |
<p><strong>π― Top Keywords in Job Description:</strong> {', '.join(analysis_results['top_jd_keywords']) if analysis_results['top_jd_keywords'] else 'N/A'}</p>
|
| 314 |
-
|
| 315 |
<h4>Experience & Education Comparison</h4>
|
| 316 |
<p><strong>π€ Your CV's Experience:</strong> <code>{analysis_results['cv_years_experience']}</code> years</p>
|
| 317 |
<p><strong>πΌ Job's Required Experience:</strong> <code>{analysis_results['jd_years_experience']}</code> years</p>
|
| 318 |
<p style='color:green;'><strong>Status:</strong> {analysis_results['experience_match_status']}</p>
|
| 319 |
-
|
| 320 |
<p><strong>π Your CV's Education:</strong> <code>{analysis_results['cv_education_level']}</code></p>
|
| 321 |
<p><strong>π Job's Required Education:</strong> <code>{analysis_results['jd_education_level']}</code></p>
|
| 322 |
<p style='color:green;'><strong>Status:</strong> {analysis_results['education_match_status']}</p>
|
| 323 |
"""
|
| 324 |
-
|
| 325 |
# Generate plots
|
| 326 |
overall_plot = create_overall_match_plot(analysis_results['overall_match_score'])
|
| 327 |
skill_plot = create_skill_match_plot(analysis_results['matched_skills'], analysis_results['missing_skills'])
|
| 328 |
keywords_plot = create_top_keywords_plot(analysis_results['top_cv_keywords'], analysis_results['top_jd_keywords'])
|
| 329 |
-
|
| 330 |
# Return all outputs in the correct order
|
| 331 |
return html_output, overall_plot, skill_plot, keywords_plot, "Analysis Complete!"
|
| 332 |
-
|
| 333 |
except Exception as e:
|
| 334 |
import traceback
|
| 335 |
error_traceback = traceback.format_exc()
|
|
@@ -338,9 +326,7 @@ def analyze_cv_match(cv_file_obj, cv_text_input, jd_text_input):
|
|
| 338 |
f"<details><summary>Click for details</summary><pre>{error_traceback}</pre></details>",
|
| 339 |
None, None, None, "Analysis Failed")
|
| 340 |
|
| 341 |
-
|
| 342 |
# --- Gradio Interface Definition ---
|
| 343 |
-
|
| 344 |
# Use gr.Blocks for more flexibility in layout
|
| 345 |
with gr.Blocks(theme=gr.themes.Soft(), title="CV-Job Match Analyzer") as demo:
|
| 346 |
gr.Markdown(
|
|
@@ -350,28 +336,22 @@ with gr.Blocks(theme=gr.themes.Soft(), title="CV-Job Match Analyzer") as demo:
|
|
| 350 |
Upload a CV (PDF, DOCX, TXT) and paste the job description text to get an instant analysis.
|
| 351 |
"""
|
| 352 |
)
|
| 353 |
-
|
| 354 |
with gr.Row(): # Arrange inputs and outputs in two columns
|
| 355 |
with gr.Column(scale=1, min_width=400): # Left column for inputs
|
| 356 |
gr.Markdown("## **1. Your CV**") # Section title for CV input
|
| 357 |
cv_file_obj = gr.File(label="Upload CV (PDF, DOCX, TXT)", file_types=[".pdf", ".docx", ".txt"])
|
| 358 |
cv_text_input = gr.Textbox(label="Or paste CV text here (overrides file upload)", lines=10, placeholder="Paste your CV content here...")
|
| 359 |
-
|
| 360 |
gr.Markdown("## **2. Job Description**") # Section title for JD input
|
| 361 |
jd_text_input = gr.Textbox(label="Paste the Job Description text here", lines=10, placeholder="Paste the job description content here...")
|
| 362 |
-
|
| 363 |
with gr.Row(): # Buttons in a row
|
| 364 |
analyze_button = gr.Button("β¨ Analyze CV Match β¨", variant="primary", scale=1)
|
| 365 |
clear_button = gr.ClearButton([cv_file_obj, cv_text_input, jd_text_input], scale=1)
|
| 366 |
-
|
| 367 |
with gr.Column(scale=2, min_width=600): # Right column for outputs (plots and HTML report)
|
| 368 |
output_html = gr.HTML(label="Analysis Report") # This will show the text-based KPIs and detailed breakdown
|
| 369 |
gr.Markdown("## **π Visual Insights**") # Title for the plots section
|
| 370 |
output_overall_plot = gr.Plot(label="Overall Match Score") # Plots will appear here
|
| 371 |
output_skill_plot = gr.Plot(label="Skill Match Breakdown")
|
| 372 |
output_keywords_plot = gr.Plot(label="Top Keywords")
|
| 373 |
-
|
| 374 |
-
|
| 375 |
# Define the action when the button is clicked
|
| 376 |
analyze_button.click(
|
| 377 |
fn=analyze_cv_match,
|
|
@@ -381,11 +361,4 @@ with gr.Blocks(theme=gr.themes.Soft(), title="CV-Job Match Analyzer") as demo:
|
|
| 381 |
# Gradio uses the last return value for the status bar if it's a string, or you can explicitly link it.
|
| 382 |
)
|
| 383 |
|
| 384 |
-
# Optional: Clear button click event if you want it to trigger separately (already linked to ClearButton)
|
| 385 |
-
# clear_button.click(
|
| 386 |
-
# fn=lambda: [None, "", ""], # Returns empty values for inputs
|
| 387 |
-
# outputs=[cv_file_obj, cv_text_input, jd_text_input]
|
| 388 |
-
# )
|
| 389 |
-
|
| 390 |
-
|
| 391 |
demo.launch()
|
|
|
|
| 46 |
"data engineer", "software engineer", "full stack", "frontend", "backend"
|
| 47 |
])
|
| 48 |
|
|
|
|
| 49 |
# --- Text Extraction Functions (Adapted for file paths in Gradio's File component) ---
|
| 50 |
# Gradio's gr.File component provides a file path to the temporary uploaded file.
|
|
|
|
| 51 |
def extract_text_from_pdf(pdf_path):
|
| 52 |
"""
|
| 53 |
Extracts text from a PDF file given its path.
|
|
|
|
| 257 |
plt.tight_layout()
|
| 258 |
return fig
|
| 259 |
|
|
|
|
| 260 |
# --- Main Gradio Interface Function ---
|
| 261 |
def analyze_cv_match(cv_file_obj, cv_text_input, jd_text_input):
|
| 262 |
"""
|
|
|
|
| 269 |
cv_content = get_file_content(cv_file_obj)
|
| 270 |
elif cv_text_input:
|
| 271 |
cv_content = cv_text_input
|
|
|
|
| 272 |
if not cv_content:
|
| 273 |
return (f"<h4><p style='color:red;'>π¨ Error: Please upload a CV file or paste your CV text.</p></h4>",
|
| 274 |
None, None, None, "Analysis Failed")
|
| 275 |
if not jd_text_input:
|
| 276 |
return (f"<h4><p style='color:red;'>π¨ Error: Please paste the Job Description text.</p></h4>",
|
| 277 |
None, None, None, "Analysis Failed")
|
|
|
|
| 278 |
try:
|
| 279 |
analysis_results = perform_cv_job_analysis(cv_content, jd_text_input)
|
|
|
|
| 280 |
# Generate HTML output for KPIs and detailed breakdown
|
| 281 |
html_output = f"""
|
| 282 |
<h2 style='text-align: center;'>π‘ Analysis Results Summary π‘</h2>
|
|
|
|
| 300 |
<p><strong>β
Matched Skills:</strong> {', '.join(analysis_results['matched_skills']) if analysis_results['matched_skills'] else 'None found matching job description.'}</p>
|
| 301 |
<p><strong>β Missing Skills (from Job Description):</strong> {', '.join(analysis_results['missing_skills']) if analysis_results['missing_skills'] else 'π₯³ None! Your CV has all specified skills.'}</p>
|
| 302 |
<p><strong>π‘ Extra Skills in CV (not in Job Description):</strong> {', '.join(analysis_results['extra_skills_in_cv']) if analysis_results['extra_skills_in_cv'] else 'None. (This is often fine, showing broader capability.)'}</p>
|
|
|
|
| 303 |
<h4>Keyword Relevance (Top TF-IDF Terms)</h4>
|
| 304 |
<p><strong>π€ Top Common Keywords:</strong> {', '.join(analysis_results['common_keywords']) if analysis_results['common_keywords'] else 'No significant common keywords beyond skills.'}</p>
|
| 305 |
<p><strong>π Top Keywords in Your CV:**</strong> {', '.join(analysis_results['top_cv_keywords']) if analysis_results['top_cv_keywords'] else 'N/A'}</p>
|
| 306 |
<p><strong>π― Top Keywords in Job Description:</strong> {', '.join(analysis_results['top_jd_keywords']) if analysis_results['top_jd_keywords'] else 'N/A'}</p>
|
|
|
|
| 307 |
<h4>Experience & Education Comparison</h4>
|
| 308 |
<p><strong>π€ Your CV's Experience:</strong> <code>{analysis_results['cv_years_experience']}</code> years</p>
|
| 309 |
<p><strong>πΌ Job's Required Experience:</strong> <code>{analysis_results['jd_years_experience']}</code> years</p>
|
| 310 |
<p style='color:green;'><strong>Status:</strong> {analysis_results['experience_match_status']}</p>
|
|
|
|
| 311 |
<p><strong>π Your CV's Education:</strong> <code>{analysis_results['cv_education_level']}</code></p>
|
| 312 |
<p><strong>π Job's Required Education:</strong> <code>{analysis_results['jd_education_level']}</code></p>
|
| 313 |
<p style='color:green;'><strong>Status:</strong> {analysis_results['education_match_status']}</p>
|
| 314 |
"""
|
|
|
|
| 315 |
# Generate plots
|
| 316 |
overall_plot = create_overall_match_plot(analysis_results['overall_match_score'])
|
| 317 |
skill_plot = create_skill_match_plot(analysis_results['matched_skills'], analysis_results['missing_skills'])
|
| 318 |
keywords_plot = create_top_keywords_plot(analysis_results['top_cv_keywords'], analysis_results['top_jd_keywords'])
|
|
|
|
| 319 |
# Return all outputs in the correct order
|
| 320 |
return html_output, overall_plot, skill_plot, keywords_plot, "Analysis Complete!"
|
|
|
|
| 321 |
except Exception as e:
|
| 322 |
import traceback
|
| 323 |
error_traceback = traceback.format_exc()
|
|
|
|
| 326 |
f"<details><summary>Click for details</summary><pre>{error_traceback}</pre></details>",
|
| 327 |
None, None, None, "Analysis Failed")
|
| 328 |
|
|
|
|
| 329 |
# --- Gradio Interface Definition ---
|
|
|
|
| 330 |
# Use gr.Blocks for more flexibility in layout
|
| 331 |
with gr.Blocks(theme=gr.themes.Soft(), title="CV-Job Match Analyzer") as demo:
|
| 332 |
gr.Markdown(
|
|
|
|
| 336 |
Upload a CV (PDF, DOCX, TXT) and paste the job description text to get an instant analysis.
|
| 337 |
"""
|
| 338 |
)
|
|
|
|
| 339 |
with gr.Row(): # Arrange inputs and outputs in two columns
|
| 340 |
with gr.Column(scale=1, min_width=400): # Left column for inputs
|
| 341 |
gr.Markdown("## **1. Your CV**") # Section title for CV input
|
| 342 |
cv_file_obj = gr.File(label="Upload CV (PDF, DOCX, TXT)", file_types=[".pdf", ".docx", ".txt"])
|
| 343 |
cv_text_input = gr.Textbox(label="Or paste CV text here (overrides file upload)", lines=10, placeholder="Paste your CV content here...")
|
|
|
|
| 344 |
gr.Markdown("## **2. Job Description**") # Section title for JD input
|
| 345 |
jd_text_input = gr.Textbox(label="Paste the Job Description text here", lines=10, placeholder="Paste the job description content here...")
|
|
|
|
| 346 |
with gr.Row(): # Buttons in a row
|
| 347 |
analyze_button = gr.Button("β¨ Analyze CV Match β¨", variant="primary", scale=1)
|
| 348 |
clear_button = gr.ClearButton([cv_file_obj, cv_text_input, jd_text_input], scale=1)
|
|
|
|
| 349 |
with gr.Column(scale=2, min_width=600): # Right column for outputs (plots and HTML report)
|
| 350 |
output_html = gr.HTML(label="Analysis Report") # This will show the text-based KPIs and detailed breakdown
|
| 351 |
gr.Markdown("## **π Visual Insights**") # Title for the plots section
|
| 352 |
output_overall_plot = gr.Plot(label="Overall Match Score") # Plots will appear here
|
| 353 |
output_skill_plot = gr.Plot(label="Skill Match Breakdown")
|
| 354 |
output_keywords_plot = gr.Plot(label="Top Keywords")
|
|
|
|
|
|
|
| 355 |
# Define the action when the button is clicked
|
| 356 |
analyze_button.click(
|
| 357 |
fn=analyze_cv_match,
|
|
|
|
| 361 |
# Gradio uses the last return value for the status bar if it's a string, or you can explicitly link it.
|
| 362 |
)
|
| 363 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
demo.launch()
|