Skip to content
mouaad.sek:~$
All case files~/work/cf-03$
CF-03Product · AutomationSTATUS: RUNNING HOURLY

JobPilot

One local system replaces a dozen job boards, inbox alerts and copy-pasted CVs, with 2,100+ automated tests keeping it honest.

ROLE
Solo, personal product
WHEN
2026
STACK
Python, Typer, FastAPI, SQLite, APScheduler, httpx, Playwright, sentence-transformers, Electron shell
LINK
Private repo, demo on request
01 / CONTEXT

The problem

Applying for internships in France means watching France Travail, Welcome to the Jungle, HelloWork, La Bonne Alternance, LinkedIn and Indeed alerts. The same offer shows up four times with different wording, and every application needs its own CV.

JobPilot turns that into a pipeline: pluggable sources, one data model, one review queue, and documents generated per offer.

02 / APPROACH

From 8 source types to one review queue

  1. 01Sources
  2. 02Normalize
  3. 03Deduplicate
  4. 04Score
  5. 05Review queue
  6. 06Tailored CV
03 / ENGINEERING DECISIONS

Engineering decisions

DECISIONAREAWHY
Idempotent ingestion on content hash and source IDDataNo double offers
One function owns every status change, logged to an audit tableStateTraceability
Mail alerts read over IMAP in read-only peek modeSecurityNever alters inbox
LLM calls log metadata only, never keys or promptsSecurityNo secret leaks
Duplicates matched on mission text, not company boilerplateMatchingReal dedup
Every source implements one interface, mockable in testsArchitecturePluggable
04 / EVIDENCE

Straight from the code

Excerpts from the actual repository and deliverables, trimmed for length.

src/jobpilot/state.pypython
def transition(
    db: sqlite3.Connection,
    application_id: int,
    to_status: str,
    *,
    detail: dict | None = None,
) -> str:
    """Move an application to to_status if legal; log a status_change event."""
    frm = current_status(db, application_id)
    allowed = LEGAL.get(frm, set())
    if to_status not in allowed:
        raise IllegalTransition(
            f"application {application_id}: {frm} -> {to_status} not allowed "
            f"(legal: {sorted(allowed) or 'none, terminal'})"
        )

    now = _utc_now()
    db.execute(
        "UPDATE applications SET status = ?, last_event_at = ? WHERE id = ?",
        (to_status, now, application_id),
    )
    payload = {"from": frm, "to": to_status, **(detail or {})}
    log_event(db, application_id, "status_change", payload, commit=False)
    db.commit()
    return to_status

The only function allowed to change an application status: illegal transitions raise, and every change is written to the audit log in the same transaction.

05 / OUTCOME

Results

2,121automated tests
8source connector types
1hcollection cycle, in the background
06 / LESSONS

What I'd do differently

Collecting offers is easy, knowing that two different postings are the same job is the real problem. Next: a cleaner web dashboard, and sharing the matching engine as a standalone library.

NEXT CASE FILE · CF-04L'Adress restaurant website
Esc

↑↓ navigate · Enter open · Esc close