Skip to content

Repository files navigation

SG Job Hunt Copilot

Free AI-powered resume analysis tool built specifically for Singapore jobseekers. Combines Gemini AI with real Singapore data (MOM salaries, government programmes, MyCareersFuture jobs) to give advice that generic LLMs can't.

The Problem

Singapore's job market is brutal. Fresh grads send 400+ applications and get ghosted. 60% of workers say job hunting got harder this year. The common advice — "just paste your resume into ChatGPT" — misses the actual gaps:

  • Salary expectations are off → LLMs hallucinate Singapore salary data (we pull live data from data.gov.sg)
  • Missing keywords → generic AI reviews don't simulate real ATS parsing
  • Government support exists → but nobody knows about WSG, TeSA, CCP, SFJS
  • Advice is generic → ChatGPT doesn't know if you qualify for Mid-Career Subsidy at 42 or SFJS if retrenched
  • No job suggestions → LLMs can't search live job portals for you

What This Does (That ChatGPT Can't)

Feature How it works Why an LLM can't
AI Resume Rewrite Gemini 2.5 Flash-Lite rewrites your full resume with Singapore context baked in Uses live GES salary data from data.gov.sg + programme eligibility in its prompt, not hallucinations
Interview Prep Generates 5-10 interview questions tailored to your resume gaps vs the JD Questions target your specific weaknesses — e.g. "You lack AWS experience but the JD requires it"
LinkedIn Optimizer Generates a recruiter-optimized LinkedIn headline + summary from your resume Knows SG recruiter search patterns and your actual skill stack
Live Job Matching Gemini suggests job titles → searches MyCareersFuture API → shows real listings with salaries Real-time API search with AI-suggested queries, not training data
Auto Profile Extraction Uploads your resume and auto-fills age, experience, field, qualification, and employment status Infers from graduation year, work history dates, and degree type
ATS Keyword Match Extracts skills from your resume and the JD, shows match score with specific missing keywords Real keyword extraction with n-gram matching, not LLM guessing
Salary Benchmarking Compares your expected salary against live Graduate Employment Survey data from data.gov.sg (with static fallback) Uses actual published data with p25/median/p75 ranges — auto-updated from government source
Smart Programme Matching Checks your age, citizenship, employment status, and income against real eligibility rules Cross-references 9 programmes with specific criteria — shows only what you actually qualify for
Downloadable Revised Resume One-click PDF download of the AI-rewritten resume in A4 format Professional formatting with proper sections, ready to send
PDF Resume Upload Drag-and-drop PDF parsing in the browser — no copy-paste needed Client-side extraction, nothing leaves your browser

What's Been Built

Done

  • Next.js 16 app with TypeScript and Tailwind CSS
  • Landing page with hero section, feature cards, and privacy notice
  • PDF resume upload — drag-and-drop or click to upload, client-side parsing via pdfjs-dist
  • Paste text fallback — toggle between upload and paste modes
  • Auto profile extraction — uploads resume and auto-fills age (from graduation year), years of experience (from work history), field of study, qualification (Bachelor's/Diploma), and employment status (employed/unemployed/student) — all editable
  • Gemini 2.5 Flash-Lite integration — AI-powered resume analysis with structured output (Key Issues, Salary & Programmes, Revised Resume, Next Steps)
  • AI-powered job suggestions — Gemini suggests 3 job titles based on your resume, then searches MyCareersFuture for real listings
  • Live job listings — shows matching jobs from MyCareersFuture with salary ranges, company names, skills, posting dates, and direct apply links
  • Interview prep generator — Gemini generates 5-10 interview questions specific to the candidate's resume gaps vs the JD, with coaching tips for weak areas
  • LinkedIn headline + summary optimizer — generates a recruiter-optimized LinkedIn headline and About section based on the resume
  • Downloadable revised resume PDF — @react-pdf/renderer generates a professional A4 PDF from the AI rewrite
  • Free public AI service — server-side Gemini API key powers all users; optional BYOK (Bring Your Own Key) for unlimited usage
  • ATS keyword analyzer — extracts skills using dictionary lookup + n-gram matching across 100+ skill terms
  • ATS score ring — animated SVG visualization with color-coded score (red/amber/green)
  • Matched vs missing keyword pills — green for matched, red for missing from resume
  • Format issue detection — checks for ATS-breaking characters, resume length, missing sections
  • Live salary benchmarking — live data from data.gov.sg GES API, with static fallback for 34 fields
  • Salary percentile visualizer — shows where your expected salary sits on the distribution
  • Smart government programme matcher — 9 programmes with real eligibility functions that check age, citizenship, employment status, income, and target role
  • Personalized programme reasons — each matched programme shows why you qualify with specific numbers
  • Profile inputs — age, employment status (employed/unemployed/retrenched/student), citizenship (SC/PR/foreigner), expected salary, years of experience, field, and qualification
  • MyCareersFuture API integration — /api/jobs endpoint querying api.mycareersfuture.gov.sg/v2/jobs
  • Salary lookup API — /api/salary endpoint to query benchmarks by field
  • Input validation + sanitization — max 15K resume / 5K JD, prompt injection detection (14 patterns), content wrapped in XML delimiters
  • Hardened Gemini prompt — anti-injection rules in system prompt, safetySettings with BLOCK_MEDIUM_AND_ABOVE for dangerous content/harassment/hate speech
  • Output safety filter — scans LLM output for <script>, javascript:, and other XSS patterns before rendering
  • Tiered rate limiting — shared key: 5 analyses/5 min per IP; own key: 5/min; 429 + Retry-After headers
  • MCF response caching — 5-minute TTL cache to reduce redundant API calls under load
  • Gemini timeout — 30-second timeout on all LLM calls to prevent hanging serverless functions
  • Graceful degradation — if Gemini is rate-limited or down, returns data-only results (ATS, salary, programmes, keyword-based jobs) with a message
  • Tabbed results layout — Resume Analysis, Interview Prep, LinkedIn, Jobs, Salary & ATS, and Programmes organized in tabs to reduce scrolling
  • Privacy-first design — no accounts, no storage, no tracking, no third-party analytics

Roadmap

  • Networking suggestions (LinkedIn alumni finder)
  • Multilingual support (Mandarin, Malay, Tamil)
  • Mobile PWA for on-the-go use

Architecture

┌──────────────────────────────────────────────────────────┐
│                      Next.js App                          │
│                                                          │
│  ┌────────────┐  ┌────────────┐  ┌──────────────────┐   │
│  │  Landing   │  │  Analyzer  │  │   Results Panel  │   │
│  │   Page     │  │   Form     │  │  ├─ AI Analysis  │   │
│  └────────────┘  └─────┬──────┘  │  ├─ Job Listings │   │
│                        │         │  ├─ Salary Bench  │   │
│  ┌─────────────┐       │         │  ├─ ATS Score     │   │
│  │ FileUpload  │───────┤         │  └─ Programmes    │   │
│  │ (PDF parse) │       │         └────────┬──────────┘   │
│  └─────────────┘       │                  │              │
│  ┌─────────────┐       │         ┌────────┴──────────┐   │
│  │ Extract     │───────┤         │ DownloadResumePDF │   │
│  │ Profile     │       │         │ (@react-pdf)      │   │
│  └─────────────┘       │         └───────────────────┘   │
│                        │                                  │
│              ┌─────────▼──────────────────────────────┐  │
│              │            API Routes                  │  │
│              │                                        │  │
│              │  /api/analyze                          │  │
│              │    ├─ Tiered rate limiter               │  │
│              │    ├─ Input validation + sanitization   │  │
│              │    ├─ ATS keyword matcher               │  │
│              │    ├─ Salary benchmarker (data.gov.sg)   │  │
│              │    ├─ Programme eligibility checker     │  │
│              │    ├─ Gemini AI analysis                │  │
│              │    ├─ Gemini interview prep             │  │
│              │    ├─ Gemini LinkedIn optimizer          │  │
│              │    ├─ Gemini job title suggestions      │  │
│              │    ├─ MyCareersFuture job search (cached)│  │
│              │    └─ Output safety filter              │  │
│              │                                        │  │
│              │  /api/salary                            │  │
│              │    └─ MOM GES data lookup               │  │
│              └────────────────────────────────────────┘  │
│                          │                │               │
│  ┌───────────────────────▼────┐  ┌───────▼────────────┐  │
│  │      Data + Intelligence  │  │   External APIs    │  │
│  │  salary-benchmarks.ts     │  │  MyCareersFuture   │  │
│  │  government-programmes.ts │  │   /v2/jobs         │  │
│  │  gemini.ts                │  │  Google Gemini     │  │
│  │  ats-analyzer.ts          │  │   2.5 Flash-Lite   │  │
│  │  extract-profile.ts       │  └────────────────────┘  │
│  │  sanitize.ts              │                           │
│  │  output-filter.ts         │                           │
│  │  rate-limit.ts            │                           │
│  └───────────────────────────┘                           │
└──────────────────────────────────────────────────────────┘

Run Locally

git clone https://github.com/YOUR_USERNAME/sg-job-hunt-copilot.git
cd sg-job-hunt-copilot
npm install
npm run dev

Open http://localhost:3000.

Environment Setup

Copy .env.example to .env.local and add your Gemini API key:

cp .env.example .env.local
# Edit .env.local and set GEMINI_API_KEY=your_key_here

Get a free key at aistudio.google.com/apikey.

Without the env variable, users can still use the app by entering their own API key in the form.

Deploy for Free

Option 1: Vercel (Recommended — $0)

npm i -g vercel
vercel

# Set the API key securely (never stored in code)
vercel env add GEMINI_API_KEY production
# Paste your key when prompted

# Redeploy to pick up the env variable
vercel --prod

Free tier includes:

  • 100GB bandwidth/month
  • Serverless functions (API routes)
  • Automatic HTTPS
  • Global CDN

Important: After deploying, go to Vercel Dashboard → Settings → Deployment Protection and set it to "Only Preview Deployments" so the public can access your production URL without logging in.

Option 2: Cloudflare Pages ($0)

npm run build
npx wrangler pages deploy .next

Option 3: AWS (if you want to stay on AWS)

Cheapest AWS setup (~$0-5/month):

Service Purpose Cost
AWS Amplify Hosting Static + SSR hosting Free tier: 1000 build mins, 15GB served/month
or S3 + CloudFront Static export ~$0.50/month at low traffic
# AWS Amplify (easiest)
npm i -g @aws-amplify/cli
amplify init
amplify push

# Or static export to S3
# Add to next.config.ts: output: 'export'
npm run build
aws s3 sync out/ s3://your-bucket --delete

Project Structure

src/
├── app/
│   ├── api/
│   │   ├── analyze/route.ts       # Main endpoint: ATS + salary + programmes + Gemini + job search
│   │   ├── jobs/route.ts          # Standalone MyCareersFuture API proxy
│   │   └── salary/route.ts        # Salary benchmark lookup
│   ├── layout.tsx
│   ├── page.tsx                   # Landing page
│   └── globals.css
├── components/
│   ├── AnalyzerForm.tsx           # Form with upload/paste toggle + auto-filled profile inputs
│   ├── DownloadResumePDF.tsx      # PDF generation for revised resume (@react-pdf/renderer)
│   ├── FileUpload.tsx             # Drag-and-drop PDF/TXT upload
│   ├── ResultsPanel.tsx           # Results: AI analysis, jobs, salary, ATS, programmes
│   └── ScoreRing.tsx              # Animated ATS score ring (SVG)
├── data/
│   ├── salary-benchmarks.ts       # Static fallback GES data — 34 fields, Bachelor's + Diploma
│   └── government-programmes.ts   # 9 programmes with match() eligibility functions
└── lib/
    ├── ats-analyzer.ts            # ATS keyword extraction engine (dictionary + n-grams)
    ├── extract-profile.ts         # Auto-extract age, experience, field, status from resume text
    ├── gemini.ts                  # Gemini 2.5 Flash-Lite: analysis, interview prep, LinkedIn, job titles
    ├── job-search.ts              # MyCareersFuture API client with 5-min TTL cache
    ├── output-filter.ts           # LLM output safety filter (XSS, injection detection)
    ├── parse-pdf.ts               # Client-side PDF text extraction (pdfjs-dist)
    ├── rate-limit.ts              # Tiered rate limiter (shared key: 5/5min, own key: 5/min)
    ├── salary-api.ts              # Live data.gov.sg GES client with 24h cache + static fallback
    └── sanitize.ts                # Input validation, length limits, prompt injection detection

Data Sources

Data Source Update Frequency
Graduate salaries Live from data.gov.sg GES API, with static fallback Auto-updated (24h cache)
Job listings MyCareersFuture API (api.mycareersfuture.gov.sg/v2/jobs) Real-time
Government programmes WSG, SSG, IMDA, CPF Board official pages Manual (check quarterly)

Programme Eligibility Logic

Each programme has a match() function that checks your profile:

Programme Key Criteria
CCP — Tech SC/PR, 2+ years experience, targeting a different role
Mid-Career Enhanced Subsidy SC only, aged 40+
SkillsFuture Credit SC only, aged 25+
SFJS SC/PR, involuntarily unemployed, previous income ≤ $5k
TeSA SC/PR, targeting ICT/tech roles
WIS SC only, aged 30+, income $500–$3,000/month
WSG Career Matching SC/PR, active jobseeker
NTUC e2i SC/PR, active jobseeker
MyCareersFuture Open to all

Programmes that don't match your profile are hidden entirely — no generic lists.

How Job Matching Works

  1. You upload your resume
  2. Gemini analyzes it and suggests 3 specific job titles tailored to your skills
  3. Each title is searched against the MyCareersFuture API (/v2/jobs)
  4. Results show real listings with salary ranges, company names, skills, and direct apply links
  5. The server-side Gemini key powers all users by default; optional BYOK for unlimited usage

Security

  • API key is server-side only — stored as an encrypted environment variable in Vercel, never in source code or client-side JavaScript. Not visible in browser DevTools, network requests, or page source.
  • Tiered rate limiting — prevents abuse of the shared API key (5 analyses per 5 minutes per IP)
  • Input sanitization — 14 prompt injection patterns detected and blocked before reaching the LLM
  • Hardened system prompt — explicit anti-injection rules prevent prompt leaking or misuse
  • Output filtering — LLM responses scanned for XSS patterns (<script>, javascript:, etc.) before rendering
  • Safety settings — Gemini configured with BLOCK_MEDIUM_AND_ABOVE for dangerous content, harassment, and hate speech

Privacy

  • No data is stored or logged
  • No user accounts
  • No analytics/tracking
  • PDF parsing happens client-side (in your browser) — the file is never uploaded
  • Server-side API key is encrypted in Vercel's environment — never in source code
  • Optional user API key is stored in browser localStorage only — never sent to our server
  • Resume text is sent to Google's Gemini API for analysis, then immediately discarded
  • Open source — audit the code yourself

Development Practices (Cursor)

This project uses Cursor with custom rules and hooks to enforce quality guardrails during AI-assisted development.

Cursor Rules (.cursor/rules/)

Rule Scope What it enforces
project.mdc Always Framework, AI model, API key handling, deploy workflow, mandatory sanitize/filter calls
security.mdc Always No hardcoded secrets, process.env only, no NEXT_PUBLIC_ for keys, input/output filtering, never set env vars via interactive CLI (agent terminals save empty values)
testing.mdc *.ts, *.tsx Vitest conventions, test placement, mock external APIs, edge case coverage
linting.mdc *.ts, *.tsx Fix introduced lint errors, don't disable rules, run npm run lint before completing work
data-gov-sg.mdc salary-api.ts, salary-benchmarks.ts, route.ts Live API → cache → fallback architecture, resource IDs, how to add new datasets

Cursor Hooks (.cursor/hooks.json)

Three event-driven hooks run automatically during agent sessions:

Hook Event What it does
block-dangerous.sh beforeShellExecution Blocks git push --force, reset --hard, rm -rf /, and interactive env var commands (vercel env add). Prompts user review for commands containing potential API keys.
lint-after-edit.sh afterFileEdit Runs ESLint on any .ts/.tsx file after the agent edits it. Feeds lint errors back to the agent as context so they get fixed immediately.
auto-commit.sh stop When the agent finishes a session, auto-commits all changes with a timestamped message — but only if ESLint passes. Skips commit and notifies on lint failures.

Lessons Learned (Why These Exist)

These rules and hooks weren't set up proactively — they were added after real incidents during development:

What went wrong What was added
Agent stored the Gemini API key in client-side code (NEXT_PUBLIC_), exposable via DevTools Security rule: never use NEXT_PUBLIC_ for secrets
Agent ran vercel env add in a non-interactive terminal, silently saving an empty value — app broke without errors Security rule + hook: block interactive env var CLI commands, instruct user to use dashboard
Agent used the wrong Unicode emoji (🇬🇸 South Georgia instead of 🇸🇬 Singapore) Manual catch — a reminder that AI agents don't verify visual output
Agent would have happily run git push --force or reset --hard Hook: block-dangerous.sh denies destructive git commands
Lint errors shipped without being noticed Hook: lint-after-edit.sh runs ESLint after every edit and feeds errors back to the agent
Forgetting to commit after a session of changes Hook: auto-commit.sh auto-commits at session end (only if lint passes)

Why This Matters

  • Rules act as persistent memory — every AI session starts with the right conventions instead of re-explaining project structure, security policies, or testing requirements.
  • Hooks enforce guardrails automatically — dangerous commands are blocked before execution, lint errors are caught during editing (not after), and clean work is committed without manual intervention.
  • Together, they let you move fast with AI assistance while preventing the common failure modes: leaked secrets, broken linting, force-pushed history, and forgotten commits.

Contributing

PRs welcome. The most impactful contributions:

  1. Salary data improvements — salary data now auto-updates from data.gov.sg; static fallback can be improved
  2. New government programmes — add to government-programmes.ts with a match() function
  3. Better ATS patterns — improve keyword extraction in ats-analyzer.ts

License

MIT

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages