Spaces:
Sleeping
Sleeping
Update src/app.py
Browse files- src/app.py +102 -27
src/app.py
CHANGED
|
@@ -31,12 +31,16 @@ import language_tool_python
|
|
| 31 |
from sklearn.feature_extraction.text import TfidfVectorizer
|
| 32 |
from sklearn.metrics.pairwise import cosine_similarity
|
| 33 |
|
| 34 |
-
# Report generation
|
| 35 |
from reportlab.lib.pagesizes import letter
|
| 36 |
-
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
|
| 37 |
from reportlab.lib.styles import getSampleStyleSheet
|
| 38 |
from reportlab.lib.units import inch
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
# Download NLTK data if not already present
|
| 41 |
@st.cache_resource
|
| 42 |
def download_nltk_data():
|
|
@@ -305,6 +309,60 @@ class ResumeAnalyzer:
|
|
| 305 |
|
| 306 |
return summary
|
| 307 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
def main():
|
| 309 |
st.set_page_config(
|
| 310 |
page_title="AI Resume Analyzer",
|
|
@@ -347,6 +405,12 @@ def main():
|
|
| 347 |
# Process the resume
|
| 348 |
st.success("β
Resume uploaded and processed successfully!")
|
| 349 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
# Create tabs for different analyses
|
| 351 |
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
| 352 |
"π Overview", "π― Skills Analysis", "π Section Breakdown",
|
|
@@ -365,9 +429,6 @@ def main():
|
|
| 365 |
|
| 366 |
st.metric("Word Count", word_count)
|
| 367 |
st.metric("Character Count", char_count)
|
| 368 |
-
|
| 369 |
-
# Extract sections
|
| 370 |
-
sections = analyzer.extract_sections(text)
|
| 371 |
st.metric("Sections Found", len([s for s in sections.values() if s]))
|
| 372 |
|
| 373 |
with col2:
|
|
@@ -391,9 +452,6 @@ def main():
|
|
| 391 |
with tab2:
|
| 392 |
st.header("Skills Analysis")
|
| 393 |
|
| 394 |
-
# Extract skills
|
| 395 |
-
tech_skills, soft_skills = analyzer.extract_skills(text)
|
| 396 |
-
|
| 397 |
col1, col2 = st.columns(2)
|
| 398 |
|
| 399 |
with col1:
|
|
@@ -405,7 +463,7 @@ def main():
|
|
| 405 |
st.info("No technical skills detected")
|
| 406 |
|
| 407 |
with col2:
|
| 408 |
-
st.subheader("π€ Soft Skills")
|
| 409 |
if soft_skills:
|
| 410 |
for skill in soft_skills:
|
| 411 |
st.badge(skill, type="primary")
|
|
@@ -414,7 +472,6 @@ def main():
|
|
| 414 |
|
| 415 |
# Job role matching
|
| 416 |
st.subheader(f"π― Match Analysis for {selected_role}")
|
| 417 |
-
found_keywords, match_percentage = analyzer.keyword_matching(text, selected_role)
|
| 418 |
|
| 419 |
# Progress bar for match percentage
|
| 420 |
st.metric("Match Percentage", f"{match_percentage:.1f}%")
|
|
@@ -435,8 +492,6 @@ def main():
|
|
| 435 |
with tab3:
|
| 436 |
st.header("Section Breakdown")
|
| 437 |
|
| 438 |
-
sections = analyzer.extract_sections(text)
|
| 439 |
-
|
| 440 |
for section_name, content in sections.items():
|
| 441 |
if content:
|
| 442 |
with st.expander(f"π {section_name.title()} Section"):
|
|
@@ -447,9 +502,6 @@ def main():
|
|
| 447 |
with tab4:
|
| 448 |
st.header("ATS Analysis")
|
| 449 |
|
| 450 |
-
# Calculate ATS score
|
| 451 |
-
ats_score = analyzer.calculate_ats_score(text, sections)
|
| 452 |
-
|
| 453 |
col1, col2 = st.columns(2)
|
| 454 |
|
| 455 |
with col1:
|
|
@@ -566,9 +618,13 @@ def main():
|
|
| 566 |
|
| 567 |
# Generate downloadable report
|
| 568 |
st.subheader("π Download Report")
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 572 |
RESUME ANALYSIS REPORT
|
| 573 |
Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 574 |
|
|
@@ -578,7 +634,7 @@ OVERVIEW:
|
|
| 578 |
- Overall Score: {overall_score:.1f}/100
|
| 579 |
|
| 580 |
PERSONA SUMMARY:
|
| 581 |
-
{
|
| 582 |
|
| 583 |
TECHNICAL SKILLS FOUND:
|
| 584 |
{', '.join(tech_skills) if tech_skills else 'None detected'}
|
|
@@ -594,14 +650,33 @@ STRENGTHS:
|
|
| 594 |
|
| 595 |
AREAS FOR IMPROVEMENT:
|
| 596 |
{chr(10).join(f'- {i}' for i in improvements)}
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 605 |
else:
|
| 606 |
st.error("β Error processing the uploaded file. Please try a different file.")
|
| 607 |
|
|
|
|
| 31 |
from sklearn.feature_extraction.text import TfidfVectorizer
|
| 32 |
from sklearn.metrics.pairwise import cosine_similarity
|
| 33 |
|
| 34 |
+
# Report generation - FIXED IMPORTS
|
| 35 |
from reportlab.lib.pagesizes import letter
|
| 36 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
|
| 37 |
from reportlab.lib.styles import getSampleStyleSheet
|
| 38 |
from reportlab.lib.units import inch
|
| 39 |
|
| 40 |
+
# Chart imports (if you need charts for PDF reports)
|
| 41 |
+
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
| 42 |
+
from reportlab.graphics.shapes import Drawing
|
| 43 |
+
|
| 44 |
# Download NLTK data if not already present
|
| 45 |
@st.cache_resource
|
| 46 |
def download_nltk_data():
|
|
|
|
| 309 |
|
| 310 |
return summary
|
| 311 |
|
| 312 |
+
def create_pdf_report(self, text, sections, ats_score, match_percentage, selected_role, tech_skills, soft_skills, found_keywords):
|
| 313 |
+
"""Create a PDF report using ReportLab"""
|
| 314 |
+
buffer = io.BytesIO()
|
| 315 |
+
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
| 316 |
+
styles = getSampleStyleSheet()
|
| 317 |
+
story = []
|
| 318 |
+
|
| 319 |
+
# Title
|
| 320 |
+
story.append(Paragraph("Resume Analysis Report", styles['Title']))
|
| 321 |
+
story.append(Spacer(1, 12))
|
| 322 |
+
|
| 323 |
+
# Date
|
| 324 |
+
story.append(Paragraph(f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
|
| 325 |
+
story.append(Spacer(1, 12))
|
| 326 |
+
|
| 327 |
+
# Overview section
|
| 328 |
+
story.append(Paragraph("Overview", styles['Heading1']))
|
| 329 |
+
story.append(Paragraph(f"ATS Score: {ats_score}/100", styles['Normal']))
|
| 330 |
+
story.append(Paragraph(f"Role Match for {selected_role}: {match_percentage:.1f}%", styles['Normal']))
|
| 331 |
+
story.append(Paragraph(f"Overall Score: {(ats_score + match_percentage) / 2:.1f}/100", styles['Normal']))
|
| 332 |
+
story.append(Spacer(1, 12))
|
| 333 |
+
|
| 334 |
+
# Skills section
|
| 335 |
+
story.append(Paragraph("Skills Analysis", styles['Heading1']))
|
| 336 |
+
if tech_skills:
|
| 337 |
+
story.append(Paragraph(f"Technical Skills: {', '.join(tech_skills)}", styles['Normal']))
|
| 338 |
+
if soft_skills:
|
| 339 |
+
story.append(Paragraph(f"Soft Skills: {', '.join(soft_skills)}", styles['Normal']))
|
| 340 |
+
if found_keywords:
|
| 341 |
+
story.append(Paragraph(f"Role-specific Keywords Found: {', '.join(found_keywords)}", styles['Normal']))
|
| 342 |
+
story.append(Spacer(1, 12))
|
| 343 |
+
|
| 344 |
+
# Recommendations
|
| 345 |
+
story.append(Paragraph("Recommendations", styles['Heading1']))
|
| 346 |
+
recommendations = []
|
| 347 |
+
|
| 348 |
+
if ats_score < 70:
|
| 349 |
+
recommendations.extend([
|
| 350 |
+
"β’ Add more bullet points to improve readability",
|
| 351 |
+
"β’ Include contact information (email, phone)",
|
| 352 |
+
"β’ Ensure all major sections are present"
|
| 353 |
+
])
|
| 354 |
+
|
| 355 |
+
if match_percentage < 60:
|
| 356 |
+
recommendations.append(f"β’ Include more {selected_role}-specific keywords")
|
| 357 |
+
|
| 358 |
+
for rec in recommendations:
|
| 359 |
+
story.append(Paragraph(rec, styles['Normal']))
|
| 360 |
+
|
| 361 |
+
# Build PDF
|
| 362 |
+
doc.build(story)
|
| 363 |
+
buffer.seek(0)
|
| 364 |
+
return buffer
|
| 365 |
+
|
| 366 |
def main():
|
| 367 |
st.set_page_config(
|
| 368 |
page_title="AI Resume Analyzer",
|
|
|
|
| 405 |
# Process the resume
|
| 406 |
st.success("β
Resume uploaded and processed successfully!")
|
| 407 |
|
| 408 |
+
# Extract data for analysis
|
| 409 |
+
sections = analyzer.extract_sections(text)
|
| 410 |
+
tech_skills, soft_skills = analyzer.extract_skills(text)
|
| 411 |
+
found_keywords, match_percentage = analyzer.keyword_matching(text, selected_role)
|
| 412 |
+
ats_score = analyzer.calculate_ats_score(text, sections)
|
| 413 |
+
|
| 414 |
# Create tabs for different analyses
|
| 415 |
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
| 416 |
"π Overview", "π― Skills Analysis", "π Section Breakdown",
|
|
|
|
| 429 |
|
| 430 |
st.metric("Word Count", word_count)
|
| 431 |
st.metric("Character Count", char_count)
|
|
|
|
|
|
|
|
|
|
| 432 |
st.metric("Sections Found", len([s for s in sections.values() if s]))
|
| 433 |
|
| 434 |
with col2:
|
|
|
|
| 452 |
with tab2:
|
| 453 |
st.header("Skills Analysis")
|
| 454 |
|
|
|
|
|
|
|
|
|
|
| 455 |
col1, col2 = st.columns(2)
|
| 456 |
|
| 457 |
with col1:
|
|
|
|
| 463 |
st.info("No technical skills detected")
|
| 464 |
|
| 465 |
with col2:
|
| 466 |
+
st.subheader("π€ Soft Skills")
|
| 467 |
if soft_skills:
|
| 468 |
for skill in soft_skills:
|
| 469 |
st.badge(skill, type="primary")
|
|
|
|
| 472 |
|
| 473 |
# Job role matching
|
| 474 |
st.subheader(f"π― Match Analysis for {selected_role}")
|
|
|
|
| 475 |
|
| 476 |
# Progress bar for match percentage
|
| 477 |
st.metric("Match Percentage", f"{match_percentage:.1f}%")
|
|
|
|
| 492 |
with tab3:
|
| 493 |
st.header("Section Breakdown")
|
| 494 |
|
|
|
|
|
|
|
| 495 |
for section_name, content in sections.items():
|
| 496 |
if content:
|
| 497 |
with st.expander(f"π {section_name.title()} Section"):
|
|
|
|
| 502 |
with tab4:
|
| 503 |
st.header("ATS Analysis")
|
| 504 |
|
|
|
|
|
|
|
|
|
|
| 505 |
col1, col2 = st.columns(2)
|
| 506 |
|
| 507 |
with col1:
|
|
|
|
| 618 |
|
| 619 |
# Generate downloadable report
|
| 620 |
st.subheader("π Download Report")
|
| 621 |
+
|
| 622 |
+
col1, col2 = st.columns(2)
|
| 623 |
+
|
| 624 |
+
with col1:
|
| 625 |
+
# Text report
|
| 626 |
+
if st.button("Generate Text Report"):
|
| 627 |
+
report_content = f"""
|
| 628 |
RESUME ANALYSIS REPORT
|
| 629 |
Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 630 |
|
|
|
|
| 634 |
- Overall Score: {overall_score:.1f}/100
|
| 635 |
|
| 636 |
PERSONA SUMMARY:
|
| 637 |
+
{analyzer.generate_persona_summary(text, sections)}
|
| 638 |
|
| 639 |
TECHNICAL SKILLS FOUND:
|
| 640 |
{', '.join(tech_skills) if tech_skills else 'None detected'}
|
|
|
|
| 650 |
|
| 651 |
AREAS FOR IMPROVEMENT:
|
| 652 |
{chr(10).join(f'- {i}' for i in improvements)}
|
| 653 |
+
"""
|
| 654 |
+
|
| 655 |
+
st.download_button(
|
| 656 |
+
label="π₯ Download Text Report",
|
| 657 |
+
data=report_content,
|
| 658 |
+
file_name=f"resume_analysis_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
|
| 659 |
+
mime="text/plain"
|
| 660 |
+
)
|
| 661 |
+
|
| 662 |
+
with col2:
|
| 663 |
+
# PDF report
|
| 664 |
+
if st.button("Generate PDF Report"):
|
| 665 |
+
try:
|
| 666 |
+
pdf_buffer = analyzer.create_pdf_report(
|
| 667 |
+
text, sections, ats_score, match_percentage,
|
| 668 |
+
selected_role, tech_skills, soft_skills, found_keywords
|
| 669 |
+
)
|
| 670 |
+
|
| 671 |
+
st.download_button(
|
| 672 |
+
label="π Download PDF Report",
|
| 673 |
+
data=pdf_buffer.getvalue(),
|
| 674 |
+
file_name=f"resume_analysis_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
|
| 675 |
+
mime="application/pdf"
|
| 676 |
+
)
|
| 677 |
+
except Exception as e:
|
| 678 |
+
st.error(f"Error generating PDF: {str(e)}")
|
| 679 |
+
st.info("PDF generation requires all ReportLab dependencies. Using text report as fallback.")
|
| 680 |
else:
|
| 681 |
st.error("β Error processing the uploaded file. Please try a different file.")
|
| 682 |
|