Job lead pipeline — Chrome extension captures offers while browsing, FastAPI backend scores them with Mistral AI, and a Streamlit CRM dashboard tracks the full application funnel.
Browse job board
→ Chrome extension extracts title / company / description
→ POST /api/ingest (FastAPI)
→ Mistral AI scores relevance 0–10 + structured fields
(matched skills, missing skills, mission types, seniority fit, nice-to-have match)
→ appended to data/jobs.csv
→ Streamlit CRM dashboard for review, status updates, outreach tracking
| Layer | Tool |
|---|---|
| Browser capture | Chrome extension (MV3) — LinkedIn, APEC, WTTJ, Indeed, HelloWork |
| Backend API | FastAPI + Uvicorn |
| Scoring | Mistral AI (mistral-small-latest) |
| Storage | CSV (data/jobs.csv) |
| CRM dashboard | Streamlit |
cd backend
python -m venv .venv
.venv\Scripts\activate # Windows
pip install -r requirements.txt
# create .env from template
cp .env.example .env
# fill in MISTRAL_API_KEY
uvicorn app:app --reload
# → http://127.0.0.1:8000# from repo root (with .venv active)
streamlit run streamlit/app.py
# → http://localhost:8501- Open
chrome://extensions - Enable Developer mode
- Click Load unpacked → select the
extension/folder
The extension injects a capture button on LinkedIn, APEC, WTTJ, Indeed, and HelloWork job pages.
Scoring is driven by backend/persona.md — a plain-text file with two sections:
- Profile — experience level, strong/weak skills, target cities, contract type
- Projects — short descriptions of built projects used to calibrate
seniority_fitandnice_to_have_match
Edit persona.md directly to update how jobs are scored. No code changes needed.
When the scoring prompt changes or new fields are added, existing CSV rows can be updated without re-browsing the job boards:
# rescore only rows with empty matched_skills (stale rows)
python scripts/backfill_scores.py
# rescore every row
python scripts/backfill_scores.py --forceSaves progress after each row — safe to interrupt.
Each ingested job is enriched with:
| Field | Type | Description |
|---|---|---|
relevance_score |
int 0–10 | Overall relevance. Penalizes missing required skills. |
fit_explanation |
str | One-sentence summary of the score |
mission_types |
list | Role focus: pipeline, transformation, cloud_migration, analytics_engineering, data_platform, consulting |
required_stack |
list | Tools explicitly required by the posting |
bonus_stack |
list | Tools listed as nice-to-have |
matched_skills |
list | Required skills the candidate has |
missing_skills |
list | Required skills the candidate is missing |
nice_to_have_match |
list | Bonus criteria the candidate matches |
seniority_fit |
int | 0 = in range (0–3y), 1 = stretch (3–5y), 2 = out of league (5y+) |
backend/
app.py FastAPI routes (ingest, list, patch, delete)
models.py Pydantic models (JobPosting, JobRecord, JobUpdate)
scorer.py Mistral AI scoring logic
crm.py CSV read/write/delete helpers
persona.md Candidate profile — edit to update scoring context
extension/
manifest.json Chrome MV3 manifest
background.js Service worker
content/
linkedin.js LinkedIn job page scraper
apec.js APEC job page scraper
wttj.js WTTJ job page scraper
indeed.js Indeed job page scraper
hellowork.js HelloWork job page scraper
popup/
popup.html Extension popup UI
popup.js Popup logic
popup.css
scripts/
backfill_scores.py Rescore stale or all CSV rows from the terminal
streamlit/
app.py CRM dashboard
data/
jobs.csv Local job store (git-ignored in production)