DEV Community

Pratiksha
Pratiksha

Posted on

I Was Losing Track of My Own Job Applications — So I Built a Tool to Fix It

"How a final-year CS student went from a chaotic email inbox to a three-service AI ecosystem in two weeks"

There's a specific kind of panic that hits when a recruiter emails asking for an update and you have absolutely no idea what stage you're at with them.

That happened to me twice in the same week.

I was managing my job search the way most final-year students do — links in browser tabs, follow-up dates on sticky notes, cover letters buried in a Drive folder I hadn't opened in weeks. So I stopped applying for a week and built the tool I actually needed. I called it JobRover.

The Problem, Stated Precisely

Before writing code, I listed every specific frustration:

  1. No idea what stage each application was at
  2. Kept forgetting to follow up
  3. Couldn't remember what I'd written in each cover letter
  4. Resume felt generic but I didn't know how to tailor it
  5. When rejected, had no idea why

Problems 1–3 are tracking problems. Problems 4–5 are AI problems. That distinction shaped the entire architecture.

Three Services, Not One

I split JobRover into three independent services:

  • P1 — Application Tracker: FastAPI + Next.js + PostgreSQL. Solves the tracking problems.
  • P2 — Company Intelligence: A REST API that parses job descriptions using an LLM and returns structured data — required skills, seniority level, and red flags.
  • P3 — AI Resume Analyser: A RAG pipeline that tailors resume bullets, generates cover letters, and analyses rejections — grounded in your actual experience, never hallucinated. Each service runs independently. Each solves a distinct problem. Together they replace every spreadsheet I was living in.

The Detail That Matters Most

The hardest part wasn't the AI layer. It was the database schema.

A job application isn't just a row with a status column — it's a state machine. It moves through stages: saved → applied → screened → interviewing → offer → rejected. Every transition gets logged with a timestamp. Three weeks in, I could see exactly which stage I was getting stuck at and how long each stage was taking. That's real signal, not vibes.

The other decision I'm glad I made early: auto-creating follow-up reminders. Every time an application moves to "applied", the backend automatically schedules a follow-up for 7 days later. I never had to remember — the system remembered for me.

The RAG Pipeline — And Why Naive AI Fails Here

The obvious approach for resume tailoring is: give the LLM your resume and the JD, ask it to rewrite. This works, kind of. The problem is hallucination — give an LLM a resume without Kubernetes experience and a JD that requires it, and it'll invent that experience for you.

P3 prevents this with a hard prompt constraint: only reframe, never fabricate. The LLM can change emphasis and mirror JD terminology, but it cannot invent skills or projects. If a requirement is genuinely missing, it surfaces in a keywords_missing field instead — an honest gap analysis rather than a polished lie.

The output looks like this:

{
  "tailored_bullets": [
    {
      "original": "Built REST APIs using Python",
      "rewritten": "Designed scalable REST APIs using Python with async patterns",
      "reason": "Mirrors JD terminology around scalability and async"
    }
  ],
  "keywords_missing": ["Kubernetes", "Go"],
  "match_score": 0.67
}
Enter fullscreen mode Exit fullscreen mode

The match score is honest. 0.67 means 0.67 — not inflated to make you feel better about applying.

The Bug That Taught Me the Most

Registration kept failing with "Email already in use" — but the database had zero users.

The actual problem was CORS. The frontend on port 3000 was being blocked before the request even reached the backend, because the CORS config still listed port 5173 from an earlier setup. The error message was completely misleading.

Lesson: when auth fails mysteriously, check the network layer first. The error you see is rarely the error that's actually happening.

What I'd Add Next:

A reranker between retrieval and generation. Right now P3 retrieves the top 5 chunks from ChromaDB. A reranker would retrieve 20, score them by relevance, and pass only the best 5 to the LLM. That's the standard production RAG pattern — and it would meaningfully improve output quality without changing the architecture.

I used JobRover for my actual job search while building it. The demo isn't synthetic data — it's 12 real applications, 12 real JDs embedded in ChromaDB, real follow-up reminders that fired when I forgot.

That's the difference between a portfolio project and a product. Build the thing you actually need, then write about what surprised you. Everything else follows.

The three repos — jobrover-tracker, jobrover-intelligence, jobrover-analyser — are each independently runnable with a one-command Docker setup.

Top comments (0)