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.
Files changed (54) hide show
  1. onet_match-0.3.0/LICENSE +21 -0
  2. onet_match-0.3.0/PKG-INFO +238 -0
  3. onet_match-0.3.0/README.md +191 -0
  4. onet_match-0.3.0/onet_analysis/__init__.py +20 -0
  5. onet_match-0.3.0/onet_analysis/cli.py +311 -0
  6. onet_match-0.3.0/onet_analysis/clustering.py +149 -0
  7. onet_match-0.3.0/onet_analysis/collect.py +54 -0
  8. onet_match-0.3.0/onet_analysis/eda.py +138 -0
  9. onet_match-0.3.0/onet_analysis/keyword_attribution.py +94 -0
  10. onet_match-0.3.0/onet_analysis/loading.py +37 -0
  11. onet_match-0.3.0/onet_analysis/matching/__init__.py +25 -0
  12. onet_match-0.3.0/onet_analysis/matching/api.py +80 -0
  13. onet_match-0.3.0/onet_analysis/matching/catalog.py +228 -0
  14. onet_match-0.3.0/onet_analysis/matching/datasets.py +225 -0
  15. onet_match-0.3.0/onet_analysis/matching/engine.py +367 -0
  16. onet_match-0.3.0/onet_analysis/matching/evaluate.py +160 -0
  17. onet_match-0.3.0/onet_analysis/matching/metrics.py +102 -0
  18. onet_match-0.3.0/onet_analysis/matching/pipeline.py +332 -0
  19. onet_match-0.3.0/onet_analysis/matching/report.py +418 -0
  20. onet_match-0.3.0/onet_analysis/matching/retrieval.py +254 -0
  21. onet_match-0.3.0/onet_analysis/matching/synth.py +368 -0
  22. onet_match-0.3.0/onet_analysis/matching/text.py +34 -0
  23. onet_match-0.3.0/onet_analysis/matching/train.py +230 -0
  24. onet_match-0.3.0/onet_analysis/paths.py +94 -0
  25. onet_match-0.3.0/onet_analysis/schema.py +51 -0
  26. onet_match-0.3.0/onet_data_collector/OnetWebService.py +13 -0
  27. onet_match-0.3.0/onet_data_collector/__init__.py +53 -0
  28. onet_match-0.3.0/onet_data_collector/_logging.py +36 -0
  29. onet_match-0.3.0/onet_data_collector/bulk.py +319 -0
  30. onet_match-0.3.0/onet_data_collector/cli.py +174 -0
  31. onet_match-0.3.0/onet_data_collector/client.py +248 -0
  32. onet_match-0.3.0/onet_data_collector/condensed_job_details.py +104 -0
  33. onet_match-0.3.0/onet_data_collector/config.py +100 -0
  34. onet_match-0.3.0/onet_data_collector/exceptions.py +42 -0
  35. onet_match-0.3.0/onet_data_collector/job_details.py +87 -0
  36. onet_match-0.3.0/onet_data_collector/keyword_search.py +115 -0
  37. onet_match-0.3.0/onet_data_collector/utils.py +55 -0
  38. onet_match-0.3.0/onet_match/__init__.py +10 -0
  39. onet_match-0.3.0/onet_match.egg-info/PKG-INFO +238 -0
  40. onet_match-0.3.0/onet_match.egg-info/SOURCES.txt +52 -0
  41. onet_match-0.3.0/onet_match.egg-info/dependency_links.txt +1 -0
  42. onet_match-0.3.0/onet_match.egg-info/entry_points.txt +4 -0
  43. onet_match-0.3.0/onet_match.egg-info/requires.txt +29 -0
  44. onet_match-0.3.0/onet_match.egg-info/top_level.txt +3 -0
  45. onet_match-0.3.0/pyproject.toml +58 -0
  46. onet_match-0.3.0/setup.cfg +4 -0
  47. onet_match-0.3.0/tests/test_clustering.py +34 -0
  48. onet_match-0.3.0/tests/test_eda.py +25 -0
  49. onet_match-0.3.0/tests/test_keyword_attribution.py +33 -0
  50. onet_match-0.3.0/tests/test_matching_data.py +111 -0
  51. onet_match-0.3.0/tests/test_matching_dense.py +102 -0
  52. onet_match-0.3.0/tests/test_matching_docs.py +32 -0
  53. onet_match-0.3.0/tests/test_matching_models.py +296 -0
  54. onet_match-0.3.0/tests/test_schema.py +33 -0
@@ -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__"]