Every buyer project on UpStage has a content vertical. The operator-managed
buyer_projects.vertical value is the singular, human-curated taxonomy field that records the
primary category. Vertical tagging is the operator-side queue at /admin/tags, backed by a
Postgres project_vertical enum.
Beyond semantic matching, the explicit category signal is separate: the
computeMatchScore() path applies its explicit verticalBoost only when the project’s
verticals array overlaps the talent’s project_types array — separate from the
singular admin taxonomy field.
The project_vertical enum is a single-valued, “primary-axis” column on
buyer_projects. Legacy rows default to short-film; new projects default the same way
unless the creator explicitly picks another value via POST /projects/new.
short-filmcommercialtheaternew-mediaEach value maps to a market category the talent operator cares about — and each is its own “axis of relevance” in the matching engine. Pick one per project.
The end-to-end workflow that takes a project from “just posted” to “ranked on a talent card” flows through three stages:
/admin/tags, chooses a vertical, hits Save.setProjectVertical(id, vertical) in
db/buyer-projects.js.computeMatchScore() applies a 5%
verticalBoost inside the 40% skills weight only when the project’s
verticals overlap talent project_types.
Admin hits /admin/tags → selects the vertical enum → on
POST /admin/tags/vertical/:id the row gets vertical written via
setProjectVertical as taxonomy metadata → on a later scheduled match check, the matching
engine re-reads the project verticals array and talent project_types array; only an
overlap adds the bounded verticalBoost and surfaces as the category sub-score on the talent card.
The tagger UI at /admin/tags is a deliberately thin admin surface — it lists
every buyer_projects row and exposes a dropdown + Save pair per row, backed by the
project_vertical enum.
The dropdown submits via POST /admin/tags/vertical/:id — gated by
requireAdminSecret at routes/admin.js:1663 so only the operator (with the secret
in the URL or the Authorization header) can alter a project’s vertical.
UpStage carries a singular operator-managed taxonomy field on the brief, plus matching arrays on the brief and talent profile.
The operator touches the brief-side taxonomy; computeMatchScore() uses the two arrays for the
explicit category signal.
vertical on buyer_projects
(Postgres enum, default short-film), plus a multi-select verticals
TEXT[] array the buyer can fill at POST /projects/new. Talent matching reads
verticals for the explicit overlap cue.
project_types TEXT[] array on seller_profiles the talent fills
when editing their profile. The match boost fires when at least one of the project’s
verticals labels overlaps a talent project_types label (case-insensitive).
The two arrays meet inside computeMatchScore() at
services/matching.js:69–86 — and the overlap yields the 5% explicit
verticalBoost that sharpens the existing 40% skills weight without changing its headroom.
Saving the singular admin vertical field persists taxonomy metadata; it does not itself rescore
a project. On a later scheduled match check, the AI matching path re-reads the project’s
verticals array and the talent’s project_types array. The daily
ai-talent-match-check cron
(referenced in the CLAUDE.md changelog for 2026-08-12) re-runs the matching
every cron run, while the 3-day cadence ensures notifications fire on the
match_run_counter % 3 == 0 rhythm (corrected in the 2026-07-15 fix).
In practice: an operator hits Save on the tagger screen, and a later cron cycle can apply the bounded
verticalBoost when those matching arrays overlap. The category score may then surface on the
talent card; Save itself does not guarantee a notification or delivery.