onet-match 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- onet_match-0.3.0/LICENSE +21 -0
- onet_match-0.3.0/PKG-INFO +238 -0
- onet_match-0.3.0/README.md +191 -0
- onet_match-0.3.0/onet_analysis/__init__.py +20 -0
- onet_match-0.3.0/onet_analysis/cli.py +311 -0
- onet_match-0.3.0/onet_analysis/clustering.py +149 -0
- onet_match-0.3.0/onet_analysis/collect.py +54 -0
- onet_match-0.3.0/onet_analysis/eda.py +138 -0
- onet_match-0.3.0/onet_analysis/keyword_attribution.py +94 -0
- onet_match-0.3.0/onet_analysis/loading.py +37 -0
- onet_match-0.3.0/onet_analysis/matching/__init__.py +25 -0
- onet_match-0.3.0/onet_analysis/matching/api.py +80 -0
- onet_match-0.3.0/onet_analysis/matching/catalog.py +228 -0
- onet_match-0.3.0/onet_analysis/matching/datasets.py +225 -0
- onet_match-0.3.0/onet_analysis/matching/engine.py +367 -0
- onet_match-0.3.0/onet_analysis/matching/evaluate.py +160 -0
- onet_match-0.3.0/onet_analysis/matching/metrics.py +102 -0
- onet_match-0.3.0/onet_analysis/matching/pipeline.py +332 -0
- onet_match-0.3.0/onet_analysis/matching/report.py +418 -0
- onet_match-0.3.0/onet_analysis/matching/retrieval.py +254 -0
- onet_match-0.3.0/onet_analysis/matching/synth.py +368 -0
- onet_match-0.3.0/onet_analysis/matching/text.py +34 -0
- onet_match-0.3.0/onet_analysis/matching/train.py +230 -0
- onet_match-0.3.0/onet_analysis/paths.py +94 -0
- onet_match-0.3.0/onet_analysis/schema.py +51 -0
- onet_match-0.3.0/onet_data_collector/OnetWebService.py +13 -0
- onet_match-0.3.0/onet_data_collector/__init__.py +53 -0
- onet_match-0.3.0/onet_data_collector/_logging.py +36 -0
- onet_match-0.3.0/onet_data_collector/bulk.py +319 -0
- onet_match-0.3.0/onet_data_collector/cli.py +174 -0
- onet_match-0.3.0/onet_data_collector/client.py +248 -0
- onet_match-0.3.0/onet_data_collector/condensed_job_details.py +104 -0
- onet_match-0.3.0/onet_data_collector/config.py +100 -0
- onet_match-0.3.0/onet_data_collector/exceptions.py +42 -0
- onet_match-0.3.0/onet_data_collector/job_details.py +87 -0
- onet_match-0.3.0/onet_data_collector/keyword_search.py +115 -0
- onet_match-0.3.0/onet_data_collector/utils.py +55 -0
- onet_match-0.3.0/onet_match/__init__.py +10 -0
- onet_match-0.3.0/onet_match.egg-info/PKG-INFO +238 -0
- onet_match-0.3.0/onet_match.egg-info/SOURCES.txt +52 -0
- onet_match-0.3.0/onet_match.egg-info/dependency_links.txt +1 -0
- onet_match-0.3.0/onet_match.egg-info/entry_points.txt +4 -0
- onet_match-0.3.0/onet_match.egg-info/requires.txt +29 -0
- onet_match-0.3.0/onet_match.egg-info/top_level.txt +3 -0
- onet_match-0.3.0/pyproject.toml +58 -0
- onet_match-0.3.0/setup.cfg +4 -0
- onet_match-0.3.0/tests/test_clustering.py +34 -0
- onet_match-0.3.0/tests/test_eda.py +25 -0
- onet_match-0.3.0/tests/test_keyword_attribution.py +33 -0
- onet_match-0.3.0/tests/test_matching_data.py +111 -0
- onet_match-0.3.0/tests/test_matching_dense.py +102 -0
- onet_match-0.3.0/tests/test_matching_docs.py +32 -0
- onet_match-0.3.0/tests/test_matching_models.py +296 -0
- onet_match-0.3.0/tests/test_schema.py +33 -0
onet_match-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Benjamin Chan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: onet-match
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Map free text (job titles, resume lines, projects, interests, skills) to O*NET occupations with skills, tasks and calibrated confidence.
|
|
5
|
+
Author: Benjamin Chan
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Benjamin-V-Chan/onet-match
|
|
8
|
+
Project-URL: Repository, https://github.com/Benjamin-V-Chan/onet-match
|
|
9
|
+
Project-URL: Documentation, https://github.com/Benjamin-V-Chan/onet-match/tree/main/docs
|
|
10
|
+
Keywords: onet,o*net,occupations,soc-codes,job-title-normalization,occupation-classification,semantic-search,job-matching,resume-parsing,career-exploration,skills-taxonomy,sentence-transformers,embeddings,reranker,riasec
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Intended Audience :: Education
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: pandas>=1.3
|
|
23
|
+
Requires-Dist: numpy>=1.21
|
|
24
|
+
Requires-Dist: scikit-learn>=1.1
|
|
25
|
+
Requires-Dist: matplotlib>=3.5
|
|
26
|
+
Requires-Dist: pyarrow>=12
|
|
27
|
+
Provides-Extra: plots
|
|
28
|
+
Requires-Dist: seaborn>=0.12; extra == "plots"
|
|
29
|
+
Provides-Extra: matching
|
|
30
|
+
Requires-Dist: torch>=2.2; extra == "matching"
|
|
31
|
+
Requires-Dist: sentence-transformers[train]>=5.0; extra == "matching"
|
|
32
|
+
Requires-Dist: transformers>=4.41; extra == "matching"
|
|
33
|
+
Requires-Dist: huggingface_hub>=0.23; extra == "matching"
|
|
34
|
+
Provides-Extra: serve
|
|
35
|
+
Requires-Dist: fastapi>=0.110; extra == "serve"
|
|
36
|
+
Requires-Dist: uvicorn>=0.29; extra == "serve"
|
|
37
|
+
Requires-Dist: pydantic>=2; extra == "serve"
|
|
38
|
+
Provides-Extra: synth
|
|
39
|
+
Requires-Dist: anthropic>=0.70; extra == "synth"
|
|
40
|
+
Requires-Dist: openai>=1.40; extra == "synth"
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
43
|
+
Requires-Dist: seaborn>=0.12; extra == "dev"
|
|
44
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
45
|
+
Requires-Dist: onet-match[matching,serve,synth]; extra == "dev"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# onet-match
|
|
49
|
+
|
|
50
|
+
**Map free text about work to O\*NET occupations.** Job titles, resume
|
|
51
|
+
bullets, job postings, project descriptions, interest statements and skill
|
|
52
|
+
lists go in. Ranked O\*NET-SOC occupations come out, each with key tasks,
|
|
53
|
+
skills, knowledge, RIASEC interests and a calibrated confidence score.
|
|
54
|
+
|
|
55
|
+
- Open source (MIT) and runs locally. No API key or account needed to match.
|
|
56
|
+
- Fine-tuned bi-encoder + cross-encoder reranker over all 923 O\*NET 31.0
|
|
57
|
+
occupations. Top-1 accuracy is **51%** on held-out O\*NET text and **65%** on
|
|
58
|
+
messy human-style text, vs 21% and 17% for a TF-IDF baseline.
|
|
59
|
+
- Python API, CLI, REST API (FastAPI) and Docker. About 47 ms per query on a laptop.
|
|
60
|
+
- Also includes the O\*NET data pipeline it's built on: bulk database download,
|
|
61
|
+
a Web Services client and analysis tools.
|
|
62
|
+
|
|
63
|
+
| input | top match (confidence) |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `ran the robotics club's CAD team` | Robotics Engineers, 17-2199.08 (0.52) |
|
|
66
|
+
| `Excel, QuickBooks, payroll, AP/AR` | Payroll and Timekeeping Clerks, 43-3051.00 (0.49) |
|
|
67
|
+
| `I love drawing and figuring out how machines work` | Mechanical Drafters, 17-3013.00 (0.41) |
|
|
68
|
+
| `Looking for someone to keep our trucks on the road, diesel exp a plus` | Heavy and Tractor-Trailer Truck Drivers, 53-3032.00 (0.30); #2 Bus and Truck Mechanics (0.21) |
|
|
69
|
+
| `Sr. Backend Eng II` | Web Developers, 15-1254.00 (0.43); #2 Software Developers (0.10). A miss: seniority-coded titles are a known weak spot |
|
|
70
|
+
|
|
71
|
+
Real outputs of the served model, saved in `outputs/examples/readme_examples.json`.
|
|
72
|
+
|
|
73
|
+
## Quickstart
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install "onet-match[matching] @ git+https://github.com/Benjamin-V-Chan/onet-match"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from onet_match import match
|
|
81
|
+
|
|
82
|
+
result = match("ran the robotics club's CAD team", top_k=5)
|
|
83
|
+
for m in result.matches:
|
|
84
|
+
print(m.soc, m.title, m.confidence)
|
|
85
|
+
print(result.profile["riasec_code"]) # aggregate interest code, e.g. "RIC"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The package imports as `onet_match` and provides the `onet-match` command.
|
|
89
|
+
The first call downloads the pretrained model bundle (about 0.5 GB) from the
|
|
90
|
+
Hugging Face Hub ([BenjaminChan12/onet-match](https://huggingface.co/BenjaminChan12/onet-match)) and caches it.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
onet-match match "Excel, QuickBooks, payroll, AP/AR" # CLI (add --json for JSON)
|
|
94
|
+
pip install "onet-match[matching,serve] @ git+https://github.com/Benjamin-V-Chan/onet-match"
|
|
95
|
+
onet-match serve --port 8000 # REST API
|
|
96
|
+
curl -s localhost:8000/match -H 'content-type: application/json' \
|
|
97
|
+
-d '{"text": "ran the robotics club'\''s CAD team", "top_k": 3}'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
REST routes: `POST /match`, `POST /match/batch`, `GET /occupation/{soc}` and
|
|
101
|
+
`GET /health`. The full request and response contract and the Docker setup are
|
|
102
|
+
in [docs/API.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/API.md).
|
|
103
|
+
|
|
104
|
+
### What each match contains
|
|
105
|
+
|
|
106
|
+
`soc`, `title`, `confidence` (calibrated, 0–1), `score`, `major_group` and its
|
|
107
|
+
title, `job_zone`, `riasec_code`, `description`, `top_tasks`, `top_skills`
|
|
108
|
+
and `top_knowledge` (with O\*NET importance and level). Each result also
|
|
109
|
+
carries a `profile` that aggregates skills, knowledge and all six RIASEC
|
|
110
|
+
interests across the matches, weighted by confidence.
|
|
111
|
+
|
|
112
|
+
## Use cases
|
|
113
|
+
|
|
114
|
+
- **Career exploration (EdTech).** Map what students say they did or enjoy to
|
|
115
|
+
occupations and skills.
|
|
116
|
+
- **Resume and profile parsing.** Code experience bullets and skill lists to
|
|
117
|
+
SOC codes.
|
|
118
|
+
- **Job-posting normalization.** Turn informal titles and descriptions into
|
|
119
|
+
standard occupations.
|
|
120
|
+
- **Workforce and labor-market analytics.** Batch-code large text collections.
|
|
121
|
+
- **Career counseling tools.** Show the tasks and skills behind a suggestion.
|
|
122
|
+
- **LLM agents.** Ground occupation questions in O\*NET instead of free
|
|
123
|
+
generation (see [`llms.txt`](https://github.com/Benjamin-V-Chan/onet-match/blob/main/llms.txt)).
|
|
124
|
+
|
|
125
|
+
Details and integration patterns: [docs/APPLICATIONS.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/APPLICATIONS.md).
|
|
126
|
+
|
|
127
|
+
## Accuracy
|
|
128
|
+
|
|
129
|
+
All models are evaluated on the same held-out splits. Every number traces to a
|
|
130
|
+
saved run in `outputs/runs/`.
|
|
131
|
+
|
|
132
|
+
| model | O\*NET test top-1 | O\*NET test top-5 | messy-text test top-1 | messy-text test top-5 | p50 latency |
|
|
133
|
+
|---|---|---|---|---|---|
|
|
134
|
+
| TF-IDF baseline | 20.5% | 40.5% | 17.4% | 39.2% | 0.5 ms |
|
|
135
|
+
| BM25 | 20.7% | 40.1% | 24.1% | 45.2% | 0.2 ms |
|
|
136
|
+
| bge-base-en-v1.5, off the shelf | 36.1% | 64.3% | 45.4% | 77.6% | 6.9 ms |
|
|
137
|
+
| fine-tuned bge-base | 50.2% | 78.2% | 65.2% | 92.6% | 6.8 ms |
|
|
138
|
+
| **fine-tuned bge-base + reranker (served)** | **51.1%** | **79.1%** | **65.4%** | **92.9%** | 46.7 ms |
|
|
139
|
+
|
|
140
|
+
- **O\*NET test:** 6,792 held-out titles, task statements, work activities and
|
|
141
|
+
descriptions.
|
|
142
|
+
- **Messy-text test:** 4,703 informal snippets written by a different LLM than
|
|
143
|
+
the one that wrote the training text.
|
|
144
|
+
- **Confidence:** temperature-calibrated, ECE 0.037.
|
|
145
|
+
- **Full results:** per-input-type tables, calibration, ablations, learning
|
|
146
|
+
curves and error analysis are in [docs/RESULTS.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/RESULTS.md).
|
|
147
|
+
|
|
148
|
+
## How it works
|
|
149
|
+
|
|
150
|
+
```mermaid
|
|
151
|
+
flowchart LR
|
|
152
|
+
B["O*NET 31.0 database<br/>(bulk download)"] --> C["Occupation catalog<br/>923 occupations"]
|
|
153
|
+
C --> P["Labeled pairs<br/>titles · tasks · activities"]
|
|
154
|
+
C --> S["LLM-written messy text<br/>train: model A · test: model B"]
|
|
155
|
+
P --> T["Fine-tune bi-encoder<br/>+ cross-encoder reranker"]
|
|
156
|
+
S --> T
|
|
157
|
+
T --> BU["Model bundle<br/>(Hugging Face Hub)"]
|
|
158
|
+
BU --> API["match() · onet-match CLI · REST API"]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
1. A fine-tuned bi-encoder (bge-base) embeds the input and retrieves the 20
|
|
162
|
+
nearest occupations.
|
|
163
|
+
2. A fine-tuned cross-encoder rescores those 20.
|
|
164
|
+
3. The two scores are blended with a weight chosen on validation data.
|
|
165
|
+
4. A temperature-scaled softmax turns the blended scores into calibrated
|
|
166
|
+
confidence.
|
|
167
|
+
|
|
168
|
+
The validation and test tasks are removed from the occupation documents the
|
|
169
|
+
models search, so no test text leaks into retrieval. Synthetic training and
|
|
170
|
+
test text come from different LLMs. Details are in
|
|
171
|
+
[docs/SYNTHETIC_DATA.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/SYNTHETIC_DATA.md).
|
|
172
|
+
|
|
173
|
+
## O\*NET data pipeline
|
|
174
|
+
|
|
175
|
+
The `onet_data_collector` package (`onet-collect`) downloads and verifies O\*NET
|
|
176
|
+
database releases, which needs no credentials. It also wraps O\*NET Web
|
|
177
|
+
Services: keyword search, occupation details and flattened profiles. See
|
|
178
|
+
[docs/COLLECTOR.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/COLLECTOR.md).
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
onet-collect bulk download # download + verify the latest O*NET database
|
|
182
|
+
onet-match collect "healthcare" "technology" # Web Services search (free O*NET account, see .env.example)
|
|
183
|
+
onet-match eda | keywords | cluster # classic analysis: coverage, keyword attribution, TF-IDF clusters
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Train your own
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
git clone https://github.com/Benjamin-V-Chan/onet-match && cd onet-match
|
|
190
|
+
pip install -e ".[dev]"
|
|
191
|
+
onet-match matching build-data # O*NET catalog, labeled pairs, splits
|
|
192
|
+
scripts/matching/reproduce.sh # every model, ablation and the results report
|
|
193
|
+
onet-match matching bundle "<spec>" --run <run> # package a model for serving
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- **Synthetic text:** `WITH_SYNTH=1 TRAIN_LLM=<model> EVAL_PROVIDER=<provider> EVAL_LLM=<model> scripts/matching/reproduce.sh`
|
|
197
|
+
also generates the messy training and test text, using a different LLM for
|
|
198
|
+
each. It needs LLM API keys; see [docs/SYNTHETIC_DATA.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/SYNTHETIC_DATA.md).
|
|
199
|
+
- **Training time:** bge-base took 58 minutes for one epoch on a laptop GPU
|
|
200
|
+
(Apple Silicon, MPS). The exact scripts behind every saved run are in
|
|
201
|
+
`scripts/matching/`.
|
|
202
|
+
- **Tests:** `pytest` runs fully offline, using tiny local models and a
|
|
203
|
+
mocked LLM.
|
|
204
|
+
|
|
205
|
+
Set `ONET_MATCH_BUNDLE=/path/to/bundle` to serve your own model, or
|
|
206
|
+
`ONET_MATCH_HF_REPO=owner/repo` to load one from the Hub.
|
|
207
|
+
|
|
208
|
+
## Background
|
|
209
|
+
|
|
210
|
+
This project was originally built in 2024 as an open-source edtech tool for
|
|
211
|
+
analyzing O*NET occupational data at scale, and as part of my capstone project
|
|
212
|
+
for an engineering course. Its analytics pipeline (EDA, occupation clustering,
|
|
213
|
+
and keyword attribution) was later integrated into production products at
|
|
214
|
+
**Project Leo**, and has inspired related work at other companies and projects.
|
|
215
|
+
|
|
216
|
+
In 2026 it was rebuilt around semantic occupation matching, and the former
|
|
217
|
+
`ONET-Data-Collector` repository was merged in.
|
|
218
|
+
|
|
219
|
+
## Citation
|
|
220
|
+
|
|
221
|
+
```bibtex
|
|
222
|
+
@software{chan_onet_match,
|
|
223
|
+
author = {Chan, Benjamin},
|
|
224
|
+
title = {onet-match: mapping free text to O*NET occupations},
|
|
225
|
+
url = {https://github.com/Benjamin-V-Chan/onet-match},
|
|
226
|
+
year = {2026}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## License and data attribution
|
|
231
|
+
|
|
232
|
+
The code is MIT-licensed; see [LICENSE](https://github.com/Benjamin-V-Chan/onet-match/blob/main/LICENSE).
|
|
233
|
+
|
|
234
|
+
This product includes information from the O\*NET 31.0 Database by the U.S.
|
|
235
|
+
Department of Labor, Employment and Training Administration (USDOL/ETA), used
|
|
236
|
+
under the [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.
|
|
237
|
+
O\*NET® is a trademark of USDOL/ETA. USDOL/ETA has not approved, endorsed or
|
|
238
|
+
tested this product.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# onet-match
|
|
2
|
+
|
|
3
|
+
**Map free text about work to O\*NET occupations.** Job titles, resume
|
|
4
|
+
bullets, job postings, project descriptions, interest statements and skill
|
|
5
|
+
lists go in. Ranked O\*NET-SOC occupations come out, each with key tasks,
|
|
6
|
+
skills, knowledge, RIASEC interests and a calibrated confidence score.
|
|
7
|
+
|
|
8
|
+
- Open source (MIT) and runs locally. No API key or account needed to match.
|
|
9
|
+
- Fine-tuned bi-encoder + cross-encoder reranker over all 923 O\*NET 31.0
|
|
10
|
+
occupations. Top-1 accuracy is **51%** on held-out O\*NET text and **65%** on
|
|
11
|
+
messy human-style text, vs 21% and 17% for a TF-IDF baseline.
|
|
12
|
+
- Python API, CLI, REST API (FastAPI) and Docker. About 47 ms per query on a laptop.
|
|
13
|
+
- Also includes the O\*NET data pipeline it's built on: bulk database download,
|
|
14
|
+
a Web Services client and analysis tools.
|
|
15
|
+
|
|
16
|
+
| input | top match (confidence) |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `ran the robotics club's CAD team` | Robotics Engineers, 17-2199.08 (0.52) |
|
|
19
|
+
| `Excel, QuickBooks, payroll, AP/AR` | Payroll and Timekeeping Clerks, 43-3051.00 (0.49) |
|
|
20
|
+
| `I love drawing and figuring out how machines work` | Mechanical Drafters, 17-3013.00 (0.41) |
|
|
21
|
+
| `Looking for someone to keep our trucks on the road, diesel exp a plus` | Heavy and Tractor-Trailer Truck Drivers, 53-3032.00 (0.30); #2 Bus and Truck Mechanics (0.21) |
|
|
22
|
+
| `Sr. Backend Eng II` | Web Developers, 15-1254.00 (0.43); #2 Software Developers (0.10). A miss: seniority-coded titles are a known weak spot |
|
|
23
|
+
|
|
24
|
+
Real outputs of the served model, saved in `outputs/examples/readme_examples.json`.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install "onet-match[matching] @ git+https://github.com/Benjamin-V-Chan/onet-match"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from onet_match import match
|
|
34
|
+
|
|
35
|
+
result = match("ran the robotics club's CAD team", top_k=5)
|
|
36
|
+
for m in result.matches:
|
|
37
|
+
print(m.soc, m.title, m.confidence)
|
|
38
|
+
print(result.profile["riasec_code"]) # aggregate interest code, e.g. "RIC"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The package imports as `onet_match` and provides the `onet-match` command.
|
|
42
|
+
The first call downloads the pretrained model bundle (about 0.5 GB) from the
|
|
43
|
+
Hugging Face Hub ([BenjaminChan12/onet-match](https://huggingface.co/BenjaminChan12/onet-match)) and caches it.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
onet-match match "Excel, QuickBooks, payroll, AP/AR" # CLI (add --json for JSON)
|
|
47
|
+
pip install "onet-match[matching,serve] @ git+https://github.com/Benjamin-V-Chan/onet-match"
|
|
48
|
+
onet-match serve --port 8000 # REST API
|
|
49
|
+
curl -s localhost:8000/match -H 'content-type: application/json' \
|
|
50
|
+
-d '{"text": "ran the robotics club'\''s CAD team", "top_k": 3}'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
REST routes: `POST /match`, `POST /match/batch`, `GET /occupation/{soc}` and
|
|
54
|
+
`GET /health`. The full request and response contract and the Docker setup are
|
|
55
|
+
in [docs/API.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/API.md).
|
|
56
|
+
|
|
57
|
+
### What each match contains
|
|
58
|
+
|
|
59
|
+
`soc`, `title`, `confidence` (calibrated, 0–1), `score`, `major_group` and its
|
|
60
|
+
title, `job_zone`, `riasec_code`, `description`, `top_tasks`, `top_skills`
|
|
61
|
+
and `top_knowledge` (with O\*NET importance and level). Each result also
|
|
62
|
+
carries a `profile` that aggregates skills, knowledge and all six RIASEC
|
|
63
|
+
interests across the matches, weighted by confidence.
|
|
64
|
+
|
|
65
|
+
## Use cases
|
|
66
|
+
|
|
67
|
+
- **Career exploration (EdTech).** Map what students say they did or enjoy to
|
|
68
|
+
occupations and skills.
|
|
69
|
+
- **Resume and profile parsing.** Code experience bullets and skill lists to
|
|
70
|
+
SOC codes.
|
|
71
|
+
- **Job-posting normalization.** Turn informal titles and descriptions into
|
|
72
|
+
standard occupations.
|
|
73
|
+
- **Workforce and labor-market analytics.** Batch-code large text collections.
|
|
74
|
+
- **Career counseling tools.** Show the tasks and skills behind a suggestion.
|
|
75
|
+
- **LLM agents.** Ground occupation questions in O\*NET instead of free
|
|
76
|
+
generation (see [`llms.txt`](https://github.com/Benjamin-V-Chan/onet-match/blob/main/llms.txt)).
|
|
77
|
+
|
|
78
|
+
Details and integration patterns: [docs/APPLICATIONS.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/APPLICATIONS.md).
|
|
79
|
+
|
|
80
|
+
## Accuracy
|
|
81
|
+
|
|
82
|
+
All models are evaluated on the same held-out splits. Every number traces to a
|
|
83
|
+
saved run in `outputs/runs/`.
|
|
84
|
+
|
|
85
|
+
| model | O\*NET test top-1 | O\*NET test top-5 | messy-text test top-1 | messy-text test top-5 | p50 latency |
|
|
86
|
+
|---|---|---|---|---|---|
|
|
87
|
+
| TF-IDF baseline | 20.5% | 40.5% | 17.4% | 39.2% | 0.5 ms |
|
|
88
|
+
| BM25 | 20.7% | 40.1% | 24.1% | 45.2% | 0.2 ms |
|
|
89
|
+
| bge-base-en-v1.5, off the shelf | 36.1% | 64.3% | 45.4% | 77.6% | 6.9 ms |
|
|
90
|
+
| fine-tuned bge-base | 50.2% | 78.2% | 65.2% | 92.6% | 6.8 ms |
|
|
91
|
+
| **fine-tuned bge-base + reranker (served)** | **51.1%** | **79.1%** | **65.4%** | **92.9%** | 46.7 ms |
|
|
92
|
+
|
|
93
|
+
- **O\*NET test:** 6,792 held-out titles, task statements, work activities and
|
|
94
|
+
descriptions.
|
|
95
|
+
- **Messy-text test:** 4,703 informal snippets written by a different LLM than
|
|
96
|
+
the one that wrote the training text.
|
|
97
|
+
- **Confidence:** temperature-calibrated, ECE 0.037.
|
|
98
|
+
- **Full results:** per-input-type tables, calibration, ablations, learning
|
|
99
|
+
curves and error analysis are in [docs/RESULTS.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/RESULTS.md).
|
|
100
|
+
|
|
101
|
+
## How it works
|
|
102
|
+
|
|
103
|
+
```mermaid
|
|
104
|
+
flowchart LR
|
|
105
|
+
B["O*NET 31.0 database<br/>(bulk download)"] --> C["Occupation catalog<br/>923 occupations"]
|
|
106
|
+
C --> P["Labeled pairs<br/>titles · tasks · activities"]
|
|
107
|
+
C --> S["LLM-written messy text<br/>train: model A · test: model B"]
|
|
108
|
+
P --> T["Fine-tune bi-encoder<br/>+ cross-encoder reranker"]
|
|
109
|
+
S --> T
|
|
110
|
+
T --> BU["Model bundle<br/>(Hugging Face Hub)"]
|
|
111
|
+
BU --> API["match() · onet-match CLI · REST API"]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
1. A fine-tuned bi-encoder (bge-base) embeds the input and retrieves the 20
|
|
115
|
+
nearest occupations.
|
|
116
|
+
2. A fine-tuned cross-encoder rescores those 20.
|
|
117
|
+
3. The two scores are blended with a weight chosen on validation data.
|
|
118
|
+
4. A temperature-scaled softmax turns the blended scores into calibrated
|
|
119
|
+
confidence.
|
|
120
|
+
|
|
121
|
+
The validation and test tasks are removed from the occupation documents the
|
|
122
|
+
models search, so no test text leaks into retrieval. Synthetic training and
|
|
123
|
+
test text come from different LLMs. Details are in
|
|
124
|
+
[docs/SYNTHETIC_DATA.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/SYNTHETIC_DATA.md).
|
|
125
|
+
|
|
126
|
+
## O\*NET data pipeline
|
|
127
|
+
|
|
128
|
+
The `onet_data_collector` package (`onet-collect`) downloads and verifies O\*NET
|
|
129
|
+
database releases, which needs no credentials. It also wraps O\*NET Web
|
|
130
|
+
Services: keyword search, occupation details and flattened profiles. See
|
|
131
|
+
[docs/COLLECTOR.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/COLLECTOR.md).
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
onet-collect bulk download # download + verify the latest O*NET database
|
|
135
|
+
onet-match collect "healthcare" "technology" # Web Services search (free O*NET account, see .env.example)
|
|
136
|
+
onet-match eda | keywords | cluster # classic analysis: coverage, keyword attribution, TF-IDF clusters
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Train your own
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
git clone https://github.com/Benjamin-V-Chan/onet-match && cd onet-match
|
|
143
|
+
pip install -e ".[dev]"
|
|
144
|
+
onet-match matching build-data # O*NET catalog, labeled pairs, splits
|
|
145
|
+
scripts/matching/reproduce.sh # every model, ablation and the results report
|
|
146
|
+
onet-match matching bundle "<spec>" --run <run> # package a model for serving
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- **Synthetic text:** `WITH_SYNTH=1 TRAIN_LLM=<model> EVAL_PROVIDER=<provider> EVAL_LLM=<model> scripts/matching/reproduce.sh`
|
|
150
|
+
also generates the messy training and test text, using a different LLM for
|
|
151
|
+
each. It needs LLM API keys; see [docs/SYNTHETIC_DATA.md](https://github.com/Benjamin-V-Chan/onet-match/blob/main/docs/SYNTHETIC_DATA.md).
|
|
152
|
+
- **Training time:** bge-base took 58 minutes for one epoch on a laptop GPU
|
|
153
|
+
(Apple Silicon, MPS). The exact scripts behind every saved run are in
|
|
154
|
+
`scripts/matching/`.
|
|
155
|
+
- **Tests:** `pytest` runs fully offline, using tiny local models and a
|
|
156
|
+
mocked LLM.
|
|
157
|
+
|
|
158
|
+
Set `ONET_MATCH_BUNDLE=/path/to/bundle` to serve your own model, or
|
|
159
|
+
`ONET_MATCH_HF_REPO=owner/repo` to load one from the Hub.
|
|
160
|
+
|
|
161
|
+
## Background
|
|
162
|
+
|
|
163
|
+
This project was originally built in 2024 as an open-source edtech tool for
|
|
164
|
+
analyzing O*NET occupational data at scale, and as part of my capstone project
|
|
165
|
+
for an engineering course. Its analytics pipeline (EDA, occupation clustering,
|
|
166
|
+
and keyword attribution) was later integrated into production products at
|
|
167
|
+
**Project Leo**, and has inspired related work at other companies and projects.
|
|
168
|
+
|
|
169
|
+
In 2026 it was rebuilt around semantic occupation matching, and the former
|
|
170
|
+
`ONET-Data-Collector` repository was merged in.
|
|
171
|
+
|
|
172
|
+
## Citation
|
|
173
|
+
|
|
174
|
+
```bibtex
|
|
175
|
+
@software{chan_onet_match,
|
|
176
|
+
author = {Chan, Benjamin},
|
|
177
|
+
title = {onet-match: mapping free text to O*NET occupations},
|
|
178
|
+
url = {https://github.com/Benjamin-V-Chan/onet-match},
|
|
179
|
+
year = {2026}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## License and data attribution
|
|
184
|
+
|
|
185
|
+
The code is MIT-licensed; see [LICENSE](https://github.com/Benjamin-V-Chan/onet-match/blob/main/LICENSE).
|
|
186
|
+
|
|
187
|
+
This product includes information from the O\*NET 31.0 Database by the U.S.
|
|
188
|
+
Department of Labor, Employment and Training Administration (USDOL/ETA), used
|
|
189
|
+
under the [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.
|
|
190
|
+
O\*NET® is a trademark of USDOL/ETA. USDOL/ETA has not approved, endorsed or
|
|
191
|
+
tested this product.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""onet_analysis — the analytics layer of the O*NET career-intelligence pipeline.
|
|
2
|
+
|
|
3
|
+
Consumes the tidy tables produced by the ``onet_data_collector`` package and
|
|
4
|
+
turns them into diagnostics and interpretable structure:
|
|
5
|
+
|
|
6
|
+
* :mod:`onet_analysis.schema` — resilient column resolution across export variants.
|
|
7
|
+
* :mod:`onet_analysis.eda` — coverage stats and distribution plots.
|
|
8
|
+
* :mod:`onet_analysis.clustering` — TF-IDF -> TruncatedSVD -> KMeans role clusters.
|
|
9
|
+
* :mod:`onet_analysis.keyword_attribution` — where each keyword lands in feature space.
|
|
10
|
+
* :mod:`onet_analysis.collect` — thin re-exports of the collector's ETL stages.
|
|
11
|
+
|
|
12
|
+
The full pipeline (collect -> EDA -> keywords -> cluster) is driven by the
|
|
13
|
+
``onet-analyze`` CLI in :mod:`onet_analysis.cli`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
__version__ = "0.3.0"
|
|
19
|
+
|
|
20
|
+
__all__ = ["__version__"]
|