Biomarkers — canonical catalogue and derived links
The biomarker database (/biomarkers, /biomarker/<slug>; API GET /v1/biomarkers,
GET /v1/biomarkers/:slug) implements SPEC §17 (biomarkers as first-class entities), §52
(biomarker → therapy context) and §121 (tumor-agnostic indications). It follows CLAUDE.md §8:
curated metadata is allowed only when every code is verified against the authority, and every
scientific link is derived from source records — never typed in.
1. What a biomarker entity is
One row of biomarkers (CI-BIO-…) is an identity record, seeded from
packages/database/src/seed-data/biomarkers.ts (idempotent by slug; ids are minted once):
| Field | Content | Rule |
|---|---|---|
slug, name, kind |
e.g. braf-v600e, "BRAF V600E", gene_mutation |
kind is one of the twelve frozen values of the schema |
gene_id |
anchor gene (HGNC symbol resolved to genes.id at seed time) |
unresolved symbols are reported and left null, never invented; null for non-gene markers |
ncit_code |
NCIt concept of the biomarker / gene alteration ("HER2/Neu Positive", "BRAF NP_004324.2:p.V600E", "NTRK Gene Fusion Positive"…), never the bare gene concept | every code verified live against the NCI EVS REST API (GET /api/v1/concept/ncit/{code}) on 2026-09-11, NCIt 26.08e — all returned active: true, conceptStatus: DEFAULT; the API's preferred name is stored in measurement.ncit.name |
description |
one neutral sentence: what is measured, by which assay family, and whether it is used as a diagnostic, predictive or prognostic marker | no dosing, no thresholds of our own, no individual advice |
measurement (jsonb) |
assays (IHC, FISH/ISH, NGS, PCR, flow cytometry, PET imaging…), scoring (conventions as published: PD-L1 TPS/CPS/IC, HER2 IHC 0/1+/2+/3+ with ISH reflex, MSI-PCR vs MMR IHC, BCR-ABL1 %IS…), notes, sources (NCI / FDA pages, all HTTP 200 on 2026-09-11), aliases, genes, variantSlugs, indicationTerms, tumorAgnostic, ncit, verification |
the seed is the only writer of this shape; there is no alias table, so aliases live here |
The catalogue holds 54 markers: HER2, ER, PR, EGFR (any) + exon 19 deletion / L858R / T790M / exon 20 insertion, ALK, ROS1, KRAS G12C, KRAS (any), NRAS, BRAF V600E, BRAF (any), MET exon 14 / amplification, RET fusion, NTRK fusion, HER2 mutation, PD-L1, MSI-H, dMMR, TMB-H, BRCA1, BRCA2, HRD, PIK3CA, ESR1 mutation, AR, AR-V7, PSMA, SSTR, CD19, CD20, BCMA, CD38, CD30, CD33, FLT3, IDH1, IDH2, NPM1, KIT, PDGFRA, FGFR2 fusion, FGFR3 alteration, BCR-ABL1, JAK2 V617F, TP53, CDKN2A deletion, ctDNA, Ki-67, del(17p).
Relationships stated in the metadata, not inferred: PD-L1 is the protein of gene CD274; HER2 is the protein of ERBB2; MSI-H (DNA phenotype, PCR/NGS) and dMMR (loss of MLH1/MSH2/MSH6/PMS2 protein by IHC) are two measurements of one biological state and are kept as two entries whose notes cross-reference each other; HER2 overexpression and HER2 (ERBB2) mutation are different markers.
2. Scope of a biomarker (what the links are computed from)
Formula biomarker-links-v1, identical in apps/web/src/lib/queries/biomarkers.ts and
apps/api/src/routes/biomarkers.ts:
- gene_ids = anchor
gene_id∪measurement.genesresolved by HGNC symbol (NTRK fusion → NTRK1/NTRK2/NTRK3; MSI-H and dMMR → MLH1/MSH2/MSH6/PMS2; BCR-ABL1 → ABL1 + BCR; del(17p) → TP53). - variant_ids =
measurement.variantSlugsresolved againstvariants(checked to exist in the production copy on 2026-09-11). When present the marker is molecular-level (BRAF V600E, EGFR L858R, KRAS G12C, MET exon 14, JAK2 V617F, HER2 amplification…) and every link is restricted to those variants instead of the whole gene. - Markers with neither (TMB-H, HRD, ctDNA) have no gene-derived blocks: the pages show
"Data not yet available" with the reason recorded in
measurement.notes. Only the indication-text rule (§3.4) applies to them.
3. Derived links (computed at query time; no biomarker link table is written)
Every derived count carries the Computed claim badge and names its rule; every table of source rows carries the native claim badge of the source (Curated for CIViC, Regulatory for approvals, Published for trials and PubMed).
3.1 Evidence and associated cancers
CIViC evidence items with status = 'ACCEPTED' and evidence_type ∈ {PREDICTIVE, PROGNOSTIC, DIAGNOSTIC} whose variant_ids (molecular markers) or gene_ids (gene-level markers) overlap the
scope. Levels A–E and directions are shown exactly as curated (never re-scaled, never collapsed).
Associated cancers = distinct mapped cancer_id of that evidence, with counts by type and by
level. Submitted and rejected items are excluded; PREDISPOSING, ONCOGENIC and FUNCTIONAL items are
not part of the biomarker scope (they belong to the gene page).
3.2 Drugs with predictive evidence
Union of (a) therapy_ids of the in-scope PREDICTIVE items and (b) targets of active
knowledge_edges PREDICTS_RESPONSE_TO whose source variant is in scope. Per drug the page shows
the number of items, sensitivity vs resistance counts (CIViC significance), the best native level,
the number of knowledge edges with their direction, and the cancer contexts — as separate columns,
never merged into a verdict.
3.3 Trials
clinical_trials having an intervention mapped (trial_interventions.drug_id) to a scope drug
and a condition mapped (trial_conditions.cancer_id) to a scope cancer. Counts: total, active
(RECRUITING, NOT_YET_RECRUITING, ENROLLING_BY_INVITATION, ACTIVE_NOT_RECRUITING), recruiting,
active phase 3. The table lists active trials, newest update first. A trial listed here tests the
drug in the cancer; it does not mean the trial selects patients on the biomarker.
3.4 Approvals
drug_approvals rows (a) whose drug_id is a scope drug — matchedBy = drug — and/or (b) whose
indication text contains one of the curated indicationTerms phrases (case-insensitive substring,
e.g. "microsatellite instability-high", "NTRK", "PD-L1") — matchedBy = indication. Each row keeps
its authority, jurisdiction, indication text, status and date. Health Canada DIN rows carry no
indication text, so they only ever match by drug.
3.5 Tumor-agnostic rule (§121)
measurement.tumorAgnostic = true is a curated statement (MSI-H, dMMR, NTRK fusion, TMB-H, BRAF
V600E, RET fusion, HER2). The UI and API never assert it alone: the badge is accompanied by the
actual drug_approvals rows with tumor_agnostic = true (the source's flag, set by the openFDA
connector from the label text) reached through §3.4. When no such row is ingested yet, the page says
so explicitly ("no tumor-agnostic approval row ingested yet") instead of implying one exists.
3.6 Literature
publication_entity_edges (status ≠ rejected) on the scope variants for molecular markers, or
on the scope genes otherwise. Bibliographic data as recorded by PubMed.
4. What is not claimed
- No clinical-utility grading of our own (no "Tier", "Level 1", "actionable" labels): only the source-native CIViC level and direction and the regulator's status are shown.
- No threshold or cut-off is stated as CancerIndex's; scoring conventions are quoted as published and labels/indications are the authority's own text.
- No "approved for biomarker X" statement: approvals are listed per authority and jurisdiction with their indication text, and the match rule (drug set / indication text) is displayed per row.
- No patient-level guidance: biomarker pages describe what a test measures, not what a person should do.
- Absence of a link is not evidence of absence: only ingested sources (CIViC, ClinicalTrials.gov, openFDA, Health Canada DPD, PubMed edges) are covered.
5. Maintenance
- Add a marker: append to
BIOMARKER_SEEDwith a verified NCIt code (fetch the concept, record the date/version in the header if the version changed), runpnpm db:seed, runpackages/database/test/biomarkers.test.ts. - Changing the derivation rules = bump
BIOMARKER_LINKS_FORMULAin both the web queries and the API route, and update §3 here.