Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions resume-analyser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv
1 change: 1 addition & 0 deletions resume-analyser/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
107 changes: 107 additions & 0 deletions resume-analyser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
Of course. Here is the complete Markdown content for your `README.md` file, ready to be copied and pasted.


# 📄 AI Resume Analyzer

An intelligent tool that analyzes your resume against a job description, providing a detailed match report, AI-powered suggestions, and links to learning resources for missing skills. This project leverages the Google Gemini API to deliver a smart, agentic analysis.

---

### **Live Demo**: [**ai-resume-analyser-fz5b.onrender.com**](https://ai-resume-analyser-fz5b.onrender.com/)

### **Google Colab Notebook**: [**Open in Colab**](https://colab.research.google.com/drive/18mYupGH0i-xdN0Uwyt8VvUUWvqsqodM6?usp=sharing)

---

## ✨ Features

- **Technical Skill Matching**: Calculates a percentage score based on how your resume's technical skills match the job requirements.
- **AI-Powered Suggestions**: Receives specific, actionable advice from the Gemini AI on how to improve your resume.
- **Visual Analysis**: Displays a nested donut chart for an at-a-glance understanding of your skill breakdown.
- **Learning Resources**: Automatically generates links to YouTube, Udemy, and Coursera tutorials for any missing technical skills.
- **Soft Skill Identification**: Recognizes and lists required soft skills to help you prepare for interviews.
- **Supports UN SDG #8**: Aligns with the Sustainable Development Goal for "Decent Work and Economic Growth" by helping job seekers improve their employability.

---

## 🤖 Agentic Workflow

The application operates as an intelligent agent. The Python environment acts as the "body" to handle files and tools, while the Gemini model acts as the "brain" to perform analysis and generate insights.

```mermaid
%%{init: {'theme': 'base', 'themeVariables': { 'background': '#FFFFFF'}}}%%
graph TD
subgraph "User & Python Environment"
A[User Input<br/>- Resume.pdf<br/>- JD.pdf] --> B{Python Environment}
B -->|1.Extracts Text| C[Tool: PyMuPDF<br/>→ Raw Text]
end

subgraph "The Agentic Core"
C -->|2.Sends Goal & Data| D[🧠 Gemini Agent]
D -->|Plan:<br/>Analyze, Categorize, Compare, Suggest| E[Structured JSON<br/>- Skill Data<br/>- AI Suggestions]
end

subgraph "Final Assembly & Output"
E -->|3.Executes Tasks| F[Tools:<br/>Matplotlib, Urllib<br/>→ Chart & Links]
F -->|4.Assembles Report| G[Final Output:<br/>Chart, Report, Links]
end

style A fill:#E3F2FD,stroke:#90CAF9
style B fill:#FFFDE7,stroke:#FFD54F
style C fill:#E8F5E9,stroke:#81C784
style D fill:#EDE7F6,stroke:#9575CD,stroke-width:3px
style E fill:#FFF3E0,stroke:#FFB74D
style F fill:#E8F5E9,stroke:#81C784
style G fill:#D1F2EB,stroke:#4DB6AC,stroke-width:2px
````

-----

## 🚀 How to Run Locally

1. **Clone the repository:**

```bash
git clone [<your-repo-url>](https://github.com/u-8d/ai-resume-analyser)
cd ai-resume-analyser
```

2. **Install dependencies:**

```bash
pip install -r requirements.txt
```

3. **Set up your API Key:**
Create a `.env` file in the root directory and add your Gemini API key:

```
GEMINI_API_KEY="YOUR_API_KEY_HERE"
```

4. **Run the application:**

```bash
python app.py
```

The application will be available at `http://127.0.0.1:7860`.

-----

## ☁️ Deployment

This application is deployed as a Web Service on [Render](https://render.com/). The deployment process is configured as follows:

- **Runtime**: Python 3
- **Build Command**: `pip install -r requirements.txt`
- **Start Command**: `python app.py`
- **Environment Variable**: The `GEMINI_API_KEY` is set securely in the Render dashboard.

<!-- end list -->

```
```



197 changes: 197 additions & 0 deletions resume-analyser/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import gradio as gr
import fitz
import google.generativeai as genai
import json
import matplotlib.pyplot as plt
import time
import numpy as np
from urllib.parse import quote
import os

def extract_text_from_pdf(pdf_file):
"""Extracts text from an uploaded PDF file, limited to the first 5 pages."""
if not pdf_file: return ""
try:
doc = fitz.open(pdf_file.name)
text = "".join(page.get_text() for page_num, page in enumerate(doc) if page_num < 5)
return text
except Exception as e:
return f"Error reading PDF: {e}"

def analyze_with_gemini(resume_text, jd_text):
"""
Analyzes documents with the fast and efficient Gemini 1.5 Flash model.
"""
api_key = os.environ.get('GEMINI_API_KEY')
if not api_key:
return False, "Error: GEMINI_API_KEY environment variable not set on the server."

try:
genai.configure(api_key=api_key)
model = genai.GenerativeModel('gemini-1.5-flash-latest')

prompt = f"""
You are a top-tier senior recruiter and career coach with 15 years of experience. Your task is to conduct a deep analysis of a resume against a job description.

1. From the job description, meticulously extract the core "jd_technical_skills" (e.g., Python, AWS, React) and "jd_soft_skills" (e.g., Leadership, Communication).
2. From the resume, extract all "resume_technical_skills" you can discern.
3. Critically compare the resume to the job description and generate a list of 2-4 highly specific, actionable "suggestions" for improving the resume. These suggestions must be insightful and go beyond simple keyword matching. For example: "The job requires 'Terraform' for infrastructure management. Your resume mentions AWS but not specific IaC tools. Consider adding a bullet point under your cloud project detailing how you automated infrastructure provisioning, even if you used a different tool."
4. IMPORTANT: Discard vague, non-skill phrases. Be discerning.
5. Return a single, clean JSON object with four keys: "jd_technical_skills", "jd_soft_skills", "resume_technical_skills", and "suggestions".

Resume Text:
---
{resume_text}
---

Job Description Text:
---
{jd_text}
---

Provide only the raw JSON object in your response.
"""

response = model.generate_content(prompt)
cleaned_response = response.text.strip().replace('```json', '').replace('```', '')
parsed_json = json.loads(cleaned_response)

jd_technical = set(skill.lower() for skill in parsed_json.get("jd_technical_skills", []))
jd_soft = set(skill.lower() for skill in parsed_json.get("jd_soft_skills", []))
resume_technical = set(skill.lower() for skill in parsed_json.get("resume_technical_skills", []))
suggestions = parsed_json.get("suggestions", [])

return True, (jd_technical, jd_soft, resume_technical, suggestions)

except Exception as e:
return False, f"Gemini API Error: {str(e)}. The model may have returned an unexpected format. Please try again."

def get_learning_resources(missing_skills):
"""
Generates a markdown string with links to learning resources for missing skills.
"""
if not missing_skills:
return ""
links_md = "## 📚 Learning Resources\nHere are some links to help you get started on the missing technical skills:\n\n"
for skill in sorted(list(missing_skills)):
query = quote(f"{skill} tutorial for beginners")
udemy_query = quote(skill)
links_md += f"### {skill.title()}\n* [Search on YouTube](https://www.youtube.com/results?search_query={query})\n* [Search on Udemy](https://www.udemy.com/courses/search/?q={udemy_query})\n* [Search on Coursera](https://www.coursera.org/search?query={udemy_query})\n\n"
return links_md

def create_pie_chart(matched_count, missing_count):
"""Generates a clean pie chart with a white background."""
if matched_count == 0 and missing_count == 0:
return None

labels = ['Matched Technical Skills', 'Missing Technical Skills']
sizes = [matched_count, missing_count]
colors = ['#2E8B57', '#CD5C5C']
explode = (0.1, 0) if matched_count > 0 else (0, 0)

fig, ax = plt.subplots(figsize=(6, 6))
fig.patch.set_facecolor('white')
ax.patch.set_facecolor('white')

wedges, texts, autotexts = ax.pie(
sizes, explode=explode, labels=labels, colors=colors,
autopct='%1.1f%%', shadow=False, startangle=140,
textprops={'fontsize': 12, 'fontweight': 'bold'}
)
plt.setp(autotexts, size=14, weight="bold", color="white")
plt.setp(texts, size=12, weight="bold", color="#333333")
ax.axis('equal')
ax.set_title('Technical Skill Match', fontsize=16, fontweight='bold', pad=20)

filepath = f"/tmp/skill_chart_{int(time.time())}.png"
plt.savefig(filepath, bbox_inches='tight', pad_inches=0.1)
plt.close(fig)
return filepath


def analyze_documents(resume_pdf, jd_pdf, progress=gr.Progress()):
"""Orchestrates the analysis process and generates the report and chart."""
if not resume_pdf or not jd_pdf:
return None, "Please upload both your Resume and the Job Description."

# Check for API key at the start of the process
if not os.environ.get('GEMINI_API_KEY'):
return None, "🔴 **Configuration Error**: The application's API key is not set on the server. Please contact the administrator."

progress(0, desc="Starting Analysis...")
progress(0.2, desc="Extracting text from PDFs...")
resume_text = extract_text_from_pdf(resume_pdf)
jd_text = extract_text_from_pdf(jd_pdf)

progress(0.4, desc="Analyzing documents with Gemini Flash...")
success, data = analyze_with_gemini(resume_text, jd_text)
if not success:
return None, f"Analysis Failed: {data}"

jd_technical, jd_soft, resume_technical, suggestions = data
if not jd_technical:
return None, "Analysis failed: Gemini could not identify any required technical skills in the Job Description."

progress(0.7, desc="Calculating score and generating visuals...")
matched_technical = resume_technical.intersection(jd_technical)
missing_technical = jd_technical.difference(resume_technical)
match_score = (len(matched_technical) / len(jd_technical)) * 100 if jd_technical else 0
chart_path = create_pie_chart(len(matched_technical), len(missing_technical))

progress(0.9, desc="Finding learning resources...")
learning_links_md = get_learning_resources(missing_technical)

# --- Build the Report ---
result = f"# ⭐ Resume Analysis Report\n\n"
result += f"Your resume has a **{match_score:.1f}%** match with the job's **core technical requirements**.\n\n"
result += f"## 💡 AI-Powered Suggestions\n\n"
if suggestions:
for suggestion in suggestions:
result += f"- {suggestion}\n"
else:
result += "No specific suggestions were generated. Your resume looks well-aligned!"

result += f"\n\n---\n"
result += f"## ✅ Matched Technical Skills ({len(matched_technical)})\n"
result += ", ".join(sorted([s.title() for s in matched_technical])) if matched_technical else "None"
result += f"\n\n## ❌ Missing Technical Skills ({len(missing_technical)})\n"
result += ", ".join(sorted([s.title() for s in missing_technical])) if missing_technical else "None! Great job."
result += f"\n\n---\n"
result += f"### 💬 Required Soft Skills ({len(jd_soft)})\n"
result += "While not part of the score, be prepared to discuss these:\n\n"
result += ", ".join(sorted([s.title() for s in jd_soft])) if jd_soft else "None specified."
result += f"\n\n---\n{learning_links_md}"
result += "\n\n*Powered by Google Gemini 1.5 Flash. This is an automated guide.*"

progress(1, desc="Done!")
return chart_path, result

# --- GRADIO UI ---
with gr.Blocks(theme=gr.themes.Default(primary_hue="blue"), css=".gradio-container {max-width: 1280px !important}") as iface:
gr.Markdown("# 📄 AI Resume Analyzer")
gr.Markdown("Get an instant, intelligent analysis of your resume against a job description. Powered by Google Gemini 1.5 Flash.")

with gr.Row(variant='panel'):
with gr.Column(scale=1, min_width=350):
gr.Markdown("### 1. Upload Documents")
resume_file = gr.File(label="Your Resume (PDF)")
jd_file = gr.File(label="Job Description (PDF)")
analyze_btn = gr.Button("Analyze Resume", variant="primary", scale=2)

with gr.Column(scale=2):
gr.Markdown("### 2. Analysis Results")
output_report = gr.Markdown(label="Analysis Report")

with gr.Row():
with gr.Column():
gr.Markdown("### 3. Match Visualization")
output_chart = gr.Image(label="Technical Skills Match", show_label=False, interactive=False)


analyze_btn.click(
fn=analyze_documents,
inputs=[resume_file, jd_file],
outputs=[output_chart, output_report],
api_name="analyze"
)
iface.launch(server_name="0.0.0.0", server_port=7860)
7 changes: 7 additions & 0 deletions resume-analyser/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
name = "ai-resume-analyser"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
5 changes: 5 additions & 0 deletions resume-analyser/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
gradio
google-generativeai
pymupdf
matplotlib
numpy