Skip to content
Merged
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
15 changes: 9 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ GEMINI_API_KEY=your-gemini-api-key-here
# GitHub Personal Access Token (for higher rate limits)
GITHUB_TOKEN=your-github-token-here

# ===== ENVIRONMENT & SERVER =====

# Environment selection: development, staging, production, testing
# Environment & Server
ENVIRONMENT=development

# Server Configuration
HOST=127.0.0.1
FLASK_HOST=127.0.0.1
FLASK_PORT=5001
PORT=5001

# Database (MongoDB)
# Default for local Docker: mongodb://localhost:27017/mployable
MONGODB_URI=mongodb://localhost:27017/mployable
MONGODB_DB_NAME=mployable
PORT=5000
DEBUG=false
WORKERS=1
Expand Down
100 changes: 100 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# 🚀 Mployable - AI-Powered Resume Builder

**Mployable** is a sophisticated full-stack platform designed to automate the creation of hyper-tailored resumes. By leveraging LLM-based analysis and semantic matching, Mployable bridges the gap between your technical profile (GitHub/LinkedIn) and specific job requirements.

## ✨ Latest Features

- **Modern Full-Stack Architecture**: React 19.2 frontend with Framer Motion animations and a robust Flask backend.
- **Semantic Tech-Stack Matching**: Intelligent selection of GitHub repositories that best align with job descriptions using skill scoring.
- **Hybrid Profile Sourcing**:
- **GitHub**: Automatic repository scraping and language analysis.
- **LinkedIn**: Structured data import via LinkedIn Data Export ZIP.
- **MongoDB Persistence**: Full database integration for user profiles, resume versioning, job application tracking, and intelligence caching.
- **Gemini AI Integration**: Advanced job description analysis to identify required skills and experience levels.
- **Professional LaTeX Generation**: Dynamic compilation of resume content into high-quality, ATS-friendly PDFs.

## 🛠️ Technology Stack

### Frontend
- **React 19.2**: Utilizing modern hooks and patterns.
- **Vite**: Ultra-fast build tool and development server.
- **Tailwind CSS**: Utility-first CSS framework for clean, responsive UI.
- **Framer Motion**: Smooth, high-performance web animations.
- **Lucide React**: Clean and consistent iconography.

### Backend
- **Python / Flask**: Scalable RESTful API service.
- **MongoDB**: Document-oriented database for flexible data storage.
- **Google Gemini API**: state-of-the-art LLM for intelligent text analysis.
- **PyGithub & BeautifulSoup**: Specialized scraping tools for profile data.
- **LaTeX (pdflatex)**: Industry-standard typesetting for professional PDFs.

### DevOps
- **Docker & Docker Compose**: Containerized environment for consistent deployment.
- **Nginx**: High-performance web server and reverse proxy.

## 📋 Prerequisites

- **Python 3.10+**
- **Node.js 18+** & npm
- **MongoDB**: Local instance or MongoDB Atlas URI.
- **LaTeX Distribution**: TeX Live (Linux/macOS) or MiKTeX (Windows).
- **Google Gemini API Key**: [Get one here](https://aistudio.google.com/app/apikey).

## 🚀 Getting Started

### 1. Clone & Setup Environments

```bash
git clone https://github.com/HrishikeshUchake/Mployable.git
cd Mployable
cp .env.example .env
```

### 2. Backend Installation

```bash
# It is recommended to use a virtual environment
python -m venv venv
source venv/bin/activate # venv\Scripts\activate on Windows
pip install -r requirements.txt
```

### 3. Frontend Installation

```bash
cd frontend
npm install
cd ..
```

### 4. Running the Application

**Option A: Manual Start**

```bash
# Terminal 1: Backend (Port 5001)
python app.py

# Terminal 2: Frontend (Port 5173)
cd frontend
npm run dev
```

**Option B: Docker (Recommended)**

```bash
docker-compose up --build
```

## 📖 How it Works

1. **Profile Connection**: Enter your GitHub username. Mployable fetches your repos, languages, and contributions.
2. **Contextual Input**: Provide your LinkedIn Data Export for professional experience.
3. **Job Analysis**: Paste a job description. The AI extracts key requirements, tech stack, and experience levels.
4. **Smart Selection**: Mployable's semantic matcher identifies which of your GitHub projects most closely match the job's tech stack.
5. **PDF Generation**: A customized LaTeX template is filled with the matched data and compiled into a downloadable PDF.

---

Built with ❤️ for developers who want to stand out.
154 changes: 154 additions & 0 deletions analyzer/interview_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
"""
Interview Preparation Generator using Google Gemini

Uses Google Gemini AI to analyze job requirements and generate
tailored interview preparation tips and sample questions.
"""

import google.generativeai as genai
import os
import json
from typing import Dict, List
from config.config import get_config


class InterviewGenerator:
def __init__(self):
"""Initialize interview generator with Gemini API"""
config = get_config()
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
print("Warning: GEMINI_API_KEY not found in environment variables")
self.model = None
else:
genai.configure(api_key=api_key)
try:
self.model = genai.GenerativeModel(config.GEMINI_MODEL)
except Exception:
self.model = genai.GenerativeModel("gemini-pro")

def generate(self, job_requirements: Dict) -> Dict:
"""
Generate interview tips and sample questions based on job requirements

Args:
job_requirements: Dictionary containing analyzed job requirements

Returns:
Dictionary containing structured interview prep content
"""
if not self.model:
print("InterviewGenerator: No model initialized, using basic prep")
return self._generate_basic_prep(job_requirements)

try:
# Prepare prompt for Gemini
prompt = self._create_prompt(job_requirements)
print(f"InterviewGenerator: Generating content with Gemini...")

# Generate content using Gemini
response = self.model.generate_content(prompt)

if not response or not response.text:
print("InterviewGenerator: Empty response from Gemini")
return self._generate_basic_prep(job_requirements)

# Parse the response
prep_content = self._parse_gemini_response(response.text)
print("InterviewGenerator: Successfully generated interview prep")

return prep_content

except Exception as e:
print(f"Error generating interview prep with Gemini: {e}")
return self._generate_basic_prep(job_requirements)

def _create_prompt(self, job_requirements: Dict) -> str:
"""Create prompt for Gemini API"""
skills = job_requirements.get("skills", {})
experience = job_requirements.get("experience", "Not specified")
education = job_requirements.get("education", "Not specified")
sections = job_requirements.get("sections", {})

# Flatten skills for prompt
all_skills = []
for category, skill_list in skills.items():
all_skills.extend(skill_list)

skills_text = ", ".join(all_skills) if all_skills else "General technical skills"

prompt = f"""
Act as an expert technical recruiter and interview coach.
Based on the following job requirements, generate a comprehensive interview preparation guide.

JOB REQUIREMENTS:
- Required Skills: {skills_text}
- Experience Level: {experience}
- Education: {education}

ADDITIONAL JOB DETAILS:
{json.dumps(sections, indent=2)}

TASKS:
1. Provide 5-7 customized Interview Preparation Tips specific to this role and these technologies.
2. Generate 5 Technical Questions based on the required skills.
3. Generate 3 Behavioral Questions tailored to the responsibilities of this role.
4. Generate 2 "Situational" or "Scenario-based" Questions.
5. Provide a brief "Winning Strategy" for this specific role.

FORMAT:
Return ONLY a JSON object with this exact structure:
{{
"tips": ["tip 1", "tip 2", ...],
"technical_questions": [
{{"question": "...", "context": "Focus on..."}}
],
"behavioral_questions": [
{{"question": "...", "context": "Focus on..."}}
],
"situational_questions": [
{{"question": "...", "context": "Focus on..."}}
],
"winning_strategy": "..."
}}
"""
return prompt

def _parse_gemini_response(self, response_text: str) -> Dict:
"""Parse Gemini JSON response"""
try:
# Find the JSON part in the response
json_start = response_text.find('{')
json_end = response_text.rfind('}') + 1
if json_start >= 0 and json_end > 0:
json_str = response_text[json_start:json_end]
return json.loads(json_str)
else:
raise ValueError("No JSON found in response")
except Exception as e:
print(f"Error parsing Gemini response: {e}")
return self._generate_basic_prep({})

def _generate_basic_prep(self, job_requirements: Dict) -> Dict:
"""Basic fallback interview prep without AI"""
return {
"tips": [
"Research the company's recent projects and values.",
"Review the core technologies mentioned in the job description.",
"Prepare examples using the STAR method (Situation, Task, Action, Result).",
"Practice explaining your technical decisions and trade-offs.",
"Have 2-3 thoughtful questions ready for the interviewer."
],
"technical_questions": [
{"question": "Can you walk us through a challenging technical problem you solved recently?", "context": "Focus on your problem-solving process and final outcome."},
{"question": "What is your experience with the core tech stack mentioned in this role?", "context": "Be specific about libraries and frameworks you've used."}
],
"behavioral_questions": [
{"question": "Tell me about a time you had a conflict with a teammate. How did you handle it?", "context": "Focus on collaboration and professionalism."},
{"question": "Where do you see your technical skills evolving in the next 2 years?", "context": "Show growth mindset and alignment with company goals."}
],
"situational_questions": [
{"question": "If you are given a task with a tight deadline and unclear requirements, what would you do?", "context": "Focus on communication and prioritization."}
],
"winning_strategy": "Be authentic, demonstrate how your unique skills solve the company's specific problems, and show enthusiasm for the role."
}
28 changes: 28 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from scrapers.github_scraper import GitHubScraper
from scrapers.linkedin_scraper import LinkedInScraper
from analyzer.job_analyzer import JobAnalyzer
from analyzer.interview_generator import InterviewGenerator
from generator.resume_generator import ResumeGenerator
from generator.latex_compiler import LaTeXCompiler

Expand All @@ -29,6 +30,7 @@
github_scraper = GitHubScraper()
linkedin_scraper = LinkedInScraper()
job_analyzer = JobAnalyzer()
interview_generator = InterviewGenerator()
resume_generator = ResumeGenerator()
latex_compiler = LaTeXCompiler()

Expand Down Expand Up @@ -251,6 +253,7 @@ def index():


@app.route("/api/generate-resume", methods=["POST"])
@app.route("/api/v1/generate-resume", methods=["POST"])
def generate_resume():
"""
Main endpoint to generate a tailored resume
Expand Down Expand Up @@ -337,6 +340,31 @@ def generate_resume():
return jsonify({"error": str(e)}), 500


@app.route("/api/interview-prep", methods=["POST"])
@app.route("/api/v1/interview-prep", methods=["POST"])
def interview_prep():
"""
Endpoint to generate interview preparation tips and questions
"""
try:
data = request.get_json(silent=True) or {}
job_description = data.get("job_description", "").strip()

if not job_description:
return jsonify({"error": "Job description is required"}), 400

# Analyze job description
job_requirements = job_analyzer.analyze(job_description)

# Generate interview prep
prep_data = interview_generator.generate(job_requirements)

return jsonify(prep_data)

except Exception as e:
return jsonify({"error": str(e)}), 500


@app.route("/health")
def health():
"""Health check endpoint"""
Expand Down
42 changes: 42 additions & 0 deletions app_production.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from scrapers.github_scraper import GitHubScraper
from scrapers.linkedin_scraper import LinkedInScraper
from analyzer.job_analyzer import JobAnalyzer
from analyzer.interview_generator import InterviewGenerator
from generator.resume_generator import ResumeGenerator
from generator.latex_compiler import LaTeXCompiler

Expand Down Expand Up @@ -74,6 +75,7 @@
github_scraper = GitHubScraper()
linkedin_scraper = LinkedInScraper()
job_analyzer = JobAnalyzer()
interview_generator = InterviewGenerator()
resume_generator = ResumeGenerator()
latex_compiler = LaTeXCompiler()
logger.info("All components initialized successfully")
Expand Down Expand Up @@ -552,6 +554,46 @@ def generate_resume():
)


@app.route("/api/v1/interview-prep", methods=["POST"])
def interview_prep():
"""
Endpoint to generate interview preparation tips and questions
"""
try:
logger.info(f"[{request.request_id}] Interview prep request")
data = request.json
if not data:
raise ValidationError("Request body is required")

job_description = data.get("job_description", "").strip()

if not job_description:
raise ValidationError("Job description is required")

# Analyze job description
job_requirements = job_analyzer.analyze(job_description)

# Generate interview prep
prep_data = interview_generator.generate(job_requirements)

return jsonify(prep_data)

except ValidationError as e:
return jsonify({"error": "VALIDATION_ERROR", "message": str(e)}), 400
except Exception as e:
logger.error(f"Interview prep generation failed: {e}", exc_info=True)
return (
jsonify(
{
"error": "INTERNAL_ERROR",
"message": "An unexpected error occurred during interview prep generation",
"status": 500,
}
),
500,
)


@app.route("/health", methods=["GET"])
def health():
"""Health check endpoint"""
Expand Down
Loading