Alexei Rojas Quiroga
← All projects

Personal project

Job Bank Alert Service

Local-first job matcher: polls Job Bank, scores offers against your CV with an LLM

Status
In progress
Role
Solo: architecture, Python backend, React frontend, LLM integration, testing

Architecture

  • Scheduled job-posting ingestion
  • Deterministic gates before the LLM
  • Fact-locked application drafts
  • No auto-send path, by design
  • Local-first: SQLite + FastAPI

The scheduler drives each stage in order: ingest from Job Bank, score pending postings, then email a digest of matches over threshold.

Architecture · 8 nodes · 1 flows
WHAT I BUILTAPSchedulerPoll schedulerInterval job + manualrun, single-flight lockhttpx + parsersIngestion andparsingPolite fetcher, postingparsersPython + LLMMatching pipelinePre-gates, LLM scoring,thresholdsaiosmtplibAlert serviceThreshold-gated emaildigestPublic job feedCanada Job BankJob feed and postingpagesOpenAI-compati…Language modelScores matches, writesdraftsSQLiteApplicationdatabasePostings, matches, CV,cache, settingsSMTPSMTP serverAlert email delivery11. run ingestion23456789
  • Service / compute
  • Data store
  • AI
  • External system
  • Synchronous
  • Async / loop
Scroll sideways to see the full diagram

How it flows, step by step

Click a step to jump to it. Click a component for details.

What it does

A local-first service that polls Canada's Job Bank, filters offers for international-candidate eligibility, and scores them against an uploaded CV using an LLM. It alerts by email and dashboard and prepares tailored cover letters and emails strictly as drafts. A React dashboard manages filters, offers, CV, drafts and settings.

The problem

Finding postings that an international candidate can actually be hired for is slow: many require work authorization, and generic matching ignores that. The service automates discovery and fit scoring while never applying on the user's behalf.

What I built

  • Deterministic pre-gate pipeline (authorization, location, keyword) runs before the LLM, so restrictive postings are rejected fail-closed with no model call and cost stays low.
  • Robust LLM contract: Pydantic-validated JSON, one repair re-prompt with the validation error, bounded retries on 429/5xx, and job identity and prompt version injected server-side, never trusted from the model.
  • Fact-locked drafting: letters and emails are written from a numbered fact sheet of CV and posting facts, then verified so the model cannot invent claims; each paragraph shows its provenance in the UI.
  • Polite ingestion: descriptive User-Agent, durable ETag / Last-Modified conditional GET persisted in SQLite, jittered pacing, capped backoff, and a single-flight scheduler lock.
  • Hard invariant that no application-send path exists: drafts are mailto links and copy-to-clipboard only, with the contract checked through the OpenAPI schema.
  • OpenAPI-first contract: the frontend client types are generated from the backend schema, and the project was delivered in tagged slices with the test suite green at each step.

Key decisions and why

01

Local-first with SQLite

Single-user data (CV, matches) stays on the machine, with no hosting cost or third-party data exposure; retention and delete-on-demand are built in.

02

Cheap-to-expensive gate ordering

Deterministic checks go first and the LLM only sees survivors, which saves cost and makes eligibility decisions auditable and fail-closed.

03

Draft-only delivery

Auto-applying could misrepresent the candidate; keeping a human in the loop for every application is a deliberate safety boundary.

04

Fact-locked prose generation

Natural writing without hallucination: the model only reorganizes numbered facts and a verifier rejects unsupported statements.

05

Layered backend with repositories and an OpenAPI contract

Separate api, services, repositories and models keep logic testable, and the exported schema keeps the typed frontend in sync.

Tech stack

Languages
Python 3.12
Backend
FastAPIAPSchedulerhttpxPydantic
Data
SQLAlchemy + SQLite
Messaging
aiosmtplib (SMTP)
Other
pypdf / python-docx / ReportLab
AI
OpenAI-compatible LLM API
Frontend
React 18 + Vite + TypeScriptTanStack Queryopenapi-typescript
Testing
pytest + respxVitest + MSW
DevOps
ruff + mypy