Skip to content

Resume Analyzer

Intermediate ai frontend database vision

AI-powered resume analysis tool that evaluates ATS compatibility, provides job match scores, generates improved summaries, and prepares interview questions.

Transform your job application process with an AI-powered resume analyzer that goes beyond basic keyword matching. This full-stack application demonstrates building a complete mobile-ready app using Habits — from AI workflows to a polished frontend UI.

What It Does

  • ATS Scoring: Evaluates your resume against Applicant Tracking System standards
  • Job Match Analysis: Compares your skills and experience to target job requirements
  • Improved Summary: AI-generates a better professional summary based on your experience
  • Interview Prep: Generates likely interview questions based on your resume and target role
  • Cover Letter Generation: Creates customized cover letters for specific job applications
  • History Tracking: Stores all analyses locally for future reference

Why This Example

This showcase demonstrates:

  • Multi-habit workflows — Multiple habits working together (analyze, generate cover letter, list, get)
  • Vision AI integration — Uses OpenAI Vision to extract text from resume images
  • Local database — PouchDB for storing analyses without external dependencies
  • Mobile-first UI — Responsive design ready for packaging as mobile/desktop app
  • Complete app packaging — Can be exported as standalone binary, desktop app, or mobile app

Workflow Visualization

Requirements

  • OpenAI API key (for GPT-4o and Vision API)

Key Files

yaml
version: "1.0"

workflows:
  - id: analyze-resume
    path: ./habits/analyze-resume.yaml
    enabled: true

  - id: generate-cover-letter
    path: ./habits/generate-cover-letter.yaml
    enabled: true

  - id: list-analyses
    path: ./habits/list-analyses.yaml
    enabled: true

  - id: get-analysis
    path: ./habits/get-analysis.yaml
    enabled: true

server:
  port: 13000
  host: "0.0.0.0"
  frontend: ./frontend
  openapi: true

logging:
  level: info
  outputs: [console]
  format: text
  colorize: true
example
OPENAI_API_KEY=
yaml
id: analyze-resume
name: Analyze Resume
description: Analyze resume image against job description and provide improvement suggestions

input:
  - id: resumeImage
    type: string
    required: true
    description: Resume image content (base64 encoded PNG/JPG/WebP)
  - id: jobDescription
    type: string
    required: false
    description: Target job description
  - id: targetRole
    type: string
    required: false
    description: Target job title/role

nodes:
  # Extract text from image using OpenAI Vision
  - id: extract-text
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-openai"
      operation: vision_prompt
      credentials:
        openai:
          apiKey: "{{habits.env.OPENAI_API_KEY}}"
      params:
        image: "{{habits.input.resumeImage}}"
        temperature: 0.1
        maxTokens: 4000
        prompt: |
          Extract all text content from this resume image.
          
          Return ONLY the extracted text content, preserving the structure and formatting as plain text.
          Include all sections: contact info, summary, experience, education, skills, certifications.

  # Parse and structure resume
  - id: parse-resume
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-openai"
      operation: ask_chatgpt
      credentials:
        openai:
          apiKey: "{{habits.env.OPENAI_API_KEY}}"
      params:
        model: gpt-4o
        temperature: 0.3
        maxTokens: 1000
        prompt: |
          Parse this resume and extract structured information:
          
          {{extract-text}}
          
          Return JSON:
          {
            "name": "Full name",
            "email": "Email",
            "phone": "Phone",
            "location": "Location",
            "summary": "Professional summary",
            "experience": [{"title": "", "company": "", "dates": "", "bullets": []}],
            "education": [{"degree": "", "school": "", "year": ""}],
            "skills": ["skill1", "skill2"],
            "certifications": [],
            "yearsExperience": number
          }

  # Score ATS compatibility
  - id: ats-score
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-openai"
      operation: ask_chatgpt
      credentials:
        openai:
          apiKey: "{{habits.env.OPENAI_API_KEY}}"
      params:
        model: gpt-4o
        temperature: 0.4
        maxTokens: 600
        prompt: |
          Analyze this resume for ATS (Applicant Tracking System) compatibility:
          
          Resume: {{extract-text}}
          Target Job: {{habits.input.jobDescription}}
          
          Evaluate:
          1. Keyword optimization (does it include relevant keywords?)
          2. Format compatibility (is it ATS-friendly?)
          3. Contact info completeness
          4. Section structure
          5. Quantifiable achievements
          
          Return JSON:
          {
            "overallScore": number (0-100),
            "keywordScore": number (0-100),
            "formatScore": number (0-100),
            "contentScore": number (0-100),
            "missingKeywords": ["keyword1", "keyword2"],
            "formatIssues": ["issue1", "issue2"],
            "suggestions": ["suggestion1", "suggestion2"]
          }

  # Analyze experience quality
  - id: experience-analysis
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-openai"
      operation: ask_chatgpt
      credentials:
        openai:
          apiKey: "{{habits.env.OPENAI_API_KEY}}"
      params:
        model: gpt-4o
        temperature: 0.5
        maxTokens: 800
        prompt: |
          Analyze the work experience section of this resume:
          
          {{extract-text}}
          
          For each position, evaluate:
          1. Action verbs usage
          2. Quantifiable results (numbers, percentages)
          3. Achievement vs. responsibility focus
          4. Relevance to target role: {{habits.input.targetRole}}
          
          Provide:
          - Strengths of each position description
          - Weaknesses and areas to improve
          - Specific rewrite suggestions for 2-3 weak bullets
          - Example of how to add metrics where missing

  # Job match analysis
  - id: job-match
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-openai"
      operation: ask_chatgpt
      credentials:
        openai:
          apiKey: "{{habits.env.OPENAI_API_KEY}}"
      params:
        model: gpt-4o
        temperature: 0.4
        maxTokens: 700
        prompt: |
          Compare this resume to the job requirements:
          
          Resume: {{extract-text}}
          
          Job Description: {{habits.input.jobDescription}}
          
          Target Role: {{habits.input.targetRole}}
          
          Return JSON:
          {
            "matchScore": number (0-100),
            "matchingSkills": ["skill1", "skill2"],
            "missingSkills": ["skill1", "skill2"],
            "experienceGaps": ["gap1", "gap2"],
            "strengthsForRole": ["strength1", "strength2"],
            "recommendations": ["rec1", "rec2"],
            "likelyOutcome": "Strong match / Moderate match / Weak match"
          }

  # Generate improved summary
  - id: generate-summary
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-openai"
      operation: ask_chatgpt
      credentials:
        openai:
          apiKey: "{{habits.env.OPENAI_API_KEY}}"
      params:
        model: gpt-4o
        temperature: 0.7
        maxTokens: 300
        roles:
          - role: system
            content: You are an expert resume writer who crafts compelling professional summaries.
        prompt: |
          Write an improved professional summary for this resume:
          
          Current Resume: {{extract-text}}
          
          Target Role: {{habits.input.targetRole}}
          
          Create a 3-4 sentence summary that:
          - Opens with years of experience and specialty
          - Highlights 2-3 key achievements
          - Includes relevant skills for the target role
          - Shows unique value proposition

  # Interview questions to prepare
  - id: interview-prep
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-openai"
      operation: ask_chatgpt
      credentials:
        openai:
          apiKey: "{{habits.env.OPENAI_API_KEY}}"
      params:
        model: gpt-4o
        temperature: 0.6
        maxTokens: 600
        prompt: |
          Based on this resume and job, generate interview preparation:
          
          Resume: {{extract-text}}
          Job: {{habits.input.jobDescription}}
          Role: {{habits.input.targetRole}}
          
          Provide:
          1. 5 likely interview questions based on resume gaps
          2. 3 behavioral questions (STAR format prep)
          3. 3 technical questions based on listed skills
          4. Suggested talking points for strengths
          5. How to address any weaknesses/gaps

  # Save analysis document using PouchDB
  - id: save-analysis
    type: bits
    data:
      framework: bits
      source: local
      module: "@ha-bits/bit-pouch"
      operation: insert
      params:
        collection: "resume_analyses"
        document:
          targetRole: "{{habits.input.targetRole}}"
          extractedText: "{{extract-text}}"
          parsed: "{{parse-resume}}"
          atsScore: "{{ats-score}}"
          experienceAnalysis: "{{experience-analysis}}"
          jobMatch: "{{job-match}}"
          improvedSummary: "{{generate-summary}}"
          interviewPrep: "{{interview-prep}}"
          createdAt: "{{habits.now}}"

  # Save image as attachment to the analysis document
  - id: save-attachment
    type: bits
    data:
      framework: bits
      source: local
      module: "@ha-bits/bit-pouch"
      operation: putAttachment
      params:
        collection: "resume_analyses"
        documentId: "{{save-analysis.id}}"
        attachmentName: "resume.webp"
        attachmentData: "{{habits.input.resumeImage}}"
        contentType: "image/webp"

edges:
  - source: extract-text
    target: parse-resume
  - source: parse-resume
    target: ats-score
  - source: parse-resume
    target: experience-analysis
  - source: parse-resume
    target: job-match
  - source: parse-resume
    target: generate-summary
  - source: job-match
    target: interview-prep
  - source: ats-score
    target: save-analysis
  - source: experience-analysis
    target: save-analysis
  - source: job-match
    target: save-analysis
  - source: generate-summary
    target: save-analysis
  - source: interview-prep
    target: save-analysis
  - source: save-analysis
    target: save-attachment

output:
  analysisId: "{{save-analysis.id}}"
  extractedText: "{{extract-text}}"
  parsed: "{{parse-resume}}"
  atsScore: "{{ats-score}}"
  experienceAnalysis: "{{experience-analysis}}"
  jobMatch: "{{job-match}}"
  improvedSummary: "{{generate-summary}}"
  interviewPrep: "{{interview-prep}}"
  attachmentSaved: "{{save-attachment.success}}"
yaml
id: generate-cover-letter
name: Generate Cover Letter
description: Generate a customized cover letter for a specific job

input:
  - id: resumeImage
    type: string
    required: true
    description: Resume image content (base64 encoded PNG/JPG/WebP)
  - id: jobDescription
    type: string
    required: true
    description: Target job description
  - id: companyName
    type: string
    required: true
    description: Company name
  - id: hiringManager
    type: string
    required: false
    description: Hiring manager name if known
  - id: tone
    type: string
    required: false
    description: formal, friendly, enthusiastic

nodes:
  # Extract text from image using OpenAI Vision
  - id: extract-text
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-openai"
      operation: vision_prompt
      credentials:
        openai:
          apiKey: "{{habits.env.OPENAI_API_KEY}}"
      params:
        image: "{{habits.input.resumeImage}}"
        temperature: 0.1
        maxTokens: 4000
        prompt: |
          Extract all text content from this resume image.
          
          Return ONLY the extracted text content, preserving the structure and formatting as plain text.

  - id: generate-letter
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-openai"
      operation: ask_chatgpt
      credentials:
        openai:
          apiKey: "{{habits.env.OPENAI_API_KEY}}"
      params:
        model: gpt-4o
        temperature: 0.7
        maxTokens: 800
        roles:
          - role: system
            content: You are an expert cover letter writer who crafts compelling, personalized letters that get interviews.
        prompt: |
          Write a cover letter for this application:
          
          Resume:
          {{extract-text}}
          
          Job Description:
          {{habits.input.jobDescription}}
          
          Company: {{habits.input.companyName}}
          Hiring Manager: {{habits.input.hiringManager}}
          Tone: {{habits.input.tone}}
          
          Structure:
          1. Opening hook that shows enthusiasm and mentions the specific role
          2. Paragraph connecting your experience to their needs (use specific examples)
          3. Paragraph showing knowledge of the company and cultural fit
          4. Strong closing with call to action
          
          Keep it to about 300-350 words. Be specific, not generic.

edges:
  - source: extract-text
    target: generate-letter

output:
  coverLetter: "{{generate-letter}}"
  extractedText: "{{extract-text}}"
yaml
id: get-analysis
name: Get Analysis
description: Get a specific resume analysis with optional PDF attachment

input:
  - id: id
    type: string
    required: true
  - id: includeAttachment
    type: boolean
    required: false
    description: Whether to include the PDF attachment

nodes:
  - id: fetch-analysis
    type: bits
    data:
      framework: bits
      source: local
      module: "@ha-bits/bit-pouch"
      operation: query
      params:
        collection: "resume_analyses"
        filter:
          _id: "{{habits.input.id}}"
        limit: 1

  - id: fetch-attachment
    type: bits
    data:
      framework: bits
      source: local
      module: "@ha-bits/bit-pouch"
      operation: getAttachment
      params:
        collection: "resume_analyses"
        documentId: "{{habits.input.id}}"
        attachmentName: "resume.pdf"

edges:
  - source: fetch-analysis
    target: fetch-attachment

output:
  analysis: "{{fetch-analysis.results[0]}}"
  found: "{{fetch-analysis.count > 0}}"
  attachment: "{{fetch-attachment}}"

Quick Start

Run directly using Cortex package, recommended for production runs, does not inlcude base or extra depdencies.

# First, download the example files
npx @ha-bits/cortex@latest server --config ./resume-analyzer/stack.yaml

Released under the Apache 2.0 License.