Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,215 +1,100 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
import
|
| 3 |
-
|
| 4 |
-
from utils.ai_analyzer import analyze_resume_with_openai
|
| 5 |
-
from config.settings import ALLOWED_EXTENSIONS
|
| 6 |
|
| 7 |
-
#
|
| 8 |
-
resume_text_state = ""
|
| 9 |
-
analysis_result_state = None
|
| 10 |
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
|
| 13 |
-
if file_obj is None:
|
| 14 |
-
return "❌ Please upload a resume file", ""
|
| 15 |
|
| 16 |
try:
|
| 17 |
-
#
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
from utils.resume_parser import parse_resume
|
| 27 |
-
resume_text = parse_resume(f.read(), file_ext)
|
| 28 |
|
| 29 |
-
|
| 30 |
-
resume_text_state = resume_text
|
| 31 |
-
|
| 32 |
-
return f"✅ Resume loaded successfully: {file_path.name}", resume_text
|
| 33 |
-
|
| 34 |
-
except Exception as e:
|
| 35 |
-
return f"❌ Error reading file: {str(e)}", ""
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
def analyze_with_ai(resume_text_input):
|
| 39 |
-
if not resume_text_input or resume_text_input.strip() == "":
|
| 40 |
-
return "❌ Please upload and load a resume first", "", "", "", "", ""
|
| 41 |
-
|
| 42 |
-
try:
|
| 43 |
-
global analysis_result_state
|
| 44 |
-
analysis = analyze_resume_with_openai(resume_text_input)
|
| 45 |
-
analysis_result_state = analysis
|
| 46 |
-
|
| 47 |
-
return (
|
| 48 |
-
format_overall_section(analysis),
|
| 49 |
-
format_skills_section(analysis),
|
| 50 |
-
format_experience_section(analysis),
|
| 51 |
-
format_education_section(analysis),
|
| 52 |
-
format_gaps_section(analysis),
|
| 53 |
-
format_recommendations_section(analysis),
|
| 54 |
-
)
|
| 55 |
|
| 56 |
except Exception as e:
|
| 57 |
-
return f"
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
def format_overall_section(analysis):
|
| 61 |
-
score = analysis.get("overall_score", {}).get("rating", 0)
|
| 62 |
-
explanation = analysis.get("overall_score", {}).get("explanation", "")
|
| 63 |
-
summary = analysis.get("summary", "")
|
| 64 |
-
|
| 65 |
-
return f"""## 📊 Overall Assessment
|
| 66 |
-
|
| 67 |
-
**Score: {score}/100**
|
| 68 |
-
|
| 69 |
-
**Explanation:** {explanation}
|
| 70 |
-
|
| 71 |
-
### 📝 Executive Summary
|
| 72 |
-
{summary}
|
| 73 |
-
"""
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
def format_skills_section(analysis):
|
| 77 |
-
skills = analysis.get("skills", {})
|
| 78 |
-
output = "## 🔧 Skills Analysis\n\n### Technical Skills\n"
|
| 79 |
-
|
| 80 |
-
technical = skills.get("technical", [])
|
| 81 |
-
output += "\n".join([f"- {s}" for s in technical]) if technical else "- No technical skills identified\n"
|
| 82 |
-
|
| 83 |
-
output += "\n\n### Soft Skills\n"
|
| 84 |
-
soft = skills.get("soft", [])
|
| 85 |
-
output += "\n".join([f"- {s}" for s in soft]) if soft else "- No soft skills identified\n"
|
| 86 |
-
|
| 87 |
-
output += "\n\n### Skills to Develop\n"
|
| 88 |
-
missing = skills.get("missing", [])
|
| 89 |
-
output += "\n".join([f"- ❌ {s}" for s in missing]) if missing else "- No significant gaps identified\n"
|
| 90 |
|
| 91 |
-
return output
|
| 92 |
|
|
|
|
| 93 |
|
| 94 |
-
def
|
| 95 |
-
experience = analysis.get("experience", {})
|
| 96 |
-
output = "## 💼 Experience Review\n\n### Highlights\n"
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
-
|
| 102 |
-
for c in experience.get("concerns", []):
|
| 103 |
-
output += f"- ⚠️ {c}\n"
|
| 104 |
|
| 105 |
-
|
|
|
|
|
|
|
| 106 |
|
|
|
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
return f"""## 🎓 Education Background
|
| 111 |
|
| 112 |
-
|
|
|
|
| 113 |
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
"""
|
| 116 |
|
|
|
|
| 117 |
|
| 118 |
-
def format_gaps_section(analysis):
|
| 119 |
-
output = "## 🔍 Career Gaps & Concerns\n\n"
|
| 120 |
-
|
| 121 |
-
gaps = analysis.get("career_gaps", [])
|
| 122 |
-
output += "\n".join([f"- {g}" for g in gaps]) if gaps else "✅ No significant career gaps identified\n"
|
| 123 |
-
|
| 124 |
-
job_compat = analysis.get("job_compatibility", {})
|
| 125 |
-
output += "\n\n## 🎯 Job Market Compatibility\n\n### Strengths\n"
|
| 126 |
-
|
| 127 |
-
for s in job_compat.get("strengths", []):
|
| 128 |
-
output += f"- ✅ {s}\n"
|
| 129 |
-
|
| 130 |
-
output += "\n### Areas to Improve\n"
|
| 131 |
-
for w in job_compat.get("weaknesses", []):
|
| 132 |
-
output += f"- ⚠️ {w}\n"
|
| 133 |
-
|
| 134 |
-
return output
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
def format_recommendations_section(analysis):
|
| 138 |
-
recs = analysis.get("recommendations", [])
|
| 139 |
-
output = "## 💡 Improvement Recommendations\n\n"
|
| 140 |
-
|
| 141 |
-
for i, r in enumerate(recs, 1):
|
| 142 |
-
output += f"{i}. {r}\n"
|
| 143 |
-
|
| 144 |
-
return output if recs else output + "No specific recommendations available\n"
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
def export_json(_):
|
| 148 |
-
if analysis_result_state is None:
|
| 149 |
-
return "No analysis available to export"
|
| 150 |
-
|
| 151 |
-
return json.dumps(analysis_result_state, indent=2)
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
def export_text(_):
|
| 155 |
-
if analysis_result_state is None:
|
| 156 |
-
return "No analysis available to export"
|
| 157 |
-
|
| 158 |
-
analysis = analysis_result_state
|
| 159 |
-
report = "RESUME ANALYSIS REPORT\n" + "=" * 60 + "\n\n"
|
| 160 |
-
report += f"OVERALL SCORE: {analysis.get('overall_score', {}).get('rating', 0)}/100\n"
|
| 161 |
-
report += f"Rating: {analysis.get('overall_score', {}).get('explanation', '')}\n\n"
|
| 162 |
-
report += analysis.get("summary", "")
|
| 163 |
-
|
| 164 |
-
return report
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
def create_interface():
|
| 168 |
-
with gr.Blocks(title="Resume Analyzer", theme=gr.themes.Soft()) as demo:
|
| 169 |
|
| 170 |
-
|
| 171 |
-
# 📄 Resume Analyzer
|
| 172 |
-
### AI-Powered Resume Analysis & Recommendations
|
| 173 |
-
""")
|
| 174 |
|
| 175 |
-
|
| 176 |
-
file_upload = gr.File(label="Upload Resume", file_types=[".pdf", ".docx", ".doc", ".txt"], type="filepath")
|
| 177 |
-
load_button = gr.Button("📖 Load Resume")
|
| 178 |
-
status_output = gr.Textbox(label="Status", interactive=False)
|
| 179 |
-
resume_preview = gr.Textbox(label="Resume Preview", lines=10, interactive=False)
|
| 180 |
-
analyze_button = gr.Button("🤖 Analyze with AI", variant="primary")
|
| 181 |
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
experience_output = gr.Markdown()
|
| 185 |
-
education_output = gr.Markdown()
|
| 186 |
-
gaps_output = gr.Markdown()
|
| 187 |
-
recommendations_output = gr.Markdown()
|
| 188 |
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
[overall_output, skills_output, experience_output, education_output, gaps_output, recommendations_output]
|
| 194 |
-
)
|
| 195 |
|
| 196 |
-
|
| 197 |
-
json_btn = gr.Button("📋 Export as JSON")
|
| 198 |
-
json_output = gr.Textbox(lines=15, interactive=False)
|
| 199 |
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
-
return demo
|
| 207 |
|
|
|
|
| 208 |
|
| 209 |
if __name__ == "__main__":
|
| 210 |
-
demo
|
| 211 |
-
demo.launch(
|
| 212 |
-
server_name="0.0.0.0",
|
| 213 |
-
server_port=7861,
|
| 214 |
-
share=True
|
| 215 |
-
)
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
import pdfplumber
|
| 3 |
+
import docx
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
# -------- Resume Text Extraction -------- #
|
|
|
|
|
|
|
| 6 |
|
| 7 |
+
def extract_text(file):
|
| 8 |
+
if file is None:
|
| 9 |
+
return "Please upload a resume."
|
| 10 |
|
| 11 |
+
file_name = file.name
|
|
|
|
|
|
|
| 12 |
|
| 13 |
try:
|
| 14 |
+
# PDF
|
| 15 |
+
if file_name.endswith(".pdf"):
|
| 16 |
+
text = ""
|
| 17 |
+
with pdfplumber.open(file) as pdf:
|
| 18 |
+
for page in pdf.pages:
|
| 19 |
+
page_text = page.extract_text()
|
| 20 |
+
if page_text:
|
| 21 |
+
text += page_text
|
| 22 |
|
| 23 |
+
# DOCX
|
| 24 |
+
elif file_name.endswith(".docx"):
|
| 25 |
+
doc = docx.Document(file)
|
| 26 |
+
text = "\n".join([p.text for p in doc.paragraphs])
|
| 27 |
|
| 28 |
+
else:
|
| 29 |
+
return "Unsupported file format. Upload PDF or DOCX."
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
return analyze_resume(text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
except Exception as e:
|
| 34 |
+
return f"Error reading file: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
|
|
|
| 36 |
|
| 37 |
+
# -------- Resume Analyzer -------- #
|
| 38 |
|
| 39 |
+
def analyze_resume(text):
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
skills = [
|
| 42 |
+
"python","machine learning","deep learning","data science",
|
| 43 |
+
"sql","pandas","numpy","tensorflow","pytorch",
|
| 44 |
+
"nlp","git","docker","flask","fastapi"
|
| 45 |
+
]
|
| 46 |
|
| 47 |
+
found_skills = []
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
for skill in skills:
|
| 50 |
+
if skill.lower() in text.lower():
|
| 51 |
+
found_skills.append(skill)
|
| 52 |
|
| 53 |
+
score = min(len(found_skills) * 10, 100)
|
| 54 |
|
| 55 |
+
result = f"""
|
| 56 |
+
Resume Score: {score}/100
|
|
|
|
| 57 |
|
| 58 |
+
Detected Skills:
|
| 59 |
+
{', '.join(found_skills) if found_skills else 'No major skills detected'}
|
| 60 |
|
| 61 |
+
Suggestions:
|
| 62 |
+
• Add more technical skills
|
| 63 |
+
• Include projects
|
| 64 |
+
• Mention measurable achievements
|
| 65 |
+
• Use clear headings
|
| 66 |
"""
|
| 67 |
|
| 68 |
+
return result
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
+
# -------- Gradio UI -------- #
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
+
with gr.Blocks(title="Resume Analyzer") as demo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
+
gr.Markdown("# 📄 AI Resume Analyzer")
|
| 76 |
+
gr.Markdown("Upload your resume and get instant feedback.")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
+
resume_input = gr.File(
|
| 79 |
+
label="Upload Resume",
|
| 80 |
+
file_types=[".pdf", ".docx"]
|
| 81 |
+
)
|
|
|
|
|
|
|
| 82 |
|
| 83 |
+
analyze_btn = gr.Button("Analyze Resume")
|
|
|
|
|
|
|
| 84 |
|
| 85 |
+
output_box = gr.Textbox(
|
| 86 |
+
label="Analysis Result",
|
| 87 |
+
lines=15
|
| 88 |
+
)
|
| 89 |
|
| 90 |
+
analyze_btn.click(
|
| 91 |
+
fn=extract_text,
|
| 92 |
+
inputs=resume_input,
|
| 93 |
+
outputs=output_box
|
| 94 |
+
)
|
| 95 |
|
|
|
|
| 96 |
|
| 97 |
+
# -------- Launch App -------- #
|
| 98 |
|
| 99 |
if __name__ == "__main__":
|
| 100 |
+
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|