Update app.py
Browse files
app.py
CHANGED
|
@@ -369,4 +369,102 @@ Upload a CV (PDF, DOCX, TXT) and paste the job description text to get an instan
|
|
| 369 |
|
| 370 |
# Input for CV
|
| 371 |
st.header("1. Upload Your CV")
|
| 372 |
-
uploaded_cv_file = st.file_uploader("Choose a CV file (PDF, DOCX, TXT)", type=["pdf", "docx", "txt"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
|
| 370 |
# Input for CV
|
| 371 |
st.header("1. Upload Your CV")
|
| 372 |
+
uploaded_cv_file = st.file_uploader("Choose a CV file (PDF, DOCX, TXT)", type=["pdf", "docx", "txt"], key="cv_uploader")
|
| 373 |
+
cv_text_area = st.text_area("Or paste your CV text here (overrides file upload)", height=250, key="cv_text_area")
|
| 374 |
+
|
| 375 |
+
cv_content = ""
|
| 376 |
+
if uploaded_cv_file is not None:
|
| 377 |
+
if uploaded_cv_file.name.endswith('.pdf'):
|
| 378 |
+
cv_content = extract_text_from_pdf(uploaded_cv_file)
|
| 379 |
+
elif uploaded_cv_file.name.endswith('.docx'):
|
| 380 |
+
cv_content = extract_text_from_docx(uploaded_cv_file)
|
| 381 |
+
elif uploaded_cv_file.name.endswith('.txt'):
|
| 382 |
+
cv_content = uploaded_cv_file.read().decode("utf-8")
|
| 383 |
+
st.success("CV file uploaded and parsed successfully!")
|
| 384 |
+
elif cv_text_area: # If text area has content and no file uploaded
|
| 385 |
+
cv_content = cv_text_area
|
| 386 |
+
|
| 387 |
+
# Input for Job Description
|
| 388 |
+
st.header("2. Input Job Description")
|
| 389 |
+
job_desc_text_area = st.text_area("Paste the Job Description text here", height=250, key="jd_text_area")
|
| 390 |
+
|
| 391 |
+
# Analyze Button
|
| 392 |
+
st.markdown("---")
|
| 393 |
+
if st.button("β¨ Analyze CV Match β¨", use_container_width=True):
|
| 394 |
+
if not cv_content:
|
| 395 |
+
st.error("π¨ Please upload a CV file or paste your CV text to proceed.")
|
| 396 |
+
if not job_desc_text_area:
|
| 397 |
+
st.error("π¨ Please paste the Job Description text to proceed.")
|
| 398 |
+
|
| 399 |
+
if cv_content and job_desc_text_area:
|
| 400 |
+
with st.spinner("π Analyzing your documents... This might take a moment!"):
|
| 401 |
+
try:
|
| 402 |
+
analysis_results = perform_cv_job_analysis(cv_content, job_desc_text_area)
|
| 403 |
+
|
| 404 |
+
st.subheader("π‘ Analysis Results Summary π‘")
|
| 405 |
+
|
| 406 |
+
# Display KPIs in columns
|
| 407 |
+
col1, col2, col3 = st.columns(3)
|
| 408 |
+
with col1:
|
| 409 |
+
st.metric(label="Overall Match Score", value=f"{analysis_results['overall_match_score']}%")
|
| 410 |
+
with col2:
|
| 411 |
+
st.metric(label="Skill Match", value=f"{analysis_results['skill_match_percentage']}%")
|
| 412 |
+
with col3:
|
| 413 |
+
exp_status = analysis_results['experience_match_status']
|
| 414 |
+
if "Meets" in exp_status or "Exceeds" in exp_status:
|
| 415 |
+
st.metric(label="Experience Match", value=exp_status, delta="Good!")
|
| 416 |
+
else:
|
| 417 |
+
st.metric(label="Experience Match", value=exp_status, delta="Needs attention")
|
| 418 |
+
|
| 419 |
+
st.markdown("---")
|
| 420 |
+
|
| 421 |
+
st.subheader("π Visual Insights")
|
| 422 |
+
|
| 423 |
+
# Overall Match Plot
|
| 424 |
+
fig_overall = create_overall_match_plot(analysis_results['overall_match_score'])
|
| 425 |
+
st.pyplot(fig_overall)
|
| 426 |
+
plt.close(fig_overall) # Close figure to free memory
|
| 427 |
+
|
| 428 |
+
# Skill Match Plot
|
| 429 |
+
fig_skill = create_skill_match_plot(analysis_results['matched_skills'], analysis_results['missing_skills'])
|
| 430 |
+
if fig_skill:
|
| 431 |
+
st.pyplot(fig_skill)
|
| 432 |
+
plt.close(fig_skill)
|
| 433 |
+
else:
|
| 434 |
+
st.info("No specific skills identified in the job description for a detailed skill match breakdown.")
|
| 435 |
+
|
| 436 |
+
# Top Keywords Plot
|
| 437 |
+
fig_keywords = create_top_keywords_plot(analysis_results['top_cv_keywords'], analysis_results['top_jd_keywords'])
|
| 438 |
+
st.pyplot(fig_keywords)
|
| 439 |
+
plt.close(fig_keywords)
|
| 440 |
+
|
| 441 |
+
st.markdown("---")
|
| 442 |
+
st.subheader("π Detailed Breakdown")
|
| 443 |
+
|
| 444 |
+
st.markdown("#### Skills Analysis")
|
| 445 |
+
st.write(f"**β
Matched Skills:**", ", ".join(analysis_results['matched_skills']) if analysis_results['matched_skills'] else "None found matching job description.")
|
| 446 |
+
st.write(f"**β Missing Skills (from Job Description):**", ", ".join(analysis_results['missing_skills']) if analysis_results['missing_skills'] else "π₯³ None! Your CV has all specified skills.")
|
| 447 |
+
st.write(f"**π‘ Extra Skills in CV (not in Job Description):**", ", ".join(analysis_results['extra_skills_in_cv']) if analysis_results['extra_skills_in_cv'] else "None. (This is often fine, showing broader capability.)")
|
| 448 |
+
|
| 449 |
+
st.markdown("#### Keyword Relevance (Top TF-IDF Terms)")
|
| 450 |
+
st.write(f"**π€ Top Common Keywords:**", ", ".join(analysis_results['common_keywords']) if analysis_results['common_keywords'] else "No significant common keywords beyond skills.")
|
| 451 |
+
st.write(f"**π Top Keywords in Your CV:**", ", ".join(analysis_results['top_cv_keywords']) if analysis_results['top_cv_keywords'] else "N/A")
|
| 452 |
+
st.write(f"**π― Top Keywords in Job Description:**", ", ".join(analysis_results['top_jd_keywords']) if analysis_results['top_jd_keywords'] else "N/A")
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
st.markdown("#### Experience & Education Comparison")
|
| 456 |
+
st.write(f"**π€ Your CV's Experience:** `{analysis_results['cv_years_experience']}` years")
|
| 457 |
+
st.write(f"**πΌ Job's Required Experience:** `{analysis_results['jd_years_experience']}` years")
|
| 458 |
+
st.info(f"**Status:** {analysis_results['experience_match_status']}")
|
| 459 |
+
|
| 460 |
+
st.write(f"**π Your CV's Education:** `{analysis_results['cv_education_level']}`")
|
| 461 |
+
st.write(f"**π Job's Required Education:** `{analysis_results['jd_education_level']}`")
|
| 462 |
+
st.info(f"**Status:** {analysis_results['education_match_status']}")
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
except Exception as e:
|
| 466 |
+
st.error(f"An unexpected error occurred during analysis: {e}")
|
| 467 |
+
st.exception(e) # Show full traceback in Streamlit debug logs
|
| 468 |
+
|
| 469 |
+
st.markdown("---")
|
| 470 |
+
st.markdown("Developed with β€οΈ for Data Science by your mentor")
|