Sankalp Rai Gambhir· Fullstack Software & AI Engineer
HomeProjectsInsightsSkillsBlogContactResume
Back to Projects

Resume Diff AI

An AI-powered platform for comparing resumes against job descriptions, providing actionable insights and skill gap analysis.

ReactTypeScriptFastAPIPythonTailwind CSSViteDocker
GitHubLive Demo
Resume Diff AI
5+
API Endpoints
PDF, DOC, DOCX, TXT
File Formats Supported
80%+
Test Coverage
Overview

Resume Diff AI is a full-stack application that leverages OpenAI's language models to analyze and compare resumes with job descriptions. The backend, built with FastAPI, processes uploaded files and text, extracts relevant information, and interacts with OpenAI to generate a detailed comparison. The frontend, a mobile-first React SPA, allows users to upload resumes and job descriptions, view match percentages, skill gaps, and export results. The system features robust validation, error handling, and a modern, accessible UI.

Timeline: 1 month
Role: Fullstack Software & AI Engineer
Implementation Overview
  • ✓AI-powered resume and job description comparison using OpenAI completions API
  • ✓Match percentage calculation and skill gap analysis
  • ✓Support for multiple resume and JD file formats (PDF, DOC, DOCX, TXT)
  • ✓Mobile-first, accessible React SPA with animated progress circle and skill chips
  • ✓Export missing skills to CSV and copy to clipboard
  • ✓Comprehensive error handling and client/server-side validation
  • ✓Request cancellation for in-flight API calls
  • ✓Dockerized backend and frontend for easy deployment

Technical Deep Dive

1
Problem

Ensuring accurate skill extraction and matching from varied resume and JD formats

✓

Solution

Used OpenAI prompt engineering to enforce strict JSON output and reliable skill matching

</>

Implementation

OpenAI Comparison Prompt Template

COMPARISON_PROMPT_TEMPLATE = "You are a strict JSON generator. Compare the following Job Description (JD) text and Resume text, and output a single JSON object with exactly these keys: matchPercent, matchedSkills, missingSkills, highlights (optional), warnings (optional). The JSON must be the only content in your response. matchPercent must be an integer from 0 to 100. matchedSkills and missingSkills must be arrays of strings. highlights (optional) can include jdMatches and resumeMatches each being arrays of objects with term and context.

JD text (start):

{{JD_TEXT}}

Resume text (start):

{{RESUME_TEXT}}

Instructions:

1. Identify skill tokens and role requirements from JD.
2. Find which JD skills/requirements are present in the resume (matched), and which are not (missing).
3. Compute matchPercent as round(100 * matched / (matched + missing)). If missing + matched = 0, set matchPercent to 0.
4. Provide matchedSkills (deduplicated), and missingSkills (deduplicated).
5. Optionally provide highlights.jdMatches and highlights.resumeMatches where each highlight object contains term and a short context excerpt showing the occurrence.
6. If you had to truncate text, or if there is ambiguity, include an entry in warnings.

Return EXACTLY one JSON object and nothing else.

Example output format:
{
  "matchPercent": 80,
  ...
}
Key Insight: This prompt template ensures OpenAI returns a strict, structured JSON response for reliable parsing and analysis.
2
Problem

Handling large file uploads and text extraction reliably

✓

Solution

Implemented robust file parsing and validation for multiple formats

</>

Implementation

Frontend API Integration Hook

export const useCompare = ({ apiBaseUrl = '/api' }: UseCompareParams = {}) => {
  const [state, setState] = useState<CompareState>({
    data: null,
    loading: false,
    error: null,
  });
  const abortControllerRef = useRef<AbortController | null>(null);
  const compare = useCallback(async (options: CompareOptions): Promise<void> => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
    abortControllerRef.current = new AbortController();
    setState({ data: null, loading: true, error: null });
    try {
      const formData = new FormData();
      if (options.jdFile) {
        formData.append('jd_file', options.jdFile);
      }
      if (options.jdText) {
        formData.append('jd_text', options.jdText);
      }
      formData.append('resume_file', options.resumeFile);
      const response = await fetch(`${apiBaseUrl}/compare`, {
        method: 'POST',
        body: formData,
        signal: abortControllerRef.current.signal,
      });
      if (!response.ok) {
        throw new Error('API error');
      }
      const data = await response.json();
      setState({ data, loading: false, error: null });
    } catch (error: any) {
      if (error.name === 'AbortError') return;
      setState({ data: null, loading: false, error: error.message });
    }
  }, [apiBaseUrl]);
  const cancel = useCallback(() => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
  }, []);
  const reset = useCallback(() => {
    setState({ data: null, loading: false, error: null });
  }, []);
  return { ...state, compare, cancel, reset };
};
Key Insight: This React hook manages API calls for resume comparison, including request cancellation and error handling.
3
Problem

Maintaining a responsive UI during long-running AI requests

✓

Solution

Added request cancellation and optimistic UI updates for better user experience

View Full Repository