Taxonomy::CategoryRequest
Purpose
A CategoryRequest records what an employer wanted when none of the 18 job categories fit. When an employer selects "Others" in the job form's category picker, an optional input appears — "What kind of job is this?" — and what they type is saved here, together with a JSON snapshot of the job form at that moment.
This table is the evidence behind the taxonomy growth rules: a new category is added when 3+ distinct employers ask for the same concept, or 5+ similar Others postings pile up. Before this table existed, the weekly review could only guess from job titles filed under Others.
The row is saved when the input loses focus, not when the job is submitted. An employer who types "dog groomer", finds no fitting category, and abandons the form is the strongest signal that the taxonomy has a gap — waiting for a successful job submit would lose exactly those cases. That is why careers_job_snapshot may hold values from a form that was never submitted.
Model Context
| Context | Details |
|---|---|
| Written by | POST /employers/taxonomy/category_requests (employers actor). The frontend fires it once per form session, on input blur, re-firing only if the text changed. Failures are silent — the request never blocks posting a job. |
| Read by | The weekly Others review, via Metabase or the console. No admin UI in v1. |
| Lifecycle | Append-only. Rows are never updated or deleted, and there is deliberately no status column (our convention: append-only tables carry no status). The outcome of a request lives in the taxonomy itself — a promoted or widened category — not as per-row workflow state. |
Schema
Table: taxonomy_category_requests
| Column | Type | Constraints | Notes |
|---|---|---|---|
id | bigint | pk | |
uuid | string | unique, not null | standard for every new table |
name | string | not null | the employer's answer to "What kind of job is this?"; max 100 characters, enforced in the validator |
careers_job_snapshot | jsonb | not null, default {} | the job form values at request time — the context behind the request; may be from an unsubmitted form |
created_by | bigint | not null, FK → org_memberships | who asked; joined to company for the distinct-employer count |
created_at | timestamptz | not null | the weekly review reads new rows by this |
updated_at | timestamptz | not null |
How the weekly review uses it
namesays what was asked for.careers_job_snapshotshows the job behind the request (title, description, pay) — enough to judge whether two requests are the same concept.created_byjoined toorg_memberships→ company gives the distinct-employer count for the promote bar.
Refs
- Spec and decision record: 2026-07-12 spec
- Growth rules and research: 2026-07 Taxonomy Categories initiative
- Anchor issue: jodapp-api#1840