swipe-me / main.py
omgy's picture
Update main.py
828d9c3 verified
import os
# Set environment variables BEFORE importing transformers
os.environ["HF_HOME"] = "/app/cache"
os.environ['TRANSFORMERS_CACHE'] = '/app/cache'
os.environ['HF_HUB_CACHE'] = '/app/cache'
from fastapi import FastAPI, File, UploadFile, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List
import json
import pandas as pd
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from scipy.special import softmax
import torch
app = FastAPI(
title="Candidate Ranking API",
description="API for ranking candidates based on resume and cover letter sentiment analysis",
version="1.0.0"
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, replace with specific domains
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Add root endpoint
@app.get("/")
async def root():
return {
"message": "Candidate Ranking API is running!",
"endpoints": {
"upload": "POST /upload - Upload Excel file with candidate data",
"predict": "GET /predict - Get ranked candidates",
"docs": "GET /docs - API documentation",
"upload_form": "GET /upload-form - Simple upload form",
"test_interface": "GET /test-interface - Complete testing interface"
},
"links": {
"api_docs": "/docs",
"upload_form": "/upload-form",
"test_interface": "/test-interface"
}
}
# Load model and tokenizer
tokenizer = AutoTokenizer.from_pretrained("mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis")
model = AutoModelForSequenceClassification.from_pretrained("mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis")
class Candidate(BaseModel):
name: str
surname: str
resume_link: str
cover_letter_link: str
priority: float = 0.0
def rank_candidate(name, surname, resume, cover_letter):
text = f"{name} {surname} {resume} {cover_letter}"
inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
with torch.no_grad():
logits = model(**inputs).logits
scores = softmax(logits.numpy()[0])
return round(float(scores[2]) * 100, 2) # Use positive sentiment score
# Add simple web interface
@app.get("/test-interface", response_class=HTMLResponse)
async def test_interface():
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Testing Interface</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1000px; margin: 20px auto; padding: 20px; background: #f5f5f5; }
.container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
h1 { color: #333; text-align: center; margin-bottom: 30px; }
.section { background: #f8f9ff; padding: 20px; margin: 20px 0; border-radius: 8px; border-left: 4px solid #007bff; }
.section h2 { color: #007bff; margin-bottom: 15px; }
.form-group { margin: 15px 0; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="file"] { width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 5px; }
button { background: #007bff; color: white; padding: 12px 20px; border: none; border-radius: 5px; cursor: pointer; margin: 5px; }
button:hover { background: #0056b3; }
button:disabled { background: #ccc; cursor: not-allowed; }
.btn-secondary { background: #28a745; }
.btn-danger { background: #dc3545; }
.response { background: #1e1e1e; color: #00ff00; padding: 15px; border-radius: 5px; font-family: monospace; white-space: pre-wrap; max-height: 300px; overflow-y: auto; margin: 15px 0; }
.loading { display: none; text-align: center; padding: 20px; }
.spinner { border: 4px solid #f3f3f3; border-top: 4px solid #007bff; border-radius: 50%; width: 30px; height: 30px; animation: spin 1s linear infinite; margin: 0 auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.candidates-table { width: 100%; border-collapse: collapse; margin: 20px 0; background: white; border-radius: 5px; overflow: hidden; }
.candidates-table th, .candidates-table td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
.candidates-table th { background: #007bff; color: white; }
.candidates-table tr:hover { background: #f8f9ff; }
.priority-high { background: #28a745; color: white; padding: 4px 8px; border-radius: 15px; }
.priority-medium { background: #ffc107; color: black; padding: 4px 8px; border-radius: 15px; }
.priority-low { background: #dc3545; color: white; padding: 4px 8px; border-radius: 15px; }
</style>
</head>
<body>
<div class="container">
<h1>🧪 Candidate Ranking API Tester</h1>
<div class="section">
<h2>📋 API Endpoints</h2>
<button onclick="testRoot()">Test Root (/)</button>
<button onclick="getCandidates()">Get Candidates (/predict)</button>
<button onclick="window.open('/docs', '_blank')" class="btn-secondary">API Docs</button>
</div>
<div class="section">
<h2>📁 File Upload Test</h2>
<form id="uploadForm" enctype="multipart/form-data">
<div class="form-group">
<label for="file">Select Excel File:</label>
<input type="file" id="file" name="file" accept=".xlsx,.xls" required>
<small>Required columns: Name, Surname, Resume Link, Cover Letter Link</small>
</div>
<button type="submit">Upload & Process File</button>
<button type="button" onclick="generateSample()" class="btn-secondary">Download Sample CSV</button>
<button type="button" onclick="clearLog()" class="btn-danger">Clear Log</button>
</form>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Processing...</p>
</div>
<div class="section">
<h2>📊 Response Log</h2>
<div class="response" id="responseLog">Ready to test API...\n</div>
</div>
<div class="section" id="resultsSection" style="display: none;">
<h2>🏆 Ranked Candidates</h2>
<div id="candidatesTable"></div>
</div>
</div>
<script>
function log(message, type = 'info') {
const logArea = document.getElementById('responseLog');
const timestamp = new Date().toLocaleTimeString();
const prefix = type === 'error' ? '❌' : type === 'success' ? '✅' : 'ℹ️';
logArea.textContent += `[${timestamp}] ${prefix} ${message}\n`;
logArea.scrollTop = logArea.scrollHeight;
}
function showLoading() {
document.getElementById('loading').style.display = 'block';
}
function hideLoading() {
document.getElementById('loading').style.display = 'none';
}
function clearLog() {
document.getElementById('responseLog').textContent = 'Log cleared...\n';
document.getElementById('resultsSection').style.display = 'none';
}
async function testRoot() {
showLoading();
log('Testing root endpoint...');
try {
const response = await fetch('/');
const data = await response.json();
log(`Status: ${response.status}`, response.ok ? 'success' : 'error');
log(`Response: ${JSON.stringify(data, null, 2)}`);
} catch (error) {
log(`Error: ${error.message}`, 'error');
}
hideLoading();
}
async function getCandidates() {
showLoading();
log('Getting ranked candidates...');
try {
const response = await fetch('/predict');
const data = await response.json();
log(`Status: ${response.status}`, response.ok ? 'success' : 'error');
if (response.ok && Array.isArray(data) && data.length > 0) {
log(`Found ${data.length} candidates`, 'success');
displayCandidates(data);
} else if (Array.isArray(data) && data.length === 0) {
log('No candidates found. Upload a file first.');
} else {
log(`Response: ${JSON.stringify(data, null, 2)}`);
}
} catch (error) {
log(`Error: ${error.message}`, 'error');
}
hideLoading();
}
function displayCandidates(candidates) {
const sorted = candidates.sort((a, b) => (b.priority || 0) - (a.priority || 0));
let tableHTML = `
<table class="candidates-table">
<thead>
<tr>
<th>Rank</th>
<th>Name</th>
<th>Surname</th>
<th>Priority Score</th>
<th>Resume</th>
<th>Cover Letter</th>
</tr>
</thead>
<tbody>
`;
sorted.forEach((candidate, index) => {
const priority = candidate.priority || 0;
let scoreClass = 'priority-low';
if (priority >= 70) scoreClass = 'priority-high';
else if (priority >= 40) scoreClass = 'priority-medium';
tableHTML += `
<tr>
<td><strong>#${index + 1}</strong></td>
<td>${candidate.name || 'N/A'}</td>
<td>${candidate.surname || 'N/A'}</td>
<td><span class="${scoreClass}">${priority.toFixed(1)}%</span></td>
<td><a href="${candidate.resume_link || '#'}" target="_blank">Link</a></td>
<td><a href="${candidate.cover_letter_link || '#'}" target="_blank">Link</a></td>
</tr>
`;
});
tableHTML += '</tbody></table>';
document.getElementById('candidatesTable').innerHTML = tableHTML;
document.getElementById('resultsSection').style.display = 'block';
}
function generateSample() {
const sampleData = [
['Name', 'Surname', 'Resume Link', 'Cover Letter Link'],
['John', 'Smith', 'https://example.com/john-resume.pdf', 'https://example.com/john-cover.pdf'],
['Sarah', 'Johnson', 'https://example.com/sarah-resume.pdf', 'https://example.com/sarah-cover.pdf'],
['Michael', 'Brown', 'https://example.com/michael-resume.pdf', 'https://example.com/michael-cover.pdf'],
['Emily', 'Davis', 'https://example.com/emily-resume.pdf', 'https://example.com/emily-cover.pdf'],
['David', 'Wilson', 'https://example.com/david-resume.pdf', 'https://example.com/david-cover.pdf']
];
const csvContent = sampleData.map(row => row.join(',')).join('\\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'sample_candidates.csv';
a.click();
window.URL.revokeObjectURL(url);
log('Sample CSV downloaded! Convert to Excel and upload.', 'success');
}
// Handle form submission
document.getElementById('uploadForm').addEventListener('submit', async function(e) {
e.preventDefault();
const fileInput = document.getElementById('file');
const file = fileInput.files[0];
if (!file) {
log('Please select a file', 'error');
return;
}
showLoading();
log(`Uploading: ${file.name} (${(file.size / 1024).toFixed(1)} KB)`);
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
log(`Upload status: ${response.status}`, response.ok ? 'success' : 'error');
log(`Response: ${JSON.stringify(data, null, 2)}`);
if (response.ok) {
log('File uploaded successfully!', 'success');
setTimeout(() => getCandidates(), 1500);
}
} catch (error) {
log(`Upload error: ${error.message}`, 'error');
}
hideLoading();
});
// Initialize
log('API Testing Interface ready!', 'success');
log('1. Test endpoints first');
log('2. Upload Excel file or download sample');
log('3. View ranked results');
</script>
</body>
</html>
"""
@app.get("/upload-form", response_class=HTMLResponse)
async def upload_form():
return """
<!DOCTYPE html>
<html>
<head>
<title>Candidate Ranking System</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
.container { background: #f5f5f5; padding: 30px; border-radius: 10px; }
h1 { color: #333; text-align: center; }
.form-group { margin: 20px 0; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="file"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; }
button:hover { background: #0056b3; }
.info { background: #e7f3ff; padding: 15px; border-radius: 5px; margin: 20px 0; }
</style>
</head>
<body>
<div class="container">
<h1>Candidate Ranking System</h1>
<div class="info">
<p><strong>Upload an Excel file with the following columns:</strong></p>
<ul>
<li>Name</li>
<li>Surname</li>
<li>Resume Link</li>
<li>Cover Letter Link</li>
</ul>
</div>
<form action="/upload" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="file">Select Excel File:</label>
<input type="file" id="file" name="file" accept=".xlsx,.xls" required>
</div>
<button type="submit">Upload and Rank Candidates</button>
</form>
<div style="margin-top: 30px;">
<a href="/predict" target="_blank">View Ranked Candidates (JSON)</a> |
<a href="/docs" target="_blank">API Documentation</a>
</div>
</div>
</body>
</html>
"""
@app.post("/upload")
async def upload_excel(file: UploadFile = File(...)):
df = pd.read_excel(file.file)
candidates = []
for _, row in df.iterrows():
name = str(row.get("Name", ""))
surname = str(row.get("Surname", ""))
resume_link = str(row.get("Resume Link", ""))
cover_letter_link = str(row.get("Cover Letter Link", ""))
# Placeholder content
resume = "This is a dummy summary of resume from link"
cover_letter = "This is a dummy summary of cover letter from link"
priority = rank_candidate(name, surname, resume, cover_letter)
candidate = {
"name": name,
"surname": surname,
"resume_link": resume_link,
"cover_letter_link": cover_letter_link,
"priority": priority
}
candidates.append(candidate)
# Save to JSON
with open("candidates.json", "w") as f:
json.dump(candidates, f, indent=4)
return {"message": "File processed and candidates.json created successfully!"}
@app.get("/predict", response_model=List[Candidate])
def get_ranked_candidates():
if not os.path.exists("candidates.json"):
return []
with open("candidates.json", "r") as f:
return json.load(f)