← All projects

Selected work · 04

Job Hunt

A local job-search dashboard that writes tailored CVs, cover letters and application emails, and refuses to claim anything the applicant's own CV does not support.

Highlights

What shipped

  • Built a local app that finds and tracks jobs, then writes a tailored CV, cover letter and email for each application.
  • Required every CV achievement to be copied verbatim from the original CV, and rejected any draft that paraphrased, merged or invented evidence.
  • Added a second, independent model pass that audits each draft's factual claims, with targeted repair of failed drafts and a hard stop after three attempts.
  • Flagged requirements the applicant should check privately, as supported, unknown or an explicit gap, instead of writing over them in the letter.
  • Kept separate profiles fully isolated, from saved jobs and uploads to background tasks, and covered that isolation with tests.

The story

Behind the build

Overview

Tailoring an application to every posting is slow, and asking a language model to do it is risky: models readily round a skill up into expertise, move a metric from one job to another, or claim a requirement is met when the CV says nothing about it. Job Hunt is built around that failure mode. It runs locally, keeps each person’s data on their own machine, and treats the uploaded CV as the only source of truth about the applicant.

I built it for my own job search and use it to track my applications. Being its main user shaped the design. A wrong claim in a cover letter is my own credibility on the line, so the app is built to stop and say what it could not confirm rather than smooth it over.

The app covers the whole workflow: import a master CV, find or add jobs, read the original posting, generate the documents, review what the model could not confirm, and export the result as PDF, Word, HTML or plain text.

Profiles and jobs

A master CV is uploaded as PDF, Word or text, and a model extracts its facts into a structured profile, leaving anything it cannot find empty. The profile is marked for review, and nothing can be generated until a person has checked and saved it. The original CV text is kept next to the structured version, because it is what every later check compares against.

Jobs come from a LinkedIn search, a pasted URL or a manual entry. The search asks the model’s web-search tool for postings and keeps only URLs it actually cited, or falls back to a separate search API. Postings are then read from the public page. Retrieval is strict: it accepts only genuine job URLs, follows redirects only while they point to the same posting, and checks the page’s canonical link. When a page is blocked or empty, generation stops and asks for the description to be pasted, rather than writing from a guess. A description saved by hand while a fetch is still running is never overwritten.

Generation and verification

The model receives the profile, the job description and a long list of explicit rules. Job requirements are not applicant facts. Proficiency, dataset sizes, leadership, eligibility or sector expertise must not be inferred. A metric stays with the achievement it belongs to. Letters are written in the first person, in plain language, without stock phrases about passion or fit. Every model call is also told to treat CVs and postings as untrusted data rather than instructions.

The output is a structured object that is then checked in code. For the CV, the model picks the most relevant achievements for each employer, and each one must appear verbatim in the original CV. Skills must already exist in the profile. Requirement notes must quote the posting exactly, and “supported” or “gap” needs an exact quote from the applicant’s CV as evidence. Letters must stay within their word budget and use the first person.

A second model pass then acts as an independent factual editor and lists only substantive unsupported claims. If any check fails, the exact correction goes back to the model together with its previous draft, so it can repair the draft without losing the quotations that were already right. After three failed attempts the generation stops, and existing documents are left untouched.

Reviewing what the model could not confirm

The most useful output is often not the letter but the list next to it. For up to three key requirements, the app records whether the CV supports them, says nothing about them, or contradicts them, with the exact quotes for both sides. These notes appear in the job view and the email composer, and they are deliberately excluded from exported documents. Student-only eligibility is flagged when enrolment is not confirmed.

The email composer follows the same rules. It writes a short attachment email around one confirmed achievement, and it only fills in a recipient address that the posting explicitly gives for applications. A general careers or help address is shown with a note but left out of the To field. The app never sends anything itself.

Engineering

The backend is FastAPI with SQLite, one database per profile, and a React and TypeScript front end. Long tasks such as CV parsing, search and generation run in the background, are tied to the profile that started them, recover after a restart, and refuse to run twice at once. The service only accepts requests from the local machine and rejects foreign origins and hosts. Each generated document stores a snapshot of the profile and posting it was made from, and PDF, Word and HTML exports share one document structure.

The app supports several model providers behind one interface, tried in a fixed order, and reports a failed request instead of silently switching to another provider. The test suite covers profile isolation, document snapshots, description retrieval, email contacts, rejected evidence, repair loops and PDF layout, with every model call mocked.

Keep exploring

More projects

All projects

Cross-Lingual RAG Evaluation

A controlled study of how much retrieval-augmented generation degrades when questions and documents are in different languages, and which fix closes the gap.

  • Python
  • RAG Architectures
  • Embeddings
  • Vector Search

qado.ai IDP: RAG and AI Agents for Procurement

My interdisciplinary project at the AI startup qado.ai: a modular RAG pipeline over contract data, a benchmark for its retrieval quality, and prototype agents for strategic purchasing.

  • Python
  • RAG Architectures
  • Embeddings
  • Vector Search

Blog Studio

A web app that turns SEO keywords into source-grounded B2B blog posts in English and German, with a human review step at every stage.

  • Python
  • FastAPI
  • React
  • TypeScript

Application Optimizer

A command-line pipeline that scores job fit, tailors a LaTeX CV towards a posting's keywords, and writes a matching cover letter, delivered as two finished PDFs.

  • Python
  • LLM Pipelines
  • Prompting
  • LaTeX

Haushalt — Personal Finance Tracker

A local budgeting app that replaced a set of spreadsheets, with linked money pots, automatic capital-gains tax handling and a one-click desktop launcher.

  • Python
  • FastAPI
  • SQL
  • Database Design

Legacy Data Migration Platform

A relational data platform consolidating historically grown legacy records into a clean target structure, with a Dash front end and REST API for exploration.

  • Python
  • SQL
  • Dash
  • REST APIs

Machine Learning Pipeline

A supervised and unsupervised modelling pipeline in Python, covering feature engineering, hyperparameter tuning, and cross-validated evaluation.

  • Python
  • Machine Learning
  • Feature Engineering
  • Model Evaluation

In design

Automated Trading System

The design for an automated swing-trading system on Interactive Brokers, built around separate processes, shared backtest and live code, and safety rules that must exist before any real order.

  • Python
  • FastAPI
  • Backtesting
  • Risk Management

Questions?

Want to talk through the details?