Introduction
In today’s competitive job market, Applicant Tracking Systems (ATS) play a crucial role in filtering resumes before they reach hiring managers. Many job seekers fail to optimize their resumes, resulting in low ATS scores and missed opportunities.
This project solves that problem by analyzing resumes against job descriptions and calculating an ATS score. The system extracts text from PDF resumes and job descriptions, identifies key skills and keywords, and determines how well a resume matches a given job posting. Additionally, it provides AI-generated feedback to improve the resume.

The project consists of two main components:
- Backend (FastAPI) – Handles file uploads, text extraction, NLP analysis, and ATS scoring.
- Frontend (Angular) – Allows users to upload resumes and job descriptions and displays ATS scores and recommendations.
FastAPI Backend: ATS Score Calculator
1. Setting Up FastAPI
The backend is built using FastAPI, a high-performance web framework for building APIs in Python.
from fastapi import FastAPI, UploadFile, File, Form
from fastapi.middleware.cors import CORSMiddleware
import os
import time
import fitz # PyMuPDF for PDF extraction
import spacy
import requests
from typing import Dict, Optional
2. Configuring CORS and File Storage
We enable CORS (Cross-Origin Resource Sharing) to allow communication between our Angular frontend and FastAPI backend. Additionally, we create an uploads
directory to store uploaded resumes and job descriptions.
app = FastAPI()
# Enable CORS for frontend (Angular)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:4200"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Folder to store uploaded files
UPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
3. File Upload Handling
We define an asynchronous function to save uploaded files with a timestamp-based filename.
async def save_uploaded_file(upload_file: UploadFile) -> str:
timestamp = int(time.time())
filename = f"{timestamp}_{upload_file.filename}"
file_path = os.path.join(UPLOAD_DIR, filename)
with open(file_path, "wb") as f:
f.write(await upload_file.read())
return file_path
4. Extracting Text from PDFs
We use PyMuPDF (fitz) to extract text from PDF resumes and job descriptions.
def extract_text_from_pdf(file_path: str) -> str:
doc = fitz.open(file_path)
text = "\n".join([page.get_text("text") for page in doc])
return text.strip()
5. NLP Processing and ATS Score Calculation
We load spaCy’s NLP model to extract relevant nouns and proper nouns from the resume and job description. The ATS score is calculated based on the keyword match percentage.
# Load spaCy NLP model
nlp = spacy.load("en_core_web_sm")
@app.post("/analyze_resume/")
async def analyze_resume(
resume: UploadFile = File(...),
job_description: Optional[UploadFile] = File(None),
job_description_text: Optional[str] = Form(None)
) -> Dict:
# Save and extract resume text
resume_path = await save_uploaded_file(resume)
resume_text = extract_text_from_pdf(resume_path)
# Extract job description text
if job_description:
job_path = await save_uploaded_file(job_description)
job_text = extract_text_from_pdf(job_path)
elif job_description_text:
job_text = job_description_text.strip()
else:
return {"error": "Please provide either a job description file or text."}
# Extract keywords
resume_keywords = [token.text for token in nlp(resume_text) if token.pos_ in ["NOUN", "PROPN"]]
job_keywords = [token.text for token in nlp(job_text) if token.pos_ in ["NOUN", "PROPN"]]
# Calculate ATS Score
matched_keywords = set(resume_keywords) & set(job_keywords)
ats_score = round((len(matched_keywords) / len(job_keywords) * 100) if job_keywords else 0, 2)
return {
"ATS Score": ats_score,
"Matched Keywords": list(matched_keywords),
"Saved Files": {"resume": resume_path, "job_description": job_text[:1000] + "..."}
}
6. AI-Generated Feedback Using Hugging Face API
To help users improve their resumes, we use Falcon-7B, a large AI model from Hugging Face, to provide recommendations.
# Hugging Face API Key
HUGGING_FACE_API_KEY = "XXXXXXXXXXX"
model_name = "tiiuae/falcon-7b-instruct"
prompt = f"Your resume has an ATS score of {ats_score}%. The matched keywords are {list(matched_keywords)}. How to improve the score?"
headers = {"Authorization": f"Bearer {HUGGING_FACE_API_KEY}"}
response = requests.post(
f"https://api-inference.huggingface.co/models/{model_name}",
headers=headers,
json={"inputs": prompt, "parameters": {"max_new_tokens": 1000, "temperature": 0.7}},
)
feedback = response.json()[0].get('generated_text', "No feedback available.") if response.status_code == 200 else "Error generating feedback."
Angular Frontend: User Interface for ATS Analysis
1. File Upload Component in Angular
The Angular component handles resume and job description uploads, text input, and API calls to FastAPI.
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import axios from 'axios';
@Component({
selector: 'app-upload',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './upload.component.html',
styleUrls: ['./upload.component.scss']
})
export class UploadComponent {
resumeFile: File | null = null;
jobFile: File | null = null;
jobText: string = ''; // Job description text
atsScore: number | null = null;
matchedKeywords: string[] = [];
feedback: string = '';
loading: boolean = false;
onResumeUpload(event: any) {
if (event.target.files.length > 0) {
this.resumeFile = event.target.files[0];
}
}
onJobUpload(event: any) {
if (event.target.files.length > 0) {
this.jobFile = event.target.files[0];
this.jobText = '';
}
}
onJobTextChange() {
if (this.jobText.trim().length > 0) {
this.jobFile = null;
}
}
async submitFiles() {
if (!this.resumeFile) {
alert("Please upload a Resume file.");
return;
}
if ((this.jobFile && this.jobText.trim().length > 0) || (!this.jobFile && !this.jobText.trim().length)) {
alert("Please upload a Job Description file OR enter the job description text, but not both.");
return;
}
this.loading = true;
const formData = new FormData();
formData.append('resume', this.resumeFile);
if (this.jobFile) {
formData.append('job_description', this.jobFile);
} else {
formData.append('job_description_text', this.jobText);
}
try {
const response = await axios.post('http://127.0.0.1:8000/analyze_resume/', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
withCredentials: true
});
this.atsScore = response.data["ATS Score"];
this.matchedKeywords = response.data["Matched Keywords"];
this.feedback = response.data["AI Feedback"];
} catch (error) {
console.error('Error uploading:', error);
alert("Failed to analyze the resume.");
} finally {
this.loading = false;
}
}
}
2. HTML Template for File Upload & Analysis Display
<div class="main-container">
<div class="upload-section">
<h1>ATS Score Calculator</h1><hr/>
<h2>Upload Resume & Job Description</h2>
<label for="resume">Upload Resume (PDF):</label>
<input type="file" id="resume" (change)="onResumeUpload($event)" accept="application/pdf">
<br/>
<label for="job">Upload Job Description (PDF)</label>
<input type="file" id="job" (change)="onJobUpload($event)" accept="application/pdf" [disabled]="jobText.trim().length > 0">
<br/>
<label for="job">Enter Job Description</label>
<textarea id="jobText" [(ngModel)]="jobText" placeholder="Enter job description if no file" rows="40" (input)="onJobTextChange()" [disabled]="jobFile !== null"></textarea>
<p *ngIf="jobFile && jobText.trim().length > 0" class="error-message">Please enter only one: a file OR text.</p>
<button (click)="submitFiles()" [disabled]="loading">Analyze Resume</button>
<div *ngIf="loading" class="loading">Processing...</div>
</div>
<div class="response-section" *ngIf="atsScore == null"><div class="hide-box">Whats your ATS Score?</div></div>
<div class="response-section" *ngIf="atsScore !== null">
<h3>ATS Score</h3>
<p class="ats-score">{{ atsScore }}%</p>
<h3>Matched Keywords</h3>
<div class="keywords-container">
<span *ngFor="let keyword of matchedKeywords" class="keyword">{{ keyword }}</span>
</div>
<h3>Feedback</h3>
<div class="feedback-container" [innerHTML]="feedback"></div>
</div>
</div>
Conclusion
This project demonstrates how FastAPI and Angular can be combined to create an ATS Resume Analyzer. It allows job seekers to analyze their resumes and get AI-driven feedback to improve their chances of passing ATS filters.