Spaces:
Running
Running
Update src/app.py
Browse files- src/app.py +333 -199
src/app.py
CHANGED
|
@@ -4,6 +4,9 @@ import numpy as np
|
|
| 4 |
import re
|
| 5 |
import io
|
| 6 |
import base64
|
|
|
|
|
|
|
|
|
|
| 7 |
from collections import Counter
|
| 8 |
import matplotlib.pyplot as plt
|
| 9 |
import seaborn as sns
|
|
@@ -58,6 +61,58 @@ from reportlab.lib.units import inch
|
|
| 58 |
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
| 59 |
from reportlab.graphics.shapes import Drawing
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
# Download NLTK data if not already present
|
| 62 |
@st.cache_resource
|
| 63 |
def download_nltk_data():
|
|
@@ -146,6 +201,7 @@ def basic_grammar_check(text):
|
|
| 146 |
class ResumeAnalyzer:
|
| 147 |
def __init__(self):
|
| 148 |
self.nlp, self.grammar_tool = init_tools()
|
|
|
|
| 149 |
|
| 150 |
try:
|
| 151 |
self.stop_words = set(stopwords.words('english'))
|
|
@@ -155,32 +211,55 @@ class ResumeAnalyzer:
|
|
| 155 |
|
| 156 |
self.lemmatizer = WordNetLemmatizer()
|
| 157 |
|
| 158 |
-
#
|
| 159 |
self.job_keywords = {
|
| 160 |
"Data Scientist": ["python", "machine learning", "statistics", "pandas", "numpy", "scikit-learn",
|
| 161 |
-
"tensorflow", "pytorch", "sql", "data analysis", "visualization", "jupyter"],
|
| 162 |
"Software Engineer": ["programming", "java", "python", "javascript", "react", "node.js", "database",
|
| 163 |
-
"git", "agile", "testing", "debugging", "api", "frontend", "backend"],
|
| 164 |
"Product Manager": ["product", "strategy", "roadmap", "stakeholder", "analytics", "user experience",
|
| 165 |
-
"market research", "agile", "scrum", "requirements", "metrics"],
|
| 166 |
"Marketing Manager": ["marketing", "digital marketing", "seo", "social media", "analytics", "campaigns",
|
| 167 |
-
"brand", "content", "advertising", "growth"],
|
| 168 |
"Data Analyst": ["sql", "excel", "python", "tableau", "power bi", "statistics", "reporting",
|
| 169 |
-
"data visualization", "business intelligence", "analytics"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
}
|
| 171 |
|
| 172 |
# Common skills database
|
| 173 |
self.technical_skills = [
|
| 174 |
-
"python", "java", "javascript", "c++", "
|
|
|
|
| 175 |
"machine learning", "deep learning", "tensorflow", "pytorch", "pandas", "numpy",
|
| 176 |
-
"docker", "kubernetes", "aws", "azure", "git", "jenkins", "ci/cd", "mongodb", "postgresql",
|
| 177 |
-
"redis", "elasticsearch", "spark", "hadoop", "tableau", "power bi", "excel"
|
|
|
|
| 178 |
]
|
| 179 |
|
| 180 |
self.soft_skills = [
|
| 181 |
"leadership", "communication", "teamwork", "problem solving", "critical thinking",
|
| 182 |
"project management", "time management", "adaptability", "creativity", "analytical",
|
| 183 |
-
"collaboration", "innovation", "strategic thinking", "customer service", "negotiation"
|
|
|
|
| 184 |
]
|
| 185 |
|
| 186 |
def extract_text_from_pdf(self, file):
|
|
@@ -406,6 +485,32 @@ class ResumeAnalyzer:
|
|
| 406 |
|
| 407 |
return summary
|
| 408 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
def create_pdf_report(self, text, sections, ats_score, match_percentage, selected_role, tech_skills, soft_skills, found_keywords):
|
| 410 |
"""Create a PDF report using ReportLab"""
|
| 411 |
buffer = io.BytesIO()
|
|
@@ -462,17 +567,17 @@ class ResumeAnalyzer:
|
|
| 462 |
|
| 463 |
def main():
|
| 464 |
st.set_page_config(
|
| 465 |
-
page_title="AI Resume Analyzer",
|
| 466 |
page_icon="π",
|
| 467 |
layout="wide"
|
| 468 |
)
|
| 469 |
|
| 470 |
-
st.title("π AI-Powered Resume Analyzer")
|
| 471 |
-
st.markdown("Upload your resume and get comprehensive analysis with
|
| 472 |
|
| 473 |
-
# Show dependency status
|
| 474 |
-
with st.expander("π
|
| 475 |
-
col1, col2, col3 = st.columns(
|
| 476 |
with col1:
|
| 477 |
if SPACY_AVAILABLE:
|
| 478 |
st.success("β
spaCy Available")
|
|
@@ -490,6 +595,12 @@ def main():
|
|
| 490 |
st.success("β
Grammar Tool Available")
|
| 491 |
else:
|
| 492 |
st.warning("β οΈ Grammar Tool Not Available")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
|
| 494 |
# Initialize analyzer
|
| 495 |
analyzer = ResumeAnalyzer()
|
|
@@ -499,6 +610,12 @@ def main():
|
|
| 499 |
job_roles = list(analyzer.job_keywords.keys())
|
| 500 |
selected_role = st.sidebar.selectbox("Select Target Job Role:", job_roles)
|
| 501 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
# File upload section
|
| 503 |
st.header("π Upload Your Resume")
|
| 504 |
uploaded_file = st.file_uploader(
|
|
@@ -523,6 +640,9 @@ def main():
|
|
| 523 |
# Process the resume
|
| 524 |
st.success("β
Resume uploaded and processed successfully!")
|
| 525 |
|
|
|
|
|
|
|
|
|
|
| 526 |
# Extract data for analysis
|
| 527 |
sections = analyzer.extract_sections(text)
|
| 528 |
tech_skills, soft_skills = analyzer.extract_skills(text)
|
|
@@ -530,9 +650,9 @@ def main():
|
|
| 530 |
ats_score = analyzer.calculate_ats_score(text, sections)
|
| 531 |
|
| 532 |
# Create tabs for different analyses
|
| 533 |
-
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
| 534 |
"π Overview", "π― Skills Analysis", "π Section Breakdown",
|
| 535 |
-
"π ATS Analysis", "π Report & Suggestions"
|
| 536 |
])
|
| 537 |
|
| 538 |
with tab1:
|
|
@@ -588,8 +708,29 @@ def main():
|
|
| 588 |
skills_text = " β’ ".join(tech_skills)
|
| 589 |
st.success(f"Found {len(tech_skills)} technical skills:")
|
| 590 |
st.write(skills_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 591 |
else:
|
| 592 |
-
st.
|
|
|
|
| 593 |
|
| 594 |
with col2:
|
| 595 |
st.subheader("π€ Soft Skills")
|
|
@@ -597,63 +738,99 @@ def main():
|
|
| 597 |
skills_text = " β’ ".join(soft_skills)
|
| 598 |
st.success(f"Found {len(soft_skills)} soft skills:")
|
| 599 |
st.write(skills_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 600 |
else:
|
| 601 |
-
st.
|
|
|
|
| 602 |
|
| 603 |
-
#
|
| 604 |
-
st.subheader(
|
| 605 |
|
| 606 |
-
#
|
| 607 |
-
st.
|
| 608 |
-
st.progress(match_percentage / 100)
|
| 609 |
|
| 610 |
-
|
| 611 |
-
st.
|
| 612 |
-
keywords_text = " β’ ".join(found_keywords)
|
| 613 |
-
st.success(keywords_text)
|
| 614 |
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
if missing_keywords:
|
| 618 |
-
st.write("**Missing Keywords (Consider Adding):**")
|
| 619 |
-
missing_text = " β’ ".join(missing_keywords[:10]) # Show top 10
|
| 620 |
-
st.warning(missing_text)
|
| 621 |
-
|
| 622 |
-
with tab3:
|
| 623 |
-
st.header("Section Breakdown")
|
| 624 |
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 629 |
else:
|
| 630 |
-
st.
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 634 |
|
| 635 |
col1, col2 = st.columns(2)
|
| 636 |
|
| 637 |
with col1:
|
| 638 |
-
#
|
| 639 |
fig = go.Figure(go.Indicator(
|
| 640 |
-
mode="gauge+number
|
| 641 |
-
value=
|
| 642 |
-
domain={'x': [0, 1], 'y': [0, 1]},
|
| 643 |
-
title={'text': "
|
| 644 |
-
|
| 645 |
-
gauge={
|
| 646 |
'axis': {'range': [None, 100]},
|
| 647 |
-
'bar': {'color': "
|
| 648 |
'steps': [
|
| 649 |
-
{'range': [0,
|
| 650 |
-
{'range': [
|
| 651 |
-
{'range': [
|
| 652 |
],
|
| 653 |
'threshold': {
|
| 654 |
'line': {'color': "red", 'width': 4},
|
| 655 |
'thickness': 0.75,
|
| 656 |
-
'value':
|
| 657 |
}
|
| 658 |
}
|
| 659 |
))
|
|
@@ -661,153 +838,110 @@ def main():
|
|
| 661 |
st.plotly_chart(fig, use_container_width=True)
|
| 662 |
|
| 663 |
with col2:
|
| 664 |
-
|
| 665 |
-
st.
|
| 666 |
-
grammar_errors = analyzer.grammar_check(text)
|
| 667 |
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
|
|
|
|
|
|
|
|
|
| 672 |
else:
|
| 673 |
-
st.
|
| 674 |
-
|
| 675 |
-
# ATS recommendations
|
| 676 |
-
st.subheader("π‘ ATS Improvement Suggestions")
|
| 677 |
-
recommendations = []
|
| 678 |
-
|
| 679 |
-
if ats_score < 70:
|
| 680 |
-
recommendations.extend([
|
| 681 |
-
"Add more bullet points to improve readability",
|
| 682 |
-
"Include contact information (email, phone)",
|
| 683 |
-
"Ensure all major sections are present",
|
| 684 |
-
"Use standard section headings"
|
| 685 |
-
])
|
| 686 |
-
|
| 687 |
-
if match_percentage < 60:
|
| 688 |
-
recommendations.append(f"Include more {selected_role}-specific keywords")
|
| 689 |
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
with col2:
|
| 707 |
-
st.metric("Role Match", f"{match_percentage:.1f}%",
|
| 708 |
-
delta=f"{match_percentage-60:.1f}% vs Good Match" if match_percentage >= 60 else f"{match_percentage-60:.1f}% vs Good Match")
|
| 709 |
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
st.
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
strengths.append("Resume is ATS-friendly")
|
| 721 |
-
if match_percentage >= 70:
|
| 722 |
-
strengths.append(f"Strong match for {selected_role} position")
|
| 723 |
-
if len(tech_skills) >= 5:
|
| 724 |
-
strengths.append("Rich technical skill set")
|
| 725 |
-
if len(sections) >= 4:
|
| 726 |
-
strengths.append("Well-structured with multiple sections")
|
| 727 |
-
|
| 728 |
-
if strengths:
|
| 729 |
-
st.success("**Strengths:**")
|
| 730 |
-
for strength in strengths:
|
| 731 |
-
st.write(f"β
{strength}")
|
| 732 |
-
|
| 733 |
-
# Areas for improvement
|
| 734 |
-
improvements = []
|
| 735 |
-
if ats_score < 70:
|
| 736 |
-
improvements.append("Improve ATS compatibility")
|
| 737 |
-
if match_percentage < 60:
|
| 738 |
-
improvements.append("Add more role-specific keywords")
|
| 739 |
-
if not sections.get('projects'):
|
| 740 |
-
improvements.append("Consider adding a projects section")
|
| 741 |
-
if len(soft_skills) < 3:
|
| 742 |
-
improvements.append("Highlight more soft skills")
|
| 743 |
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
for improvement in improvements:
|
| 747 |
-
st.write(f"β οΈ {improvement}")
|
| 748 |
|
| 749 |
-
#
|
| 750 |
-
|
|
|
|
| 751 |
|
| 752 |
-
|
|
|
|
| 753 |
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
report_content = f"""
|
| 757 |
-
RESUME ANALYSIS REPORT
|
| 758 |
-
Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 759 |
-
|
| 760 |
-
OVERVIEW:
|
| 761 |
-
- ATS Score: {ats_score}/100
|
| 762 |
-
- Role Match: {match_percentage:.1f}%
|
| 763 |
-
- Overall Score: {overall_score:.1f}/100
|
| 764 |
-
|
| 765 |
-
PERSONA SUMMARY:
|
| 766 |
-
{analyzer.generate_persona_summary(text, sections)}
|
| 767 |
-
|
| 768 |
-
TECHNICAL SKILLS FOUND:
|
| 769 |
-
{', '.join(tech_skills) if tech_skills else 'None detected'}
|
| 770 |
-
|
| 771 |
-
SOFT SKILLS FOUND:
|
| 772 |
-
{', '.join(soft_skills) if soft_skills else 'None detected'}
|
| 773 |
-
|
| 774 |
-
ROLE-SPECIFIC KEYWORDS FOUND:
|
| 775 |
-
{', '.join(found_keywords) if found_keywords else 'None found'}
|
| 776 |
-
|
| 777 |
-
STRENGTHS:
|
| 778 |
-
{chr(10).join(f'- {s}' for s in strengths)}
|
| 779 |
-
|
| 780 |
-
AREAS FOR IMPROVEMENT:
|
| 781 |
-
{chr(10).join(f'- {i}' for i in improvements)}
|
| 782 |
-
"""
|
| 783 |
|
| 784 |
-
st.
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
)
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import re
|
| 5 |
import io
|
| 6 |
import base64
|
| 7 |
+
import os
|
| 8 |
+
import requests
|
| 9 |
+
import json
|
| 10 |
from collections import Counter
|
| 11 |
import matplotlib.pyplot as plt
|
| 12 |
import seaborn as sns
|
|
|
|
| 61 |
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
| 62 |
from reportlab.graphics.shapes import Drawing
|
| 63 |
|
| 64 |
+
# Claude Chatbot Class
|
| 65 |
+
class ClaudeChatbot:
|
| 66 |
+
def __init__(self):
|
| 67 |
+
self.api_key = os.getenv('OPENROUTER_API_KEY')
|
| 68 |
+
self.base_url = "https://openrouter.ai/api/v1/chat/completions"
|
| 69 |
+
self.model = "anthropic/claude-sonnet-4"
|
| 70 |
+
|
| 71 |
+
if not self.api_key:
|
| 72 |
+
st.error("β OPENROUTER_API_KEY not found in environment variables!")
|
| 73 |
+
st.info("Please set your OpenRouter API key in the environment variables.")
|
| 74 |
+
|
| 75 |
+
def generate_response(self, prompt, context="", max_tokens=1500):
|
| 76 |
+
"""Generate response using Claude via OpenRouter"""
|
| 77 |
+
if not self.api_key:
|
| 78 |
+
return "Error: API key not configured"
|
| 79 |
+
|
| 80 |
+
headers = {
|
| 81 |
+
"Authorization": f"Bearer {self.api_key}",
|
| 82 |
+
"Content-Type": "application/json",
|
| 83 |
+
"HTTP-Referer": "https://your-app-url.com", # Replace with your actual URL
|
| 84 |
+
"X-Title": "AI Resume Analyzer"
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
system_prompt = """You are an expert resume and career consultant with deep knowledge of hiring practices, ATS systems, and industry requirements. You provide actionable, specific advice to help job seekers improve their resumes and career prospects. Always be encouraging but honest in your feedback."""
|
| 88 |
+
|
| 89 |
+
if context:
|
| 90 |
+
system_prompt += f"\n\nContext about the user's resume:\n{context}"
|
| 91 |
+
|
| 92 |
+
data = {
|
| 93 |
+
"model": self.model,
|
| 94 |
+
"messages": [
|
| 95 |
+
{"role": "system", "content": system_prompt},
|
| 96 |
+
{"role": "user", "content": prompt}
|
| 97 |
+
],
|
| 98 |
+
"max_tokens": max_tokens,
|
| 99 |
+
"temperature": 0.7
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
try:
|
| 103 |
+
response = requests.post(self.base_url, headers=headers, json=data, timeout=30)
|
| 104 |
+
response.raise_for_status()
|
| 105 |
+
|
| 106 |
+
result = response.json()
|
| 107 |
+
return result['choices'][0]['message']['content']
|
| 108 |
+
|
| 109 |
+
except requests.exceptions.RequestException as e:
|
| 110 |
+
return f"Error connecting to API: {str(e)}"
|
| 111 |
+
except KeyError as e:
|
| 112 |
+
return f"Error parsing response: {str(e)}"
|
| 113 |
+
except Exception as e:
|
| 114 |
+
return f"Unexpected error: {str(e)}"
|
| 115 |
+
|
| 116 |
# Download NLTK data if not already present
|
| 117 |
@st.cache_resource
|
| 118 |
def download_nltk_data():
|
|
|
|
| 201 |
class ResumeAnalyzer:
|
| 202 |
def __init__(self):
|
| 203 |
self.nlp, self.grammar_tool = init_tools()
|
| 204 |
+
self.chatbot = ClaudeChatbot()
|
| 205 |
|
| 206 |
try:
|
| 207 |
self.stop_words = set(stopwords.words('english'))
|
|
|
|
| 211 |
|
| 212 |
self.lemmatizer = WordNetLemmatizer()
|
| 213 |
|
| 214 |
+
# Expanded job role keywords dictionary (15 total roles)
|
| 215 |
self.job_keywords = {
|
| 216 |
"Data Scientist": ["python", "machine learning", "statistics", "pandas", "numpy", "scikit-learn",
|
| 217 |
+
"tensorflow", "pytorch", "sql", "data analysis", "visualization", "jupyter", "r", "statistics", "deep learning"],
|
| 218 |
"Software Engineer": ["programming", "java", "python", "javascript", "react", "node.js", "database",
|
| 219 |
+
"git", "agile", "testing", "debugging", "api", "frontend", "backend", "algorithms", "data structures"],
|
| 220 |
"Product Manager": ["product", "strategy", "roadmap", "stakeholder", "analytics", "user experience",
|
| 221 |
+
"market research", "agile", "scrum", "requirements", "metrics", "wireframes", "user stories"],
|
| 222 |
"Marketing Manager": ["marketing", "digital marketing", "seo", "social media", "analytics", "campaigns",
|
| 223 |
+
"brand", "content", "advertising", "growth", "conversion", "roi", "crm"],
|
| 224 |
"Data Analyst": ["sql", "excel", "python", "tableau", "power bi", "statistics", "reporting",
|
| 225 |
+
"data visualization", "business intelligence", "analytics", "dashboards", "kpi"],
|
| 226 |
+
"DevOps Engineer": ["docker", "kubernetes", "aws", "azure", "gcp", "jenkins", "ci/cd", "terraform",
|
| 227 |
+
"ansible", "monitoring", "linux", "bash", "infrastructure", "deployment"],
|
| 228 |
+
"UI/UX Designer": ["figma", "sketch", "adobe xd", "prototyping", "wireframes", "user research",
|
| 229 |
+
"usability testing", "design systems", "responsive design", "accessibility", "typography"],
|
| 230 |
+
"Cybersecurity Analyst": ["security", "penetration testing", "vulnerability assessment", "siem", "firewall",
|
| 231 |
+
"incident response", "compliance", "risk assessment", "cryptography", "network security"],
|
| 232 |
+
"Business Analyst": ["requirements gathering", "process improvement", "stakeholder management", "documentation",
|
| 233 |
+
"business process", "gap analysis", "user stories", "workflow", "project management"],
|
| 234 |
+
"Full Stack Developer": ["html", "css", "javascript", "react", "angular", "vue", "node.js", "express",
|
| 235 |
+
"mongodb", "postgresql", "rest api", "graphql", "version control", "responsive design"],
|
| 236 |
+
"Machine Learning Engineer": ["tensorflow", "pytorch", "keras", "scikit-learn", "mlops", "model deployment",
|
| 237 |
+
"feature engineering", "model optimization", "docker", "kubernetes", "python", "deep learning"],
|
| 238 |
+
"Cloud Architect": ["aws", "azure", "gcp", "cloud migration", "serverless", "microservices", "containerization",
|
| 239 |
+
"infrastructure as code", "cost optimization", "scalability", "security"],
|
| 240 |
+
"Sales Manager": ["sales", "crm", "lead generation", "client relationship", "negotiation", "revenue growth",
|
| 241 |
+
"pipeline management", "forecasting", "team leadership", "quota attainment"],
|
| 242 |
+
"Project Manager": ["project management", "pmp", "agile", "scrum", "kanban", "risk management",
|
| 243 |
+
"stakeholder communication", "budget management", "timeline", "resource allocation"],
|
| 244 |
+
"Quality Assurance Engineer": ["testing", "automation", "selenium", "junit", "test cases", "bug tracking",
|
| 245 |
+
"regression testing", "performance testing", "api testing", "quality standards"]
|
| 246 |
}
|
| 247 |
|
| 248 |
# Common skills database
|
| 249 |
self.technical_skills = [
|
| 250 |
+
"python", "java", "javascript", "c++", "c#", "php", "ruby", "go", "rust", "swift",
|
| 251 |
+
"sql", "html", "css", "react", "angular", "vue", "node.js", "express", "django", "flask",
|
| 252 |
"machine learning", "deep learning", "tensorflow", "pytorch", "pandas", "numpy",
|
| 253 |
+
"docker", "kubernetes", "aws", "azure", "gcp", "git", "jenkins", "ci/cd", "mongodb", "postgresql",
|
| 254 |
+
"redis", "elasticsearch", "spark", "hadoop", "tableau", "power bi", "excel", "figma", "sketch",
|
| 255 |
+
"linux", "bash", "terraform", "ansible", "selenium", "junit", "jira", "confluence"
|
| 256 |
]
|
| 257 |
|
| 258 |
self.soft_skills = [
|
| 259 |
"leadership", "communication", "teamwork", "problem solving", "critical thinking",
|
| 260 |
"project management", "time management", "adaptability", "creativity", "analytical",
|
| 261 |
+
"collaboration", "innovation", "strategic thinking", "customer service", "negotiation",
|
| 262 |
+
"presentation", "mentoring", "conflict resolution", "decision making", "emotional intelligence"
|
| 263 |
]
|
| 264 |
|
| 265 |
def extract_text_from_pdf(self, file):
|
|
|
|
| 485 |
|
| 486 |
return summary
|
| 487 |
|
| 488 |
+
def get_claude_analysis(self, text, sections, job_role, ats_score, match_percentage):
|
| 489 |
+
"""Get detailed analysis from Claude"""
|
| 490 |
+
context = f"""
|
| 491 |
+
Resume Analysis Data:
|
| 492 |
+
- Target Role: {job_role}
|
| 493 |
+
- ATS Score: {ats_score}/100
|
| 494 |
+
- Role Match: {match_percentage:.1f}%
|
| 495 |
+
- Word Count: {len(text.split())}
|
| 496 |
+
- Sections Found: {list(sections.keys())}
|
| 497 |
+
- Resume Text (first 2000 chars): {text[:2000]}
|
| 498 |
+
"""
|
| 499 |
+
|
| 500 |
+
prompt = f"""
|
| 501 |
+
Please provide a comprehensive analysis of this resume for a {job_role} position. Include:
|
| 502 |
+
|
| 503 |
+
1. **Strengths**: What are the standout elements?
|
| 504 |
+
2. **Improvement Areas**: Specific areas that need work
|
| 505 |
+
3. **Missing Elements**: Key components that should be added
|
| 506 |
+
4. **Industry-Specific Advice**: Tailored recommendations for {job_role}
|
| 507 |
+
5. **Action Items**: 3-5 concrete steps to improve the resume
|
| 508 |
+
|
| 509 |
+
Keep the analysis professional, constructive, and actionable.
|
| 510 |
+
"""
|
| 511 |
+
|
| 512 |
+
return self.chatbot.generate_response(prompt, context)
|
| 513 |
+
|
| 514 |
def create_pdf_report(self, text, sections, ats_score, match_percentage, selected_role, tech_skills, soft_skills, found_keywords):
|
| 515 |
"""Create a PDF report using ReportLab"""
|
| 516 |
buffer = io.BytesIO()
|
|
|
|
| 567 |
|
| 568 |
def main():
|
| 569 |
st.set_page_config(
|
| 570 |
+
page_title="AI Resume Analyzer with Claude",
|
| 571 |
page_icon="π",
|
| 572 |
layout="wide"
|
| 573 |
)
|
| 574 |
|
| 575 |
+
st.title("π AI-Powered Resume Analyzer with Claude Assistant")
|
| 576 |
+
st.markdown("Upload your resume and get comprehensive analysis with AI-powered insights!")
|
| 577 |
|
| 578 |
+
# Show dependency and API status
|
| 579 |
+
with st.expander("π System Status", expanded=False):
|
| 580 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 581 |
with col1:
|
| 582 |
if SPACY_AVAILABLE:
|
| 583 |
st.success("β
spaCy Available")
|
|
|
|
| 595 |
st.success("β
Grammar Tool Available")
|
| 596 |
else:
|
| 597 |
st.warning("β οΈ Grammar Tool Not Available")
|
| 598 |
+
|
| 599 |
+
with col4:
|
| 600 |
+
if os.getenv('OPENROUTER_API_KEY'):
|
| 601 |
+
st.success("β
Claude API Available")
|
| 602 |
+
else:
|
| 603 |
+
st.error("β Claude API Not Available")
|
| 604 |
|
| 605 |
# Initialize analyzer
|
| 606 |
analyzer = ResumeAnalyzer()
|
|
|
|
| 610 |
job_roles = list(analyzer.job_keywords.keys())
|
| 611 |
selected_role = st.sidebar.selectbox("Select Target Job Role:", job_roles)
|
| 612 |
|
| 613 |
+
# Initialize session state for chat
|
| 614 |
+
if "chat_history" not in st.session_state:
|
| 615 |
+
st.session_state.chat_history = []
|
| 616 |
+
if "resume_context" not in st.session_state:
|
| 617 |
+
st.session_state.resume_context = ""
|
| 618 |
+
|
| 619 |
# File upload section
|
| 620 |
st.header("π Upload Your Resume")
|
| 621 |
uploaded_file = st.file_uploader(
|
|
|
|
| 640 |
# Process the resume
|
| 641 |
st.success("β
Resume uploaded and processed successfully!")
|
| 642 |
|
| 643 |
+
# Store resume context for chatbot
|
| 644 |
+
st.session_state.resume_context = text
|
| 645 |
+
|
| 646 |
# Extract data for analysis
|
| 647 |
sections = analyzer.extract_sections(text)
|
| 648 |
tech_skills, soft_skills = analyzer.extract_skills(text)
|
|
|
|
| 650 |
ats_score = analyzer.calculate_ats_score(text, sections)
|
| 651 |
|
| 652 |
# Create tabs for different analyses
|
| 653 |
+
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
|
| 654 |
"π Overview", "π― Skills Analysis", "π Section Breakdown",
|
| 655 |
+
"π ATS Analysis", "π Report & Suggestions", "π€ AI Assistant"
|
| 656 |
])
|
| 657 |
|
| 658 |
with tab1:
|
|
|
|
| 708 |
skills_text = " β’ ".join(tech_skills)
|
| 709 |
st.success(f"Found {len(tech_skills)} technical skills:")
|
| 710 |
st.write(skills_text)
|
| 711 |
+
|
| 712 |
+
# Skills distribution chart
|
| 713 |
+
if len(tech_skills) > 5:
|
| 714 |
+
skills_df = pd.DataFrame({
|
| 715 |
+
'Skill': tech_skills[:10],
|
| 716 |
+
'Count': [1] * len(tech_skills[:10])
|
| 717 |
+
})
|
| 718 |
+
fig = px.pie(skills_df, values='Count', names='Skill',
|
| 719 |
+
title='Technical Skills Distribution')
|
| 720 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 721 |
+
else:
|
| 722 |
+
# Simple bar chart for fewer skills
|
| 723 |
+
skills_df = pd.DataFrame({
|
| 724 |
+
'Skill': tech_skills,
|
| 725 |
+
'Count': [1] * len(tech_skills)
|
| 726 |
+
})
|
| 727 |
+
fig = px.bar(skills_df, x='Skill', y='Count',
|
| 728 |
+
title='Technical Skills Found')
|
| 729 |
+
fig.update_xaxis(tickangle=45)
|
| 730 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 731 |
else:
|
| 732 |
+
st.warning("No technical skills detected")
|
| 733 |
+
st.info("π‘ Consider adding technical skills relevant to your field")
|
| 734 |
|
| 735 |
with col2:
|
| 736 |
st.subheader("π€ Soft Skills")
|
|
|
|
| 738 |
skills_text = " β’ ".join(soft_skills)
|
| 739 |
st.success(f"Found {len(soft_skills)} soft skills:")
|
| 740 |
st.write(skills_text)
|
| 741 |
+
|
| 742 |
+
# Soft skills chart
|
| 743 |
+
if len(soft_skills) > 3:
|
| 744 |
+
soft_df = pd.DataFrame({
|
| 745 |
+
'Skill': soft_skills[:8],
|
| 746 |
+
'Count': [1] * len(soft_skills[:8])
|
| 747 |
+
})
|
| 748 |
+
fig = px.bar(soft_df, x='Skill', y='Count',
|
| 749 |
+
title='Soft Skills Found',
|
| 750 |
+
color='Skill')
|
| 751 |
+
fig.update_xaxis(tickangle=45)
|
| 752 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 753 |
+
else:
|
| 754 |
+
# Display as simple list for fewer skills
|
| 755 |
+
for skill in soft_skills:
|
| 756 |
+
st.write(f"β
{skill}")
|
| 757 |
else:
|
| 758 |
+
st.warning("No soft skills detected")
|
| 759 |
+
st.info("π‘ Consider highlighting leadership, communication, and teamwork skills")
|
| 760 |
|
| 761 |
+
# Skills comparison section
|
| 762 |
+
st.subheader("π Skills Overview")
|
| 763 |
|
| 764 |
+
# Create metrics row
|
| 765 |
+
col1, col2, col3, col4 = st.columns(4)
|
|
|
|
| 766 |
|
| 767 |
+
with col1:
|
| 768 |
+
st.metric("Technical Skills", len(tech_skills))
|
|
|
|
|
|
|
| 769 |
|
| 770 |
+
with col2:
|
| 771 |
+
st.metric("Soft Skills", len(soft_skills))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
|
| 773 |
+
with col3:
|
| 774 |
+
total_skills = len(tech_skills) + len(soft_skills)
|
| 775 |
+
st.metric("Total Skills", total_skills)
|
| 776 |
+
|
| 777 |
+
with col4:
|
| 778 |
+
# Calculate skills balance
|
| 779 |
+
if total_skills > 0:
|
| 780 |
+
tech_ratio = len(tech_skills) / total_skills * 100
|
| 781 |
+
st.metric("Tech/Soft Ratio", f"{tech_ratio:.0f}%/{100-tech_ratio:.0f}%")
|
| 782 |
else:
|
| 783 |
+
st.metric("Tech/Soft Ratio", "0%/0%")
|
| 784 |
+
|
| 785 |
+
# Skills recommendations
|
| 786 |
+
st.subheader("π‘ Skills Recommendations")
|
| 787 |
+
|
| 788 |
+
recommendations = []
|
| 789 |
+
|
| 790 |
+
# Technical skills recommendations
|
| 791 |
+
if len(tech_skills) < 5:
|
| 792 |
+
recommendations.append("π Add more technical skills relevant to your field")
|
| 793 |
+
|
| 794 |
+
# Soft skills recommendations
|
| 795 |
+
if len(soft_skills) < 3:
|
| 796 |
+
recommendations.append("π€ Highlight more soft skills like leadership and communication")
|
| 797 |
+
|
| 798 |
+
# Balance recommendations
|
| 799 |
+
if len(tech_skills) > 0 and len(soft_skills) == 0:
|
| 800 |
+
recommendations.append("βοΈ Balance technical skills with soft skills")
|
| 801 |
+
elif len(soft_skills) > 0 and len(tech_skills) == 0:
|
| 802 |
+
recommendations.append("βοΈ Add technical skills to complement your soft skills")
|
| 803 |
+
|
| 804 |
+
if recommendations:
|
| 805 |
+
for rec in recommendations:
|
| 806 |
+
st.info(rec)
|
| 807 |
+
else:
|
| 808 |
+
st.success("β
Good balance of technical and soft skills!")
|
| 809 |
+
|
| 810 |
+
# Role-specific keyword analysis
|
| 811 |
+
st.subheader(f"π― {selected_role} Keywords Analysis")
|
| 812 |
|
| 813 |
col1, col2 = st.columns(2)
|
| 814 |
|
| 815 |
with col1:
|
| 816 |
+
# Match percentage visualization
|
| 817 |
fig = go.Figure(go.Indicator(
|
| 818 |
+
mode = "gauge+number",
|
| 819 |
+
value = match_percentage,
|
| 820 |
+
domain = {'x': [0, 1], 'y': [0, 1]},
|
| 821 |
+
title = {'text': f"{selected_role} Match"},
|
| 822 |
+
gauge = {
|
|
|
|
| 823 |
'axis': {'range': [None, 100]},
|
| 824 |
+
'bar': {'color': "darkgreen"},
|
| 825 |
'steps': [
|
| 826 |
+
{'range': [0, 40], 'color': "lightcoral"},
|
| 827 |
+
{'range': [40, 70], 'color': "yellow"},
|
| 828 |
+
{'range': [70, 100], 'color': "lightgreen"}
|
| 829 |
],
|
| 830 |
'threshold': {
|
| 831 |
'line': {'color': "red", 'width': 4},
|
| 832 |
'thickness': 0.75,
|
| 833 |
+
'value': 80
|
| 834 |
}
|
| 835 |
}
|
| 836 |
))
|
|
|
|
| 838 |
st.plotly_chart(fig, use_container_width=True)
|
| 839 |
|
| 840 |
with col2:
|
| 841 |
+
st.metric("Keywords Found", len(found_keywords))
|
| 842 |
+
st.metric("Match Percentage", f"{match_percentage:.1f}%")
|
|
|
|
| 843 |
|
| 844 |
+
# Match level indicator
|
| 845 |
+
if match_percentage >= 80:
|
| 846 |
+
st.success("π Excellent match!")
|
| 847 |
+
elif match_percentage >= 60:
|
| 848 |
+
st.warning("π Good match")
|
| 849 |
+
elif match_percentage >= 40:
|
| 850 |
+
st.warning("β οΈ Fair match")
|
| 851 |
else:
|
| 852 |
+
st.error("β Poor match")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 853 |
|
| 854 |
+
# Keywords found section
|
| 855 |
+
if found_keywords:
|
| 856 |
+
st.subheader("β
Keywords Found")
|
| 857 |
+
|
| 858 |
+
# Display found keywords in a nice format
|
| 859 |
+
keyword_cols = st.columns(3)
|
| 860 |
+
for i, keyword in enumerate(found_keywords):
|
| 861 |
+
with keyword_cols[i % 3]:
|
| 862 |
+
st.success(f"β {keyword}")
|
| 863 |
+
else:
|
| 864 |
+
st.warning("οΏ½οΏ½οΏ½ No role-specific keywords found")
|
| 865 |
+
|
| 866 |
+
# Missing keywords section
|
| 867 |
+
all_keywords = analyzer.job_keywords[selected_role]
|
| 868 |
+
missing_keywords = [kw for kw in all_keywords if kw not in found_keywords]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 869 |
|
| 870 |
+
if missing_keywords:
|
| 871 |
+
st.subheader("π Suggested Keywords to Add")
|
| 872 |
+
st.info(f"Consider adding these {selected_role}-specific keywords to improve your match score:")
|
| 873 |
+
|
| 874 |
+
# Show missing keywords in expandable sections
|
| 875 |
+
with st.expander(f"View all {len(missing_keywords)} missing keywords", expanded=len(missing_keywords) <= 10):
|
| 876 |
+
missing_cols = st.columns(3)
|
| 877 |
+
for i, keyword in enumerate(missing_keywords):
|
| 878 |
+
with missing_cols[i % 3]:
|
| 879 |
+
st.write(f"π {keyword}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 880 |
|
| 881 |
+
# Skills gap analysis
|
| 882 |
+
st.subheader("π Skills Gap Analysis")
|
|
|
|
|
|
|
| 883 |
|
| 884 |
+
# Calculate skills coverage for the role
|
| 885 |
+
role_technical_skills = [skill for skill in analyzer.technical_skills
|
| 886 |
+
if skill in analyzer.job_keywords[selected_role]]
|
| 887 |
|
| 888 |
+
found_role_skills = [skill for skill in tech_skills if skill in role_technical_skills]
|
| 889 |
+
missing_role_skills = [skill for skill in role_technical_skills if skill not in tech_skills]
|
| 890 |
|
| 891 |
+
if role_technical_skills:
|
| 892 |
+
coverage_percentage = (len(found_role_skills) / len(role_technical_skills)) * 100
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 893 |
|
| 894 |
+
col1, col2 = st.columns(2)
|
| 895 |
+
|
| 896 |
+
with col1:
|
| 897 |
+
st.metric("Role Skills Coverage", f"{coverage_percentage:.1f}%")
|
| 898 |
+
|
| 899 |
+
if coverage_percentage >= 80:
|
| 900 |
+
st.success("π― Excellent coverage of role-specific skills!")
|
| 901 |
+
elif coverage_percentage >= 60:
|
| 902 |
+
st.warning("π Good coverage, consider adding a few more")
|
| 903 |
+
else:
|
| 904 |
+
st.error("β οΈ Low coverage, focus on adding role-specific skills")
|
| 905 |
+
|
| 906 |
+
with col2:
|
| 907 |
+
if missing_role_skills:
|
| 908 |
+
st.write("**Priority skills to add:**")
|
| 909 |
+
for skill in missing_role_skills[:5]:
|
| 910 |
+
st.write(f"π― {skill}")
|
| 911 |
+
else:
|
| 912 |
+
st.success("β
All key role skills covered!")
|
| 913 |
+
|
| 914 |
+
# Skills trend analysis (if we had historical data)
|
| 915 |
+
st.subheader("π Skills Insights")
|
| 916 |
+
|
| 917 |
+
insights = []
|
| 918 |
+
|
| 919 |
+
# Programming languages analysis
|
| 920 |
+
programming_langs = ['python', 'java', 'javascript', 'c++', 'c#', 'php', 'ruby', 'go']
|
| 921 |
+
found_langs = [lang for lang in programming_langs if lang in [s.lower() for s in tech_skills]]
|
| 922 |
+
|
| 923 |
+
if len(found_langs) >= 3:
|
| 924 |
+
insights.append(f"π» Strong programming portfolio with {len(found_langs)} languages")
|
| 925 |
+
elif len(found_langs) >= 1:
|
| 926 |
+
insights.append(f"π» Programming experience in {', '.join(found_langs)}")
|
| 927 |
+
|
| 928 |
+
# Cloud skills analysis
|
| 929 |
+
cloud_skills = ['aws', 'azure', 'gcp', 'docker', 'kubernetes']
|
| 930 |
+
found_cloud = [skill for skill in cloud_skills if skill in [s.lower() for s in tech_skills]]
|
| 931 |
+
|
| 932 |
+
if found_cloud:
|
| 933 |
+
insights.append(f"βοΈ Cloud-ready with {', '.join(found_cloud)} experience")
|
| 934 |
+
|
| 935 |
+
# Data skills analysis
|
| 936 |
+
data_skills = ['sql', 'python', 'tableau', 'power bi', 'excel', 'pandas', 'numpy']
|
| 937 |
+
found_data = [skill for skill in data_skills if skill in [s.lower() for s in tech_skills]]
|
| 938 |
+
|
| 939 |
+
if len(found_data) >= 3:
|
| 940 |
+
insights.append(f"π Strong data analysis capabilities")
|
| 941 |
+
|
| 942 |
+
if insights:
|
| 943 |
+
for insight in insights:
|
| 944 |
+
st.info(insight)
|
| 945 |
+
else:
|
| 946 |
+
st.info("π‘ Add more technical skills to unlock insights about your profile")
|
| 947 |
+
|