Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧭 AI Job Search & Matching Platform

Upload a resume once. Get back only the jobs actually worth applying to.

A full-stack app that searches jobs across a dozen sources, scores every posting against your actual resume (not just the job title), and shows you only the ones that clear the bar — with a transparent breakdown of exactly why each one matched.

License: MIT Python Node FastAPI React Status


Warning

Auto Apply does not work yet. The UI for it exists and the plumbing is wired up, but the actual "submit applications on your behalf" part is unimplemented for every portal except a best-effort, unverified LinkedIn Easy Apply flow. Job search and matching are the parts that work — see What's Working below.

Contents


✨ What it does

  1. Upload a resume (PDF/DOCX) → parsed once into a structured, reusable profile (skills, titles, experience, education) and embedded, so it's never re-processed per job.
  2. Tell it what you want: job role, one or more locations (Remote included), minimum salary. No maximum salary field — that's not a real constraint anyone searches by.
  3. It searches up to 12 job sources in parallel, normalizes every result into one schema, and deduplicates postings that show up on more than one portal.
  4. Every remaining posting is scored 0–100 against the actual job description (not just the title) — required skills, preferred skills, seniority/experience fit, domain overlap, semantic similarity via local sentence embeddings. Domain-agnostic: works the same for a software engineer resume as an instrumentation engineer's.
  5. Only postings at or above a configurable threshold survive, each with a plain-language breakdown: ✅ matching skills, 〜 partial matches, ❌ what's missing.
  6. Export the qualified list to Excel, ranked by match score, relevance, salary, and recency.
flowchart TD
    A[Resume Upload] --> B[Parse Once: Structured Profile + Embeddings]
    B --> C[Search Job Portals]
    C --> D[Normalize to Common Schema]
    D --> E[Deduplicate Across Portals]
    E --> F[Cheap Filters: Role, Location, Salary]
    F --> G[Fetch Missing Job Descriptions]
    G --> H[Batch Semantic Matching]
    H --> I{Score at or above threshold?}
    I -- No --> J[Discard]
    I -- Yes --> K[Rank: Score, Relevance, Salary, Date]
    K --> L[Display Results + Export to Excel]
Loading

The expensive step — semantic matching — only ever runs on the small pool that survives the cheap role/location/salary filters, so a search that finds 1,000+ raw listings still finishes in seconds, not minutes.


✅ What's Working

Capability Status
Resume parsing (PDF/DOCX → structured profile) ✅ Working
Job search across multiple portals in parallel ✅ Working
Cross-portal deduplication ✅ Working
Resume ↔ job description matching + explainability ✅ Working
Configurable score threshold, ranking ✅ Working
Excel export of qualified jobs ✅ Working
Auto Apply (automated form submission) 🔴 Not working yet

Portals that work out of the box, no setup beyond cloning: Freehire, RemoteOK, Arbeitnow. Working with a free API key: Adzuna, Jooble. Working with extra setup: LinkedIn (needs an external CLI, see LinkedIn search). Full per-portal status — including the ones that don't work and why — is in Supported job portals.

Auto Apply has a complete UI and real backend plumbing (batching, status tracking, OTP/CAPTCHA handling), but actual form submission is only attempted for LinkedIn Easy Apply, and that path is unverified against the live site. Every other portal is marked "not supported" rather than silently doing nothing. Don't rely on it yet — job search and matching are what this project actually delivers today.


🧠 Matching, explained

Every score comes with a receipt — the UI shows exactly what drove it:

Software Engineer                              91%
ABC Technologies · Mumbai · ₹10–15 LPA

✅ Strong Matches     Python · FastAPI · PostgreSQL · AWS
〜 Partial            Docker · CI/CD
❌ Missing            Kubernetes

The scoring model combines:

  • Skill extraction that isn't tied to a fixed vocabulary — it reads whatever the candidate labeled under their own "Skills" section and cross-checks against a broad tech-term dictionary, so it works equally well outside software roles.
  • Semantic similarity (local sentence-transformers, no API calls) between the resume's facets (titles, skills, domains, responsibilities, education) and the posting text.
  • Experience alignment that's forgiving of small gaps (a posting asking for a couple more years than you have isn't auto-rejected — real hiring gives near-misses a shot) but honestly penalizes large ones.
  • An optional LLM refinement pass (if OPENAI_API_KEY is set) for borderline cases only — the expensive step never runs on the whole pool.

🏗 Architecture

flowchart LR
    FE[React + TypeScript<br/>Frontend]
    BE[FastAPI Backend]
    JS[Job Search Engine<br/>12 portal adapters]
    ME[Matching Engine<br/>local embeddings + skill extraction]
    DB[(SQLite)]
    XL[Excel Export / Import]

    FE -- REST API --> BE
    BE --> JS
    JS --> ME
    ME --> DB
    DB --> BE
    BE --> XL
    BE -- JSON --> FE
Loading

The existing frontend and backend stay the frontend and backend — job search / matching is a capability the backend calls into, not a separate app bolted on top.


🌍 Supported job portals

Portal Method API key needed Status
Freehire Public JSON API No ✅ Working
RemoteOK Public JSON API No ✅ Working
Arbeitnow Public JSON API No ✅ Working (light rate limits)
Adzuna Official API Yes (free) ✅ Working
Jooble Official API Yes (free) ✅ Working
LinkedIn External CLI (public guest endpoints) No, but needs the CLI 🟡 Needs setup, see below
Glassdoor HTML scraping No 🟡 Unverified
JSearch (RapidAPI) Official API Yes (free tier) 🟡 Plan-dependent
Naukri HTML/API scraping No 🔴 Bot-blocked
Indeed RSS + HTML scraping No 🔴 Bot-blocked
Shine HTML scraping No 🔴 Site broken
TimesJobs HTML scraping No 🔴 Site broken

Adding a new portal means writing one adapter with an async search(query, location, **kwargs) method returning a list of dicts — see any file in backend/app/job_search/ as a template, then register it in backend/app/job_search/__init__.py.

LinkedIn search

LinkedIn results come from a small external CLI that talks to LinkedIn's public jobs-guest endpoints (no login, zero runtime dependencies, just bun + fetch). It is not bundled with this repo. Its own README is explicit that this is for personal use only — LinkedIn's Terms of Service prohibit automated access, so keep volume low and don't use it commercially.

If you have (or build) a compatible CLI, point LINKEDIN_CLI_PATH in backend/.env at it and LinkedIn results will start flowing in. If you don't, nothing breaks — the search simply logs a warning for that portal and continues with everything else.


🚀 Quick start

Prerequisites

  • Python 3.10+
  • Node.js 18+
  • (Optional) bun — only needed for LinkedIn search

Backend

cd backend
python -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate
pip install -r requirements.txt
playwright install chromium     # only needed for the (experimental) auto-apply path
cp .env.example .env            # then fill in whatever keys you want to use
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

Frontend

cd frontend
npm install
npm run dev

Open http://localhost:5173. Every API key in .env is optional — the app runs and returns real results with zero keys configured (Freehire + RemoteOK + Arbeitnow, plus LinkedIn if you've set up the CLI).

Docker

docker compose up --build

⚙️ Configuration

Full list lives in backend/.env.example. The ones worth knowing about:

Variable Default What it does
MATCH_THRESHOLD_PERCENT 50 The one place the qualification cutoff lives — never hardcoded elsewhere.
SALARY_UNDISCLOSED_POLICY include include keeps salary-undisclosed jobs (labeled "Not Disclosed"); exclude drops them.
LLM_REFINEMENT_MARGIN 10 Borderline jobs within this many points of the threshold get an optional LLM second opinion (needs OPENAI_API_KEY).
LINKEDIN_CLI_PATH Path to the external LinkedIn CLI. See LinkedIn search.
JSEARCH_API_KEY / JOOBLE_API_KEY / ADZUNA_APP_ID + ADZUNA_APP_KEY Free-tier API keys. Portals without a key are simply skipped, not errored.
OPENAI_API_KEY Upgrades resume parsing from regex/heuristics to LLM extraction, and enables borderline-match refinement.

📁 Project structure

job-automation/
├── frontend/                # React + TypeScript + Vite + Tailwind
│   └── src/
│       ├── pages/            # FindJobs, AutoApply
│       ├── components/       # JobCard, MatchScore, PortalBadge, ...
│       ├── services/         # API client
│       └── store/            # Zustand state
├── backend/                 # FastAPI + Python
│   └── app/
│       ├── api/               # REST endpoints
│       ├── job_search/        # One adapter per portal
│       ├── matching/          # Scoring / explanation engine
│       ├── services/          # Pipeline stages: dedup, filters, excel, resume parsing
│       ├── auto_apply/        # LinkedIn Easy Apply automation (unverified)
│       ├── models/            # SQLAlchemy models
│       ├── schemas/           # Pydantic schemas
│       └── security/          # Credential encryption (Fernet)
└── docker-compose.yml

🧪 Testing

cd backend
pytest

Covers deduplication, cheap filters, salary parsing, and the matching/scoring logic.


🗺 Roadmap

  • Verify (and likely fix) the LinkedIn Easy Apply flow against the live site
  • Implement auto-apply for at least one more portal, or be explicit that it stays LinkedIn-only
  • Resolve the JSearch /search 404 (pending RapidAPI plan verification)
  • Live-verify the Glassdoor adapter
  • Package the LinkedIn CLI as an installable dependency instead of a manual path

🤝 Contributing

Ways to help
  • Fix a broken portal adapter — Naukri/Indeed are actively bot-blocked and out of scope (see Disclaimer), but Shine/TimesJobs/JSearch/Glassdoor are plausibly fixable.
  • Verify or fix the auto-apply flow against a real LinkedIn account.
  • Add a new portal adapter — one async search() method, see Supported job portals.
  • Improve matching qualitybackend/app/matching/matching_service.py is the whole scoring model in one file.

Open a PR. Run pytest in backend/ first.


⚠️ Disclaimer

This project scrapes and queries third-party job portals. Several of those portals' Terms of Service restrict or prohibit automated access — this project does not attempt to bypass CAPTCHAs, bot-detection, or other access controls, and adapters that hit such walls (Naukri, Indeed) are documented as blocked rather than worked around. Use every portal integration, especially LinkedIn search and the auto-apply feature, at your own risk and in accordance with that portal's terms. Not affiliated with LinkedIn, Naukri, Indeed, Glassdoor, Shine, TimesJobs, or any other listed portal.

📜 License

MIT

About

Full-stack job search & resume matching platform — searches multiple job portals in parallel, scores every posting against your actual resume (not just the title) with explainable results, and exports qualified matches to Excel.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages