Case study · personal product
JobTrack
In developmentAn AI-assisted job-search workspace — import a role, tailor your CV, and track every application from applied to offer.
TL;DR
- What
- Full-stack job-search workspace: pipeline, CV match, Gmail threads, attachments, analytics.
- Why
- A real problem — my own job search needed production-grade tooling, not a spreadsheet.
- Stack
- React + ASP.NET Core (EF Core) · FastAPI/Ollama local AI · Gmail OAuth · Docker.
- Role
- Everything: product, backend, frontend, ops and security.
Problem & context
A serious job search spreads across spreadsheets, email threads, notes apps and scattered documents. Nothing shows you, at a glance, which applications need attention or what was said last. I wanted one focused workspace — from importing a role to the final offer — built to the standard I would ship at work, not as a throwaway.
Architecture
A React + TypeScript SPA talks to an ASP.NET Core API that owns the domain logic — pipeline, follow-up rules, CV keyword matching — persisting via EF Core with attachments on disk. A small FastAPI service backed by a local Ollama model drafts CVs, cover letters and follow-ups, and the API imports correspondence from the Gmail API over OAuth2. The whole thing runs behind one Docker Compose file.
- Pipeline: a Kanban board — Applied, Waiting, Interview, Offer, Rejected, Ghosted — drag to update.
- CV match: deterministic keyword coverage (matched vs missing), not a black-box score.
- Gmail: import full threads over OAuth2; linked threads auto-refresh onto the right job.
- AI is assistive, never autonomous: it drafts, you always review and send — no auto-apply.
Key decisions & trade-offs
-
01 Run the AI locally with Ollama instead of a cloud API.
alt: A hosted LLM API would have been faster to wire up.
Job-search data is sensitive and I wanted zero per-call cost and no third party in the loop. The trade-off is more setup and heavier local resources — acceptable for a self-hosted tool.
-
02 Make CV matching deterministic, not an AI score.
alt: Let the model rate the fit.
A number a candidate can’t interrogate is useless. Deterministic keyword coverage shows exactly which terms matched and which are missing, so the advice is honest and actionable.
-
03 Ship the PWA with no offline service-worker cache.
alt: A cache would enable full offline use.
I deploy frequently, so an aggressive cache risks serving stale builds — a worse failure than a brief offline gap. The manifest still provides installability and share-to-capture. A deliberate anti-feature.
Security & production notes
- Optional Google sign-in (Google ID tokens) protects the API; every record is scoped to its owner.
- File uploads are validated and stored per-application with ownership checks on every access.
- The AI layer is advisory only — it drafts, it never sends or auto-applies.
- Runs as a reproducible Docker Compose stack with a documented .env; JSON/CSV exports for data portability.
Screenshots
Status & what’s next
In active development. The follow-up rules are simple date logic I’d like to make configurable per stage; next up is a proper integration-test pass around the Gmail import edge cases and tighter grounding on the AI drafts now that I have real usage to learn from.