- Python 78.2%
- Jupyter Notebook 21.4%
- Makefile 0.4%
Practice B2B title cleaning like a chord progression: multi-field input, VALID/INVALID with confidence, ESCO/O*NET/UK SOC/India NCO neighbours, rule + n-gram explanations, looping sequences, and a from-scratch notebook with Mac replication placeholders. |
||
|---|---|---|
| data | ||
| docs | ||
| notebooks | ||
| results | ||
| scripts | ||
| src/jtv_workbench | ||
| tests | ||
| .gitignore | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| requirements.txt | ||
| START-HERE.md | ||
Job title validation workbench
Public: https://git.binary.ovh/brahm/job-title-workbench
A Jupyter workbench for practicing B2B contact-data job-title validation the same way a musician practices a chord progression: load a sequence, loop it at a tempo, highlight the active title, edit the progression, correct a row, and see why the engine called it valid or invalid.
This is a notebook, not a web app. You read the code in the cell, then the output under it. Fork it. Change a rule. Swap the embedder. Score your own CSV. The from-scratch notebook is empty on purpose.
What it does
| Surface | What you get |
|---|---|
| Multi-field input | job title (required) + optional company, country, first name, last name, email |
| Binary verdict | VALID / INVALID plus a confidence score (distance from the 0.5 boundary) |
| Taxonomies | nearest matches from ESCO, O*NET, UK SOC 2020, India NCO-2015 |
| Explanation | triggered rules + char n-gram cosine + shared n-grams + multi-signal consistency |
| Presets | common valid titles, common junk, noisy/borderline rows |
| Batch / “tempo” | loop a sequence, adjustable titles-per-minute, highlighted active row |
| Edits | rewrite the sequence as CSV; override a single row’s verdict |
It does not tell you whether this person holds this job at this company today. That is a registry question. This workbench answers is this string a plausible job title, and do the other fields on the row collide with it?
5 minutes
git clone https://git.binary.ovh/brahm/job-title-workbench.git
cd job-title-workbench
python3 -m venv .venv
source .venv/bin/activate # Mac/Linux
pip install -r requirements.txt
export PYTHONPATH=src
python tests/test_engine.py
python scripts/smoke.py
python -m jtv_workbench.cli "Chief Executive Officer" --country GB
jupyter lab notebooks/00-start-here.ipynb
On a Mac, follow docs/MAC.md instead — same steps, with Homebrew / Apple Silicon placeholders.
Notebooks
| Notebook | Role |
|---|---|
notebooks/00-start-here.ipynb |
Orientation, smoke scores, then the live workbench |
notebooks/01-how-it-works.ipynb |
The engine in the open: normalize → rules → taxonomies → n-grams → fusion |
notebooks/02-from-scratch.ipynb |
Empty functions, # >>> YOUR CODE HERE, compare against the reference |
How scoring works
contact fields
│
├─ rules (hard structural fails, soft linguistic fails)
├─ char 3-gram TF-IDF cosine vs ESCO / O*NET / UK SOC / India NCO
└─ consistency (title vs company, country, name, email)
│
▼
fused score in [0, 1]
VALID if ≥ 0.5 else INVALID
confidence = |score − 0.5| × 2
Hard fails (empty, email-in-title, placeholder, title-equals-company, …) clip the fused score to ≤ 0.18, so junk cannot be rescued by a lucky n-gram overlap.
The embedder is not a neural net. It is character 3-grams with IDF
weights so you can print the shared grams that made two strings look
close. That is the point of a practice workbench. On a Mac you can drop
in sentence-transformers — there is a placeholder cell for it.
Weights live in src/jtv_workbench/score.py as WEIGHTS. Change them in
the notebook and re-score. They are not calibrated probabilities.
Data
Curated slices, vendored so the notebooks run offline:
| File | Slice | Licence / source |
|---|---|---|
data/taxonomies/esco.json |
1,699 English ESCO occupations | CC BY 4.0, European Commission |
data/taxonomies/onet.json |
1,016 O*NET-SOC titles (db 30.3) | CC BY 4.0, U.S. DOL/ETA |
data/taxonomies/uk_soc.json |
SOC 2020 unit groups (practice slice) | OGL v3.0, ONS |
data/taxonomies/india_nco.json |
NCO-2015 unit groups (ISCO-08 aligned slice) | Govt of India classification titles |
These are not the full official files. A 251k-entry lexicon still only resolved 32.9% of real titles in the sibling job-title-validation project. Nearest-neighbour hits here are “what official title is this closest to?”, not “therefore valid”.
To replace a slice with an official download, see
scripts/fetch_taxonomies.py (URLs are marked PLACEHOLDER — confirm them
on the authority site; some are portals, not direct files).
Layout
notebooks/ the thing you open
src/jtv_workbench/ the same engine, importable
data/ taxonomies, presets, names
scripts/ smoke + official-file placeholders
tests/ assertions the from-scratch notebook can fail against
docs/MAC.md Mac replication
docs/FROM-SCRATCH.md
results/ smoke output committed so you can compare
Honest limits
- Binary VALID/INVALID is a practice UI. Production contact-trust systems keep a three-way VALID / REVIEW / INVALID so weak evidence is not forced into a false accept. Low confidence here is that REVIEW band.
- Char n-gram cosine ranks strings. It is not mmBERT. A measured neural nearest-neighbour baseline (0.959 acc on O*NET vs curated negatives) lives in ml-tutorials.
- Occupational surnames (
Baker,Cook,Taylor) are genuinely ambiguous. The engine uses role-heads + the contact’s own name when supplied; it will still get some of these wrong. That is the drill. - No registry lookup. A well-formed fake executive passes.
Related
- contact-trust — L1 grammar + L3 registries
- job-title-validation — the design docs this practice tool sits under
- titlebert — learned classifier, not a lookup