# Biomarkers — canonical catalogue and derived links The biomarker database (`/biomarkers`, `/biomarker/`; 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.genes` resolved 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.variantSlugs` resolved against `variants` (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_SEED` with a **verified** NCIt code (fetch the concept, record the date/version in the header if the version changed), run `pnpm db:seed`, run `packages/database/test/biomarkers.test.ts`. - Changing the derivation rules = bump `BIOMARKER_LINKS_FORMULA` in both the web queries and the API route, and update §3 here.