JobHinge · Plan for review
Three layers. Two are already built. Embeddings are the third one — not the first.
August 6, 2026 · Every number below was read directly out of the live database on VPS2, not from documentation · Source of record: plans/jobhinge-search-layers/plan.mdx
Your parser has already extracted about 100 structured fields on every one of your 298,956 jobs — skills, seniority, work mode, years of experience, salary, location, category.
Which means a search like "remote python developer" is a plain database filter over data you already paid to produce. Embeddings are only for the questions the other two layers genuinely can't answer — and that set is smaller than it feels when you've been talking about AI all night.
Watch which layer answers each question. This is the entire plan in one table.
| What someone types | What answers it | Why |
|---|---|---|
| remote python developer | LAYER 1 | Work mode is "remote," skills contain "python." Exact, instant, and you can explain any result. |
| registered nurse night shift Tampa | LAYER 1 | Title, shift and city are all already parsed columns. |
| senior devops kubernetes terraform | LAYER 2 | Loose pile of skill words. Full-text ranking handles the mix without needing a field for it. |
| a job where I won't be micromanaged | LAYER 3 | No field holds this. No keyword matches it. Meaning is the only handle. |
| something like my last job but less travel | LAYER 3 | Needs comparing to a whole profile, not matching terms. |
| Column | Size | Rows in it | Reality |
|---|---|---|---|
| company_enrichment.embedding | 1536 | 0 | Built, indexed, never filled |
| user_profiles.profile_embedding | 1536 | 0 | Built, indexed, never filled |
| anything on jobs | — | — | Doesn't exist |
Both are built for 1536 numbers — that's OpenAI's shape. Both models we tested produce 1024. Neither fits. Whatever we do starts with something new, not filling in something old.
Same 12 job postings, same 5 plain-English searches, graded on whether the right job came back first.
| Engine | Size | Correct | Speed | All 298,956 jobs |
|---|---|---|---|---|
| Talos — already installed | 1024 | 5 / 5 | 8.2/sec | ~10 hrs, once, overnight |
| NVIDIA hosted | 1024 | 5 / 5 | 17.6/sec | ~4.7 hrs, once |
| NVIDIA hosted (bigger) | 2048 | 5 / 5 | 16.7/sec | ~5 hrs, once |
It scored identically. It's hardware you already own and already pay for. And it carries none of NVIDIA's problems — no 40-per-minute ceiling, no terms saying "testing only," no model that can be retired out from under you, no question about who sees your data.
The entire cost of choosing it is about five extra hours on a one-time overnight job, on a box that sits idle anyway.
Embeddings are calculated once and stored in your own database. Nothing calls out to anyone when a user searches — that math happens inside your Postgres.
It's a batch job, not a live dependency. That's the exact opposite of the parser or the classifier, where every single job needs a live call and an outage stops the pipeline. Those would be reckless to move. This one isn't.
These get settled before the work, because 298,956 stored vectors are painful to redo.
| Decision | Recommendation | Why you can't easily change it later |
|---|---|---|
| Which model | Talos, local | Vectors from different models aren't comparable. Switching = redo everything. |
| Size | 1024 (follows the model) | Column type is fixed when created. Changing it is a migration plus a full redo. |
| Where it lives | A new side table, not a column on the jobs table | The jobs table is ~100 columns and busy. A side table can be dropped without touching it. |
| What text gets used | Title + summary + must-have skills | Change the recipe and every stored vector is invalid. Decide before the run, not after. |
| Index tuning | Skip it for the trial | Needs real data present to tune. Building it early just means rebuilding it. |
Nothing live is modified. If it doesn't impress you, we drop one table and walk away.
That 5-out-of-5 result came from 12 clean job postings and 5 queries I wrote myself. Your real corpus is 298,956 messy ones.
It shows the local model isn't disqualified. It does not prove it wins on real data. The trial exists specifically to replace that weak evidence with real evidence before you commit to anything.
| # | Work | Effort | Expected payoff |
|---|---|---|---|
| 1 | Use the ~100 fields you already extracted | Low — plain database queries | Highest. Exact, explainable, nothing new to break. |
| 2 | Tune the full-text search you already have | Low — already built and filled | Moderate. Better ranking on messy multi-word searches. |
| 3 | Embeddings trial on 5,000 jobs | Medium — one table, one overnight run | Unknown until measured. That's the point of a trial. |
| 4 | Full corpus + index tuning | Higher — 10-hour run | Only if step 3 visibly wins. |