SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%

Foundation: monorepo, schema, provenance SDK, ontology resolver, ranking engine, OncoTree connector

Simon-Pierre Boucher committed 16 days ago (Sep 8, 2026)

74 changed files +13,851 −0

added .env.example +34 −0
@@ -0,0 +1,34 @@
1 +# CancerIndex — environment (never commit real values)
2 +NODE_ENV=development
3 +DATABASE_URL=postgres://localhost:5432/cancerindex
4 +DB_POOL_MAX=10
5 +
6 +WEB_PORT=8250
7 +API_PORT=8251
8 +API_HOST=127.0.0.1
9 +CI_API_URL=http://127.0.0.1:8251
10 +NEXT_PUBLIC_SITE_URL=http://localhost:8250
11 +NEXT_TELEMETRY_DISABLED=1
12 +
13 +# Data lake (raw connector payloads, gzip JSON) — outside git
14 +CI_DATA_DIR=./data
15 +
16 +# Admin console (/admin) bearer token
17 +ADMIN_TOKEN=change-me
18 +
19 +# NCBI E-utilities (PubMed, ClinVar) — tool/email are mandatory, api key raises the limit to 10 req/s
20 +NCBI_TOOL=cancerindex
21 +NCBI_EMAIL=
22 +NCBI_API_KEY=
23 +
24 +# SEER API (free key from https://api.seer.cancer.gov/) — connector stays "awaiting_credentials" without it
25 +SEER_API_KEY=
26 +
27 +# Optional AI provider for /ask (Phase 5) — the database works without it
28 +ANTHROPIC_API_KEY=
29 +OPENAI_API_KEY=
30 +OPENAI_BASE_URL=
31 +
32 +WORKER_CONCURRENCY=4
33 +CI_MAX_RUN_MINUTES=45
34 +LOG_LEVEL=info
added .gitignore +15 −0
@@ -0,0 +1,15 @@
1 +node_modules/
2 +.next/
3 +dist/
4 +*.tsbuildinfo
5 +.env
6 +.env.*
7 +!.env.example
8 +data/raw/
9 +data/cache/
10 +data/tmp/
11 +logs/
12 +tmp/
13 +coverage/
14 +.DS_Store
15 +.claude/
added .npmrc +2 −0
@@ -0,0 +1,2 @@
1 +auto-install-peers=true
2 +strict-peer-dependencies=false
added CLAUDE.md +82 −0
@@ -0,0 +1,82 @@
1 +# CLAUDE.md — CancerIndex.io (repository guide)
2 +
3 +The full product specification (364 sections) lives in `docs/SPEC-original.md`. This file is the
4 +condensed, operational version every contributor (human or agent) must follow.
5 +
6 +## Mission
7 +Build **the global index of cancer**: a provenance-first, continuously updated, transparently
8 +sourced oncology ontology connecting epidemiology, genomics, biomarkers, therapies, trials,
9 +regulatory evidence and literature — for every recognized malignant disease entity, not a list of
10 +30 common cancers. Not a physician, not a diagnostic tool, no treatment recommendations.
11 +
12 +## Non-negotiables (from the spec)
13 +1. **Provenance first** — no scientific number without a `provenance` row; derived values carry a
14 + `formula_version` and their inputs (§2, §250-253). Raw payloads go to the data lake (§26).
15 +2. **Layers stay separable**: RAW → NORMALIZED → CANONICAL → DERIVED → RANKED → AI (§2).
16 +3. **Never fake data**: no mock/hardcoded statistics in UI; show "Data not yet available" (§281-283).
17 +4. **Identifiers are first-class**: keep every upstream ID in `*_codes` / dedicated columns (§347);
18 + public IDs are `CI-<NS>-00000001` minted via `mintId` (§6), never DB integers.
19 +5. **Reconciliation before ingestion**: IDs → curated aliases → normalized strings; LLM only as a
20 + *candidate* generator; unknown labels go to `unresolved_labels`, never dropped (§69, §222, §340).
21 +6. **Time-aware observations**: never overwrite year X with year Y; store both (§71).
22 +7. **Context on every edge**: cancer context, direction, evidence level, provenance (§29, §244).
23 +8. **Licensing gate**: no connector goes live before manifest `licenseStatus` is reviewed; IARC/
24 + GLOBOCAN stays `review`, SEER `awaiting_credentials` until keys/terms are settled (§10.4, §142).
25 +9. **Verify docs before coding a connector** — official docs, smoke test with curl, fixture, tests,
26 + prove on 10/100/1000 records before a full sync (§224-227). Record `documentationVerifiedAt`.
27 +10. **Rate limits, idempotency, restartability, anomaly guard** (never mass-delete on a shrunken
28 + response) (§90-92, §171, §229).
29 +11. **Rankings need scope + year + source + formula version + "Why this rank?"** (§33, §183).
30 + Composite scores are Phase 2+ (§353).
31 +12. **Scientific safety labels**: observed / published / curated / regulatory / guideline /
32 + computed / AI-generated — never merged (§3). Population survival ≠ individual prognosis (§325).
33 +
34 +## Repository layout
35 +```
36 +apps/web Next.js 16 (webpack build, --webpack), Tailwind v4, server components, /api/v1 proxied to apps/api
37 +apps/api Fastify /v1 public API (JSON envelope { data, sources, dataRelease })
38 +workers/ pg-boss scheduler: connector schedules, counters, rankings
39 +packages/shared ids, provenance types, normalization, logger
40 +packages/database Drizzle schema (snake_case casing), migrations, seed (metrics + geographies only)
41 +packages/ontology qualifier rules, CancerResolver (alias/code reconciliation), TOP_LEVEL_CANCERS
42 +packages/connectors SDK (manifest, HttpClient, RawLake, RunContext, validators) + connectors/<id>/{manifest.ts,index.ts,fixtures/,*.test.ts}
43 +packages/ranking counters (entity_counters), ranking engine (snapshots + lineage), trace
44 +scripts/ci.ts operator CLI: `pnpm cix connectors|run <id>|run-all|health|sources:sync|counters|rank|stats|trace`
45 +docs/ architecture, data model, methodology, source policy, ADRs, connector docs
46 +data/raw data lake (gitignored) — {source}/{date}/{entity}/{runId}-{part}.jsonl.gz
47 +```
48 +
49 +## Conventions
50 +- TypeScript strict, ESM, NodeNext imports with `.js` suffix inside packages; web app uses
51 + `moduleResolution: bundler` + webpack `extensionAlias` (never import `@cancerindex/connectors` in web).
52 +- Database: `getDb()` singleton; Drizzle `casing: 'snake_case'`; raw SQL via `sql` template for
53 + aggregates. Schema changes = new migration via `pnpm db:generate` (one per integration), never
54 + manual prod mutation. Agents developing in parallel use `drizzle-kit push` on their own DB and do
55 + **not** commit migration files; the integrator generates the migration.
56 +- Connector = `class extends Connector` with `manifest`, `healthCheck(ctx)`, `sync(ctx)`; register
57 + in `packages/connectors/src/registry.ts`. Use `ctx.http` (rate-limited), `ctx.upsertSourceRecord`
58 + (idempotent + raw lake), `ctx.addProvenance`, `ctx.recordUnresolved`, `ctx.cursor` (restart),
59 + `ctx.shouldStop()` between pages (time budget), validators from `sdk/validate.ts`.
60 +- Reconciliation: `new CancerResolver(db)``warm()``byCode()/byLabel()/resolve()`; store
61 + `match_type` on every mapping; enqueue misses with `ctx.recordUnresolved`.
62 +- Tests: vitest; connector tests run against **fixtures** (sanitized JSON in `fixtures/`), never
63 + live APIs; cover normal / empty / pagination / rate-limit / server error / malformed record.
64 +- UI: scientific-editorial aesthetic (off-white, charcoal, muted neutrals, dense tables, no
65 + gradients/cards-everywhere), WCAG-minded, mobile first-class. Every number shows source badge,
66 + unit, population, period and a freshness line. Empty state = "Data not yet available".
67 +- Language: English UI; code comments English; commit messages English.
68 +
69 +## Running locally
70 +```
71 +createdb cancerindex && cp .env.example .env
72 +pnpm install && pnpm db:migrate && pnpm db:seed && pnpm cix sources:sync
73 +pnpm cix run oncotree --mode dry_run # smoke; then without --mode
74 +pnpm cix counters && pnpm cix rank
75 +pnpm dev:api & pnpm dev:web
76 +```
77 +
78 +## Deployment
79 +MacLustr cluster via `mld` (gateway M1M32): manifest `~/dispatch/apps/cancerindex.json`,
80 +`mld stage <dir> cancerindex` then `mld deploy cancerindex`. Web :8250, API :8251, PM2 processes
81 +`cancerindex-web|api|worker|ngrok`, domain www.cancerindex.io (ngrok reserved). Postgres 17 +
82 +pgvector on the node, database `cancerindex`. Source of truth for the code: spbgit `cancerindex.git`.
added package.json +40 −0
@@ -0,0 +1,40 @@
1 +{
2 + "name": "cancerindex",
3 + "version": "0.1.0",
4 + "private": true,
5 + "description": "CancerIndex.io — the global index of cancer: provenance-first oncology ontology, epidemiology, genomics, trials, literature, rankings",
6 + "type": "module",
7 + "packageManager": "pnpm@11.1.2",
8 + "engines": {
9 + "node": ">=22"
10 + },
11 + "scripts": {
12 + "build": "pnpm -r --filter './packages/**' run build && pnpm --filter @cancerindex/api run build && pnpm --filter @cancerindex/web run build",
13 + "typecheck": "pnpm -r run typecheck",
14 + "test": "pnpm -r run test",
15 + "db:generate": "pnpm --filter @cancerindex/database run generate",
16 + "db:migrate": "pnpm --filter @cancerindex/database run migrate",
17 + "db:seed": "pnpm --filter @cancerindex/database run seed",
18 + "dev:web": "pnpm --filter @cancerindex/web run dev",
19 + "dev:api": "pnpm --filter @cancerindex/api run dev",
20 + "worker": "tsx workers/main.ts",
21 + "cix": "tsx scripts/ci.ts",
22 + "registry": "tsx scripts/build-registry.ts",
23 + "registry:check": "tsx scripts/build-registry.ts --check"
24 + },
25 + "dependencies": {
26 + "@cancerindex/connectors": "workspace:*",
27 + "@cancerindex/database": "workspace:*",
28 + "@cancerindex/ontology": "workspace:*",
29 + "@cancerindex/ranking": "workspace:*",
30 + "@cancerindex/shared": "workspace:*",
31 + "drizzle-orm": "^0.45.0",
32 + "postgres": "^3.4.7"
33 + },
34 + "devDependencies": {
35 + "@types/node": "^24.0.0",
36 + "tsx": "^4.20.0",
37 + "typescript": "^5.9.3",
38 + "vitest": "^3.2.0"
39 + }
40 +}
added packages/connectors/package.json +36 −0
@@ -0,0 +1,36 @@
1 +{
2 + "name": "@cancerindex/connectors",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "exports": {
7 + ".": {
8 + "types": "./src/index.ts",
9 + "default": "./src/index.ts"
10 + },
11 + "./sdk": {
12 + "types": "./src/sdk/index.ts",
13 + "default": "./src/sdk/index.ts"
14 + }
15 + },
16 + "scripts": {
17 + "build": "tsc -p tsconfig.json --noEmit",
18 + "typecheck": "tsc -p tsconfig.json --noEmit",
19 + "test": "vitest run --passWithNoTests"
20 + },
21 + "dependencies": {
22 + "@cancerindex/database": "workspace:*",
23 + "@cancerindex/ontology": "workspace:*",
24 + "@cancerindex/shared": "workspace:*",
25 + "drizzle-orm": "^0.45.0",
26 + "fast-xml-parser": "^5.2.0",
27 + "postgres": "^3.4.7",
28 + "yaml": "^2.8.0",
29 + "zod": "^4.0.0"
30 + },
31 + "devDependencies": {
32 + "@types/node": "^24.0.0",
33 + "typescript": "^5.9.3",
34 + "vitest": "^3.2.0"
35 + }
36 +}
added packages/connectors/src/connectors/oncotree/index.ts +185 −0
@@ -0,0 +1,185 @@
1 +import { eq, sql } from 'drizzle-orm';
2 +import { z } from 'zod';
3 +import { normalizeLabel, slugify } from '@cancerindex/shared';
4 +import { anatomicalSites, cancerAliases, cancerAnatomy, cancerCodes, cancerHierarchy, cancers, mintId } from '@cancerindex/database';
5 +import { CancerResolver } from '@cancerindex/ontology';
6 +import { Connector, type ConnectorHealth, type RunContext } from '../../sdk/run.js';
7 +import { manifest } from './manifest.js';
8 +
9 +const TumorType = z.object({
10 + code: z.string(),
11 + name: z.string(),
12 + mainType: z.string().nullable().optional(),
13 + tissue: z.string().nullable().optional(),
14 + parent: z.string().nullable().optional(),
15 + level: z.number().int(),
16 + externalReferences: z.record(z.string(), z.array(z.string())).default({}),
17 + history: z.array(z.string()).default([]),
18 + revocations: z.array(z.string()).default([]),
19 + precursors: z.array(z.string()).default([]),
20 + color: z.string().nullable().optional(),
21 +});
22 +type TumorType = z.infer<typeof TumorType>;
23 +
24 +const HEME_RE = /lymphoid|myeloid|blood|leukemia|lymphoma|myeloma|histiocyt|mastocyt|plasma cell/i;
25 +const BENIGN_RE = /\bbenign\b|borderline|\bnevus\b|\bnevi\b|\badenoma\b|hyperplasia|\bcyst\b|hamartoma|\blipoma\b|leiomyoma\b|\bfibroma\b|hemangioma\b|papilloma\b|dysplasia|in situ|\bpolyp\b|schwannoma\b|meningioma\b|paraganglioma\b|pheochromocytoma\b|teratoma\b|thymoma\b/i;
26 +
27 +/**
28 + * OncoTree connector: maps every OncoTree node onto the canonical cancer set.
29 + * - level-1 "tissue" nodes (parent TISSUE) are anatomical sites, not cancers → anatomical_sites
30 + * - NCIt cross-reference → EXACT_IDENTIFIER match to the NCIt-derived canonical entity
31 + * - otherwise preferred-name match → ONTOLOGY_EXACT
32 + * - otherwise a new canonical entity is created *only* because OncoTree is a recognized ontology
33 + * (CLAUDE.md §340) — flagged with primaryOncotreeCode and no NCIt code.
34 + * The OncoTree parent/child structure is stored as hierarchy_type = "oncotree" (levels ≥ 2), and
35 + * level-2 nodes are linked to their tissue via cancer_anatomy.
36 + */
37 +export class OncotreeConnector extends Connector {
38 + readonly manifest = manifest;
39 +
40 + async healthCheck(ctx: RunContext): Promise<ConnectorHealth> {
41 + try {
42 + const versions = await ctx.http.json<Array<{ api_identifier: string; release_date: string }>>(`${manifest.access.baseUrl}/versions`);
43 + const stable = versions.find((v) => v.api_identifier === 'oncotree_latest_stable');
44 + return { status: 'healthy', detail: `latest stable ${stable?.release_date ?? '?'}` };
45 + } catch (e) {
46 + return { status: 'failing', detail: (e as Error).message };
47 + }
48 + }
49 +
50 + async sync(ctx: RunContext): Promise<void> {
51 + const versions = await ctx.http.json<Array<{ api_identifier: string; release_date: string; visible: boolean }>>(`${manifest.access.baseUrl}/versions`);
52 + const stable = versions.find((v) => v.api_identifier === 'oncotree_latest_stable');
53 + // Pin to the dated release identifier so the dataset version is reproducible.
54 + const dated = versions.find((v) => v.release_date === stable?.release_date && v.api_identifier !== 'oncotree_latest_stable');
55 + const version = dated?.api_identifier ?? 'oncotree_latest_stable';
56 + ctx.datasetVersion = version;
57 + ctx.info(`OncoTree version ${version}`);
58 +
59 + const raw = await ctx.http.json<unknown[]>(`${manifest.access.baseUrl}/tumorTypes?version=${encodeURIComponent(version)}`);
60 + const nodes: TumorType[] = [];
61 + for (const r of raw) {
62 + const parsed = TumorType.safeParse(r);
63 + if (!parsed.success) {
64 + ctx.counters.validationFailures++;
65 + ctx.counters.rejected++;
66 + ctx.warn(`invalid tumor type record: ${parsed.error.issues[0]?.message}`);
67 + continue;
68 + }
69 + nodes.push(parsed.data);
70 + }
71 + ctx.info(`fetched ${nodes.length} tumor types`);
72 + if (nodes.length < 500) throw new Error(`anomaly: only ${nodes.length} OncoTree nodes returned (expected ~900) — refusing to persist (CLAUDE.md §171)`);
73 + if (ctx.mode === 'dry_run') {
74 + ctx.counters.fetched = nodes.length;
75 + return;
76 + }
77 +
78 + const resolver = new CancerResolver(ctx.db);
79 + await resolver.warm();
80 + const byCode = new Map(nodes.map((n) => [n.code, n]));
81 + const canonicalByOncotree = new Map<string, string>();
82 + const siteByOncotree = new Map<string, string>();
83 + await ctx.addProvenance({ dataset: 'OncoTree tumor types', datasetVersion: version, sourceUrl: `${manifest.access.baseUrl}/tumorTypes?version=${version}`, evidenceType: 'expert_curation', accessLevel: 'open', methodology: 'OncoTree curated tumor type hierarchy' });
84 +
85 + // Pass 0: tissue roots → anatomical sites
86 + for (const node of nodes.filter((n) => n.parent === 'TISSUE')) {
87 + await ctx.upsertSourceRecord('tissue', node.code, node);
88 + const slug = slugify(node.name);
89 + const ncit = node.externalReferences['NCI']?.[0] ?? null;
90 + const [existing] = await ctx.db.select({ id: anatomicalSites.id }).from(anatomicalSites).where(eq(anatomicalSites.slug, slug)).limit(1);
91 + let siteId = existing?.id;
92 + if (!siteId) {
93 + siteId = await mintId(ctx.db, 'ANAT');
94 + await ctx.db.insert(anatomicalSites).values({ id: siteId, name: node.name, slug, ncitCode: ncit, system: node.tissue ?? node.name });
95 + } else await ctx.db.update(anatomicalSites).set({ name: node.name, ncitCode: ncit }).where(eq(anatomicalSites.id, siteId));
96 + siteByOncotree.set(node.code, siteId);
97 + }
98 +
99 + // Pass 1: resolve / create canonical entities (parents before children thanks to level ordering).
100 + for (const node of [...nodes].filter((n) => n.parent !== 'TISSUE').sort((a, b) => a.level - b.level)) {
101 + const rec = await ctx.upsertSourceRecord('tumor_type', node.code, node);
102 + let cancerId: string | null = null;
103 + let matchType = 'UNRESOLVED';
104 + const ncitCodes = node.externalReferences['NCI'] ?? [];
105 + for (const c of ncitCodes) {
106 + const hit = resolver.byCode('ncit', c);
107 + if (hit) {
108 + cancerId = hit.cancerId;
109 + matchType = hit.matchType;
110 + break;
111 + }
112 + }
113 + if (!cancerId) {
114 + const hit = resolver.byCode('oncotree', node.code);
115 + if (hit) {
116 + cancerId = hit.cancerId;
117 + matchType = hit.matchType;
118 + }
119 + }
120 + if (!cancerId) {
121 + const hit = resolver.byLabel(node.name);
122 + if (hit && hit.matchType === 'ONTOLOGY_EXACT') {
123 + cancerId = hit.cancerId;
124 + matchType = hit.matchType;
125 + }
126 + }
127 + if (!cancerId) {
128 + // Recognized ontology node without NCIt anchor → new canonical entity (CLAUDE.md §340 satisfied: OncoTree is a recognized ontology).
129 + const slugBase = slugify(node.name);
130 + let slug = slugBase;
131 + for (let i = 2; ; i++) {
132 + const [clash] = await ctx.db.select({ id: cancers.id }).from(cancers).where(eq(cancers.slug, slug)).limit(1);
133 + if (!clash) break;
134 + slug = `${slugBase}-${i}`;
135 + }
136 + const heme = HEME_RE.test(`${node.tissue ?? ''} ${node.mainType ?? ''} ${node.name}`);
137 + cancerId = await mintId(ctx.db, 'CAN');
138 + await ctx.db.insert(cancers).values({
139 + id: cancerId,
140 + slug,
141 + canonicalName: node.name,
142 + entityType: node.level >= 3 ? 'subtype' : heme ? 'hematologic_malignancy' : 'cancer',
143 + malignant: !BENIGN_RE.test(node.name),
144 + solidTumor: !heme,
145 + hematologic: heme,
146 + primaryOncotreeCode: node.code,
147 + depth: node.level,
148 + classificationVersion: version,
149 + });
150 + await ctx.db.insert(cancerAliases).values({ cancerId, alias: node.name, normalized: normalizeLabel(node.name), aliasType: 'preferred', sourceId: ctx.sourceId, sourceTerminology: 'OncoTree' }).onConflictDoNothing();
151 + matchType = 'EXACT_IDENTIFIER';
152 + await ctx.recordChange('cancer', cancerId, 'created', `Created from OncoTree ${node.code} (${node.name})`);
153 + } else {
154 + await ctx.db.insert(cancerAliases).values({ cancerId, alias: node.name, normalized: normalizeLabel(node.name), aliasType: 'synonym', sourceId: ctx.sourceId, sourceTerminology: 'OncoTree' }).onConflictDoNothing();
155 + }
156 + canonicalByOncotree.set(node.code, cancerId);
157 + await ctx.db.insert(cancerCodes).values({ cancerId, system: 'oncotree', code: node.code, matchType, sourceId: ctx.sourceId, validFrom: stable?.release_date }).onConflictDoNothing();
158 + for (const u of node.externalReferences['UMLS'] ?? []) await ctx.db.insert(cancerCodes).values({ cancerId, system: 'umls', code: u, matchType: 'EXACT_IDENTIFIER', sourceId: ctx.sourceId }).onConflictDoNothing();
159 + for (const c of ncitCodes) await ctx.db.insert(cancerCodes).values({ cancerId, system: 'ncit', code: c, matchType: 'EXACT_IDENTIFIER', sourceId: ctx.sourceId }).onConflictDoNothing();
160 + // Record the OncoTree code on the entity only when it has none yet (first mapping wins; others stay in cancer_codes).
161 + await ctx.db.execute(sql`UPDATE cancers SET primary_oncotree_code = ${node.code} WHERE id = ${cancerId} AND primary_oncotree_code IS NULL`);
162 + // Level-2 nodes hang directly under a tissue → anatomical relation.
163 + if (node.parent && siteByOncotree.has(node.parent)) {
164 + await ctx.db.insert(cancerAnatomy).values({ cancerId, siteId: siteByOncotree.get(node.parent)!, relation: 'primary', sourceId: ctx.sourceId }).onConflictDoNothing();
165 + }
166 + void rec;
167 + }
168 +
169 + // Pass 2: hierarchy edges (oncotree dimension), levels ≥ 2 only.
170 + let edges = 0;
171 + for (const node of nodes) {
172 + if (!node.parent || node.parent === 'TISSUE' || siteByOncotree.has(node.parent)) continue;
173 + const childId = canonicalByOncotree.get(node.code);
174 + const parentId = canonicalByOncotree.get(node.parent);
175 + if (!childId || !parentId || childId === parentId) continue;
176 + if (!byCode.has(node.parent)) continue;
177 + await ctx.db.insert(cancerHierarchy).values({ parentId, childId, hierarchyType: 'oncotree', sourceId: ctx.sourceId }).onConflictDoNothing();
178 + edges++;
179 + }
180 + ctx.info(`tissues ${siteByOncotree.size}; canonical mapped ${canonicalByOncotree.size}; hierarchy edges ${edges}`);
181 + ctx.cursor = { version, syncedAt: new Date().toISOString() };
182 + }
183 +}
184 +
185 +export const connector = new OncotreeConnector();
added packages/connectors/src/connectors/oncotree/manifest.ts +34 −0
@@ -0,0 +1,34 @@
1 +import { defineManifest } from '../../sdk/manifest.js';
2 +
3 +/**
4 + * Docs verified 2026-09-08: https://oncotree.mskcc.org (API tab) — GET /api/tumorTypes?version=…,
5 + * GET /api/versions, GET /api/mainTypes. Response fields: code, name, mainType, tissue, parent,
6 + * level, externalReferences{UMLS[],NCI[]}, history[], revocations[], precursors[], color.
7 + * License (repo README, cBioPortal/oncotree): CC BY 4.0.
8 + */
9 +export const manifest = defineManifest({
10 + id: 'oncotree',
11 + name: 'OncoTree',
12 + organization: 'Memorial Sloan Kettering Cancer Center / cBioPortal',
13 + category: 'terminology',
14 + tier: 0,
15 + description: 'Open cancer classification used by cBioPortal and clinical sequencing programs. Provides a tissue-based tumor type hierarchy with NCIt and UMLS cross-references. Used as a second hierarchy dimension and to anchor cohort/genomic sources.',
16 + homepage: 'https://oncotree.mskcc.org',
17 + docsUrl: 'https://oncotree.mskcc.org/#/home?tab=api',
18 + termsUrl: 'https://github.com/cBioPortal/oncotree/blob/master/LICENSE',
19 + access: { type: 'api', auth: 'none', baseUrl: 'https://oncotree.mskcc.org/api' },
20 + license: 'CC BY 4.0',
21 + licenseStatus: 'approved',
22 + commercialUse: 'allowed',
23 + redistribution: 'attribution',
24 + attribution: 'OncoTree (Memorial Sloan Kettering Cancer Center), CC BY 4.0, https://oncotree.mskcc.org',
25 + termsReviewedAt: '2026-09-08',
26 + updateFrequency: 'Stable releases a few times per year',
27 + supportsIncrementalSync: false,
28 + entities: ['cancer', 'cancer_hierarchy', 'cancer_codes'],
29 + rateLimits: { requestsPerSecond: 2, maxConcurrency: 1 },
30 + rawRetention: 'full',
31 + documentationVerifiedAt: '2026-09-08',
32 + status: 'active',
33 + schedule: '0 4 * * 1',
34 +});
added packages/connectors/src/index.ts +4 −0
@@ -0,0 +1,4 @@
1 +export * from './sdk/index.js';
2 +export * from './registry.js';
3 +export * from './sources-sync.js';
4 +export * from './planned.js';
added packages/connectors/src/planned.ts +60 −0
@@ -0,0 +1,60 @@
1 +import { defineManifest, type ConnectorManifest } from './sdk/manifest.js';
2 +
3 +/**
4 + * Sources that are registered (visible on /sources with their compliance status) but have no
5 + * ingesting connector yet — either because license review is pending (CLAUDE.md §10.4, §142) or
6 + * because credentials are required. Nothing here ingests data.
7 + */
8 +export const PLANNED_MANIFESTS: ConnectorManifest[] = [
9 + defineManifest({
10 + id: 'iarc-globocan',
11 + name: 'IARC Global Cancer Observatory (GLOBOCAN)',
12 + organization: 'International Agency for Research on Cancer (WHO)',
13 + category: 'epidemiology',
14 + tier: 0,
15 + description: 'Global estimates of cancer incidence, mortality and prevalence by country, sex, age and cancer site. The intended global epidemiology layer of CancerIndex.',
16 + homepage: 'https://gco.iarc.who.int/',
17 + docsUrl: 'https://gco.iarc.who.int/today/en/about',
18 + termsUrl: 'https://gco.iarc.who.int/en/terms-of-use',
19 + access: { type: 'api', auth: 'none', baseUrl: 'https://gco.iarc.who.int/' },
20 + license: 'IARC/WHO terms of use — redistribution rights under review',
21 + licenseStatus: 'review',
22 + commercialUse: 'unknown',
23 + redistribution: 'unknown',
24 + attribution: 'Ferlay J, Ervik M, Lam F, et al. Global Cancer Observatory: Cancer Today. Lyon, France: IARC.',
25 + termsNotes:
26 + 'Terms page is a JavaScript application; automated review on 2026-09-08 could not extract the text. Per CLAUDE.md §10.4 no automated bulk ingestion or republication of GLOBOCAN estimates until a human has reviewed the terms and, if required, obtained permission from IARC. Country/global rankings therefore remain unavailable and are shown as "awaiting license review".',
27 + updateFrequency: 'Major releases every ~2 years (GLOBOCAN 2022 current)',
28 + supportsIncrementalSync: false,
29 + entities: ['epidemiology_observations'],
30 + metrics: ['incidence_count', 'mortality_count', 'as_incidence_rate', 'as_mortality_rate', 'prevalence_5y'],
31 + rateLimits: { requestsPerSecond: 1, maxConcurrency: 1 },
32 + documentationVerifiedAt: '2026-09-08',
33 + status: 'review',
34 + }),
35 + defineManifest({
36 + id: 'seer',
37 + name: 'SEER (Surveillance, Epidemiology, and End Results)',
38 + organization: 'National Cancer Institute',
39 + category: 'epidemiology',
40 + tier: 0,
41 + description: 'US population-based registry statistics: incidence, mortality, survival, stage, age, sex, race/ethnicity. Authoritative for US relative survival.',
42 + homepage: 'https://seer.cancer.gov/',
43 + docsUrl: 'https://api.seer.cancer.gov/',
44 + termsUrl: 'https://seer.cancer.gov/about/terms.html',
45 + access: { type: 'api', auth: 'api_key', baseUrl: 'https://api.seer.cancer.gov/rest/' },
46 + license: 'US Government work (public domain); SEER*Explorer statistics citable with attribution; API requires a free key',
47 + licenseStatus: 'review',
48 + commercialUse: 'unknown',
49 + redistribution: 'attribution',
50 + attribution: 'Surveillance, Epidemiology, and End Results (SEER) Program (www.seer.cancer.gov), National Cancer Institute.',
51 + termsNotes: 'Connector is implemented against the SEER API only once a key is present (SEER_API_KEY). Status awaiting_credentials until then. Never present SEER (US) estimates as global.',
52 + updateFrequency: 'Annual (April)',
53 + supportsIncrementalSync: false,
54 + entities: ['epidemiology_observations', 'survival_observations'],
55 + metrics: ['incidence_count', 'as_incidence_rate', 'mortality_count', 'as_mortality_rate', 'five_year_survival'],
56 + rateLimits: { requestsPerSecond: 2, maxConcurrency: 1 },
57 + documentationVerifiedAt: '2026-09-08',
58 + status: 'awaiting_credentials',
59 + }),
60 +];
added packages/connectors/src/registry.ts +13 −0
@@ -0,0 +1,13 @@
1 +import type { Connector } from './sdk/run.js';
2 +import { connector as oncotree } from './connectors/oncotree/index.js';
3 +
4 +/**
5 + * Connector registry. Order = recommended first-run order (CLAUDE.md §351): terminology first,
6 + * then genes, then genomics/trials/literature/variants/evidence, then epidemiology.
7 + * Add new connectors here; `pnpm cix sources:sync` seeds their manifests into `sources`.
8 + */
9 +export const CONNECTORS: Connector[] = [oncotree];
10 +
11 +export function getConnector(id: string): Connector | undefined {
12 + return CONNECTORS.find((c) => c.manifest.id === id);
13 +}
added packages/connectors/src/sdk/http.ts +162 −0
@@ -0,0 +1,162 @@
1 +import { setTimeout as sleep } from 'node:timers/promises';
2 +import type { ConnectorManifest } from './manifest.js';
3 +
4 +export interface HttpStats {
5 + requests: number;
6 + failures: number;
7 + rateLimitEvents: number;
8 + retries: number;
9 +}
10 +
11 +export class HttpError extends Error {
12 + constructor(
13 + public readonly status: number,
14 + public readonly url: string,
15 + public readonly bodySnippet: string,
16 + ) {
17 + super(`HTTP ${status} for ${url}: ${bodySnippet.slice(0, 200)}`);
18 + }
19 +}
20 +
21 +/** Token bucket rate limiter (CLAUDE.md §229). */
22 +class TokenBucket {
23 + private tokens: number;
24 + private last = Date.now();
25 + constructor(
26 + private readonly ratePerSec: number,
27 + private readonly burst: number,
28 + ) {
29 + this.tokens = burst;
30 + }
31 + async take(): Promise<void> {
32 + for (;;) {
33 + const now = Date.now();
34 + this.tokens = Math.min(this.burst, this.tokens + ((now - this.last) / 1000) * this.ratePerSec);
35 + this.last = now;
36 + if (this.tokens >= 1) {
37 + this.tokens -= 1;
38 + return;
39 + }
40 + await sleep(Math.ceil(((1 - this.tokens) / this.ratePerSec) * 1000));
41 + }
42 + }
43 +}
44 +
45 +class Semaphore {
46 + private queue: Array<() => void> = [];
47 + private active = 0;
48 + constructor(private readonly max: number) {}
49 + async acquire(): Promise<() => void> {
50 + if (this.active >= this.max) await new Promise<void>((resolve) => this.queue.push(resolve));
51 + this.active++;
52 + let released = false;
53 + return () => {
54 + if (released) return;
55 + released = true;
56 + this.active--;
57 + const next = this.queue.shift();
58 + if (next) next();
59 + };
60 + }
61 +}
62 +
63 +export interface HttpClientOptions {
64 + userAgent?: string;
65 + timeoutMs?: number;
66 + headers?: Record<string, string>;
67 +}
68 +
69 +/**
70 + * Source-aware HTTP client: token bucket, bounded concurrency, exponential backoff with jitter,
71 + * Retry-After respect, request accounting for the ingest run.
72 + */
73 +export class HttpClient {
74 + readonly stats: HttpStats = { requests: 0, failures: 0, rateLimitEvents: 0, retries: 0 };
75 + private readonly bucket: TokenBucket | null;
76 + private readonly sem: Semaphore;
77 + private readonly retry: NonNullable<ConnectorManifest['retryPolicy']>;
78 + private readonly ua: string;
79 + private readonly timeoutMs: number;
80 + private readonly headers: Record<string, string>;
81 +
82 + constructor(manifest: ConnectorManifest, opts: HttpClientOptions = {}) {
83 + const rps = manifest.rateLimits.requestsPerSecond ?? (manifest.rateLimits.requestsPerMinute ? manifest.rateLimits.requestsPerMinute / 60 : undefined);
84 + this.bucket = rps ? new TokenBucket(rps, Math.max(1, Math.ceil(rps))) : null;
85 + this.sem = new Semaphore(manifest.rateLimits.maxConcurrency);
86 + this.retry = manifest.retryPolicy;
87 + // Plain product token only: some WAFs (e.g. oncotree.mskcc.org, Akamai) reject UAs containing URLs/parentheses.
88 + this.ua = opts.userAgent ?? `CancerIndex/0.1`;
89 + this.timeoutMs = opts.timeoutMs ?? 60_000;
90 + this.headers = opts.headers ?? {};
91 + }
92 +
93 + /** One attempt: returns the Response (any status) or throws on network error. */
94 + private async attempt(url: string, init: RequestInit): Promise<Response> {
95 + const release = await this.sem.acquire();
96 + try {
97 + if (this.bucket) await this.bucket.take();
98 + this.stats.requests++;
99 + const controller = new AbortController();
100 + const timer = setTimeout(() => controller.abort(), this.timeoutMs);
101 + try {
102 + return await fetch(url, {
103 + ...init,
104 + headers: { 'user-agent': this.ua, accept: 'application/json, text/plain, */*', ...this.headers, ...(init.headers as Record<string, string> | undefined) },
105 + signal: controller.signal,
106 + });
107 + } finally {
108 + clearTimeout(timer);
109 + }
110 + } finally {
111 + release();
112 + }
113 + }
114 +
115 + async request(url: string, init: RequestInit = {}): Promise<Response> {
116 + for (let attempt = 0; ; attempt++) {
117 + let res: Response;
118 + try {
119 + res = await this.attempt(url, init);
120 + } catch (err) {
121 + if (attempt >= this.retry.maxRetries) {
122 + this.stats.failures++;
123 + throw err;
124 + }
125 + this.stats.retries++;
126 + await sleep(this.backoff(attempt));
127 + continue;
128 + }
129 + if (res.ok) return res;
130 + const body = await res.text().catch(() => '');
131 + if (res.status === 429) this.stats.rateLimitEvents++;
132 + const retryable = res.status === 429 || res.status >= 500 || res.status === 408;
133 + if (!retryable || attempt >= this.retry.maxRetries) {
134 + this.stats.failures++;
135 + throw new HttpError(res.status, url, body);
136 + }
137 + const retryAfter = Number(res.headers.get('retry-after'));
138 + const delay = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter * 1000, 120_000) : this.backoff(attempt);
139 + this.stats.retries++;
140 + await sleep(delay);
141 + }
142 + }
143 +
144 + async json<T = unknown>(url: string, init?: RequestInit): Promise<T> {
145 + const res = await this.request(url, init);
146 + return (await res.json()) as T;
147 + }
148 +
149 + async text(url: string, init?: RequestInit): Promise<string> {
150 + const res = await this.request(url, init);
151 + return await res.text();
152 + }
153 +
154 + async postJson<T = unknown>(url: string, body: unknown, init: RequestInit = {}): Promise<T> {
155 + return this.json<T>(url, { ...init, method: 'POST', headers: { 'content-type': 'application/json', ...(init.headers as Record<string, string> | undefined) }, body: JSON.stringify(body) });
156 + }
157 +
158 + private backoff(attempt: number): number {
159 + const base = Math.min(this.retry.maxDelayMs, this.retry.baseDelayMs * 2 ** attempt);
160 + return Math.round(base / 2 + Math.random() * (base / 2));
161 + }
162 +}
added packages/connectors/src/sdk/index.ts +5 −0
@@ -0,0 +1,5 @@
1 +export * from './manifest.js';
2 +export * from './http.js';
3 +export * from './lake.js';
4 +export * from './run.js';
5 +export * from './validate.js';
added packages/connectors/src/sdk/lake.ts +105 −0
@@ -0,0 +1,105 @@
1 +import { createWriteStream, existsSync, mkdirSync, createReadStream } from 'node:fs';
2 +import { mkdir, stat } from 'node:fs/promises';
3 +import path from 'node:path';
4 +import { createGzip, createGunzip } from 'node:zlib';
5 +import { pipeline } from 'node:stream/promises';
6 +import { createInterface } from 'node:readline';
7 +import { dataDir } from '@cancerindex/shared';
8 +
9 +/**
10 + * Raw data lake (CLAUDE.md §26): every source payload retained when licensing allows.
11 + * Layout: {CI_DATA_DIR}/raw/{source}/{YYYY-MM-DD}/{entity}/{runId}-{part}.jsonl.gz
12 + * A record's `rawPath` is "<file>#<line>" so any canonical value can be traced to its raw payload.
13 + */
14 +export class RawLake {
15 + private readonly root: string;
16 + private writers = new Map<string, { path: string; stream: ReturnType<typeof createWriteStream>; gz: ReturnType<typeof createGzip>; lines: number; part: number; bytes: number }>();
17 + constructor(
18 + private readonly source: string,
19 + private readonly runId: string,
20 + private readonly date = new Date(),
21 + root?: string,
22 + ) {
23 + this.root = root ?? path.join(dataDir(), 'raw');
24 + }
25 +
26 + dir(entity: string): string {
27 + const day = this.date.toISOString().slice(0, 10);
28 + return path.join(this.root, this.source, day, entity);
29 + }
30 +
31 + /** Append one raw JSON payload; returns its rawPath reference. */
32 + async put(entity: string, payload: unknown): Promise<string> {
33 + let w = this.writers.get(entity);
34 + if (!w || w.bytes > 64 * 1024 * 1024) {
35 + if (w) await this.closeWriter(entity);
36 + const part = (w?.part ?? 0) + 1;
37 + const dir = this.dir(entity);
38 + if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
39 + const file = path.join(dir, `${this.runId}-${String(part).padStart(3, '0')}.jsonl.gz`);
40 + const stream = createWriteStream(file);
41 + const gz = createGzip({ level: 6 });
42 + gz.pipe(stream);
43 + w = { path: file, stream, gz, lines: 0, part, bytes: 0 };
44 + this.writers.set(entity, w);
45 + }
46 + const line = JSON.stringify(payload) + '\n';
47 + w.bytes += line.length;
48 + const ref = `${w.path}#${w.lines}`;
49 + w.lines++;
50 + if (!w.gz.write(line)) await new Promise<void>((r) => w!.gz.once('drain', () => r()));
51 + return ref;
52 + }
53 +
54 + private async closeWriter(entity: string): Promise<void> {
55 + const w = this.writers.get(entity);
56 + if (!w) return;
57 + await new Promise<void>((resolve, reject) => {
58 + w.stream.once('finish', () => resolve());
59 + w.stream.once('error', reject);
60 + w.gz.end();
61 + });
62 + this.writers.delete(entity);
63 + }
64 +
65 + async close(): Promise<void> {
66 + for (const entity of [...this.writers.keys()]) await this.closeWriter(entity);
67 + }
68 +
69 + /** Read a raw payload back by reference (admin TRACE, reprocessing). */
70 + static async read(ref: string): Promise<unknown | null> {
71 + const [file, lineStr] = ref.split('#');
72 + if (!file || lineStr === undefined) return null;
73 + const target = Number(lineStr);
74 + const rl = createInterface({ input: createReadStream(file).pipe(createGunzip()) });
75 + let i = 0;
76 + for await (const line of rl) {
77 + if (i === target) {
78 + rl.close();
79 + return JSON.parse(line);
80 + }
81 + i++;
82 + }
83 + return null;
84 + }
85 +}
86 +
87 +/** Download a bulk file to the lake (CLAUDE.md §228-230) with checksum + size recorded. */
88 +export async function downloadBulk(url: string, dest: string, fetchImpl: typeof fetch = fetch, headers: Record<string, string> = {}): Promise<{ path: string; bytes: number; sha256: string }> {
89 + await mkdir(path.dirname(dest), { recursive: true });
90 + const res = await fetchImpl(url, { headers: { 'user-agent': 'CancerIndex/0.1 (+https://www.cancerindex.io)', ...headers } });
91 + if (!res.ok || !res.body) throw new Error(`bulk download failed ${res.status} ${url}`);
92 + const { createHash } = await import('node:crypto');
93 + const hash = createHash('sha256');
94 + const { Transform } = await import('node:stream');
95 + const tap = new Transform({
96 + transform(chunk, _enc, cb) {
97 + hash.update(chunk);
98 + cb(null, chunk);
99 + },
100 + });
101 + const { Readable } = await import('node:stream');
102 + await pipeline(Readable.fromWeb(res.body as never), tap, createWriteStream(dest));
103 + const s = await stat(dest);
104 + return { path: dest, bytes: s.size, sha256: hash.digest('hex') };
105 +}
added packages/connectors/src/sdk/manifest.ts +61 −0
@@ -0,0 +1,61 @@
1 +import { z } from 'zod';
2 +
3 +/** Connector manifest (CLAUDE.md §9). Seeds the `sources` table; drives the public /source pages. */
4 +export const ConnectorManifest = z.object({
5 + id: z.string().regex(/^[a-z0-9-]+$/),
6 + name: z.string(),
7 + organization: z.string(),
8 + category: z.enum(['terminology', 'genes', 'genomics', 'variants', 'epidemiology', 'trials', 'literature', 'drugs', 'regulatory', 'evidence', 'pathways', 'funding', 'guidelines']),
9 + tier: z.number().int().min(0).max(11),
10 + description: z.string(),
11 + homepage: z.string().url(),
12 + docsUrl: z.string().url().optional(),
13 + termsUrl: z.string().url().optional(),
14 + access: z.object({
15 + type: z.enum(['api', 'bulk', 'rss', 'ftp', 'graphql', 'rest', 'scrape', 'manual']),
16 + auth: z.enum(['none', 'api_key', 'oauth', 'account', 'controlled']),
17 + baseUrl: z.string().url().optional(),
18 + }),
19 + license: z.string(),
20 + licenseStatus: z.enum(['review', 'approved', 'restricted', 'blocked']),
21 + commercialUse: z.enum(['allowed', 'restricted', 'prohibited', 'unknown']),
22 + redistribution: z.enum(['allowed', 'attribution', 'restricted', 'prohibited', 'unknown']),
23 + attribution: z.string().optional(),
24 + termsReviewedAt: z.string().optional(), // YYYY-MM-DD, human review date
25 + termsNotes: z.string().optional(),
26 + updateFrequency: z.string(),
27 + expectedLatency: z.string().optional(),
28 + supportsIncrementalSync: z.boolean(),
29 + entities: z.array(z.string()),
30 + metrics: z.array(z.string()).default([]),
31 + rateLimits: z.object({
32 + requestsPerSecond: z.number().positive().optional(),
33 + requestsPerMinute: z.number().positive().optional(),
34 + maxConcurrency: z.number().int().positive().default(1),
35 + notes: z.string().optional(),
36 + }),
37 + retryPolicy: z
38 + .object({
39 + maxRetries: z.number().int().min(0).default(4),
40 + baseDelayMs: z.number().int().min(0).default(500),
41 + maxDelayMs: z.number().int().min(0).default(30_000),
42 + })
43 + .default({ maxRetries: 4, baseDelayMs: 500, maxDelayMs: 30_000 }),
44 + rawRetention: z.enum(['full', 'summary', 'none']).default('full'),
45 + schemaVersion: z.string().default('1'),
46 + documentationVerifiedAt: z.string(), // YYYY-MM-DD, the date official docs were checked (CLAUDE.md §224-225)
47 + owner: z.string().default('cancerindex'),
48 + status: z.enum(['planned', 'active', 'paused', 'awaiting_credentials', 'review', 'retired']),
49 + schedule: z.string().optional(), // cron expression for the worker scheduler
50 + anomalyGuard: z
51 + .object({
52 + /** Refuse to mark records source_missing when the fetched count drops below this ratio of the previous run. */
53 + minRatioOfPrevious: z.number().min(0).max(1).default(0.5),
54 + })
55 + .default({ minRatioOfPrevious: 0.5 }),
56 +});
57 +export type ConnectorManifest = z.infer<typeof ConnectorManifest>;
58 +
59 +export function defineManifest(m: z.input<typeof ConnectorManifest>): ConnectorManifest {
60 + return ConnectorManifest.parse(m);
61 +}
added packages/connectors/src/sdk/run.ts +0 −0

Binary file not shown.

added packages/connectors/src/sdk/validate.ts +49 −0
@@ -0,0 +1,49 @@
1 +/** Scientific invariants applied to normalized records (CLAUDE.md §110, §154). */
2 +
3 +export interface ValidationIssue {
4 + field: string;
5 + message: string;
6 +}
7 +
8 +export function validateEpidemiology(o: { metric: string; value: number; year: number; lowerCi?: number | null; upperCi?: number | null; unit: string }): ValidationIssue[] {
9 + const issues: ValidationIssue[] = [];
10 + if (!Number.isFinite(o.value)) issues.push({ field: 'value', message: 'not finite' });
11 + if (o.value < 0) issues.push({ field: 'value', message: 'negative count/rate' });
12 + if (o.year < 1900 || o.year > new Date().getFullYear() + 30) issues.push({ field: 'year', message: `implausible year ${o.year}` });
13 + if (o.lowerCi != null && o.lowerCi > o.value) issues.push({ field: 'lowerCi', message: 'lowerCI > estimate' });
14 + if (o.upperCi != null && o.upperCi < o.value) issues.push({ field: 'upperCi', message: 'upperCI < estimate' });
15 + if (o.metric.endsWith('_count') && o.unit !== 'count') issues.push({ field: 'unit', message: 'count metric must use unit=count' });
16 + if (o.metric.endsWith('_rate') && o.unit !== 'per_100k') issues.push({ field: 'unit', message: 'rate metric must use unit=per_100k' });
17 + return issues;
18 +}
19 +
20 +export function validateSurvival(o: { probability?: number | null; durationMonths: number; lowerCi?: number | null; upperCi?: number | null; cohortSize?: number | null }): ValidationIssue[] {
21 + const issues: ValidationIssue[] = [];
22 + if (o.probability != null && (o.probability < 0 || o.probability > 1)) issues.push({ field: 'probability', message: 'survival must be within [0,1]' });
23 + if (o.durationMonths <= 0) issues.push({ field: 'durationMonths', message: 'duration must be positive' });
24 + if (o.probability != null && o.lowerCi != null && o.lowerCi > o.probability) issues.push({ field: 'lowerCi', message: 'lowerCI > estimate' });
25 + if (o.probability != null && o.upperCi != null && o.upperCi < o.probability) issues.push({ field: 'upperCi', message: 'upperCI < estimate' });
26 + if (o.cohortSize != null && o.cohortSize < 0) issues.push({ field: 'cohortSize', message: 'negative cohort' });
27 + return issues;
28 +}
29 +
30 +export const TRIAL_PHASES = new Set(['EARLY_PHASE1', 'PHASE1', 'PHASE2', 'PHASE3', 'PHASE4', 'NA']);
31 +export const TRIAL_STATUSES = new Set(['ACTIVE_NOT_RECRUITING', 'COMPLETED', 'ENROLLING_BY_INVITATION', 'NOT_YET_RECRUITING', 'RECRUITING', 'SUSPENDED', 'TERMINATED', 'WITHDRAWN', 'AVAILABLE', 'NO_LONGER_AVAILABLE', 'TEMPORARILY_NOT_AVAILABLE', 'APPROVED_FOR_MARKETING', 'WITHHELD', 'UNKNOWN']);
32 +
33 +export function validateTrial(o: { nctId: string; phases: string[]; overallStatus?: string | null }): ValidationIssue[] {
34 + const issues: ValidationIssue[] = [];
35 + if (!/^NCT\d{8}$/.test(o.nctId)) issues.push({ field: 'nctId', message: 'malformed NCT id' });
36 + for (const p of o.phases) if (!TRIAL_PHASES.has(p)) issues.push({ field: 'phases', message: `unknown phase enum ${p}` });
37 + if (o.overallStatus && !TRIAL_STATUSES.has(o.overallStatus)) issues.push({ field: 'overallStatus', message: `unknown status enum ${o.overallStatus}` });
38 + return issues;
39 +}
40 +
41 +export function validateFrequency(o: { casesAffected: number; casesProfiled: number }): ValidationIssue[] {
42 + const issues: ValidationIssue[] = [];
43 + if (o.casesProfiled <= 0) issues.push({ field: 'casesProfiled', message: 'denominator must be positive' });
44 + if (o.casesAffected < 0 || o.casesAffected > o.casesProfiled) issues.push({ field: 'casesAffected', message: 'numerator out of range' });
45 + return issues;
46 +}
47 +
48 +/** Gene symbol sanity (HGNC style). */
49 +export const GENE_SYMBOL_RE = /^[A-Z][A-Z0-9-]{0,15}(?:@|orf\d+)?$/i;
added packages/connectors/src/sources-sync.ts +51 −0
@@ -0,0 +1,51 @@
1 +import { eq } from 'drizzle-orm';
2 +import { type Database, sources, mintId } from '@cancerindex/database';
3 +import { CONNECTORS } from './registry.js';
4 +import type { ConnectorManifest } from './sdk/manifest.js';
5 +
6 +/** Sync connector manifests into the `sources` registry (CLAUDE.md §95, §142, §307). Idempotent. */
7 +export async function syncSources(db: Database, extra: ConnectorManifest[] = []): Promise<{ created: number; updated: number }> {
8 + let created = 0;
9 + let updated = 0;
10 + const manifests = [...CONNECTORS.map((c) => c.manifest), ...extra];
11 + for (const m of manifests) {
12 + const values = {
13 + slug: m.id,
14 + name: m.name,
15 + organization: m.organization,
16 + category: m.category,
17 + description: m.description,
18 + homepage: m.homepage,
19 + docsUrl: m.docsUrl ?? null,
20 + termsUrl: m.termsUrl ?? null,
21 + accessType: m.access.type,
22 + accessAuth: m.access.auth,
23 + license: m.license,
24 + licenseStatus: m.licenseStatus,
25 + commercialUse: m.commercialUse,
26 + redistribution: m.redistribution,
27 + attribution: m.attribution ?? null,
28 + licenseReviewedAt: m.termsReviewedAt ? new Date(m.termsReviewedAt) : null,
29 + approvedForProduction: m.licenseStatus === 'approved',
30 + updateFrequency: m.updateFrequency,
31 + supportsIncremental: m.supportsIncrementalSync,
32 + entities: m.entities,
33 + metrics: m.metrics,
34 + rateLimit: m.rateLimits.requestsPerSecond ? `${m.rateLimits.requestsPerSecond} req/s` : m.rateLimits.requestsPerMinute ? `${m.rateLimits.requestsPerMinute} req/min` : null,
35 + tier: m.tier,
36 + manifest: m as unknown as Record<string, unknown>,
37 + updatedAt: new Date(),
38 + };
39 + const [existing] = await db.select({ id: sources.id, status: sources.status }).from(sources).where(eq(sources.slug, m.id)).limit(1);
40 + if (existing) {
41 + // Keep runtime status (active/degraded) unless manifest declares a gating status.
42 + const gating = ['awaiting_credentials', 'review', 'retired', 'paused'].includes(m.status);
43 + await db.update(sources).set({ ...values, ...(gating ? { status: m.status } : {}) }).where(eq(sources.id, existing.id));
44 + updated++;
45 + } else {
46 + await db.insert(sources).values({ id: await mintId(db, 'SOURCE'), ...values, status: m.status });
47 + created++;
48 + }
49 + }
50 + return { created, updated };
51 +}
added packages/connectors/tsconfig.json +8 −0
@@ -0,0 +1,8 @@
1 +{
2 + "extends": "../../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": ".",
5 + "noEmit": true
6 + },
7 + "include": ["src", "test"]
8 +}
added packages/connectors/vitest.config.ts +8 −0
@@ -0,0 +1,8 @@
1 +import { defineConfig } from 'vitest/config';
2 +
3 +export default defineConfig({
4 + test: {
5 + include: ['src/**/*.test.ts', 'test/**/*.test.ts'],
6 + testTimeout: 20_000,
7 + },
8 +});
added packages/database/drizzle.config.ts +11 −0
@@ -0,0 +1,11 @@
1 +import { defineConfig } from 'drizzle-kit';
2 +
3 +export default defineConfig({
4 + dialect: 'postgresql',
5 + schema: './src/schema/index.ts',
6 + out: './migrations',
7 + dbCredentials: { url: process.env.DATABASE_URL ?? 'postgres://localhost:5432/cancerindex' },
8 + casing: 'snake_case',
9 + strict: true,
10 + verbose: true,
11 +});
added packages/database/migrations/0000_small_blazing_skull.sql +936 −0
@@ -0,0 +1,936 @@
1 +CREATE TABLE "audit_log" (
2 + "id" bigserial PRIMARY KEY NOT NULL,
3 + "actor" text NOT NULL,
4 + "action" text NOT NULL,
5 + "entity_type" text,
6 + "entity_id" text,
7 + "before" jsonb,
8 + "after" jsonb,
9 + "reason" text,
10 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
11 +);
12 +--> statement-breakpoint
13 +CREATE TABLE "change_events" (
14 + "id" bigserial PRIMARY KEY NOT NULL,
15 + "entity_type" text NOT NULL,
16 + "entity_id" text NOT NULL,
17 + "kind" text NOT NULL,
18 + "summary" text NOT NULL,
19 + "before" jsonb,
20 + "after" jsonb,
21 + "ingest_run_id" text,
22 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
23 +);
24 +--> statement-breakpoint
25 +CREATE TABLE "connector_cursors" (
26 + "connector_id" text PRIMARY KEY NOT NULL,
27 + "cursor" jsonb DEFAULT '{}'::jsonb NOT NULL,
28 + "last_success_at" timestamp with time zone,
29 + "last_attempt_at" timestamp with time zone,
30 + "paused" boolean DEFAULT false NOT NULL,
31 + "health" text DEFAULT 'unknown' NOT NULL,
32 + "health_detail" text,
33 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
34 +);
35 +--> statement-breakpoint
36 +CREATE TABLE "connector_field_stats" (
37 + "id" bigserial PRIMARY KEY NOT NULL,
38 + "connector_id" text NOT NULL,
39 + "entity" text NOT NULL,
40 + "field" text NOT NULL,
41 + "types" text[] DEFAULT '{}' NOT NULL,
42 + "seen_count" integer DEFAULT 0 NOT NULL,
43 + "null_count" integer DEFAULT 0 NOT NULL,
44 + "first_seen_run" text,
45 + "last_seen_run" text,
46 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
47 +);
48 +--> statement-breakpoint
49 +CREATE TABLE "entity_merges" (
50 + "id" bigserial PRIMARY KEY NOT NULL,
51 + "entity_type" text NOT NULL,
52 + "keep_id" text NOT NULL,
53 + "merge_id" text NOT NULL,
54 + "evidence" jsonb DEFAULT '{}'::jsonb NOT NULL,
55 + "status" text DEFAULT 'proposed' NOT NULL,
56 + "decided_by" text,
57 + "decided_at" timestamp with time zone,
58 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
59 +);
60 +--> statement-breakpoint
61 +CREATE TABLE "id_sequences" (
62 + "namespace" varchar(16) PRIMARY KEY NOT NULL,
63 + "next" bigint DEFAULT 1 NOT NULL
64 +);
65 +--> statement-breakpoint
66 +CREATE TABLE "ingest_runs" (
67 + "id" text PRIMARY KEY NOT NULL,
68 + "connector_id" text NOT NULL,
69 + "source_id" varchar(32) NOT NULL,
70 + "mode" text DEFAULT 'incremental' NOT NULL,
71 + "status" text DEFAULT 'running' NOT NULL,
72 + "started_at" timestamp with time zone DEFAULT now() NOT NULL,
73 + "finished_at" timestamp with time zone,
74 + "duration_ms" integer,
75 + "records_fetched" integer DEFAULT 0 NOT NULL,
76 + "records_created" integer DEFAULT 0 NOT NULL,
77 + "records_updated" integer DEFAULT 0 NOT NULL,
78 + "records_unchanged" integer DEFAULT 0 NOT NULL,
79 + "records_rejected" integer DEFAULT 0 NOT NULL,
80 + "http_requests" integer DEFAULT 0 NOT NULL,
81 + "http_failures" integer DEFAULT 0 NOT NULL,
82 + "rate_limit_events" integer DEFAULT 0 NOT NULL,
83 + "validation_failures" integer DEFAULT 0 NOT NULL,
84 + "schema_drift" jsonb DEFAULT '[]'::jsonb NOT NULL,
85 + "cursor_before" jsonb,
86 + "cursor_after" jsonb,
87 + "error" text,
88 + "log" jsonb DEFAULT '[]'::jsonb NOT NULL,
89 + "dataset_version" text,
90 + "anomaly" text
91 +);
92 +--> statement-breakpoint
93 +CREATE TABLE "provenance" (
94 + "id" bigserial PRIMARY KEY NOT NULL,
95 + "public_id" varchar(32),
96 + "source_id" varchar(32) NOT NULL,
97 + "source_record_id" text,
98 + "source_url" text,
99 + "dataset" text,
100 + "dataset_version" text,
101 + "publication_id" varchar(32),
102 + "pmid" text,
103 + "doi" text,
104 + "retrieved_at" timestamp with time zone NOT NULL,
105 + "published_at" text,
106 + "updated_at_source" text,
107 + "geography" text,
108 + "population" text,
109 + "cohort_size" integer,
110 + "methodology" text,
111 + "evidence_type" text NOT NULL,
112 + "access_level" text DEFAULT 'open' NOT NULL,
113 + "confidence" real,
114 + "license" text,
115 + "ingest_run_id" text,
116 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
117 +);
118 +--> statement-breakpoint
119 +CREATE TABLE "source_records" (
120 + "id" bigserial PRIMARY KEY NOT NULL,
121 + "source_id" varchar(32) NOT NULL,
122 + "entity_kind" text NOT NULL,
123 + "source_record_id" text NOT NULL,
124 + "payload_hash" text NOT NULL,
125 + "raw_path" text,
126 + "status" text DEFAULT 'active' NOT NULL,
127 + "first_seen_run" text,
128 + "last_seen_run" text,
129 + "retrieved_at" timestamp with time zone DEFAULT now() NOT NULL,
130 + "source_updated_at" timestamp with time zone,
131 + "canonical_type" text,
132 + "canonical_id" text,
133 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
134 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
135 +);
136 +--> statement-breakpoint
137 +CREATE TABLE "sources" (
138 + "id" varchar(32) PRIMARY KEY NOT NULL,
139 + "slug" text NOT NULL,
140 + "name" text NOT NULL,
141 + "organization" text,
142 + "category" text NOT NULL,
143 + "description" text,
144 + "homepage" text,
145 + "docs_url" text,
146 + "terms_url" text,
147 + "access_type" text NOT NULL,
148 + "access_auth" text NOT NULL,
149 + "license" text,
150 + "license_status" text DEFAULT 'review' NOT NULL,
151 + "commercial_use" text DEFAULT 'unknown' NOT NULL,
152 + "redistribution" text DEFAULT 'unknown' NOT NULL,
153 + "attribution" text,
154 + "license_reviewed_at" timestamp with time zone,
155 + "approved_for_production" boolean DEFAULT false NOT NULL,
156 + "update_frequency" text,
157 + "supports_incremental" boolean DEFAULT false NOT NULL,
158 + "entities" text[] DEFAULT '{}' NOT NULL,
159 + "metrics" text[] DEFAULT '{}' NOT NULL,
160 + "rate_limit" text,
161 + "status" text DEFAULT 'planned' NOT NULL,
162 + "tier" integer DEFAULT 0 NOT NULL,
163 + "manifest" jsonb DEFAULT '{}'::jsonb NOT NULL,
164 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
165 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
166 +);
167 +--> statement-breakpoint
168 +CREATE TABLE "unresolved_labels" (
169 + "id" bigserial PRIMARY KEY NOT NULL,
170 + "source_id" varchar(32) NOT NULL,
171 + "entity_kind" text NOT NULL,
172 + "source_text" text NOT NULL,
173 + "normalized" text NOT NULL,
174 + "context" jsonb DEFAULT '{}'::jsonb NOT NULL,
175 + "count" integer DEFAULT 1 NOT NULL,
176 + "status" text DEFAULT 'open' NOT NULL,
177 + "suggested_id" text,
178 + "suggested_match_type" text,
179 + "suggested_score" real,
180 + "resolved_id" text,
181 + "resolved_by" text,
182 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
183 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
184 +);
185 +--> statement-breakpoint
186 +CREATE TABLE "anatomical_sites" (
187 + "id" varchar(32) PRIMARY KEY NOT NULL,
188 + "name" text NOT NULL,
189 + "slug" text NOT NULL,
190 + "ncit_code" text,
191 + "uberon_id" text,
192 + "parent_id" varchar(32),
193 + "system" text
194 +);
195 +--> statement-breakpoint
196 +CREATE TABLE "cancer_aliases" (
197 + "id" bigserial PRIMARY KEY NOT NULL,
198 + "cancer_id" varchar(32) NOT NULL,
199 + "alias" text NOT NULL,
200 + "normalized" text NOT NULL,
201 + "alias_type" text DEFAULT 'synonym' NOT NULL,
202 + "source_id" varchar(32),
203 + "source_terminology" text,
204 + "language" text DEFAULT 'en' NOT NULL
205 +);
206 +--> statement-breakpoint
207 +CREATE TABLE "cancer_anatomy" (
208 + "id" bigserial PRIMARY KEY NOT NULL,
209 + "cancer_id" varchar(32) NOT NULL,
210 + "site_id" varchar(32) NOT NULL,
211 + "relation" text DEFAULT 'primary' NOT NULL,
212 + "source_id" varchar(32)
213 +);
214 +--> statement-breakpoint
215 +CREATE TABLE "cancer_codes" (
216 + "id" bigserial PRIMARY KEY NOT NULL,
217 + "cancer_id" varchar(32) NOT NULL,
218 + "system" text NOT NULL,
219 + "code" text NOT NULL,
220 + "match_type" text DEFAULT 'EXACT_IDENTIFIER' NOT NULL,
221 + "source_id" varchar(32),
222 + "valid_from" text,
223 + "valid_to" text
224 +);
225 +--> statement-breakpoint
226 +CREATE TABLE "cancer_hierarchy" (
227 + "id" bigserial PRIMARY KEY NOT NULL,
228 + "parent_id" varchar(32) NOT NULL,
229 + "child_id" varchar(32) NOT NULL,
230 + "hierarchy_type" text NOT NULL,
231 + "source_id" varchar(32)
232 +);
233 +--> statement-breakpoint
234 +CREATE TABLE "cancers" (
235 + "id" varchar(32) PRIMARY KEY NOT NULL,
236 + "slug" text NOT NULL,
237 + "canonical_name" text NOT NULL,
238 + "short_name" text,
239 + "entity_type" text DEFAULT 'cancer' NOT NULL,
240 + "malignant" boolean DEFAULT true NOT NULL,
241 + "solid_tumor" boolean DEFAULT true NOT NULL,
242 + "hematologic" boolean DEFAULT false NOT NULL,
243 + "pediatric_relevant" boolean DEFAULT false NOT NULL,
244 + "rare_cancer" boolean,
245 + "top_level" boolean DEFAULT false NOT NULL,
246 + "description" text,
247 + "description_provenance_id" integer,
248 + "primary_ncit_code" text,
249 + "primary_oncotree_code" text,
250 + "depth" integer DEFAULT 0 NOT NULL,
251 + "status" text DEFAULT 'active' NOT NULL,
252 + "merged_into" varchar(32),
253 + "deprecated_reason" text,
254 + "classification_version" text,
255 + "semantic_types" text[] DEFAULT '{}' NOT NULL,
256 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
257 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
258 +);
259 +--> statement-breakpoint
260 +CREATE TABLE "cohort_definitions" (
261 + "id" bigserial PRIMARY KEY NOT NULL,
262 + "name" text NOT NULL,
263 + "cancer_id" varchar(32) NOT NULL,
264 + "biomarker_ids" text[] DEFAULT '{}' NOT NULL,
265 + "variant_ids" text[] DEFAULT '{}' NOT NULL,
266 + "stage" text,
267 + "attributes" jsonb DEFAULT '{}'::jsonb NOT NULL,
268 + "confidence" real,
269 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
270 +);
271 +--> statement-breakpoint
272 +CREATE TABLE "geographies" (
273 + "id" varchar(32) PRIMARY KEY NOT NULL,
274 + "slug" text NOT NULL,
275 + "name" text NOT NULL,
276 + "kind" text NOT NULL,
277 + "iso2" text,
278 + "iso3" text,
279 + "parent_id" varchar(32),
280 + "who_region" text,
281 + "population" integer,
282 + "population_year" integer
283 +);
284 +--> statement-breakpoint
285 +CREATE TABLE "biomarkers" (
286 + "id" varchar(32) PRIMARY KEY NOT NULL,
287 + "slug" text NOT NULL,
288 + "name" text NOT NULL,
289 + "kind" text NOT NULL,
290 + "gene_id" varchar(32),
291 + "ncit_code" text,
292 + "description" text,
293 + "measurement" jsonb DEFAULT '{}'::jsonb NOT NULL,
294 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
295 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
296 +);
297 +--> statement-breakpoint
298 +CREATE TABLE "cancer_gene_frequencies" (
299 + "id" bigserial PRIMARY KEY NOT NULL,
300 + "cohort_id" varchar(32) NOT NULL,
301 + "cancer_id" varchar(32),
302 + "gene_id" varchar(32),
303 + "gene_symbol" text NOT NULL,
304 + "alteration_type" text DEFAULT 'ssm' NOT NULL,
305 + "cases_affected" integer NOT NULL,
306 + "cases_profiled" integer NOT NULL,
307 + "frequency" real NOT NULL,
308 + "rank" integer,
309 + "data_release" text,
310 + "provenance_id" integer NOT NULL,
311 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
312 +);
313 +--> statement-breakpoint
314 +CREATE TABLE "entity_embeddings" (
315 + "id" bigserial PRIMARY KEY NOT NULL,
316 + "entity_type" text NOT NULL,
317 + "entity_id" text NOT NULL,
318 + "model" text NOT NULL,
319 + "dimensions" integer NOT NULL,
320 + "text_hash" text NOT NULL,
321 + "embedding" text,
322 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
323 +);
324 +--> statement-breakpoint
325 +CREATE TABLE "gene_aliases" (
326 + "id" bigserial PRIMARY KEY NOT NULL,
327 + "gene_id" varchar(32) NOT NULL,
328 + "alias" text NOT NULL,
329 + "alias_type" text NOT NULL,
330 + "source_id" varchar(32)
331 +);
332 +--> statement-breakpoint
333 +CREATE TABLE "genes" (
334 + "id" varchar(32) PRIMARY KEY NOT NULL,
335 + "hgnc_id" text,
336 + "symbol" text NOT NULL,
337 + "name" text,
338 + "locus_type" text,
339 + "locus_group" text,
340 + "location" text,
341 + "chromosome" text,
342 + "ensembl_gene_id" text,
343 + "ncbi_gene_id" text,
344 + "omim_ids" text[] DEFAULT '{}' NOT NULL,
345 + "uniprot_ids" text[] DEFAULT '{}' NOT NULL,
346 + "refseq_accession" text,
347 + "prev_symbols" text[] DEFAULT '{}' NOT NULL,
348 + "alias_symbols" text[] DEFAULT '{}' NOT NULL,
349 + "gene_families" text[] DEFAULT '{}' NOT NULL,
350 + "status" text DEFAULT 'Approved' NOT NULL,
351 + "is_cancer_gene" boolean DEFAULT false NOT NULL,
352 + "civic_gene_id" integer,
353 + "description" text,
354 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
355 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
356 +);
357 +--> statement-breakpoint
358 +CREATE TABLE "genomic_cohorts" (
359 + "id" varchar(32) PRIMARY KEY NOT NULL,
360 + "source_id" varchar(32) NOT NULL,
361 + "study_id" text NOT NULL,
362 + "name" text NOT NULL,
363 + "program" text,
364 + "primary_sites" text[] DEFAULT '{}' NOT NULL,
365 + "disease_types" text[] DEFAULT '{}' NOT NULL,
366 + "cancer_id" varchar(32),
367 + "cancer_match_type" text,
368 + "case_count" integer,
369 + "cases_with_ssm" integer,
370 + "data_release" text,
371 + "access_level" text DEFAULT 'open' NOT NULL,
372 + "url" text,
373 + "provenance_id" integer,
374 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
375 +);
376 +--> statement-breakpoint
377 +CREATE TABLE "variant_aliases" (
378 + "id" bigserial PRIMARY KEY NOT NULL,
379 + "variant_id" varchar(32) NOT NULL,
380 + "alias" text NOT NULL,
381 + "source_id" varchar(32)
382 +);
383 +--> statement-breakpoint
384 +CREATE TABLE "variant_clinical_significance" (
385 + "id" bigserial PRIMARY KEY NOT NULL,
386 + "variant_id" varchar(32) NOT NULL,
387 + "clinvar_variation_id" text NOT NULL,
388 + "clinical_significance" text NOT NULL,
389 + "review_status" text,
390 + "star_rating" integer,
391 + "last_evaluated" text,
392 + "conditions" text[] DEFAULT '{}' NOT NULL,
393 + "condition_cancer_ids" text[] DEFAULT '{}' NOT NULL,
394 + "origin_simple" text,
395 + "number_submitters" integer,
396 + "provenance_id" integer NOT NULL,
397 + "ingest_run_id" text,
398 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
399 +);
400 +--> statement-breakpoint
401 +CREATE TABLE "variants" (
402 + "id" varchar(32) PRIMARY KEY NOT NULL,
403 + "slug" text NOT NULL,
404 + "gene_id" varchar(32),
405 + "gene_symbol" text,
406 + "name" text NOT NULL,
407 + "variant_type" text,
408 + "hgvs_g" text,
409 + "hgvs_c" text,
410 + "hgvs_p" text,
411 + "assembly" text,
412 + "chromosome" text,
413 + "start" integer,
414 + "end" integer,
415 + "reference_bases" text,
416 + "alternate_bases" text,
417 + "coordinates" jsonb DEFAULT '[]'::jsonb NOT NULL,
418 + "clinvar_variation_id" text,
419 + "civic_variant_id" integer,
420 + "dbsnp_ids" text[] DEFAULT '{}' NOT NULL,
421 + "fusion_partners" text[] DEFAULT '{}' NOT NULL,
422 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
423 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
424 +);
425 +--> statement-breakpoint
426 +CREATE TABLE "drug_aliases" (
427 + "id" bigserial PRIMARY KEY NOT NULL,
428 + "drug_id" varchar(32) NOT NULL,
429 + "alias" text NOT NULL,
430 + "normalized" text NOT NULL,
431 + "alias_type" text DEFAULT 'synonym' NOT NULL,
432 + "source_id" varchar(32)
433 +);
434 +--> statement-breakpoint
435 +CREATE TABLE "drug_approvals" (
436 + "id" bigserial PRIMARY KEY NOT NULL,
437 + "drug_id" varchar(32) NOT NULL,
438 + "cancer_id" varchar(32),
439 + "biomarker_ids" text[] DEFAULT '{}' NOT NULL,
440 + "tumor_agnostic" boolean DEFAULT false NOT NULL,
441 + "jurisdiction" text NOT NULL,
442 + "authority" text NOT NULL,
443 + "indication" text NOT NULL,
444 + "line_of_therapy" text,
445 + "disease_stage" text,
446 + "approval_type" text,
447 + "accelerated" boolean,
448 + "conditional" boolean,
449 + "approval_date" text,
450 + "withdrawal_date" text,
451 + "status" text NOT NULL,
452 + "application_number" text,
453 + "source_id" varchar(32) NOT NULL,
454 + "provenance_id" integer NOT NULL,
455 + "raw" jsonb,
456 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
457 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
458 +);
459 +--> statement-breakpoint
460 +CREATE TABLE "drugs" (
461 + "id" varchar(32) PRIMARY KEY NOT NULL,
462 + "slug" text NOT NULL,
463 + "name" text NOT NULL,
464 + "kind" text,
465 + "ncit_code" text,
466 + "chembl_id" text,
467 + "civic_therapy_id" integer,
468 + "drugbank_id" text,
469 + "pubchem_cid" text,
470 + "unii" text,
471 + "mechanism" text,
472 + "target_gene_ids" text[] DEFAULT '{}' NOT NULL,
473 + "development_status" text,
474 + "description" text,
475 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
476 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
477 +);
478 +--> statement-breakpoint
479 +CREATE TABLE "treatment_regimens" (
480 + "id" varchar(32) PRIMARY KEY NOT NULL,
481 + "slug" text NOT NULL,
482 + "name" text NOT NULL,
483 + "component_drug_ids" text[] DEFAULT '{}' NOT NULL,
484 + "modality" text DEFAULT 'drug_combination' NOT NULL,
485 + "description" text,
486 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
487 +);
488 +--> statement-breakpoint
489 +CREATE TABLE "clinical_trials" (
490 + "id" varchar(32) PRIMARY KEY NOT NULL,
491 + "nct_id" text NOT NULL,
492 + "brief_title" text NOT NULL,
493 + "official_title" text,
494 + "acronym" text,
495 + "study_type" text,
496 + "phases" text[] DEFAULT '{}' NOT NULL,
497 + "overall_status" text,
498 + "why_stopped" text,
499 + "start_date" text,
500 + "primary_completion_date" text,
501 + "completion_date" text,
502 + "first_posted_date" text,
503 + "last_update_posted_date" text,
504 + "results_first_posted_date" text,
505 + "has_results" boolean DEFAULT false NOT NULL,
506 + "enrollment_count" integer,
507 + "enrollment_type" text,
508 + "lead_sponsor" text,
509 + "lead_sponsor_class" text,
510 + "collaborators" text[] DEFAULT '{}' NOT NULL,
511 + "conditions" text[] DEFAULT '{}' NOT NULL,
512 + "keywords" text[] DEFAULT '{}' NOT NULL,
513 + "interventions" jsonb DEFAULT '[]'::jsonb NOT NULL,
514 + "arms" jsonb DEFAULT '[]'::jsonb NOT NULL,
515 + "primary_outcomes" jsonb DEFAULT '[]'::jsonb NOT NULL,
516 + "secondary_outcomes" jsonb DEFAULT '[]'::jsonb NOT NULL,
517 + "eligibility" jsonb DEFAULT '{}'::jsonb NOT NULL,
518 + "sex" text,
519 + "minimum_age" text,
520 + "maximum_age" text,
521 + "countries" text[] DEFAULT '{}' NOT NULL,
522 + "locations_count" integer DEFAULT 0 NOT NULL,
523 + "references" jsonb DEFAULT '[]'::jsonb NOT NULL,
524 + "brief_summary" text,
525 + "is_oncology" boolean DEFAULT true NOT NULL,
526 + "source_record_id" integer,
527 + "ingest_run_id" text,
528 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
529 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
530 +);
531 +--> statement-breakpoint
532 +CREATE TABLE "trial_conditions" (
533 + "id" bigserial PRIMARY KEY NOT NULL,
534 + "trial_id" varchar(32) NOT NULL,
535 + "condition_text" text NOT NULL,
536 + "normalized" text NOT NULL,
537 + "cancer_id" varchar(32),
538 + "match_type" text DEFAULT 'UNRESOLVED' NOT NULL,
539 + "confidence" real
540 +);
541 +--> statement-breakpoint
542 +CREATE TABLE "trial_interventions" (
543 + "id" bigserial PRIMARY KEY NOT NULL,
544 + "trial_id" varchar(32) NOT NULL,
545 + "name" text NOT NULL,
546 + "normalized" text NOT NULL,
547 + "intervention_type" text,
548 + "drug_id" varchar(32),
549 + "match_type" text DEFAULT 'UNRESOLVED' NOT NULL
550 +);
551 +--> statement-breakpoint
552 +CREATE TABLE "trial_locations" (
553 + "id" bigserial PRIMARY KEY NOT NULL,
554 + "trial_id" varchar(32) NOT NULL,
555 + "facility" text,
556 + "city" text,
557 + "state" text,
558 + "zip" text,
559 + "country" text,
560 + "status" text,
561 + "lat" real,
562 + "lng" real
563 +);
564 +--> statement-breakpoint
565 +CREATE TABLE "trial_pulse" (
566 + "id" bigserial PRIMARY KEY NOT NULL,
567 + "day" date NOT NULL,
568 + "cancer_id" varchar(32),
569 + "phase" text,
570 + "new_trials" integer NOT NULL,
571 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
572 +);
573 +--> statement-breakpoint
574 +CREATE TABLE "literature_counts" (
575 + "id" bigserial PRIMARY KEY NOT NULL,
576 + "cancer_id" varchar(32) NOT NULL,
577 + "window_key" text NOT NULL,
578 + "window_start" text,
579 + "window_end" text,
580 + "query" text NOT NULL,
581 + "count" integer NOT NULL,
582 + "provenance_id" integer NOT NULL,
583 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
584 +);
585 +--> statement-breakpoint
586 +CREATE TABLE "publication_entity_edges" (
587 + "id" bigserial PRIMARY KEY NOT NULL,
588 + "publication_id" varchar(32) NOT NULL,
589 + "entity_type" text NOT NULL,
590 + "entity_id" text NOT NULL,
591 + "method" text NOT NULL,
592 + "confidence" real,
593 + "status" text DEFAULT 'candidate' NOT NULL,
594 + "source_id" varchar(32),
595 + "ingest_run_id" text,
596 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
597 +);
598 +--> statement-breakpoint
599 +CREATE TABLE "publications" (
600 + "id" varchar(32) PRIMARY KEY NOT NULL,
601 + "pmid" text,
602 + "doi" text,
603 + "pmcid" text,
604 + "title" text NOT NULL,
605 + "abstract" text,
606 + "journal" text,
607 + "journal_iso" text,
608 + "pub_date" text,
609 + "pub_year" integer,
610 + "publication_types" text[] DEFAULT '{}' NOT NULL,
611 + "mesh_terms" jsonb DEFAULT '[]'::jsonb NOT NULL,
612 + "authors" jsonb DEFAULT '[]'::jsonb NOT NULL,
613 + "language" text,
614 + "is_preprint" boolean DEFAULT false NOT NULL,
615 + "retracted" boolean DEFAULT false NOT NULL,
616 + "retraction_notice" text,
617 + "nct_ids" text[] DEFAULT '{}' NOT NULL,
618 + "cited_by_count" integer,
619 + "source_record_id" integer,
620 + "ingest_run_id" text,
621 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
622 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
623 +);
624 +--> statement-breakpoint
625 +CREATE TABLE "civic_evidence_items" (
626 + "id" bigserial PRIMARY KEY NOT NULL,
627 + "civic_id" integer NOT NULL,
628 + "name" text,
629 + "molecular_profile_id" integer,
630 + "molecular_profile_name" text,
631 + "gene_symbols" text[] DEFAULT '{}' NOT NULL,
632 + "gene_ids" text[] DEFAULT '{}' NOT NULL,
633 + "variant_ids" text[] DEFAULT '{}' NOT NULL,
634 + "civic_variant_ids" integer[] DEFAULT '{}' NOT NULL,
635 + "disease_name" text,
636 + "doid" text,
637 + "cancer_id" varchar(32),
638 + "cancer_match_type" text,
639 + "therapy_names" text[] DEFAULT '{}' NOT NULL,
640 + "therapy_ids" text[] DEFAULT '{}' NOT NULL,
641 + "therapy_interaction_type" text,
642 + "evidence_type" text,
643 + "evidence_level" text,
644 + "evidence_direction" text,
645 + "significance" text,
646 + "evidence_rating" integer,
647 + "status" text,
648 + "description" text,
649 + "pmid" text,
650 + "source_citation" text,
651 + "phenotypes" text[] DEFAULT '{}' NOT NULL,
652 + "provenance_id" integer NOT NULL,
653 + "ingest_run_id" text,
654 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
655 +);
656 +--> statement-breakpoint
657 +CREATE TABLE "knowledge_edges" (
658 + "id" bigserial PRIMARY KEY NOT NULL,
659 + "source_entity_type" text NOT NULL,
660 + "source_entity_id" text NOT NULL,
661 + "target_entity_type" text NOT NULL,
662 + "target_entity_id" text NOT NULL,
663 + "relationship_type" text NOT NULL,
664 + "cancer_context_ids" text[] DEFAULT '{}' NOT NULL,
665 + "predictive" boolean,
666 + "prognostic" boolean,
667 + "diagnostic" boolean,
668 + "predisposing" boolean,
669 + "direction" text,
670 + "evidence_level" text,
671 + "evidence_score" real,
672 + "evidence_category" text DEFAULT 'curated_evidence' NOT NULL,
673 + "status" text DEFAULT 'active' NOT NULL,
674 + "source_id" varchar(32) NOT NULL,
675 + "source_record_id" text,
676 + "provenance_ids" integer[] DEFAULT '{}' NOT NULL,
677 + "support_count" integer DEFAULT 1 NOT NULL,
678 + "first_seen_at" timestamp with time zone DEFAULT now() NOT NULL,
679 + "last_seen_at" timestamp with time zone DEFAULT now() NOT NULL
680 +);
681 +--> statement-breakpoint
682 +CREATE TABLE "risk_factors" (
683 + "id" bigserial PRIMARY KEY NOT NULL,
684 + "slug" text NOT NULL,
685 + "name" text NOT NULL,
686 + "kind" text NOT NULL,
687 + "classification_authority" text,
688 + "classification" text,
689 + "description" text,
690 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
691 +);
692 +--> statement-breakpoint
693 +CREATE TABLE "epidemiology_observations" (
694 + "id" bigserial PRIMARY KEY NOT NULL,
695 + "cancer_id" varchar(32) NOT NULL,
696 + "geography_id" varchar(32) NOT NULL,
697 + "year" integer NOT NULL,
698 + "year_end" integer,
699 + "sex" text DEFAULT 'all' NOT NULL,
700 + "age_group" text DEFAULT 'all' NOT NULL,
701 + "metric" text NOT NULL,
702 + "value" double precision NOT NULL,
703 + "unit" text NOT NULL,
704 + "lower_ci" double precision,
705 + "upper_ci" double precision,
706 + "standard_population" text,
707 + "estimate_type" text DEFAULT 'observed' NOT NULL,
708 + "site_definition" text,
709 + "source_id" varchar(32) NOT NULL,
710 + "provenance_id" integer NOT NULL,
711 + "ingest_run_id" text,
712 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
713 +);
714 +--> statement-breakpoint
715 +CREATE TABLE "survival_observations" (
716 + "id" bigserial PRIMARY KEY NOT NULL,
717 + "cancer_id" varchar(32) NOT NULL,
718 + "geography_id" varchar(32),
719 + "stage" text,
720 + "staging_system" text,
721 + "sex" text DEFAULT 'all' NOT NULL,
722 + "age_group" text DEFAULT 'all' NOT NULL,
723 + "diagnosis_period" text,
724 + "survival_type" text NOT NULL,
725 + "duration_months" integer NOT NULL,
726 + "probability" real,
727 + "median_months" real,
728 + "cohort_size" integer,
729 + "lower_ci" real,
730 + "upper_ci" real,
731 + "method" text,
732 + "source_id" varchar(32) NOT NULL,
733 + "provenance_id" integer NOT NULL,
734 + "ingest_run_id" text,
735 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
736 +);
737 +--> statement-breakpoint
738 +CREATE TABLE "ai_answers" (
739 + "id" bigserial PRIMARY KEY NOT NULL,
740 + "kind" text NOT NULL,
741 + "subject_id" text,
742 + "question_hash" text NOT NULL,
743 + "question" text,
744 + "answer" jsonb NOT NULL,
745 + "model" text NOT NULL,
746 + "prompt_version" text NOT NULL,
747 + "source_snapshot" jsonb DEFAULT '{}'::jsonb NOT NULL,
748 + "data_as_of" timestamp with time zone NOT NULL,
749 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
750 +);
751 +--> statement-breakpoint
752 +CREATE TABLE "api_keys" (
753 + "id" bigserial PRIMARY KEY NOT NULL,
754 + "key_hash" text NOT NULL,
755 + "prefix" text NOT NULL,
756 + "label" text,
757 + "owner_email" text,
758 + "tier" text DEFAULT 'free' NOT NULL,
759 + "rate_limit_per_minute" integer DEFAULT 60 NOT NULL,
760 + "active" boolean DEFAULT true NOT NULL,
761 + "last_used_at" timestamp with time zone,
762 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
763 +);
764 +--> statement-breakpoint
765 +CREATE TABLE "entity_counters" (
766 + "id" bigserial PRIMARY KEY NOT NULL,
767 + "entity_type" text NOT NULL,
768 + "entity_id" text NOT NULL,
769 + "trial_count" integer DEFAULT 0 NOT NULL,
770 + "active_trial_count" integer DEFAULT 0 NOT NULL,
771 + "recruiting_trial_count" integer DEFAULT 0 NOT NULL,
772 + "phase3_trial_count" integer DEFAULT 0 NOT NULL,
773 + "publication_count" integer DEFAULT 0 NOT NULL,
774 + "publication_count_5y" integer DEFAULT 0 NOT NULL,
775 + "publication_count_12m" integer DEFAULT 0 NOT NULL,
776 + "gene_count" integer DEFAULT 0 NOT NULL,
777 + "variant_count" integer DEFAULT 0 NOT NULL,
778 + "drug_count" integer DEFAULT 0 NOT NULL,
779 + "approved_drug_count" integer DEFAULT 0 NOT NULL,
780 + "evidence_count" integer DEFAULT 0 NOT NULL,
781 + "cohort_count" integer DEFAULT 0 NOT NULL,
782 + "subtype_count" integer DEFAULT 0 NOT NULL,
783 + "descendant_count" integer DEFAULT 0 NOT NULL,
784 + "epidemiology_obs_count" integer DEFAULT 0 NOT NULL,
785 + "survival_obs_count" integer DEFAULT 0 NOT NULL,
786 + "completeness" jsonb DEFAULT '{}'::jsonb NOT NULL,
787 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
788 +);
789 +--> statement-breakpoint
790 +CREATE TABLE "metric_definitions" (
791 + "id" varchar(32) PRIMARY KEY NOT NULL,
792 + "slug" text NOT NULL,
793 + "name" text NOT NULL,
794 + "description" text NOT NULL,
795 + "formula" text NOT NULL,
796 + "formula_version" text NOT NULL,
797 + "unit" text NOT NULL,
798 + "higher_is_worse" boolean,
799 + "aggregation" text,
800 + "valid_dimensions" text[] DEFAULT '{}' NOT NULL,
801 + "source_slugs" text[] DEFAULT '{}' NOT NULL,
802 + "category" text NOT NULL,
803 + "eligibility" jsonb DEFAULT '{}'::jsonb NOT NULL,
804 + "experimental" boolean DEFAULT false NOT NULL,
805 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
806 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
807 +);
808 +--> statement-breakpoint
809 +CREATE TABLE "ranking_snapshots" (
810 + "id" bigserial PRIMARY KEY NOT NULL,
811 + "metric_id" varchar(32) NOT NULL,
812 + "metric_slug" text NOT NULL,
813 + "scope_key" text NOT NULL,
814 + "geography" text DEFAULT 'WORLD' NOT NULL,
815 + "sex" text DEFAULT 'all' NOT NULL,
816 + "age_group" text DEFAULT 'all' NOT NULL,
817 + "year" integer,
818 + "entity_level" text DEFAULT 'top' NOT NULL,
819 + "formula_version" text NOT NULL,
820 + "eligible_entities" integer NOT NULL,
821 + "inputs_hash" text NOT NULL,
822 + "source_ids" text[] DEFAULT '{}' NOT NULL,
823 + "is_current" boolean DEFAULT true NOT NULL,
824 + "generated_at" timestamp with time zone DEFAULT now() NOT NULL
825 +);
826 +--> statement-breakpoint
827 +CREATE TABLE "rankings" (
828 + "id" bigserial PRIMARY KEY NOT NULL,
829 + "snapshot_id" integer NOT NULL,
830 + "metric_slug" text NOT NULL,
831 + "scope_key" text NOT NULL,
832 + "cancer_id" varchar(32) NOT NULL,
833 + "rank" integer NOT NULL,
834 + "eligible_entities" integer NOT NULL,
835 + "percentile" real NOT NULL,
836 + "value" double precision NOT NULL,
837 + "unit" text NOT NULL,
838 + "confidence" text DEFAULT 'MEDIUM' NOT NULL,
839 + "inputs" jsonb DEFAULT '{}'::jsonb NOT NULL,
840 + "breakdown" jsonb,
841 + "previous_rank" integer,
842 + "generated_at" timestamp with time zone DEFAULT now() NOT NULL
843 +);
844 +--> statement-breakpoint
845 +CREATE INDEX "change_events_entity_idx" ON "change_events" USING btree ("entity_type","entity_id","created_at");--> statement-breakpoint
846 +CREATE UNIQUE INDEX "connector_field_stats_uq" ON "connector_field_stats" USING btree ("connector_id","entity","field");--> statement-breakpoint
847 +CREATE INDEX "ingest_runs_connector_idx" ON "ingest_runs" USING btree ("connector_id","started_at");--> statement-breakpoint
848 +CREATE INDEX "provenance_source_idx" ON "provenance" USING btree ("source_id","source_record_id");--> statement-breakpoint
849 +CREATE INDEX "provenance_pmid_idx" ON "provenance" USING btree ("pmid");--> statement-breakpoint
850 +CREATE UNIQUE INDEX "source_records_uq" ON "source_records" USING btree ("source_id","entity_kind","source_record_id");--> statement-breakpoint
851 +CREATE INDEX "source_records_canonical_idx" ON "source_records" USING btree ("canonical_type","canonical_id");--> statement-breakpoint
852 +CREATE UNIQUE INDEX "sources_slug_uq" ON "sources" USING btree ("slug");--> statement-breakpoint
853 +CREATE UNIQUE INDEX "unresolved_labels_uq" ON "unresolved_labels" USING btree ("source_id","entity_kind","normalized");--> statement-breakpoint
854 +CREATE INDEX "unresolved_labels_count_idx" ON "unresolved_labels" USING btree ("status","count");--> statement-breakpoint
855 +CREATE UNIQUE INDEX "anatomical_sites_slug_uq" ON "anatomical_sites" USING btree ("slug");--> statement-breakpoint
856 +CREATE UNIQUE INDEX "cancer_aliases_uq" ON "cancer_aliases" USING btree ("cancer_id","normalized","alias_type");--> statement-breakpoint
857 +CREATE INDEX "cancer_aliases_norm_idx" ON "cancer_aliases" USING btree ("normalized");--> statement-breakpoint
858 +CREATE UNIQUE INDEX "cancer_anatomy_uq" ON "cancer_anatomy" USING btree ("cancer_id","site_id","relation");--> statement-breakpoint
859 +CREATE UNIQUE INDEX "cancer_codes_uq" ON "cancer_codes" USING btree ("cancer_id","system","code");--> statement-breakpoint
860 +CREATE INDEX "cancer_codes_lookup_idx" ON "cancer_codes" USING btree ("system","code");--> statement-breakpoint
861 +CREATE UNIQUE INDEX "cancer_hierarchy_uq" ON "cancer_hierarchy" USING btree ("parent_id","child_id","hierarchy_type");--> statement-breakpoint
862 +CREATE INDEX "cancer_hierarchy_child_idx" ON "cancer_hierarchy" USING btree ("child_id");--> statement-breakpoint
863 +CREATE UNIQUE INDEX "cancers_slug_uq" ON "cancers" USING btree ("slug");--> statement-breakpoint
864 +CREATE UNIQUE INDEX "cancers_ncit_uq" ON "cancers" USING btree ("primary_ncit_code");--> statement-breakpoint
865 +CREATE INDEX "cancers_name_idx" ON "cancers" USING btree ("canonical_name");--> statement-breakpoint
866 +CREATE INDEX "cancers_type_idx" ON "cancers" USING btree ("entity_type","malignant","top_level");--> statement-breakpoint
867 +CREATE UNIQUE INDEX "geographies_slug_uq" ON "geographies" USING btree ("slug");--> statement-breakpoint
868 +CREATE INDEX "geographies_iso3_idx" ON "geographies" USING btree ("iso3");--> statement-breakpoint
869 +CREATE UNIQUE INDEX "biomarkers_slug_uq" ON "biomarkers" USING btree ("slug");--> statement-breakpoint
870 +CREATE UNIQUE INDEX "cancer_gene_freq_uq" ON "cancer_gene_frequencies" USING btree ("cohort_id","gene_symbol","alteration_type");--> statement-breakpoint
871 +CREATE INDEX "cancer_gene_freq_cancer_idx" ON "cancer_gene_frequencies" USING btree ("cancer_id","frequency");--> statement-breakpoint
872 +CREATE INDEX "cancer_gene_freq_gene_idx" ON "cancer_gene_frequencies" USING btree ("gene_id");--> statement-breakpoint
873 +CREATE UNIQUE INDEX "entity_embeddings_uq" ON "entity_embeddings" USING btree ("entity_type","entity_id","model");--> statement-breakpoint
874 +CREATE UNIQUE INDEX "gene_aliases_uq" ON "gene_aliases" USING btree ("gene_id","alias","alias_type");--> statement-breakpoint
875 +CREATE INDEX "gene_aliases_alias_idx" ON "gene_aliases" USING btree ("alias");--> statement-breakpoint
876 +CREATE UNIQUE INDEX "genes_symbol_uq" ON "genes" USING btree ("symbol");--> statement-breakpoint
877 +CREATE UNIQUE INDEX "genes_hgnc_uq" ON "genes" USING btree ("hgnc_id");--> statement-breakpoint
878 +CREATE INDEX "genes_ensembl_idx" ON "genes" USING btree ("ensembl_gene_id");--> statement-breakpoint
879 +CREATE INDEX "genes_ncbi_idx" ON "genes" USING btree ("ncbi_gene_id");--> statement-breakpoint
880 +CREATE UNIQUE INDEX "genomic_cohorts_uq" ON "genomic_cohorts" USING btree ("source_id","study_id");--> statement-breakpoint
881 +CREATE INDEX "genomic_cohorts_cancer_idx" ON "genomic_cohorts" USING btree ("cancer_id");--> statement-breakpoint
882 +CREATE UNIQUE INDEX "variant_aliases_uq" ON "variant_aliases" USING btree ("variant_id","alias");--> statement-breakpoint
883 +CREATE UNIQUE INDEX "variant_clinsig_uq" ON "variant_clinical_significance" USING btree ("clinvar_variation_id");--> statement-breakpoint
884 +CREATE UNIQUE INDEX "variants_slug_uq" ON "variants" USING btree ("slug");--> statement-breakpoint
885 +CREATE INDEX "variants_gene_idx" ON "variants" USING btree ("gene_id");--> statement-breakpoint
886 +CREATE INDEX "variants_clinvar_idx" ON "variants" USING btree ("clinvar_variation_id");--> statement-breakpoint
887 +CREATE INDEX "variants_civic_idx" ON "variants" USING btree ("civic_variant_id");--> statement-breakpoint
888 +CREATE UNIQUE INDEX "drug_aliases_uq" ON "drug_aliases" USING btree ("drug_id","normalized","alias_type");--> statement-breakpoint
889 +CREATE INDEX "drug_aliases_norm_idx" ON "drug_aliases" USING btree ("normalized");--> statement-breakpoint
890 +CREATE INDEX "drug_approvals_drug_idx" ON "drug_approvals" USING btree ("drug_id");--> statement-breakpoint
891 +CREATE INDEX "drug_approvals_cancer_idx" ON "drug_approvals" USING btree ("cancer_id");--> statement-breakpoint
892 +CREATE UNIQUE INDEX "drugs_slug_uq" ON "drugs" USING btree ("slug");--> statement-breakpoint
893 +CREATE INDEX "drugs_ncit_idx" ON "drugs" USING btree ("ncit_code");--> statement-breakpoint
894 +CREATE INDEX "drugs_civic_idx" ON "drugs" USING btree ("civic_therapy_id");--> statement-breakpoint
895 +CREATE INDEX "drugs_chembl_idx" ON "drugs" USING btree ("chembl_id");--> statement-breakpoint
896 +CREATE UNIQUE INDEX "treatment_regimens_slug_uq" ON "treatment_regimens" USING btree ("slug");--> statement-breakpoint
897 +CREATE UNIQUE INDEX "clinical_trials_nct_uq" ON "clinical_trials" USING btree ("nct_id");--> statement-breakpoint
898 +CREATE INDEX "clinical_trials_status_idx" ON "clinical_trials" USING btree ("overall_status");--> statement-breakpoint
899 +CREATE INDEX "clinical_trials_updated_idx" ON "clinical_trials" USING btree ("last_update_posted_date");--> statement-breakpoint
900 +CREATE INDEX "clinical_trials_sponsor_idx" ON "clinical_trials" USING btree ("lead_sponsor");--> statement-breakpoint
901 +CREATE UNIQUE INDEX "trial_conditions_uq" ON "trial_conditions" USING btree ("trial_id","normalized");--> statement-breakpoint
902 +CREATE INDEX "trial_conditions_cancer_idx" ON "trial_conditions" USING btree ("cancer_id");--> statement-breakpoint
903 +CREATE INDEX "trial_conditions_norm_idx" ON "trial_conditions" USING btree ("normalized");--> statement-breakpoint
904 +CREATE UNIQUE INDEX "trial_interventions_uq" ON "trial_interventions" USING btree ("trial_id","normalized");--> statement-breakpoint
905 +CREATE INDEX "trial_interventions_drug_idx" ON "trial_interventions" USING btree ("drug_id");--> statement-breakpoint
906 +CREATE INDEX "trial_locations_trial_idx" ON "trial_locations" USING btree ("trial_id");--> statement-breakpoint
907 +CREATE INDEX "trial_locations_country_idx" ON "trial_locations" USING btree ("country");--> statement-breakpoint
908 +CREATE UNIQUE INDEX "trial_pulse_uq" ON "trial_pulse" USING btree ("day","cancer_id","phase");--> statement-breakpoint
909 +CREATE UNIQUE INDEX "literature_counts_uq" ON "literature_counts" USING btree ("cancer_id","window_key");--> statement-breakpoint
910 +CREATE INDEX "literature_counts_window_idx" ON "literature_counts" USING btree ("window_key","count");--> statement-breakpoint
911 +CREATE UNIQUE INDEX "pub_entity_edges_uq" ON "publication_entity_edges" USING btree ("publication_id","entity_type","entity_id","method");--> statement-breakpoint
912 +CREATE INDEX "pub_entity_edges_entity_idx" ON "publication_entity_edges" USING btree ("entity_type","entity_id");--> statement-breakpoint
913 +CREATE UNIQUE INDEX "publications_pmid_uq" ON "publications" USING btree ("pmid");--> statement-breakpoint
914 +CREATE INDEX "publications_doi_idx" ON "publications" USING btree ("doi");--> statement-breakpoint
915 +CREATE INDEX "publications_year_idx" ON "publications" USING btree ("pub_year");--> statement-breakpoint
916 +CREATE INDEX "publications_retracted_idx" ON "publications" USING btree ("retracted");--> statement-breakpoint
917 +CREATE UNIQUE INDEX "civic_evidence_uq" ON "civic_evidence_items" USING btree ("civic_id");--> statement-breakpoint
918 +CREATE INDEX "civic_evidence_cancer_idx" ON "civic_evidence_items" USING btree ("cancer_id");--> statement-breakpoint
919 +CREATE INDEX "civic_evidence_gene_idx" ON "civic_evidence_items" USING btree ("gene_symbols");--> statement-breakpoint
920 +CREATE UNIQUE INDEX "knowledge_edges_uq" ON "knowledge_edges" USING btree ("source_entity_type","source_entity_id","target_entity_type","target_entity_id","relationship_type","source_id","source_record_id");--> statement-breakpoint
921 +CREATE INDEX "knowledge_edges_source_idx" ON "knowledge_edges" USING btree ("source_entity_type","source_entity_id","relationship_type");--> statement-breakpoint
922 +CREATE INDEX "knowledge_edges_target_idx" ON "knowledge_edges" USING btree ("target_entity_type","target_entity_id","relationship_type");--> statement-breakpoint
923 +CREATE UNIQUE INDEX "risk_factors_slug_uq" ON "risk_factors" USING btree ("slug");--> statement-breakpoint
924 +CREATE UNIQUE INDEX "epi_obs_uq" ON "epidemiology_observations" USING btree ("cancer_id","geography_id","year","sex","age_group","metric","source_id","site_definition");--> statement-breakpoint
925 +CREATE INDEX "epi_obs_lookup_idx" ON "epidemiology_observations" USING btree ("metric","geography_id","year","sex");--> statement-breakpoint
926 +CREATE INDEX "epi_obs_cancer_idx" ON "epidemiology_observations" USING btree ("cancer_id","metric");--> statement-breakpoint
927 +CREATE INDEX "survival_obs_cancer_idx" ON "survival_observations" USING btree ("cancer_id","survival_type","duration_months");--> statement-breakpoint
928 +CREATE UNIQUE INDEX "ai_answers_uq" ON "ai_answers" USING btree ("kind","question_hash","prompt_version");--> statement-breakpoint
929 +CREATE UNIQUE INDEX "api_keys_hash_uq" ON "api_keys" USING btree ("key_hash");--> statement-breakpoint
930 +CREATE UNIQUE INDEX "entity_counters_uq" ON "entity_counters" USING btree ("entity_type","entity_id");--> statement-breakpoint
931 +CREATE INDEX "entity_counters_trials_idx" ON "entity_counters" USING btree ("entity_type","active_trial_count");--> statement-breakpoint
932 +CREATE UNIQUE INDEX "metric_definitions_slug_uq" ON "metric_definitions" USING btree ("slug");--> statement-breakpoint
933 +CREATE INDEX "ranking_snapshots_lookup_idx" ON "ranking_snapshots" USING btree ("metric_slug","scope_key","is_current");--> statement-breakpoint
934 +CREATE UNIQUE INDEX "rankings_uq" ON "rankings" USING btree ("snapshot_id","cancer_id");--> statement-breakpoint
935 +CREATE INDEX "rankings_cancer_idx" ON "rankings" USING btree ("cancer_id","metric_slug");--> statement-breakpoint
936 +CREATE INDEX "rankings_lookup_idx" ON "rankings" USING btree ("metric_slug","scope_key","rank");
\ No newline at end of file
added packages/database/migrations/meta/0000_snapshot.json +6918 −0
@@ -0,0 +1,6918 @@
1 +{
2 + "id": "7cee03f3-3bb2-47c2-b164-ea15161ffedd",
3 + "prevId": "00000000-0000-0000-0000-000000000000",
4 + "version": "7",
5 + "dialect": "postgresql",
6 + "tables": {
7 + "public.audit_log": {
8 + "name": "audit_log",
9 + "schema": "",
10 + "columns": {
11 + "id": {
12 + "name": "id",
13 + "type": "bigserial",
14 + "primaryKey": true,
15 + "notNull": true
16 + },
17 + "actor": {
18 + "name": "actor",
19 + "type": "text",
20 + "primaryKey": false,
21 + "notNull": true
22 + },
23 + "action": {
24 + "name": "action",
25 + "type": "text",
26 + "primaryKey": false,
27 + "notNull": true
28 + },
29 + "entity_type": {
30 + "name": "entity_type",
31 + "type": "text",
32 + "primaryKey": false,
33 + "notNull": false
34 + },
35 + "entity_id": {
36 + "name": "entity_id",
37 + "type": "text",
38 + "primaryKey": false,
39 + "notNull": false
40 + },
41 + "before": {
42 + "name": "before",
43 + "type": "jsonb",
44 + "primaryKey": false,
45 + "notNull": false
46 + },
47 + "after": {
48 + "name": "after",
49 + "type": "jsonb",
50 + "primaryKey": false,
51 + "notNull": false
52 + },
53 + "reason": {
54 + "name": "reason",
55 + "type": "text",
56 + "primaryKey": false,
57 + "notNull": false
58 + },
59 + "created_at": {
60 + "name": "created_at",
61 + "type": "timestamp with time zone",
62 + "primaryKey": false,
63 + "notNull": true,
64 + "default": "now()"
65 + }
66 + },
67 + "indexes": {},
68 + "foreignKeys": {},
69 + "compositePrimaryKeys": {},
70 + "uniqueConstraints": {},
71 + "policies": {},
72 + "checkConstraints": {},
73 + "isRLSEnabled": false
74 + },
75 + "public.change_events": {
76 + "name": "change_events",
77 + "schema": "",
78 + "columns": {
79 + "id": {
80 + "name": "id",
81 + "type": "bigserial",
82 + "primaryKey": true,
83 + "notNull": true
84 + },
85 + "entity_type": {
86 + "name": "entity_type",
87 + "type": "text",
88 + "primaryKey": false,
89 + "notNull": true
90 + },
91 + "entity_id": {
92 + "name": "entity_id",
93 + "type": "text",
94 + "primaryKey": false,
95 + "notNull": true
96 + },
97 + "kind": {
98 + "name": "kind",
99 + "type": "text",
100 + "primaryKey": false,
101 + "notNull": true
102 + },
103 + "summary": {
104 + "name": "summary",
105 + "type": "text",
106 + "primaryKey": false,
107 + "notNull": true
108 + },
109 + "before": {
110 + "name": "before",
111 + "type": "jsonb",
112 + "primaryKey": false,
113 + "notNull": false
114 + },
115 + "after": {
116 + "name": "after",
117 + "type": "jsonb",
118 + "primaryKey": false,
119 + "notNull": false
120 + },
121 + "ingest_run_id": {
122 + "name": "ingest_run_id",
123 + "type": "text",
124 + "primaryKey": false,
125 + "notNull": false
126 + },
127 + "created_at": {
128 + "name": "created_at",
129 + "type": "timestamp with time zone",
130 + "primaryKey": false,
131 + "notNull": true,
132 + "default": "now()"
133 + }
134 + },
135 + "indexes": {
136 + "change_events_entity_idx": {
137 + "name": "change_events_entity_idx",
138 + "columns": [
139 + {
140 + "expression": "entity_type",
141 + "isExpression": false,
142 + "asc": true,
143 + "nulls": "last"
144 + },
145 + {
146 + "expression": "entity_id",
147 + "isExpression": false,
148 + "asc": true,
149 + "nulls": "last"
150 + },
151 + {
152 + "expression": "created_at",
153 + "isExpression": false,
154 + "asc": true,
155 + "nulls": "last"
156 + }
157 + ],
158 + "isUnique": false,
159 + "concurrently": false,
160 + "method": "btree",
161 + "with": {}
162 + }
163 + },
164 + "foreignKeys": {},
165 + "compositePrimaryKeys": {},
166 + "uniqueConstraints": {},
167 + "policies": {},
168 + "checkConstraints": {},
169 + "isRLSEnabled": false
170 + },
171 + "public.connector_cursors": {
172 + "name": "connector_cursors",
173 + "schema": "",
174 + "columns": {
175 + "connector_id": {
176 + "name": "connector_id",
177 + "type": "text",
178 + "primaryKey": true,
179 + "notNull": true
180 + },
181 + "cursor": {
182 + "name": "cursor",
183 + "type": "jsonb",
184 + "primaryKey": false,
185 + "notNull": true,
186 + "default": "'{}'::jsonb"
187 + },
188 + "last_success_at": {
189 + "name": "last_success_at",
190 + "type": "timestamp with time zone",
191 + "primaryKey": false,
192 + "notNull": false
193 + },
194 + "last_attempt_at": {
195 + "name": "last_attempt_at",
196 + "type": "timestamp with time zone",
197 + "primaryKey": false,
198 + "notNull": false
199 + },
200 + "paused": {
201 + "name": "paused",
202 + "type": "boolean",
203 + "primaryKey": false,
204 + "notNull": true,
205 + "default": false
206 + },
207 + "health": {
208 + "name": "health",
209 + "type": "text",
210 + "primaryKey": false,
211 + "notNull": true,
212 + "default": "'unknown'"
213 + },
214 + "health_detail": {
215 + "name": "health_detail",
216 + "type": "text",
217 + "primaryKey": false,
218 + "notNull": false
219 + },
220 + "updated_at": {
221 + "name": "updated_at",
222 + "type": "timestamp with time zone",
223 + "primaryKey": false,
224 + "notNull": true,
225 + "default": "now()"
226 + }
227 + },
228 + "indexes": {},
229 + "foreignKeys": {},
230 + "compositePrimaryKeys": {},
231 + "uniqueConstraints": {},
232 + "policies": {},
233 + "checkConstraints": {},
234 + "isRLSEnabled": false
235 + },
236 + "public.connector_field_stats": {
237 + "name": "connector_field_stats",
238 + "schema": "",
239 + "columns": {
240 + "id": {
241 + "name": "id",
242 + "type": "bigserial",
243 + "primaryKey": true,
244 + "notNull": true
245 + },
246 + "connector_id": {
247 + "name": "connector_id",
248 + "type": "text",
249 + "primaryKey": false,
250 + "notNull": true
251 + },
252 + "entity": {
253 + "name": "entity",
254 + "type": "text",
255 + "primaryKey": false,
256 + "notNull": true
257 + },
258 + "field": {
259 + "name": "field",
260 + "type": "text",
261 + "primaryKey": false,
262 + "notNull": true
263 + },
264 + "types": {
265 + "name": "types",
266 + "type": "text[]",
267 + "primaryKey": false,
268 + "notNull": true,
269 + "default": "'{}'"
270 + },
271 + "seen_count": {
272 + "name": "seen_count",
273 + "type": "integer",
274 + "primaryKey": false,
275 + "notNull": true,
276 + "default": 0
277 + },
278 + "null_count": {
279 + "name": "null_count",
280 + "type": "integer",
281 + "primaryKey": false,
282 + "notNull": true,
283 + "default": 0
284 + },
285 + "first_seen_run": {
286 + "name": "first_seen_run",
287 + "type": "text",
288 + "primaryKey": false,
289 + "notNull": false
290 + },
291 + "last_seen_run": {
292 + "name": "last_seen_run",
293 + "type": "text",
294 + "primaryKey": false,
295 + "notNull": false
296 + },
297 + "updated_at": {
298 + "name": "updated_at",
299 + "type": "timestamp with time zone",
300 + "primaryKey": false,
301 + "notNull": true,
302 + "default": "now()"
303 + }
304 + },
305 + "indexes": {
306 + "connector_field_stats_uq": {
307 + "name": "connector_field_stats_uq",
308 + "columns": [
309 + {
310 + "expression": "connector_id",
311 + "isExpression": false,
312 + "asc": true,
313 + "nulls": "last"
314 + },
315 + {
316 + "expression": "entity",
317 + "isExpression": false,
318 + "asc": true,
319 + "nulls": "last"
320 + },
321 + {
322 + "expression": "field",
323 + "isExpression": false,
324 + "asc": true,
325 + "nulls": "last"
326 + }
327 + ],
328 + "isUnique": true,
329 + "concurrently": false,
330 + "method": "btree",
331 + "with": {}
332 + }
333 + },
334 + "foreignKeys": {},
335 + "compositePrimaryKeys": {},
336 + "uniqueConstraints": {},
337 + "policies": {},
338 + "checkConstraints": {},
339 + "isRLSEnabled": false
340 + },
341 + "public.entity_merges": {
342 + "name": "entity_merges",
343 + "schema": "",
344 + "columns": {
345 + "id": {
346 + "name": "id",
347 + "type": "bigserial",
348 + "primaryKey": true,
349 + "notNull": true
350 + },
351 + "entity_type": {
352 + "name": "entity_type",
353 + "type": "text",
354 + "primaryKey": false,
355 + "notNull": true
356 + },
357 + "keep_id": {
358 + "name": "keep_id",
359 + "type": "text",
360 + "primaryKey": false,
361 + "notNull": true
362 + },
363 + "merge_id": {
364 + "name": "merge_id",
365 + "type": "text",
366 + "primaryKey": false,
367 + "notNull": true
368 + },
369 + "evidence": {
370 + "name": "evidence",
371 + "type": "jsonb",
372 + "primaryKey": false,
373 + "notNull": true,
374 + "default": "'{}'::jsonb"
375 + },
376 + "status": {
377 + "name": "status",
378 + "type": "text",
379 + "primaryKey": false,
380 + "notNull": true,
381 + "default": "'proposed'"
382 + },
383 + "decided_by": {
384 + "name": "decided_by",
385 + "type": "text",
386 + "primaryKey": false,
387 + "notNull": false
388 + },
389 + "decided_at": {
390 + "name": "decided_at",
391 + "type": "timestamp with time zone",
392 + "primaryKey": false,
393 + "notNull": false
394 + },
395 + "created_at": {
396 + "name": "created_at",
397 + "type": "timestamp with time zone",
398 + "primaryKey": false,
399 + "notNull": true,
400 + "default": "now()"
401 + }
402 + },
403 + "indexes": {},
404 + "foreignKeys": {},
405 + "compositePrimaryKeys": {},
406 + "uniqueConstraints": {},
407 + "policies": {},
408 + "checkConstraints": {},
409 + "isRLSEnabled": false
410 + },
411 + "public.id_sequences": {
412 + "name": "id_sequences",
413 + "schema": "",
414 + "columns": {
415 + "namespace": {
416 + "name": "namespace",
417 + "type": "varchar(16)",
418 + "primaryKey": true,
419 + "notNull": true
420 + },
421 + "next": {
422 + "name": "next",
423 + "type": "bigint",
424 + "primaryKey": false,
425 + "notNull": true,
426 + "default": 1
427 + }
428 + },
429 + "indexes": {},
430 + "foreignKeys": {},
431 + "compositePrimaryKeys": {},
432 + "uniqueConstraints": {},
433 + "policies": {},
434 + "checkConstraints": {},
435 + "isRLSEnabled": false
436 + },
437 + "public.ingest_runs": {
438 + "name": "ingest_runs",
439 + "schema": "",
440 + "columns": {
441 + "id": {
442 + "name": "id",
443 + "type": "text",
444 + "primaryKey": true,
445 + "notNull": true
446 + },
447 + "connector_id": {
448 + "name": "connector_id",
449 + "type": "text",
450 + "primaryKey": false,
451 + "notNull": true
452 + },
453 + "source_id": {
454 + "name": "source_id",
455 + "type": "varchar(32)",
456 + "primaryKey": false,
457 + "notNull": true
458 + },
459 + "mode": {
460 + "name": "mode",
461 + "type": "text",
462 + "primaryKey": false,
463 + "notNull": true,
464 + "default": "'incremental'"
465 + },
466 + "status": {
467 + "name": "status",
468 + "type": "text",
469 + "primaryKey": false,
470 + "notNull": true,
471 + "default": "'running'"
472 + },
473 + "started_at": {
474 + "name": "started_at",
475 + "type": "timestamp with time zone",
476 + "primaryKey": false,
477 + "notNull": true,
478 + "default": "now()"
479 + },
480 + "finished_at": {
481 + "name": "finished_at",
482 + "type": "timestamp with time zone",
483 + "primaryKey": false,
484 + "notNull": false
485 + },
486 + "duration_ms": {
487 + "name": "duration_ms",
488 + "type": "integer",
489 + "primaryKey": false,
490 + "notNull": false
491 + },
492 + "records_fetched": {
493 + "name": "records_fetched",
494 + "type": "integer",
495 + "primaryKey": false,
496 + "notNull": true,
497 + "default": 0
498 + },
499 + "records_created": {
500 + "name": "records_created",
501 + "type": "integer",
502 + "primaryKey": false,
503 + "notNull": true,
504 + "default": 0
505 + },
506 + "records_updated": {
507 + "name": "records_updated",
508 + "type": "integer",
509 + "primaryKey": false,
510 + "notNull": true,
511 + "default": 0
512 + },
513 + "records_unchanged": {
514 + "name": "records_unchanged",
515 + "type": "integer",
516 + "primaryKey": false,
517 + "notNull": true,
518 + "default": 0
519 + },
520 + "records_rejected": {
521 + "name": "records_rejected",
522 + "type": "integer",
523 + "primaryKey": false,
524 + "notNull": true,
525 + "default": 0
526 + },
527 + "http_requests": {
528 + "name": "http_requests",
529 + "type": "integer",
530 + "primaryKey": false,
531 + "notNull": true,
532 + "default": 0
533 + },
534 + "http_failures": {
535 + "name": "http_failures",
536 + "type": "integer",
537 + "primaryKey": false,
538 + "notNull": true,
539 + "default": 0
540 + },
541 + "rate_limit_events": {
542 + "name": "rate_limit_events",
543 + "type": "integer",
544 + "primaryKey": false,
545 + "notNull": true,
546 + "default": 0
547 + },
548 + "validation_failures": {
549 + "name": "validation_failures",
550 + "type": "integer",
551 + "primaryKey": false,
552 + "notNull": true,
553 + "default": 0
554 + },
555 + "schema_drift": {
556 + "name": "schema_drift",
557 + "type": "jsonb",
558 + "primaryKey": false,
559 + "notNull": true,
560 + "default": "'[]'::jsonb"
561 + },
562 + "cursor_before": {
563 + "name": "cursor_before",
564 + "type": "jsonb",
565 + "primaryKey": false,
566 + "notNull": false
567 + },
568 + "cursor_after": {
569 + "name": "cursor_after",
570 + "type": "jsonb",
571 + "primaryKey": false,
572 + "notNull": false
573 + },
574 + "error": {
575 + "name": "error",
576 + "type": "text",
577 + "primaryKey": false,
578 + "notNull": false
579 + },
580 + "log": {
581 + "name": "log",
582 + "type": "jsonb",
583 + "primaryKey": false,
584 + "notNull": true,
585 + "default": "'[]'::jsonb"
586 + },
587 + "dataset_version": {
588 + "name": "dataset_version",
589 + "type": "text",
590 + "primaryKey": false,
591 + "notNull": false
592 + },
593 + "anomaly": {
594 + "name": "anomaly",
595 + "type": "text",
596 + "primaryKey": false,
597 + "notNull": false
598 + }
599 + },
600 + "indexes": {
601 + "ingest_runs_connector_idx": {
602 + "name": "ingest_runs_connector_idx",
603 + "columns": [
604 + {
605 + "expression": "connector_id",
606 + "isExpression": false,
607 + "asc": true,
608 + "nulls": "last"
609 + },
610 + {
611 + "expression": "started_at",
612 + "isExpression": false,
613 + "asc": true,
614 + "nulls": "last"
615 + }
616 + ],
617 + "isUnique": false,
618 + "concurrently": false,
619 + "method": "btree",
620 + "with": {}
621 + }
622 + },
623 + "foreignKeys": {},
624 + "compositePrimaryKeys": {},
625 + "uniqueConstraints": {},
626 + "policies": {},
627 + "checkConstraints": {},
628 + "isRLSEnabled": false
629 + },
630 + "public.provenance": {
631 + "name": "provenance",
632 + "schema": "",
633 + "columns": {
634 + "id": {
635 + "name": "id",
636 + "type": "bigserial",
637 + "primaryKey": true,
638 + "notNull": true
639 + },
640 + "public_id": {
641 + "name": "public_id",
642 + "type": "varchar(32)",
643 + "primaryKey": false,
644 + "notNull": false
645 + },
646 + "source_id": {
647 + "name": "source_id",
648 + "type": "varchar(32)",
649 + "primaryKey": false,
650 + "notNull": true
651 + },
652 + "source_record_id": {
653 + "name": "source_record_id",
654 + "type": "text",
655 + "primaryKey": false,
656 + "notNull": false
657 + },
658 + "source_url": {
659 + "name": "source_url",
660 + "type": "text",
661 + "primaryKey": false,
662 + "notNull": false
663 + },
664 + "dataset": {
665 + "name": "dataset",
666 + "type": "text",
667 + "primaryKey": false,
668 + "notNull": false
669 + },
670 + "dataset_version": {
671 + "name": "dataset_version",
672 + "type": "text",
673 + "primaryKey": false,
674 + "notNull": false
675 + },
676 + "publication_id": {
677 + "name": "publication_id",
678 + "type": "varchar(32)",
679 + "primaryKey": false,
680 + "notNull": false
681 + },
682 + "pmid": {
683 + "name": "pmid",
684 + "type": "text",
685 + "primaryKey": false,
686 + "notNull": false
687 + },
688 + "doi": {
689 + "name": "doi",
690 + "type": "text",
691 + "primaryKey": false,
692 + "notNull": false
693 + },
694 + "retrieved_at": {
695 + "name": "retrieved_at",
696 + "type": "timestamp with time zone",
697 + "primaryKey": false,
698 + "notNull": true
699 + },
700 + "published_at": {
701 + "name": "published_at",
702 + "type": "text",
703 + "primaryKey": false,
704 + "notNull": false
705 + },
706 + "updated_at_source": {
707 + "name": "updated_at_source",
708 + "type": "text",
709 + "primaryKey": false,
710 + "notNull": false
711 + },
712 + "geography": {
713 + "name": "geography",
714 + "type": "text",
715 + "primaryKey": false,
716 + "notNull": false
717 + },
718 + "population": {
719 + "name": "population",
720 + "type": "text",
721 + "primaryKey": false,
722 + "notNull": false
723 + },
724 + "cohort_size": {
725 + "name": "cohort_size",
726 + "type": "integer",
727 + "primaryKey": false,
728 + "notNull": false
729 + },
730 + "methodology": {
731 + "name": "methodology",
732 + "type": "text",
733 + "primaryKey": false,
734 + "notNull": false
735 + },
736 + "evidence_type": {
737 + "name": "evidence_type",
738 + "type": "text",
739 + "primaryKey": false,
740 + "notNull": true
741 + },
742 + "access_level": {
743 + "name": "access_level",
744 + "type": "text",
745 + "primaryKey": false,
746 + "notNull": true,
747 + "default": "'open'"
748 + },
749 + "confidence": {
750 + "name": "confidence",
751 + "type": "real",
752 + "primaryKey": false,
753 + "notNull": false
754 + },
755 + "license": {
756 + "name": "license",
757 + "type": "text",
758 + "primaryKey": false,
759 + "notNull": false
760 + },
761 + "ingest_run_id": {
762 + "name": "ingest_run_id",
763 + "type": "text",
764 + "primaryKey": false,
765 + "notNull": false
766 + },
767 + "created_at": {
768 + "name": "created_at",
769 + "type": "timestamp with time zone",
770 + "primaryKey": false,
771 + "notNull": true,
772 + "default": "now()"
773 + }
774 + },
775 + "indexes": {
776 + "provenance_source_idx": {
777 + "name": "provenance_source_idx",
778 + "columns": [
779 + {
780 + "expression": "source_id",
781 + "isExpression": false,
782 + "asc": true,
783 + "nulls": "last"
784 + },
785 + {
786 + "expression": "source_record_id",
787 + "isExpression": false,
788 + "asc": true,
789 + "nulls": "last"
790 + }
791 + ],
792 + "isUnique": false,
793 + "concurrently": false,
794 + "method": "btree",
795 + "with": {}
796 + },
797 + "provenance_pmid_idx": {
798 + "name": "provenance_pmid_idx",
799 + "columns": [
800 + {
801 + "expression": "pmid",
802 + "isExpression": false,
803 + "asc": true,
804 + "nulls": "last"
805 + }
806 + ],
807 + "isUnique": false,
808 + "concurrently": false,
809 + "method": "btree",
810 + "with": {}
811 + }
812 + },
813 + "foreignKeys": {},
814 + "compositePrimaryKeys": {},
815 + "uniqueConstraints": {},
816 + "policies": {},
817 + "checkConstraints": {},
818 + "isRLSEnabled": false
819 + },
820 + "public.source_records": {
821 + "name": "source_records",
822 + "schema": "",
823 + "columns": {
824 + "id": {
825 + "name": "id",
826 + "type": "bigserial",
827 + "primaryKey": true,
828 + "notNull": true
829 + },
830 + "source_id": {
831 + "name": "source_id",
832 + "type": "varchar(32)",
833 + "primaryKey": false,
834 + "notNull": true
835 + },
836 + "entity_kind": {
837 + "name": "entity_kind",
838 + "type": "text",
839 + "primaryKey": false,
840 + "notNull": true
841 + },
842 + "source_record_id": {
843 + "name": "source_record_id",
844 + "type": "text",
845 + "primaryKey": false,
846 + "notNull": true
847 + },
848 + "payload_hash": {
849 + "name": "payload_hash",
850 + "type": "text",
851 + "primaryKey": false,
852 + "notNull": true
853 + },
854 + "raw_path": {
855 + "name": "raw_path",
856 + "type": "text",
857 + "primaryKey": false,
858 + "notNull": false
859 + },
860 + "status": {
861 + "name": "status",
862 + "type": "text",
863 + "primaryKey": false,
864 + "notNull": true,
865 + "default": "'active'"
866 + },
867 + "first_seen_run": {
868 + "name": "first_seen_run",
869 + "type": "text",
870 + "primaryKey": false,
871 + "notNull": false
872 + },
873 + "last_seen_run": {
874 + "name": "last_seen_run",
875 + "type": "text",
876 + "primaryKey": false,
877 + "notNull": false
878 + },
879 + "retrieved_at": {
880 + "name": "retrieved_at",
881 + "type": "timestamp with time zone",
882 + "primaryKey": false,
883 + "notNull": true,
884 + "default": "now()"
885 + },
886 + "source_updated_at": {
887 + "name": "source_updated_at",
888 + "type": "timestamp with time zone",
889 + "primaryKey": false,
890 + "notNull": false
891 + },
892 + "canonical_type": {
893 + "name": "canonical_type",
894 + "type": "text",
895 + "primaryKey": false,
896 + "notNull": false
897 + },
898 + "canonical_id": {
899 + "name": "canonical_id",
900 + "type": "text",
901 + "primaryKey": false,
902 + "notNull": false
903 + },
904 + "created_at": {
905 + "name": "created_at",
906 + "type": "timestamp with time zone",
907 + "primaryKey": false,
908 + "notNull": true,
909 + "default": "now()"
910 + },
911 + "updated_at": {
912 + "name": "updated_at",
913 + "type": "timestamp with time zone",
914 + "primaryKey": false,
915 + "notNull": true,
916 + "default": "now()"
917 + }
918 + },
919 + "indexes": {
920 + "source_records_uq": {
921 + "name": "source_records_uq",
922 + "columns": [
923 + {
924 + "expression": "source_id",
925 + "isExpression": false,
926 + "asc": true,
927 + "nulls": "last"
928 + },
929 + {
930 + "expression": "entity_kind",
931 + "isExpression": false,
932 + "asc": true,
933 + "nulls": "last"
934 + },
935 + {
936 + "expression": "source_record_id",
937 + "isExpression": false,
938 + "asc": true,
939 + "nulls": "last"
940 + }
941 + ],
942 + "isUnique": true,
943 + "concurrently": false,
944 + "method": "btree",
945 + "with": {}
946 + },
947 + "source_records_canonical_idx": {
948 + "name": "source_records_canonical_idx",
949 + "columns": [
950 + {
951 + "expression": "canonical_type",
952 + "isExpression": false,
953 + "asc": true,
954 + "nulls": "last"
955 + },
956 + {
957 + "expression": "canonical_id",
958 + "isExpression": false,
959 + "asc": true,
960 + "nulls": "last"
961 + }
962 + ],
963 + "isUnique": false,
964 + "concurrently": false,
965 + "method": "btree",
966 + "with": {}
967 + }
968 + },
969 + "foreignKeys": {},
970 + "compositePrimaryKeys": {},
971 + "uniqueConstraints": {},
972 + "policies": {},
973 + "checkConstraints": {},
974 + "isRLSEnabled": false
975 + },
976 + "public.sources": {
977 + "name": "sources",
978 + "schema": "",
979 + "columns": {
980 + "id": {
981 + "name": "id",
982 + "type": "varchar(32)",
983 + "primaryKey": true,
984 + "notNull": true
985 + },
986 + "slug": {
987 + "name": "slug",
988 + "type": "text",
989 + "primaryKey": false,
990 + "notNull": true
991 + },
992 + "name": {
993 + "name": "name",
994 + "type": "text",
995 + "primaryKey": false,
996 + "notNull": true
997 + },
998 + "organization": {
999 + "name": "organization",
1000 + "type": "text",
1001 + "primaryKey": false,
1002 + "notNull": false
1003 + },
1004 + "category": {
1005 + "name": "category",
1006 + "type": "text",
1007 + "primaryKey": false,
1008 + "notNull": true
1009 + },
1010 + "description": {
1011 + "name": "description",
1012 + "type": "text",
1013 + "primaryKey": false,
1014 + "notNull": false
1015 + },
1016 + "homepage": {
1017 + "name": "homepage",
1018 + "type": "text",
1019 + "primaryKey": false,
1020 + "notNull": false
1021 + },
1022 + "docs_url": {
1023 + "name": "docs_url",
1024 + "type": "text",
1025 + "primaryKey": false,
1026 + "notNull": false
1027 + },
1028 + "terms_url": {
1029 + "name": "terms_url",
1030 + "type": "text",
1031 + "primaryKey": false,
1032 + "notNull": false
1033 + },
1034 + "access_type": {
1035 + "name": "access_type",
1036 + "type": "text",
1037 + "primaryKey": false,
1038 + "notNull": true
1039 + },
1040 + "access_auth": {
1041 + "name": "access_auth",
1042 + "type": "text",
1043 + "primaryKey": false,
1044 + "notNull": true
1045 + },
1046 + "license": {
1047 + "name": "license",
1048 + "type": "text",
1049 + "primaryKey": false,
1050 + "notNull": false
1051 + },
1052 + "license_status": {
1053 + "name": "license_status",
1054 + "type": "text",
1055 + "primaryKey": false,
1056 + "notNull": true,
1057 + "default": "'review'"
1058 + },
1059 + "commercial_use": {
1060 + "name": "commercial_use",
1061 + "type": "text",
1062 + "primaryKey": false,
1063 + "notNull": true,
1064 + "default": "'unknown'"
1065 + },
1066 + "redistribution": {
1067 + "name": "redistribution",
1068 + "type": "text",
1069 + "primaryKey": false,
1070 + "notNull": true,
1071 + "default": "'unknown'"
1072 + },
1073 + "attribution": {
1074 + "name": "attribution",
1075 + "type": "text",
1076 + "primaryKey": false,
1077 + "notNull": false
1078 + },
1079 + "license_reviewed_at": {
1080 + "name": "license_reviewed_at",
1081 + "type": "timestamp with time zone",
1082 + "primaryKey": false,
1083 + "notNull": false
1084 + },
1085 + "approved_for_production": {
1086 + "name": "approved_for_production",
1087 + "type": "boolean",
1088 + "primaryKey": false,
1089 + "notNull": true,
1090 + "default": false
1091 + },
1092 + "update_frequency": {
1093 + "name": "update_frequency",
1094 + "type": "text",
1095 + "primaryKey": false,
1096 + "notNull": false
1097 + },
1098 + "supports_incremental": {
1099 + "name": "supports_incremental",
1100 + "type": "boolean",
1101 + "primaryKey": false,
1102 + "notNull": true,
1103 + "default": false
1104 + },
1105 + "entities": {
1106 + "name": "entities",
1107 + "type": "text[]",
1108 + "primaryKey": false,
1109 + "notNull": true,
1110 + "default": "'{}'"
1111 + },
1112 + "metrics": {
1113 + "name": "metrics",
1114 + "type": "text[]",
1115 + "primaryKey": false,
1116 + "notNull": true,
1117 + "default": "'{}'"
1118 + },
1119 + "rate_limit": {
1120 + "name": "rate_limit",
1121 + "type": "text",
1122 + "primaryKey": false,
1123 + "notNull": false
1124 + },
1125 + "status": {
1126 + "name": "status",
1127 + "type": "text",
1128 + "primaryKey": false,
1129 + "notNull": true,
1130 + "default": "'planned'"
1131 + },
1132 + "tier": {
1133 + "name": "tier",
1134 + "type": "integer",
1135 + "primaryKey": false,
1136 + "notNull": true,
1137 + "default": 0
1138 + },
1139 + "manifest": {
1140 + "name": "manifest",
1141 + "type": "jsonb",
1142 + "primaryKey": false,
1143 + "notNull": true,
1144 + "default": "'{}'::jsonb"
1145 + },
1146 + "created_at": {
1147 + "name": "created_at",
1148 + "type": "timestamp with time zone",
1149 + "primaryKey": false,
1150 + "notNull": true,
1151 + "default": "now()"
1152 + },
1153 + "updated_at": {
1154 + "name": "updated_at",
1155 + "type": "timestamp with time zone",
1156 + "primaryKey": false,
1157 + "notNull": true,
1158 + "default": "now()"
1159 + }
1160 + },
1161 + "indexes": {
1162 + "sources_slug_uq": {
1163 + "name": "sources_slug_uq",
1164 + "columns": [
1165 + {
1166 + "expression": "slug",
1167 + "isExpression": false,
1168 + "asc": true,
1169 + "nulls": "last"
1170 + }
1171 + ],
1172 + "isUnique": true,
1173 + "concurrently": false,
1174 + "method": "btree",
1175 + "with": {}
1176 + }
1177 + },
1178 + "foreignKeys": {},
1179 + "compositePrimaryKeys": {},
1180 + "uniqueConstraints": {},
1181 + "policies": {},
1182 + "checkConstraints": {},
1183 + "isRLSEnabled": false
1184 + },
1185 + "public.unresolved_labels": {
1186 + "name": "unresolved_labels",
1187 + "schema": "",
1188 + "columns": {
1189 + "id": {
1190 + "name": "id",
1191 + "type": "bigserial",
1192 + "primaryKey": true,
1193 + "notNull": true
1194 + },
1195 + "source_id": {
1196 + "name": "source_id",
1197 + "type": "varchar(32)",
1198 + "primaryKey": false,
1199 + "notNull": true
1200 + },
1201 + "entity_kind": {
1202 + "name": "entity_kind",
1203 + "type": "text",
1204 + "primaryKey": false,
1205 + "notNull": true
1206 + },
1207 + "source_text": {
1208 + "name": "source_text",
1209 + "type": "text",
1210 + "primaryKey": false,
1211 + "notNull": true
1212 + },
1213 + "normalized": {
1214 + "name": "normalized",
1215 + "type": "text",
1216 + "primaryKey": false,
1217 + "notNull": true
1218 + },
1219 + "context": {
1220 + "name": "context",
1221 + "type": "jsonb",
1222 + "primaryKey": false,
1223 + "notNull": true,
1224 + "default": "'{}'::jsonb"
1225 + },
1226 + "count": {
1227 + "name": "count",
1228 + "type": "integer",
1229 + "primaryKey": false,
1230 + "notNull": true,
1231 + "default": 1
1232 + },
1233 + "status": {
1234 + "name": "status",
1235 + "type": "text",
1236 + "primaryKey": false,
1237 + "notNull": true,
1238 + "default": "'open'"
1239 + },
1240 + "suggested_id": {
1241 + "name": "suggested_id",
1242 + "type": "text",
1243 + "primaryKey": false,
1244 + "notNull": false
1245 + },
1246 + "suggested_match_type": {
1247 + "name": "suggested_match_type",
1248 + "type": "text",
1249 + "primaryKey": false,
1250 + "notNull": false
1251 + },
1252 + "suggested_score": {
1253 + "name": "suggested_score",
1254 + "type": "real",
1255 + "primaryKey": false,
1256 + "notNull": false
1257 + },
1258 + "resolved_id": {
1259 + "name": "resolved_id",
1260 + "type": "text",
1261 + "primaryKey": false,
1262 + "notNull": false
1263 + },
1264 + "resolved_by": {
1265 + "name": "resolved_by",
1266 + "type": "text",
1267 + "primaryKey": false,
1268 + "notNull": false
1269 + },
1270 + "created_at": {
1271 + "name": "created_at",
1272 + "type": "timestamp with time zone",
1273 + "primaryKey": false,
1274 + "notNull": true,
1275 + "default": "now()"
1276 + },
1277 + "updated_at": {
1278 + "name": "updated_at",
1279 + "type": "timestamp with time zone",
1280 + "primaryKey": false,
1281 + "notNull": true,
1282 + "default": "now()"
1283 + }
1284 + },
1285 + "indexes": {
1286 + "unresolved_labels_uq": {
1287 + "name": "unresolved_labels_uq",
1288 + "columns": [
1289 + {
1290 + "expression": "source_id",
1291 + "isExpression": false,
1292 + "asc": true,
1293 + "nulls": "last"
1294 + },
1295 + {
1296 + "expression": "entity_kind",
1297 + "isExpression": false,
1298 + "asc": true,
1299 + "nulls": "last"
1300 + },
1301 + {
1302 + "expression": "normalized",
1303 + "isExpression": false,
1304 + "asc": true,
1305 + "nulls": "last"
1306 + }
1307 + ],
1308 + "isUnique": true,
1309 + "concurrently": false,
1310 + "method": "btree",
1311 + "with": {}
1312 + },
1313 + "unresolved_labels_count_idx": {
1314 + "name": "unresolved_labels_count_idx",
1315 + "columns": [
1316 + {
1317 + "expression": "status",
1318 + "isExpression": false,
1319 + "asc": true,
1320 + "nulls": "last"
1321 + },
1322 + {
1323 + "expression": "count",
1324 + "isExpression": false,
1325 + "asc": true,
1326 + "nulls": "last"
1327 + }
1328 + ],
1329 + "isUnique": false,
1330 + "concurrently": false,
1331 + "method": "btree",
1332 + "with": {}
1333 + }
1334 + },
1335 + "foreignKeys": {},
1336 + "compositePrimaryKeys": {},
1337 + "uniqueConstraints": {},
1338 + "policies": {},
1339 + "checkConstraints": {},
1340 + "isRLSEnabled": false
1341 + },
1342 + "public.anatomical_sites": {
1343 + "name": "anatomical_sites",
1344 + "schema": "",
1345 + "columns": {
1346 + "id": {
1347 + "name": "id",
1348 + "type": "varchar(32)",
1349 + "primaryKey": true,
1350 + "notNull": true
1351 + },
1352 + "name": {
1353 + "name": "name",
1354 + "type": "text",
1355 + "primaryKey": false,
1356 + "notNull": true
1357 + },
1358 + "slug": {
1359 + "name": "slug",
1360 + "type": "text",
1361 + "primaryKey": false,
1362 + "notNull": true
1363 + },
1364 + "ncit_code": {
1365 + "name": "ncit_code",
1366 + "type": "text",
1367 + "primaryKey": false,
1368 + "notNull": false
1369 + },
1370 + "uberon_id": {
1371 + "name": "uberon_id",
1372 + "type": "text",
1373 + "primaryKey": false,
1374 + "notNull": false
1375 + },
1376 + "parent_id": {
1377 + "name": "parent_id",
1378 + "type": "varchar(32)",
1379 + "primaryKey": false,
1380 + "notNull": false
1381 + },
1382 + "system": {
1383 + "name": "system",
1384 + "type": "text",
1385 + "primaryKey": false,
1386 + "notNull": false
1387 + }
1388 + },
1389 + "indexes": {
1390 + "anatomical_sites_slug_uq": {
1391 + "name": "anatomical_sites_slug_uq",
1392 + "columns": [
1393 + {
1394 + "expression": "slug",
1395 + "isExpression": false,
1396 + "asc": true,
1397 + "nulls": "last"
1398 + }
1399 + ],
1400 + "isUnique": true,
1401 + "concurrently": false,
1402 + "method": "btree",
1403 + "with": {}
1404 + }
1405 + },
1406 + "foreignKeys": {},
1407 + "compositePrimaryKeys": {},
1408 + "uniqueConstraints": {},
1409 + "policies": {},
1410 + "checkConstraints": {},
1411 + "isRLSEnabled": false
1412 + },
1413 + "public.cancer_aliases": {
1414 + "name": "cancer_aliases",
1415 + "schema": "",
1416 + "columns": {
1417 + "id": {
1418 + "name": "id",
1419 + "type": "bigserial",
1420 + "primaryKey": true,
1421 + "notNull": true
1422 + },
1423 + "cancer_id": {
1424 + "name": "cancer_id",
1425 + "type": "varchar(32)",
1426 + "primaryKey": false,
1427 + "notNull": true
1428 + },
1429 + "alias": {
1430 + "name": "alias",
1431 + "type": "text",
1432 + "primaryKey": false,
1433 + "notNull": true
1434 + },
1435 + "normalized": {
1436 + "name": "normalized",
1437 + "type": "text",
1438 + "primaryKey": false,
1439 + "notNull": true
1440 + },
1441 + "alias_type": {
1442 + "name": "alias_type",
1443 + "type": "text",
1444 + "primaryKey": false,
1445 + "notNull": true,
1446 + "default": "'synonym'"
1447 + },
1448 + "source_id": {
1449 + "name": "source_id",
1450 + "type": "varchar(32)",
1451 + "primaryKey": false,
1452 + "notNull": false
1453 + },
1454 + "source_terminology": {
1455 + "name": "source_terminology",
1456 + "type": "text",
1457 + "primaryKey": false,
1458 + "notNull": false
1459 + },
1460 + "language": {
1461 + "name": "language",
1462 + "type": "text",
1463 + "primaryKey": false,
1464 + "notNull": true,
1465 + "default": "'en'"
1466 + }
1467 + },
1468 + "indexes": {
1469 + "cancer_aliases_uq": {
1470 + "name": "cancer_aliases_uq",
1471 + "columns": [
1472 + {
1473 + "expression": "cancer_id",
1474 + "isExpression": false,
1475 + "asc": true,
1476 + "nulls": "last"
1477 + },
1478 + {
1479 + "expression": "normalized",
1480 + "isExpression": false,
1481 + "asc": true,
1482 + "nulls": "last"
1483 + },
1484 + {
1485 + "expression": "alias_type",
1486 + "isExpression": false,
1487 + "asc": true,
1488 + "nulls": "last"
1489 + }
1490 + ],
1491 + "isUnique": true,
1492 + "concurrently": false,
1493 + "method": "btree",
1494 + "with": {}
1495 + },
1496 + "cancer_aliases_norm_idx": {
1497 + "name": "cancer_aliases_norm_idx",
1498 + "columns": [
1499 + {
1500 + "expression": "normalized",
1501 + "isExpression": false,
1502 + "asc": true,
1503 + "nulls": "last"
1504 + }
1505 + ],
1506 + "isUnique": false,
1507 + "concurrently": false,
1508 + "method": "btree",
1509 + "with": {}
1510 + }
1511 + },
1512 + "foreignKeys": {},
1513 + "compositePrimaryKeys": {},
1514 + "uniqueConstraints": {},
1515 + "policies": {},
1516 + "checkConstraints": {},
1517 + "isRLSEnabled": false
1518 + },
1519 + "public.cancer_anatomy": {
1520 + "name": "cancer_anatomy",
1521 + "schema": "",
1522 + "columns": {
1523 + "id": {
1524 + "name": "id",
1525 + "type": "bigserial",
1526 + "primaryKey": true,
1527 + "notNull": true
1528 + },
1529 + "cancer_id": {
1530 + "name": "cancer_id",
1531 + "type": "varchar(32)",
1532 + "primaryKey": false,
1533 + "notNull": true
1534 + },
1535 + "site_id": {
1536 + "name": "site_id",
1537 + "type": "varchar(32)",
1538 + "primaryKey": false,
1539 + "notNull": true
1540 + },
1541 + "relation": {
1542 + "name": "relation",
1543 + "type": "text",
1544 + "primaryKey": false,
1545 + "notNull": true,
1546 + "default": "'primary'"
1547 + },
1548 + "source_id": {
1549 + "name": "source_id",
1550 + "type": "varchar(32)",
1551 + "primaryKey": false,
1552 + "notNull": false
1553 + }
1554 + },
1555 + "indexes": {
1556 + "cancer_anatomy_uq": {
1557 + "name": "cancer_anatomy_uq",
1558 + "columns": [
1559 + {
1560 + "expression": "cancer_id",
1561 + "isExpression": false,
1562 + "asc": true,
1563 + "nulls": "last"
1564 + },
1565 + {
1566 + "expression": "site_id",
1567 + "isExpression": false,
1568 + "asc": true,
1569 + "nulls": "last"
1570 + },
1571 + {
1572 + "expression": "relation",
1573 + "isExpression": false,
1574 + "asc": true,
1575 + "nulls": "last"
1576 + }
1577 + ],
1578 + "isUnique": true,
1579 + "concurrently": false,
1580 + "method": "btree",
1581 + "with": {}
1582 + }
1583 + },
1584 + "foreignKeys": {},
1585 + "compositePrimaryKeys": {},
1586 + "uniqueConstraints": {},
1587 + "policies": {},
1588 + "checkConstraints": {},
1589 + "isRLSEnabled": false
1590 + },
1591 + "public.cancer_codes": {
1592 + "name": "cancer_codes",
1593 + "schema": "",
1594 + "columns": {
1595 + "id": {
1596 + "name": "id",
1597 + "type": "bigserial",
1598 + "primaryKey": true,
1599 + "notNull": true
1600 + },
1601 + "cancer_id": {
1602 + "name": "cancer_id",
1603 + "type": "varchar(32)",
1604 + "primaryKey": false,
1605 + "notNull": true
1606 + },
1607 + "system": {
1608 + "name": "system",
1609 + "type": "text",
1610 + "primaryKey": false,
1611 + "notNull": true
1612 + },
1613 + "code": {
1614 + "name": "code",
1615 + "type": "text",
1616 + "primaryKey": false,
1617 + "notNull": true
1618 + },
1619 + "match_type": {
1620 + "name": "match_type",
1621 + "type": "text",
1622 + "primaryKey": false,
1623 + "notNull": true,
1624 + "default": "'EXACT_IDENTIFIER'"
1625 + },
1626 + "source_id": {
1627 + "name": "source_id",
1628 + "type": "varchar(32)",
1629 + "primaryKey": false,
1630 + "notNull": false
1631 + },
1632 + "valid_from": {
1633 + "name": "valid_from",
1634 + "type": "text",
1635 + "primaryKey": false,
1636 + "notNull": false
1637 + },
1638 + "valid_to": {
1639 + "name": "valid_to",
1640 + "type": "text",
1641 + "primaryKey": false,
1642 + "notNull": false
1643 + }
1644 + },
1645 + "indexes": {
1646 + "cancer_codes_uq": {
1647 + "name": "cancer_codes_uq",
1648 + "columns": [
1649 + {
1650 + "expression": "cancer_id",
1651 + "isExpression": false,
1652 + "asc": true,
1653 + "nulls": "last"
1654 + },
1655 + {
1656 + "expression": "system",
1657 + "isExpression": false,
1658 + "asc": true,
1659 + "nulls": "last"
1660 + },
1661 + {
1662 + "expression": "code",
1663 + "isExpression": false,
1664 + "asc": true,
1665 + "nulls": "last"
1666 + }
1667 + ],
1668 + "isUnique": true,
1669 + "concurrently": false,
1670 + "method": "btree",
1671 + "with": {}
1672 + },
1673 + "cancer_codes_lookup_idx": {
1674 + "name": "cancer_codes_lookup_idx",
1675 + "columns": [
1676 + {
1677 + "expression": "system",
1678 + "isExpression": false,
1679 + "asc": true,
1680 + "nulls": "last"
1681 + },
1682 + {
1683 + "expression": "code",
1684 + "isExpression": false,
1685 + "asc": true,
1686 + "nulls": "last"
1687 + }
1688 + ],
1689 + "isUnique": false,
1690 + "concurrently": false,
1691 + "method": "btree",
1692 + "with": {}
1693 + }
1694 + },
1695 + "foreignKeys": {},
1696 + "compositePrimaryKeys": {},
1697 + "uniqueConstraints": {},
1698 + "policies": {},
1699 + "checkConstraints": {},
1700 + "isRLSEnabled": false
1701 + },
1702 + "public.cancer_hierarchy": {
1703 + "name": "cancer_hierarchy",
1704 + "schema": "",
1705 + "columns": {
1706 + "id": {
1707 + "name": "id",
1708 + "type": "bigserial",
1709 + "primaryKey": true,
1710 + "notNull": true
1711 + },
1712 + "parent_id": {
1713 + "name": "parent_id",
1714 + "type": "varchar(32)",
1715 + "primaryKey": false,
1716 + "notNull": true
1717 + },
1718 + "child_id": {
1719 + "name": "child_id",
1720 + "type": "varchar(32)",
1721 + "primaryKey": false,
1722 + "notNull": true
1723 + },
1724 + "hierarchy_type": {
1725 + "name": "hierarchy_type",
1726 + "type": "text",
1727 + "primaryKey": false,
1728 + "notNull": true
1729 + },
1730 + "source_id": {
1731 + "name": "source_id",
1732 + "type": "varchar(32)",
1733 + "primaryKey": false,
1734 + "notNull": false
1735 + }
1736 + },
1737 + "indexes": {
1738 + "cancer_hierarchy_uq": {
1739 + "name": "cancer_hierarchy_uq",
1740 + "columns": [
1741 + {
1742 + "expression": "parent_id",
1743 + "isExpression": false,
1744 + "asc": true,
1745 + "nulls": "last"
1746 + },
1747 + {
1748 + "expression": "child_id",
1749 + "isExpression": false,
1750 + "asc": true,
1751 + "nulls": "last"
1752 + },
1753 + {
1754 + "expression": "hierarchy_type",
1755 + "isExpression": false,
1756 + "asc": true,
1757 + "nulls": "last"
1758 + }
1759 + ],
1760 + "isUnique": true,
1761 + "concurrently": false,
1762 + "method": "btree",
1763 + "with": {}
1764 + },
1765 + "cancer_hierarchy_child_idx": {
1766 + "name": "cancer_hierarchy_child_idx",
1767 + "columns": [
1768 + {
1769 + "expression": "child_id",
1770 + "isExpression": false,
1771 + "asc": true,
1772 + "nulls": "last"
1773 + }
1774 + ],
1775 + "isUnique": false,
1776 + "concurrently": false,
1777 + "method": "btree",
1778 + "with": {}
1779 + }
1780 + },
1781 + "foreignKeys": {},
1782 + "compositePrimaryKeys": {},
1783 + "uniqueConstraints": {},
1784 + "policies": {},
1785 + "checkConstraints": {},
1786 + "isRLSEnabled": false
1787 + },
1788 + "public.cancers": {
1789 + "name": "cancers",
1790 + "schema": "",
1791 + "columns": {
1792 + "id": {
1793 + "name": "id",
1794 + "type": "varchar(32)",
1795 + "primaryKey": true,
1796 + "notNull": true
1797 + },
1798 + "slug": {
1799 + "name": "slug",
1800 + "type": "text",
1801 + "primaryKey": false,
1802 + "notNull": true
1803 + },
1804 + "canonical_name": {
1805 + "name": "canonical_name",
1806 + "type": "text",
1807 + "primaryKey": false,
1808 + "notNull": true
1809 + },
1810 + "short_name": {
1811 + "name": "short_name",
1812 + "type": "text",
1813 + "primaryKey": false,
1814 + "notNull": false
1815 + },
1816 + "entity_type": {
1817 + "name": "entity_type",
1818 + "type": "text",
1819 + "primaryKey": false,
1820 + "notNull": true,
1821 + "default": "'cancer'"
1822 + },
1823 + "malignant": {
1824 + "name": "malignant",
1825 + "type": "boolean",
1826 + "primaryKey": false,
1827 + "notNull": true,
1828 + "default": true
1829 + },
1830 + "solid_tumor": {
1831 + "name": "solid_tumor",
1832 + "type": "boolean",
1833 + "primaryKey": false,
1834 + "notNull": true,
1835 + "default": true
1836 + },
1837 + "hematologic": {
1838 + "name": "hematologic",
1839 + "type": "boolean",
1840 + "primaryKey": false,
1841 + "notNull": true,
1842 + "default": false
1843 + },
1844 + "pediatric_relevant": {
1845 + "name": "pediatric_relevant",
1846 + "type": "boolean",
1847 + "primaryKey": false,
1848 + "notNull": true,
1849 + "default": false
1850 + },
1851 + "rare_cancer": {
1852 + "name": "rare_cancer",
1853 + "type": "boolean",
1854 + "primaryKey": false,
1855 + "notNull": false
1856 + },
1857 + "top_level": {
1858 + "name": "top_level",
1859 + "type": "boolean",
1860 + "primaryKey": false,
1861 + "notNull": true,
1862 + "default": false
1863 + },
1864 + "description": {
1865 + "name": "description",
1866 + "type": "text",
1867 + "primaryKey": false,
1868 + "notNull": false
1869 + },
1870 + "description_provenance_id": {
1871 + "name": "description_provenance_id",
1872 + "type": "integer",
1873 + "primaryKey": false,
1874 + "notNull": false
1875 + },
1876 + "primary_ncit_code": {
1877 + "name": "primary_ncit_code",
1878 + "type": "text",
1879 + "primaryKey": false,
1880 + "notNull": false
1881 + },
1882 + "primary_oncotree_code": {
1883 + "name": "primary_oncotree_code",
1884 + "type": "text",
1885 + "primaryKey": false,
1886 + "notNull": false
1887 + },
1888 + "depth": {
1889 + "name": "depth",
1890 + "type": "integer",
1891 + "primaryKey": false,
1892 + "notNull": true,
1893 + "default": 0
1894 + },
1895 + "status": {
1896 + "name": "status",
1897 + "type": "text",
1898 + "primaryKey": false,
1899 + "notNull": true,
1900 + "default": "'active'"
1901 + },
1902 + "merged_into": {
1903 + "name": "merged_into",
1904 + "type": "varchar(32)",
1905 + "primaryKey": false,
1906 + "notNull": false
1907 + },
1908 + "deprecated_reason": {
1909 + "name": "deprecated_reason",
1910 + "type": "text",
1911 + "primaryKey": false,
1912 + "notNull": false
1913 + },
1914 + "classification_version": {
1915 + "name": "classification_version",
1916 + "type": "text",
1917 + "primaryKey": false,
1918 + "notNull": false
1919 + },
1920 + "semantic_types": {
1921 + "name": "semantic_types",
1922 + "type": "text[]",
1923 + "primaryKey": false,
1924 + "notNull": true,
1925 + "default": "'{}'"
1926 + },
1927 + "created_at": {
1928 + "name": "created_at",
1929 + "type": "timestamp with time zone",
1930 + "primaryKey": false,
1931 + "notNull": true,
1932 + "default": "now()"
1933 + },
1934 + "updated_at": {
1935 + "name": "updated_at",
1936 + "type": "timestamp with time zone",
1937 + "primaryKey": false,
1938 + "notNull": true,
1939 + "default": "now()"
1940 + }
1941 + },
1942 + "indexes": {
1943 + "cancers_slug_uq": {
1944 + "name": "cancers_slug_uq",
1945 + "columns": [
1946 + {
1947 + "expression": "slug",
1948 + "isExpression": false,
1949 + "asc": true,
1950 + "nulls": "last"
1951 + }
1952 + ],
1953 + "isUnique": true,
1954 + "concurrently": false,
1955 + "method": "btree",
1956 + "with": {}
1957 + },
1958 + "cancers_ncit_uq": {
1959 + "name": "cancers_ncit_uq",
1960 + "columns": [
1961 + {
1962 + "expression": "primary_ncit_code",
1963 + "isExpression": false,
1964 + "asc": true,
1965 + "nulls": "last"
1966 + }
1967 + ],
1968 + "isUnique": true,
1969 + "concurrently": false,
1970 + "method": "btree",
1971 + "with": {}
1972 + },
1973 + "cancers_name_idx": {
1974 + "name": "cancers_name_idx",
1975 + "columns": [
1976 + {
1977 + "expression": "canonical_name",
1978 + "isExpression": false,
1979 + "asc": true,
1980 + "nulls": "last"
1981 + }
1982 + ],
1983 + "isUnique": false,
1984 + "concurrently": false,
1985 + "method": "btree",
1986 + "with": {}
1987 + },
1988 + "cancers_type_idx": {
1989 + "name": "cancers_type_idx",
1990 + "columns": [
1991 + {
1992 + "expression": "entity_type",
1993 + "isExpression": false,
1994 + "asc": true,
1995 + "nulls": "last"
1996 + },
1997 + {
1998 + "expression": "malignant",
1999 + "isExpression": false,
2000 + "asc": true,
2001 + "nulls": "last"
2002 + },
2003 + {
2004 + "expression": "top_level",
2005 + "isExpression": false,
2006 + "asc": true,
2007 + "nulls": "last"
2008 + }
2009 + ],
2010 + "isUnique": false,
2011 + "concurrently": false,
2012 + "method": "btree",
2013 + "with": {}
2014 + }
2015 + },
2016 + "foreignKeys": {},
2017 + "compositePrimaryKeys": {},
2018 + "uniqueConstraints": {},
2019 + "policies": {},
2020 + "checkConstraints": {},
2021 + "isRLSEnabled": false
2022 + },
2023 + "public.cohort_definitions": {
2024 + "name": "cohort_definitions",
2025 + "schema": "",
2026 + "columns": {
2027 + "id": {
2028 + "name": "id",
2029 + "type": "bigserial",
2030 + "primaryKey": true,
2031 + "notNull": true
2032 + },
2033 + "name": {
2034 + "name": "name",
2035 + "type": "text",
2036 + "primaryKey": false,
2037 + "notNull": true
2038 + },
2039 + "cancer_id": {
2040 + "name": "cancer_id",
2041 + "type": "varchar(32)",
2042 + "primaryKey": false,
2043 + "notNull": true
2044 + },
2045 + "biomarker_ids": {
2046 + "name": "biomarker_ids",
2047 + "type": "text[]",
2048 + "primaryKey": false,
2049 + "notNull": true,
2050 + "default": "'{}'"
2051 + },
2052 + "variant_ids": {
2053 + "name": "variant_ids",
2054 + "type": "text[]",
2055 + "primaryKey": false,
2056 + "notNull": true,
2057 + "default": "'{}'"
2058 + },
2059 + "stage": {
2060 + "name": "stage",
2061 + "type": "text",
2062 + "primaryKey": false,
2063 + "notNull": false
2064 + },
2065 + "attributes": {
2066 + "name": "attributes",
2067 + "type": "jsonb",
2068 + "primaryKey": false,
2069 + "notNull": true,
2070 + "default": "'{}'::jsonb"
2071 + },
2072 + "confidence": {
2073 + "name": "confidence",
2074 + "type": "real",
2075 + "primaryKey": false,
2076 + "notNull": false
2077 + },
2078 + "created_at": {
2079 + "name": "created_at",
2080 + "type": "timestamp with time zone",
2081 + "primaryKey": false,
2082 + "notNull": true,
2083 + "default": "now()"
2084 + }
2085 + },
2086 + "indexes": {},
2087 + "foreignKeys": {},
2088 + "compositePrimaryKeys": {},
2089 + "uniqueConstraints": {},
2090 + "policies": {},
2091 + "checkConstraints": {},
2092 + "isRLSEnabled": false
2093 + },
2094 + "public.geographies": {
2095 + "name": "geographies",
2096 + "schema": "",
2097 + "columns": {
2098 + "id": {
2099 + "name": "id",
2100 + "type": "varchar(32)",
2101 + "primaryKey": true,
2102 + "notNull": true
2103 + },
2104 + "slug": {
2105 + "name": "slug",
2106 + "type": "text",
2107 + "primaryKey": false,
2108 + "notNull": true
2109 + },
2110 + "name": {
2111 + "name": "name",
2112 + "type": "text",
2113 + "primaryKey": false,
2114 + "notNull": true
2115 + },
2116 + "kind": {
2117 + "name": "kind",
2118 + "type": "text",
2119 + "primaryKey": false,
2120 + "notNull": true
2121 + },
2122 + "iso2": {
2123 + "name": "iso2",
2124 + "type": "text",
2125 + "primaryKey": false,
2126 + "notNull": false
2127 + },
2128 + "iso3": {
2129 + "name": "iso3",
2130 + "type": "text",
2131 + "primaryKey": false,
2132 + "notNull": false
2133 + },
2134 + "parent_id": {
2135 + "name": "parent_id",
2136 + "type": "varchar(32)",
2137 + "primaryKey": false,
2138 + "notNull": false
2139 + },
2140 + "who_region": {
2141 + "name": "who_region",
2142 + "type": "text",
2143 + "primaryKey": false,
2144 + "notNull": false
2145 + },
2146 + "population": {
2147 + "name": "population",
2148 + "type": "integer",
2149 + "primaryKey": false,
2150 + "notNull": false
2151 + },
2152 + "population_year": {
2153 + "name": "population_year",
2154 + "type": "integer",
2155 + "primaryKey": false,
2156 + "notNull": false
2157 + }
2158 + },
2159 + "indexes": {
2160 + "geographies_slug_uq": {
2161 + "name": "geographies_slug_uq",
2162 + "columns": [
2163 + {
2164 + "expression": "slug",
2165 + "isExpression": false,
2166 + "asc": true,
2167 + "nulls": "last"
2168 + }
2169 + ],
2170 + "isUnique": true,
2171 + "concurrently": false,
2172 + "method": "btree",
2173 + "with": {}
2174 + },
2175 + "geographies_iso3_idx": {
2176 + "name": "geographies_iso3_idx",
2177 + "columns": [
2178 + {
2179 + "expression": "iso3",
2180 + "isExpression": false,
2181 + "asc": true,
2182 + "nulls": "last"
2183 + }
2184 + ],
2185 + "isUnique": false,
2186 + "concurrently": false,
2187 + "method": "btree",
2188 + "with": {}
2189 + }
2190 + },
2191 + "foreignKeys": {},
2192 + "compositePrimaryKeys": {},
2193 + "uniqueConstraints": {},
2194 + "policies": {},
2195 + "checkConstraints": {},
2196 + "isRLSEnabled": false
2197 + },
2198 + "public.biomarkers": {
2199 + "name": "biomarkers",
2200 + "schema": "",
2201 + "columns": {
2202 + "id": {
2203 + "name": "id",
2204 + "type": "varchar(32)",
2205 + "primaryKey": true,
2206 + "notNull": true
2207 + },
2208 + "slug": {
2209 + "name": "slug",
2210 + "type": "text",
2211 + "primaryKey": false,
2212 + "notNull": true
2213 + },
2214 + "name": {
2215 + "name": "name",
2216 + "type": "text",
2217 + "primaryKey": false,
2218 + "notNull": true
2219 + },
2220 + "kind": {
2221 + "name": "kind",
2222 + "type": "text",
2223 + "primaryKey": false,
2224 + "notNull": true
2225 + },
2226 + "gene_id": {
2227 + "name": "gene_id",
2228 + "type": "varchar(32)",
2229 + "primaryKey": false,
2230 + "notNull": false
2231 + },
2232 + "ncit_code": {
2233 + "name": "ncit_code",
2234 + "type": "text",
2235 + "primaryKey": false,
2236 + "notNull": false
2237 + },
2238 + "description": {
2239 + "name": "description",
2240 + "type": "text",
2241 + "primaryKey": false,
2242 + "notNull": false
2243 + },
2244 + "measurement": {
2245 + "name": "measurement",
2246 + "type": "jsonb",
2247 + "primaryKey": false,
2248 + "notNull": true,
2249 + "default": "'{}'::jsonb"
2250 + },
2251 + "created_at": {
2252 + "name": "created_at",
2253 + "type": "timestamp with time zone",
2254 + "primaryKey": false,
2255 + "notNull": true,
2256 + "default": "now()"
2257 + },
2258 + "updated_at": {
2259 + "name": "updated_at",
2260 + "type": "timestamp with time zone",
2261 + "primaryKey": false,
2262 + "notNull": true,
2263 + "default": "now()"
2264 + }
2265 + },
2266 + "indexes": {
2267 + "biomarkers_slug_uq": {
2268 + "name": "biomarkers_slug_uq",
2269 + "columns": [
2270 + {
2271 + "expression": "slug",
2272 + "isExpression": false,
2273 + "asc": true,
2274 + "nulls": "last"
2275 + }
2276 + ],
2277 + "isUnique": true,
2278 + "concurrently": false,
2279 + "method": "btree",
2280 + "with": {}
2281 + }
2282 + },
2283 + "foreignKeys": {},
2284 + "compositePrimaryKeys": {},
2285 + "uniqueConstraints": {},
2286 + "policies": {},
2287 + "checkConstraints": {},
2288 + "isRLSEnabled": false
2289 + },
2290 + "public.cancer_gene_frequencies": {
2291 + "name": "cancer_gene_frequencies",
2292 + "schema": "",
2293 + "columns": {
2294 + "id": {
2295 + "name": "id",
2296 + "type": "bigserial",
2297 + "primaryKey": true,
2298 + "notNull": true
2299 + },
2300 + "cohort_id": {
2301 + "name": "cohort_id",
2302 + "type": "varchar(32)",
2303 + "primaryKey": false,
2304 + "notNull": true
2305 + },
2306 + "cancer_id": {
2307 + "name": "cancer_id",
2308 + "type": "varchar(32)",
2309 + "primaryKey": false,
2310 + "notNull": false
2311 + },
2312 + "gene_id": {
2313 + "name": "gene_id",
2314 + "type": "varchar(32)",
2315 + "primaryKey": false,
2316 + "notNull": false
2317 + },
2318 + "gene_symbol": {
2319 + "name": "gene_symbol",
2320 + "type": "text",
2321 + "primaryKey": false,
2322 + "notNull": true
2323 + },
2324 + "alteration_type": {
2325 + "name": "alteration_type",
2326 + "type": "text",
2327 + "primaryKey": false,
2328 + "notNull": true,
2329 + "default": "'ssm'"
2330 + },
2331 + "cases_affected": {
2332 + "name": "cases_affected",
2333 + "type": "integer",
2334 + "primaryKey": false,
2335 + "notNull": true
2336 + },
2337 + "cases_profiled": {
2338 + "name": "cases_profiled",
2339 + "type": "integer",
2340 + "primaryKey": false,
2341 + "notNull": true
2342 + },
2343 + "frequency": {
2344 + "name": "frequency",
2345 + "type": "real",
2346 + "primaryKey": false,
2347 + "notNull": true
2348 + },
2349 + "rank": {
2350 + "name": "rank",
2351 + "type": "integer",
2352 + "primaryKey": false,
2353 + "notNull": false
2354 + },
2355 + "data_release": {
2356 + "name": "data_release",
2357 + "type": "text",
2358 + "primaryKey": false,
2359 + "notNull": false
2360 + },
2361 + "provenance_id": {
2362 + "name": "provenance_id",
2363 + "type": "integer",
2364 + "primaryKey": false,
2365 + "notNull": true
2366 + },
2367 + "updated_at": {
2368 + "name": "updated_at",
2369 + "type": "timestamp with time zone",
2370 + "primaryKey": false,
2371 + "notNull": true,
2372 + "default": "now()"
2373 + }
2374 + },
2375 + "indexes": {
2376 + "cancer_gene_freq_uq": {
2377 + "name": "cancer_gene_freq_uq",
2378 + "columns": [
2379 + {
2380 + "expression": "cohort_id",
2381 + "isExpression": false,
2382 + "asc": true,
2383 + "nulls": "last"
2384 + },
2385 + {
2386 + "expression": "gene_symbol",
2387 + "isExpression": false,
2388 + "asc": true,
2389 + "nulls": "last"
2390 + },
2391 + {
2392 + "expression": "alteration_type",
2393 + "isExpression": false,
2394 + "asc": true,
2395 + "nulls": "last"
2396 + }
2397 + ],
2398 + "isUnique": true,
2399 + "concurrently": false,
2400 + "method": "btree",
2401 + "with": {}
2402 + },
2403 + "cancer_gene_freq_cancer_idx": {
2404 + "name": "cancer_gene_freq_cancer_idx",
2405 + "columns": [
2406 + {
2407 + "expression": "cancer_id",
2408 + "isExpression": false,
2409 + "asc": true,
2410 + "nulls": "last"
2411 + },
2412 + {
2413 + "expression": "frequency",
2414 + "isExpression": false,
2415 + "asc": true,
2416 + "nulls": "last"
2417 + }
2418 + ],
2419 + "isUnique": false,
2420 + "concurrently": false,
2421 + "method": "btree",
2422 + "with": {}
2423 + },
2424 + "cancer_gene_freq_gene_idx": {
2425 + "name": "cancer_gene_freq_gene_idx",
2426 + "columns": [
2427 + {
2428 + "expression": "gene_id",
2429 + "isExpression": false,
2430 + "asc": true,
2431 + "nulls": "last"
2432 + }
2433 + ],
2434 + "isUnique": false,
2435 + "concurrently": false,
2436 + "method": "btree",
2437 + "with": {}
2438 + }
2439 + },
2440 + "foreignKeys": {},
2441 + "compositePrimaryKeys": {},
2442 + "uniqueConstraints": {},
2443 + "policies": {},
2444 + "checkConstraints": {},
2445 + "isRLSEnabled": false
2446 + },
2447 + "public.entity_embeddings": {
2448 + "name": "entity_embeddings",
2449 + "schema": "",
2450 + "columns": {
2451 + "id": {
2452 + "name": "id",
2453 + "type": "bigserial",
2454 + "primaryKey": true,
2455 + "notNull": true
2456 + },
2457 + "entity_type": {
2458 + "name": "entity_type",
2459 + "type": "text",
2460 + "primaryKey": false,
2461 + "notNull": true
2462 + },
2463 + "entity_id": {
2464 + "name": "entity_id",
2465 + "type": "text",
2466 + "primaryKey": false,
2467 + "notNull": true
2468 + },
2469 + "model": {
2470 + "name": "model",
2471 + "type": "text",
2472 + "primaryKey": false,
2473 + "notNull": true
2474 + },
2475 + "dimensions": {
2476 + "name": "dimensions",
2477 + "type": "integer",
2478 + "primaryKey": false,
2479 + "notNull": true
2480 + },
2481 + "text_hash": {
2482 + "name": "text_hash",
2483 + "type": "text",
2484 + "primaryKey": false,
2485 + "notNull": true
2486 + },
2487 + "embedding": {
2488 + "name": "embedding",
2489 + "type": "text",
2490 + "primaryKey": false,
2491 + "notNull": false
2492 + },
2493 + "created_at": {
2494 + "name": "created_at",
2495 + "type": "timestamp with time zone",
2496 + "primaryKey": false,
2497 + "notNull": true,
2498 + "default": "now()"
2499 + }
2500 + },
2501 + "indexes": {
2502 + "entity_embeddings_uq": {
2503 + "name": "entity_embeddings_uq",
2504 + "columns": [
2505 + {
2506 + "expression": "entity_type",
2507 + "isExpression": false,
2508 + "asc": true,
2509 + "nulls": "last"
2510 + },
2511 + {
2512 + "expression": "entity_id",
2513 + "isExpression": false,
2514 + "asc": true,
2515 + "nulls": "last"
2516 + },
2517 + {
2518 + "expression": "model",
2519 + "isExpression": false,
2520 + "asc": true,
2521 + "nulls": "last"
2522 + }
2523 + ],
2524 + "isUnique": true,
2525 + "concurrently": false,
2526 + "method": "btree",
2527 + "with": {}
2528 + }
2529 + },
2530 + "foreignKeys": {},
2531 + "compositePrimaryKeys": {},
2532 + "uniqueConstraints": {},
2533 + "policies": {},
2534 + "checkConstraints": {},
2535 + "isRLSEnabled": false
2536 + },
2537 + "public.gene_aliases": {
2538 + "name": "gene_aliases",
2539 + "schema": "",
2540 + "columns": {
2541 + "id": {
2542 + "name": "id",
2543 + "type": "bigserial",
2544 + "primaryKey": true,
2545 + "notNull": true
2546 + },
2547 + "gene_id": {
2548 + "name": "gene_id",
2549 + "type": "varchar(32)",
2550 + "primaryKey": false,
2551 + "notNull": true
2552 + },
2553 + "alias": {
2554 + "name": "alias",
2555 + "type": "text",
2556 + "primaryKey": false,
2557 + "notNull": true
2558 + },
2559 + "alias_type": {
2560 + "name": "alias_type",
2561 + "type": "text",
2562 + "primaryKey": false,
2563 + "notNull": true
2564 + },
2565 + "source_id": {
2566 + "name": "source_id",
2567 + "type": "varchar(32)",
2568 + "primaryKey": false,
2569 + "notNull": false
2570 + }
2571 + },
2572 + "indexes": {
2573 + "gene_aliases_uq": {
2574 + "name": "gene_aliases_uq",
2575 + "columns": [
2576 + {
2577 + "expression": "gene_id",
2578 + "isExpression": false,
2579 + "asc": true,
2580 + "nulls": "last"
2581 + },
2582 + {
2583 + "expression": "alias",
2584 + "isExpression": false,
2585 + "asc": true,
2586 + "nulls": "last"
2587 + },
2588 + {
2589 + "expression": "alias_type",
2590 + "isExpression": false,
2591 + "asc": true,
2592 + "nulls": "last"
2593 + }
2594 + ],
2595 + "isUnique": true,
2596 + "concurrently": false,
2597 + "method": "btree",
2598 + "with": {}
2599 + },
2600 + "gene_aliases_alias_idx": {
2601 + "name": "gene_aliases_alias_idx",
2602 + "columns": [
2603 + {
2604 + "expression": "alias",
2605 + "isExpression": false,
2606 + "asc": true,
2607 + "nulls": "last"
2608 + }
2609 + ],
2610 + "isUnique": false,
2611 + "concurrently": false,
2612 + "method": "btree",
2613 + "with": {}
2614 + }
2615 + },
2616 + "foreignKeys": {},
2617 + "compositePrimaryKeys": {},
2618 + "uniqueConstraints": {},
2619 + "policies": {},
2620 + "checkConstraints": {},
2621 + "isRLSEnabled": false
2622 + },
2623 + "public.genes": {
2624 + "name": "genes",
2625 + "schema": "",
2626 + "columns": {
2627 + "id": {
2628 + "name": "id",
2629 + "type": "varchar(32)",
2630 + "primaryKey": true,
2631 + "notNull": true
2632 + },
2633 + "hgnc_id": {
2634 + "name": "hgnc_id",
2635 + "type": "text",
2636 + "primaryKey": false,
2637 + "notNull": false
2638 + },
2639 + "symbol": {
2640 + "name": "symbol",
2641 + "type": "text",
2642 + "primaryKey": false,
2643 + "notNull": true
2644 + },
2645 + "name": {
2646 + "name": "name",
2647 + "type": "text",
2648 + "primaryKey": false,
2649 + "notNull": false
2650 + },
2651 + "locus_type": {
2652 + "name": "locus_type",
2653 + "type": "text",
2654 + "primaryKey": false,
2655 + "notNull": false
2656 + },
2657 + "locus_group": {
2658 + "name": "locus_group",
2659 + "type": "text",
2660 + "primaryKey": false,
2661 + "notNull": false
2662 + },
2663 + "location": {
2664 + "name": "location",
2665 + "type": "text",
2666 + "primaryKey": false,
2667 + "notNull": false
2668 + },
2669 + "chromosome": {
2670 + "name": "chromosome",
2671 + "type": "text",
2672 + "primaryKey": false,
2673 + "notNull": false
2674 + },
2675 + "ensembl_gene_id": {
2676 + "name": "ensembl_gene_id",
2677 + "type": "text",
2678 + "primaryKey": false,
2679 + "notNull": false
2680 + },
2681 + "ncbi_gene_id": {
2682 + "name": "ncbi_gene_id",
2683 + "type": "text",
2684 + "primaryKey": false,
2685 + "notNull": false
2686 + },
2687 + "omim_ids": {
2688 + "name": "omim_ids",
2689 + "type": "text[]",
2690 + "primaryKey": false,
2691 + "notNull": true,
2692 + "default": "'{}'"
2693 + },
2694 + "uniprot_ids": {
2695 + "name": "uniprot_ids",
2696 + "type": "text[]",
2697 + "primaryKey": false,
2698 + "notNull": true,
2699 + "default": "'{}'"
2700 + },
2701 + "refseq_accession": {
2702 + "name": "refseq_accession",
2703 + "type": "text",
2704 + "primaryKey": false,
2705 + "notNull": false
2706 + },
2707 + "prev_symbols": {
2708 + "name": "prev_symbols",
2709 + "type": "text[]",
2710 + "primaryKey": false,
2711 + "notNull": true,
2712 + "default": "'{}'"
2713 + },
2714 + "alias_symbols": {
2715 + "name": "alias_symbols",
2716 + "type": "text[]",
2717 + "primaryKey": false,
2718 + "notNull": true,
2719 + "default": "'{}'"
2720 + },
2721 + "gene_families": {
2722 + "name": "gene_families",
2723 + "type": "text[]",
2724 + "primaryKey": false,
2725 + "notNull": true,
2726 + "default": "'{}'"
2727 + },
2728 + "status": {
2729 + "name": "status",
2730 + "type": "text",
2731 + "primaryKey": false,
2732 + "notNull": true,
2733 + "default": "'Approved'"
2734 + },
2735 + "is_cancer_gene": {
2736 + "name": "is_cancer_gene",
2737 + "type": "boolean",
2738 + "primaryKey": false,
2739 + "notNull": true,
2740 + "default": false
2741 + },
2742 + "civic_gene_id": {
2743 + "name": "civic_gene_id",
2744 + "type": "integer",
2745 + "primaryKey": false,
2746 + "notNull": false
2747 + },
2748 + "description": {
2749 + "name": "description",
2750 + "type": "text",
2751 + "primaryKey": false,
2752 + "notNull": false
2753 + },
2754 + "created_at": {
2755 + "name": "created_at",
2756 + "type": "timestamp with time zone",
2757 + "primaryKey": false,
2758 + "notNull": true,
2759 + "default": "now()"
2760 + },
2761 + "updated_at": {
2762 + "name": "updated_at",
2763 + "type": "timestamp with time zone",
2764 + "primaryKey": false,
2765 + "notNull": true,
2766 + "default": "now()"
2767 + }
2768 + },
2769 + "indexes": {
2770 + "genes_symbol_uq": {
2771 + "name": "genes_symbol_uq",
2772 + "columns": [
2773 + {
2774 + "expression": "symbol",
2775 + "isExpression": false,
2776 + "asc": true,
2777 + "nulls": "last"
2778 + }
2779 + ],
2780 + "isUnique": true,
2781 + "concurrently": false,
2782 + "method": "btree",
2783 + "with": {}
2784 + },
2785 + "genes_hgnc_uq": {
2786 + "name": "genes_hgnc_uq",
2787 + "columns": [
2788 + {
2789 + "expression": "hgnc_id",
2790 + "isExpression": false,
2791 + "asc": true,
2792 + "nulls": "last"
2793 + }
2794 + ],
2795 + "isUnique": true,
2796 + "concurrently": false,
2797 + "method": "btree",
2798 + "with": {}
2799 + },
2800 + "genes_ensembl_idx": {
2801 + "name": "genes_ensembl_idx",
2802 + "columns": [
2803 + {
2804 + "expression": "ensembl_gene_id",
2805 + "isExpression": false,
2806 + "asc": true,
2807 + "nulls": "last"
2808 + }
2809 + ],
2810 + "isUnique": false,
2811 + "concurrently": false,
2812 + "method": "btree",
2813 + "with": {}
2814 + },
2815 + "genes_ncbi_idx": {
2816 + "name": "genes_ncbi_idx",
2817 + "columns": [
2818 + {
2819 + "expression": "ncbi_gene_id",
2820 + "isExpression": false,
2821 + "asc": true,
2822 + "nulls": "last"
2823 + }
2824 + ],
2825 + "isUnique": false,
2826 + "concurrently": false,
2827 + "method": "btree",
2828 + "with": {}
2829 + }
2830 + },
2831 + "foreignKeys": {},
2832 + "compositePrimaryKeys": {},
2833 + "uniqueConstraints": {},
2834 + "policies": {},
2835 + "checkConstraints": {},
2836 + "isRLSEnabled": false
2837 + },
2838 + "public.genomic_cohorts": {
2839 + "name": "genomic_cohorts",
2840 + "schema": "",
2841 + "columns": {
2842 + "id": {
2843 + "name": "id",
2844 + "type": "varchar(32)",
2845 + "primaryKey": true,
2846 + "notNull": true
2847 + },
2848 + "source_id": {
2849 + "name": "source_id",
2850 + "type": "varchar(32)",
2851 + "primaryKey": false,
2852 + "notNull": true
2853 + },
2854 + "study_id": {
2855 + "name": "study_id",
2856 + "type": "text",
2857 + "primaryKey": false,
2858 + "notNull": true
2859 + },
2860 + "name": {
2861 + "name": "name",
2862 + "type": "text",
2863 + "primaryKey": false,
2864 + "notNull": true
2865 + },
2866 + "program": {
2867 + "name": "program",
2868 + "type": "text",
2869 + "primaryKey": false,
2870 + "notNull": false
2871 + },
2872 + "primary_sites": {
2873 + "name": "primary_sites",
2874 + "type": "text[]",
2875 + "primaryKey": false,
2876 + "notNull": true,
2877 + "default": "'{}'"
2878 + },
2879 + "disease_types": {
2880 + "name": "disease_types",
2881 + "type": "text[]",
2882 + "primaryKey": false,
2883 + "notNull": true,
2884 + "default": "'{}'"
2885 + },
2886 + "cancer_id": {
2887 + "name": "cancer_id",
2888 + "type": "varchar(32)",
2889 + "primaryKey": false,
2890 + "notNull": false
2891 + },
2892 + "cancer_match_type": {
2893 + "name": "cancer_match_type",
2894 + "type": "text",
2895 + "primaryKey": false,
2896 + "notNull": false
2897 + },
2898 + "case_count": {
2899 + "name": "case_count",
2900 + "type": "integer",
2901 + "primaryKey": false,
2902 + "notNull": false
2903 + },
2904 + "cases_with_ssm": {
2905 + "name": "cases_with_ssm",
2906 + "type": "integer",
2907 + "primaryKey": false,
2908 + "notNull": false
2909 + },
2910 + "data_release": {
2911 + "name": "data_release",
2912 + "type": "text",
2913 + "primaryKey": false,
2914 + "notNull": false
2915 + },
2916 + "access_level": {
2917 + "name": "access_level",
2918 + "type": "text",
2919 + "primaryKey": false,
2920 + "notNull": true,
2921 + "default": "'open'"
2922 + },
2923 + "url": {
2924 + "name": "url",
2925 + "type": "text",
2926 + "primaryKey": false,
2927 + "notNull": false
2928 + },
2929 + "provenance_id": {
2930 + "name": "provenance_id",
2931 + "type": "integer",
2932 + "primaryKey": false,
2933 + "notNull": false
2934 + },
2935 + "updated_at": {
2936 + "name": "updated_at",
2937 + "type": "timestamp with time zone",
2938 + "primaryKey": false,
2939 + "notNull": true,
2940 + "default": "now()"
2941 + }
2942 + },
2943 + "indexes": {
2944 + "genomic_cohorts_uq": {
2945 + "name": "genomic_cohorts_uq",
2946 + "columns": [
2947 + {
2948 + "expression": "source_id",
2949 + "isExpression": false,
2950 + "asc": true,
2951 + "nulls": "last"
2952 + },
2953 + {
2954 + "expression": "study_id",
2955 + "isExpression": false,
2956 + "asc": true,
2957 + "nulls": "last"
2958 + }
2959 + ],
2960 + "isUnique": true,
2961 + "concurrently": false,
2962 + "method": "btree",
2963 + "with": {}
2964 + },
2965 + "genomic_cohorts_cancer_idx": {
2966 + "name": "genomic_cohorts_cancer_idx",
2967 + "columns": [
2968 + {
2969 + "expression": "cancer_id",
2970 + "isExpression": false,
2971 + "asc": true,
2972 + "nulls": "last"
2973 + }
2974 + ],
2975 + "isUnique": false,
2976 + "concurrently": false,
2977 + "method": "btree",
2978 + "with": {}
2979 + }
2980 + },
2981 + "foreignKeys": {},
2982 + "compositePrimaryKeys": {},
2983 + "uniqueConstraints": {},
2984 + "policies": {},
2985 + "checkConstraints": {},
2986 + "isRLSEnabled": false
2987 + },
2988 + "public.variant_aliases": {
2989 + "name": "variant_aliases",
2990 + "schema": "",
2991 + "columns": {
2992 + "id": {
2993 + "name": "id",
2994 + "type": "bigserial",
2995 + "primaryKey": true,
2996 + "notNull": true
2997 + },
2998 + "variant_id": {
2999 + "name": "variant_id",
3000 + "type": "varchar(32)",
3001 + "primaryKey": false,
3002 + "notNull": true
3003 + },
3004 + "alias": {
3005 + "name": "alias",
3006 + "type": "text",
3007 + "primaryKey": false,
3008 + "notNull": true
3009 + },
3010 + "source_id": {
3011 + "name": "source_id",
3012 + "type": "varchar(32)",
3013 + "primaryKey": false,
3014 + "notNull": false
3015 + }
3016 + },
3017 + "indexes": {
3018 + "variant_aliases_uq": {
3019 + "name": "variant_aliases_uq",
3020 + "columns": [
3021 + {
3022 + "expression": "variant_id",
3023 + "isExpression": false,
3024 + "asc": true,
3025 + "nulls": "last"
3026 + },
3027 + {
3028 + "expression": "alias",
3029 + "isExpression": false,
3030 + "asc": true,
3031 + "nulls": "last"
3032 + }
3033 + ],
3034 + "isUnique": true,
3035 + "concurrently": false,
3036 + "method": "btree",
3037 + "with": {}
3038 + }
3039 + },
3040 + "foreignKeys": {},
3041 + "compositePrimaryKeys": {},
3042 + "uniqueConstraints": {},
3043 + "policies": {},
3044 + "checkConstraints": {},
3045 + "isRLSEnabled": false
3046 + },
3047 + "public.variant_clinical_significance": {
3048 + "name": "variant_clinical_significance",
3049 + "schema": "",
3050 + "columns": {
3051 + "id": {
3052 + "name": "id",
3053 + "type": "bigserial",
3054 + "primaryKey": true,
3055 + "notNull": true
3056 + },
3057 + "variant_id": {
3058 + "name": "variant_id",
3059 + "type": "varchar(32)",
3060 + "primaryKey": false,
3061 + "notNull": true
3062 + },
3063 + "clinvar_variation_id": {
3064 + "name": "clinvar_variation_id",
3065 + "type": "text",
3066 + "primaryKey": false,
3067 + "notNull": true
3068 + },
3069 + "clinical_significance": {
3070 + "name": "clinical_significance",
3071 + "type": "text",
3072 + "primaryKey": false,
3073 + "notNull": true
3074 + },
3075 + "review_status": {
3076 + "name": "review_status",
3077 + "type": "text",
3078 + "primaryKey": false,
3079 + "notNull": false
3080 + },
3081 + "star_rating": {
3082 + "name": "star_rating",
3083 + "type": "integer",
3084 + "primaryKey": false,
3085 + "notNull": false
3086 + },
3087 + "last_evaluated": {
3088 + "name": "last_evaluated",
3089 + "type": "text",
3090 + "primaryKey": false,
3091 + "notNull": false
3092 + },
3093 + "conditions": {
3094 + "name": "conditions",
3095 + "type": "text[]",
3096 + "primaryKey": false,
3097 + "notNull": true,
3098 + "default": "'{}'"
3099 + },
3100 + "condition_cancer_ids": {
3101 + "name": "condition_cancer_ids",
3102 + "type": "text[]",
3103 + "primaryKey": false,
3104 + "notNull": true,
3105 + "default": "'{}'"
3106 + },
3107 + "origin_simple": {
3108 + "name": "origin_simple",
3109 + "type": "text",
3110 + "primaryKey": false,
3111 + "notNull": false
3112 + },
3113 + "number_submitters": {
3114 + "name": "number_submitters",
3115 + "type": "integer",
3116 + "primaryKey": false,
3117 + "notNull": false
3118 + },
3119 + "provenance_id": {
3120 + "name": "provenance_id",
3121 + "type": "integer",
3122 + "primaryKey": false,
3123 + "notNull": true
3124 + },
3125 + "ingest_run_id": {
3126 + "name": "ingest_run_id",
3127 + "type": "text",
3128 + "primaryKey": false,
3129 + "notNull": false
3130 + },
3131 + "updated_at": {
3132 + "name": "updated_at",
3133 + "type": "timestamp with time zone",
3134 + "primaryKey": false,
3135 + "notNull": true,
3136 + "default": "now()"
3137 + }
3138 + },
3139 + "indexes": {
3140 + "variant_clinsig_uq": {
3141 + "name": "variant_clinsig_uq",
3142 + "columns": [
3143 + {
3144 + "expression": "clinvar_variation_id",
3145 + "isExpression": false,
3146 + "asc": true,
3147 + "nulls": "last"
3148 + }
3149 + ],
3150 + "isUnique": true,
3151 + "concurrently": false,
3152 + "method": "btree",
3153 + "with": {}
3154 + }
3155 + },
3156 + "foreignKeys": {},
3157 + "compositePrimaryKeys": {},
3158 + "uniqueConstraints": {},
3159 + "policies": {},
3160 + "checkConstraints": {},
3161 + "isRLSEnabled": false
3162 + },
3163 + "public.variants": {
3164 + "name": "variants",
3165 + "schema": "",
3166 + "columns": {
3167 + "id": {
3168 + "name": "id",
3169 + "type": "varchar(32)",
3170 + "primaryKey": true,
3171 + "notNull": true
3172 + },
3173 + "slug": {
3174 + "name": "slug",
3175 + "type": "text",
3176 + "primaryKey": false,
3177 + "notNull": true
3178 + },
3179 + "gene_id": {
3180 + "name": "gene_id",
3181 + "type": "varchar(32)",
3182 + "primaryKey": false,
3183 + "notNull": false
3184 + },
3185 + "gene_symbol": {
3186 + "name": "gene_symbol",
3187 + "type": "text",
3188 + "primaryKey": false,
3189 + "notNull": false
3190 + },
3191 + "name": {
3192 + "name": "name",
3193 + "type": "text",
3194 + "primaryKey": false,
3195 + "notNull": true
3196 + },
3197 + "variant_type": {
3198 + "name": "variant_type",
3199 + "type": "text",
3200 + "primaryKey": false,
3201 + "notNull": false
3202 + },
3203 + "hgvs_g": {
3204 + "name": "hgvs_g",
3205 + "type": "text",
3206 + "primaryKey": false,
3207 + "notNull": false
3208 + },
3209 + "hgvs_c": {
3210 + "name": "hgvs_c",
3211 + "type": "text",
3212 + "primaryKey": false,
3213 + "notNull": false
3214 + },
3215 + "hgvs_p": {
3216 + "name": "hgvs_p",
3217 + "type": "text",
3218 + "primaryKey": false,
3219 + "notNull": false
3220 + },
3221 + "assembly": {
3222 + "name": "assembly",
3223 + "type": "text",
3224 + "primaryKey": false,
3225 + "notNull": false
3226 + },
3227 + "chromosome": {
3228 + "name": "chromosome",
3229 + "type": "text",
3230 + "primaryKey": false,
3231 + "notNull": false
3232 + },
3233 + "start": {
3234 + "name": "start",
3235 + "type": "integer",
3236 + "primaryKey": false,
3237 + "notNull": false
3238 + },
3239 + "end": {
3240 + "name": "end",
3241 + "type": "integer",
3242 + "primaryKey": false,
3243 + "notNull": false
3244 + },
3245 + "reference_bases": {
3246 + "name": "reference_bases",
3247 + "type": "text",
3248 + "primaryKey": false,
3249 + "notNull": false
3250 + },
3251 + "alternate_bases": {
3252 + "name": "alternate_bases",
3253 + "type": "text",
3254 + "primaryKey": false,
3255 + "notNull": false
3256 + },
3257 + "coordinates": {
3258 + "name": "coordinates",
3259 + "type": "jsonb",
3260 + "primaryKey": false,
3261 + "notNull": true,
3262 + "default": "'[]'::jsonb"
3263 + },
3264 + "clinvar_variation_id": {
3265 + "name": "clinvar_variation_id",
3266 + "type": "text",
3267 + "primaryKey": false,
3268 + "notNull": false
3269 + },
3270 + "civic_variant_id": {
3271 + "name": "civic_variant_id",
3272 + "type": "integer",
3273 + "primaryKey": false,
3274 + "notNull": false
3275 + },
3276 + "dbsnp_ids": {
3277 + "name": "dbsnp_ids",
3278 + "type": "text[]",
3279 + "primaryKey": false,
3280 + "notNull": true,
3281 + "default": "'{}'"
3282 + },
3283 + "fusion_partners": {
3284 + "name": "fusion_partners",
3285 + "type": "text[]",
3286 + "primaryKey": false,
3287 + "notNull": true,
3288 + "default": "'{}'"
3289 + },
3290 + "created_at": {
3291 + "name": "created_at",
3292 + "type": "timestamp with time zone",
3293 + "primaryKey": false,
3294 + "notNull": true,
3295 + "default": "now()"
3296 + },
3297 + "updated_at": {
3298 + "name": "updated_at",
3299 + "type": "timestamp with time zone",
3300 + "primaryKey": false,
3301 + "notNull": true,
3302 + "default": "now()"
3303 + }
3304 + },
3305 + "indexes": {
3306 + "variants_slug_uq": {
3307 + "name": "variants_slug_uq",
3308 + "columns": [
3309 + {
3310 + "expression": "slug",
3311 + "isExpression": false,
3312 + "asc": true,
3313 + "nulls": "last"
3314 + }
3315 + ],
3316 + "isUnique": true,
3317 + "concurrently": false,
3318 + "method": "btree",
3319 + "with": {}
3320 + },
3321 + "variants_gene_idx": {
3322 + "name": "variants_gene_idx",
3323 + "columns": [
3324 + {
3325 + "expression": "gene_id",
3326 + "isExpression": false,
3327 + "asc": true,
3328 + "nulls": "last"
3329 + }
3330 + ],
3331 + "isUnique": false,
3332 + "concurrently": false,
3333 + "method": "btree",
3334 + "with": {}
3335 + },
3336 + "variants_clinvar_idx": {
3337 + "name": "variants_clinvar_idx",
3338 + "columns": [
3339 + {
3340 + "expression": "clinvar_variation_id",
3341 + "isExpression": false,
3342 + "asc": true,
3343 + "nulls": "last"
3344 + }
3345 + ],
3346 + "isUnique": false,
3347 + "concurrently": false,
3348 + "method": "btree",
3349 + "with": {}
3350 + },
3351 + "variants_civic_idx": {
3352 + "name": "variants_civic_idx",
3353 + "columns": [
3354 + {
3355 + "expression": "civic_variant_id",
3356 + "isExpression": false,
3357 + "asc": true,
3358 + "nulls": "last"
3359 + }
3360 + ],
3361 + "isUnique": false,
3362 + "concurrently": false,
3363 + "method": "btree",
3364 + "with": {}
3365 + }
3366 + },
3367 + "foreignKeys": {},
3368 + "compositePrimaryKeys": {},
3369 + "uniqueConstraints": {},
3370 + "policies": {},
3371 + "checkConstraints": {},
3372 + "isRLSEnabled": false
3373 + },
3374 + "public.drug_aliases": {
3375 + "name": "drug_aliases",
3376 + "schema": "",
3377 + "columns": {
3378 + "id": {
3379 + "name": "id",
3380 + "type": "bigserial",
3381 + "primaryKey": true,
3382 + "notNull": true
3383 + },
3384 + "drug_id": {
3385 + "name": "drug_id",
3386 + "type": "varchar(32)",
3387 + "primaryKey": false,
3388 + "notNull": true
3389 + },
3390 + "alias": {
3391 + "name": "alias",
3392 + "type": "text",
3393 + "primaryKey": false,
3394 + "notNull": true
3395 + },
3396 + "normalized": {
3397 + "name": "normalized",
3398 + "type": "text",
3399 + "primaryKey": false,
3400 + "notNull": true
3401 + },
3402 + "alias_type": {
3403 + "name": "alias_type",
3404 + "type": "text",
3405 + "primaryKey": false,
3406 + "notNull": true,
3407 + "default": "'synonym'"
3408 + },
3409 + "source_id": {
3410 + "name": "source_id",
3411 + "type": "varchar(32)",
3412 + "primaryKey": false,
3413 + "notNull": false
3414 + }
3415 + },
3416 + "indexes": {
3417 + "drug_aliases_uq": {
3418 + "name": "drug_aliases_uq",
3419 + "columns": [
3420 + {
3421 + "expression": "drug_id",
3422 + "isExpression": false,
3423 + "asc": true,
3424 + "nulls": "last"
3425 + },
3426 + {
3427 + "expression": "normalized",
3428 + "isExpression": false,
3429 + "asc": true,
3430 + "nulls": "last"
3431 + },
3432 + {
3433 + "expression": "alias_type",
3434 + "isExpression": false,
3435 + "asc": true,
3436 + "nulls": "last"
3437 + }
3438 + ],
3439 + "isUnique": true,
3440 + "concurrently": false,
3441 + "method": "btree",
3442 + "with": {}
3443 + },
3444 + "drug_aliases_norm_idx": {
3445 + "name": "drug_aliases_norm_idx",
3446 + "columns": [
3447 + {
3448 + "expression": "normalized",
3449 + "isExpression": false,
3450 + "asc": true,
3451 + "nulls": "last"
3452 + }
3453 + ],
3454 + "isUnique": false,
3455 + "concurrently": false,
3456 + "method": "btree",
3457 + "with": {}
3458 + }
3459 + },
3460 + "foreignKeys": {},
3461 + "compositePrimaryKeys": {},
3462 + "uniqueConstraints": {},
3463 + "policies": {},
3464 + "checkConstraints": {},
3465 + "isRLSEnabled": false
3466 + },
3467 + "public.drug_approvals": {
3468 + "name": "drug_approvals",
3469 + "schema": "",
3470 + "columns": {
3471 + "id": {
3472 + "name": "id",
3473 + "type": "bigserial",
3474 + "primaryKey": true,
3475 + "notNull": true
3476 + },
3477 + "drug_id": {
3478 + "name": "drug_id",
3479 + "type": "varchar(32)",
3480 + "primaryKey": false,
3481 + "notNull": true
3482 + },
3483 + "cancer_id": {
3484 + "name": "cancer_id",
3485 + "type": "varchar(32)",
3486 + "primaryKey": false,
3487 + "notNull": false
3488 + },
3489 + "biomarker_ids": {
3490 + "name": "biomarker_ids",
3491 + "type": "text[]",
3492 + "primaryKey": false,
3493 + "notNull": true,
3494 + "default": "'{}'"
3495 + },
3496 + "tumor_agnostic": {
3497 + "name": "tumor_agnostic",
3498 + "type": "boolean",
3499 + "primaryKey": false,
3500 + "notNull": true,
3501 + "default": false
3502 + },
3503 + "jurisdiction": {
3504 + "name": "jurisdiction",
3505 + "type": "text",
3506 + "primaryKey": false,
3507 + "notNull": true
3508 + },
3509 + "authority": {
3510 + "name": "authority",
3511 + "type": "text",
3512 + "primaryKey": false,
3513 + "notNull": true
3514 + },
3515 + "indication": {
3516 + "name": "indication",
3517 + "type": "text",
3518 + "primaryKey": false,
3519 + "notNull": true
3520 + },
3521 + "line_of_therapy": {
3522 + "name": "line_of_therapy",
3523 + "type": "text",
3524 + "primaryKey": false,
3525 + "notNull": false
3526 + },
3527 + "disease_stage": {
3528 + "name": "disease_stage",
3529 + "type": "text",
3530 + "primaryKey": false,
3531 + "notNull": false
3532 + },
3533 + "approval_type": {
3534 + "name": "approval_type",
3535 + "type": "text",
3536 + "primaryKey": false,
3537 + "notNull": false
3538 + },
3539 + "accelerated": {
3540 + "name": "accelerated",
3541 + "type": "boolean",
3542 + "primaryKey": false,
3543 + "notNull": false
3544 + },
3545 + "conditional": {
3546 + "name": "conditional",
3547 + "type": "boolean",
3548 + "primaryKey": false,
3549 + "notNull": false
3550 + },
3551 + "approval_date": {
3552 + "name": "approval_date",
3553 + "type": "text",
3554 + "primaryKey": false,
3555 + "notNull": false
3556 + },
3557 + "withdrawal_date": {
3558 + "name": "withdrawal_date",
3559 + "type": "text",
3560 + "primaryKey": false,
3561 + "notNull": false
3562 + },
3563 + "status": {
3564 + "name": "status",
3565 + "type": "text",
3566 + "primaryKey": false,
3567 + "notNull": true
3568 + },
3569 + "application_number": {
3570 + "name": "application_number",
3571 + "type": "text",
3572 + "primaryKey": false,
3573 + "notNull": false
3574 + },
3575 + "source_id": {
3576 + "name": "source_id",
3577 + "type": "varchar(32)",
3578 + "primaryKey": false,
3579 + "notNull": true
3580 + },
3581 + "provenance_id": {
3582 + "name": "provenance_id",
3583 + "type": "integer",
3584 + "primaryKey": false,
3585 + "notNull": true
3586 + },
3587 + "raw": {
3588 + "name": "raw",
3589 + "type": "jsonb",
3590 + "primaryKey": false,
3591 + "notNull": false
3592 + },
3593 + "created_at": {
3594 + "name": "created_at",
3595 + "type": "timestamp with time zone",
3596 + "primaryKey": false,
3597 + "notNull": true,
3598 + "default": "now()"
3599 + },
3600 + "updated_at": {
3601 + "name": "updated_at",
3602 + "type": "timestamp with time zone",
3603 + "primaryKey": false,
3604 + "notNull": true,
3605 + "default": "now()"
3606 + }
3607 + },
3608 + "indexes": {
3609 + "drug_approvals_drug_idx": {
3610 + "name": "drug_approvals_drug_idx",
3611 + "columns": [
3612 + {
3613 + "expression": "drug_id",
3614 + "isExpression": false,
3615 + "asc": true,
3616 + "nulls": "last"
3617 + }
3618 + ],
3619 + "isUnique": false,
3620 + "concurrently": false,
3621 + "method": "btree",
3622 + "with": {}
3623 + },
3624 + "drug_approvals_cancer_idx": {
3625 + "name": "drug_approvals_cancer_idx",
3626 + "columns": [
3627 + {
3628 + "expression": "cancer_id",
3629 + "isExpression": false,
3630 + "asc": true,
3631 + "nulls": "last"
3632 + }
3633 + ],
3634 + "isUnique": false,
3635 + "concurrently": false,
3636 + "method": "btree",
3637 + "with": {}
3638 + }
3639 + },
3640 + "foreignKeys": {},
3641 + "compositePrimaryKeys": {},
3642 + "uniqueConstraints": {},
3643 + "policies": {},
3644 + "checkConstraints": {},
3645 + "isRLSEnabled": false
3646 + },
3647 + "public.drugs": {
3648 + "name": "drugs",
3649 + "schema": "",
3650 + "columns": {
3651 + "id": {
3652 + "name": "id",
3653 + "type": "varchar(32)",
3654 + "primaryKey": true,
3655 + "notNull": true
3656 + },
3657 + "slug": {
3658 + "name": "slug",
3659 + "type": "text",
3660 + "primaryKey": false,
3661 + "notNull": true
3662 + },
3663 + "name": {
3664 + "name": "name",
3665 + "type": "text",
3666 + "primaryKey": false,
3667 + "notNull": true
3668 + },
3669 + "kind": {
3670 + "name": "kind",
3671 + "type": "text",
3672 + "primaryKey": false,
3673 + "notNull": false
3674 + },
3675 + "ncit_code": {
3676 + "name": "ncit_code",
3677 + "type": "text",
3678 + "primaryKey": false,
3679 + "notNull": false
3680 + },
3681 + "chembl_id": {
3682 + "name": "chembl_id",
3683 + "type": "text",
3684 + "primaryKey": false,
3685 + "notNull": false
3686 + },
3687 + "civic_therapy_id": {
3688 + "name": "civic_therapy_id",
3689 + "type": "integer",
3690 + "primaryKey": false,
3691 + "notNull": false
3692 + },
3693 + "drugbank_id": {
3694 + "name": "drugbank_id",
3695 + "type": "text",
3696 + "primaryKey": false,
3697 + "notNull": false
3698 + },
3699 + "pubchem_cid": {
3700 + "name": "pubchem_cid",
3701 + "type": "text",
3702 + "primaryKey": false,
3703 + "notNull": false
3704 + },
3705 + "unii": {
3706 + "name": "unii",
3707 + "type": "text",
3708 + "primaryKey": false,
3709 + "notNull": false
3710 + },
3711 + "mechanism": {
3712 + "name": "mechanism",
3713 + "type": "text",
3714 + "primaryKey": false,
3715 + "notNull": false
3716 + },
3717 + "target_gene_ids": {
3718 + "name": "target_gene_ids",
3719 + "type": "text[]",
3720 + "primaryKey": false,
3721 + "notNull": true,
3722 + "default": "'{}'"
3723 + },
3724 + "development_status": {
3725 + "name": "development_status",
3726 + "type": "text",
3727 + "primaryKey": false,
3728 + "notNull": false
3729 + },
3730 + "description": {
3731 + "name": "description",
3732 + "type": "text",
3733 + "primaryKey": false,
3734 + "notNull": false
3735 + },
3736 + "created_at": {
3737 + "name": "created_at",
3738 + "type": "timestamp with time zone",
3739 + "primaryKey": false,
3740 + "notNull": true,
3741 + "default": "now()"
3742 + },
3743 + "updated_at": {
3744 + "name": "updated_at",
3745 + "type": "timestamp with time zone",
3746 + "primaryKey": false,
3747 + "notNull": true,
3748 + "default": "now()"
3749 + }
3750 + },
3751 + "indexes": {
3752 + "drugs_slug_uq": {
3753 + "name": "drugs_slug_uq",
3754 + "columns": [
3755 + {
3756 + "expression": "slug",
3757 + "isExpression": false,
3758 + "asc": true,
3759 + "nulls": "last"
3760 + }
3761 + ],
3762 + "isUnique": true,
3763 + "concurrently": false,
3764 + "method": "btree",
3765 + "with": {}
3766 + },
3767 + "drugs_ncit_idx": {
3768 + "name": "drugs_ncit_idx",
3769 + "columns": [
3770 + {
3771 + "expression": "ncit_code",
3772 + "isExpression": false,
3773 + "asc": true,
3774 + "nulls": "last"
3775 + }
3776 + ],
3777 + "isUnique": false,
3778 + "concurrently": false,
3779 + "method": "btree",
3780 + "with": {}
3781 + },
3782 + "drugs_civic_idx": {
3783 + "name": "drugs_civic_idx",
3784 + "columns": [
3785 + {
3786 + "expression": "civic_therapy_id",
3787 + "isExpression": false,
3788 + "asc": true,
3789 + "nulls": "last"
3790 + }
3791 + ],
3792 + "isUnique": false,
3793 + "concurrently": false,
3794 + "method": "btree",
3795 + "with": {}
3796 + },
3797 + "drugs_chembl_idx": {
3798 + "name": "drugs_chembl_idx",
3799 + "columns": [
3800 + {
3801 + "expression": "chembl_id",
3802 + "isExpression": false,
3803 + "asc": true,
3804 + "nulls": "last"
3805 + }
3806 + ],
3807 + "isUnique": false,
3808 + "concurrently": false,
3809 + "method": "btree",
3810 + "with": {}
3811 + }
3812 + },
3813 + "foreignKeys": {},
3814 + "compositePrimaryKeys": {},
3815 + "uniqueConstraints": {},
3816 + "policies": {},
3817 + "checkConstraints": {},
3818 + "isRLSEnabled": false
3819 + },
3820 + "public.treatment_regimens": {
3821 + "name": "treatment_regimens",
3822 + "schema": "",
3823 + "columns": {
3824 + "id": {
3825 + "name": "id",
3826 + "type": "varchar(32)",
3827 + "primaryKey": true,
3828 + "notNull": true
3829 + },
3830 + "slug": {
3831 + "name": "slug",
3832 + "type": "text",
3833 + "primaryKey": false,
3834 + "notNull": true
3835 + },
3836 + "name": {
3837 + "name": "name",
3838 + "type": "text",
3839 + "primaryKey": false,
3840 + "notNull": true
3841 + },
3842 + "component_drug_ids": {
3843 + "name": "component_drug_ids",
3844 + "type": "text[]",
3845 + "primaryKey": false,
3846 + "notNull": true,
3847 + "default": "'{}'"
3848 + },
3849 + "modality": {
3850 + "name": "modality",
3851 + "type": "text",
3852 + "primaryKey": false,
3853 + "notNull": true,
3854 + "default": "'drug_combination'"
3855 + },
3856 + "description": {
3857 + "name": "description",
3858 + "type": "text",
3859 + "primaryKey": false,
3860 + "notNull": false
3861 + },
3862 + "created_at": {
3863 + "name": "created_at",
3864 + "type": "timestamp with time zone",
3865 + "primaryKey": false,
3866 + "notNull": true,
3867 + "default": "now()"
3868 + }
3869 + },
3870 + "indexes": {
3871 + "treatment_regimens_slug_uq": {
3872 + "name": "treatment_regimens_slug_uq",
3873 + "columns": [
3874 + {
3875 + "expression": "slug",
3876 + "isExpression": false,
3877 + "asc": true,
3878 + "nulls": "last"
3879 + }
3880 + ],
3881 + "isUnique": true,
3882 + "concurrently": false,
3883 + "method": "btree",
3884 + "with": {}
3885 + }
3886 + },
3887 + "foreignKeys": {},
3888 + "compositePrimaryKeys": {},
3889 + "uniqueConstraints": {},
3890 + "policies": {},
3891 + "checkConstraints": {},
3892 + "isRLSEnabled": false
3893 + },
3894 + "public.clinical_trials": {
3895 + "name": "clinical_trials",
3896 + "schema": "",
3897 + "columns": {
3898 + "id": {
3899 + "name": "id",
3900 + "type": "varchar(32)",
3901 + "primaryKey": true,
3902 + "notNull": true
3903 + },
3904 + "nct_id": {
3905 + "name": "nct_id",
3906 + "type": "text",
3907 + "primaryKey": false,
3908 + "notNull": true
3909 + },
3910 + "brief_title": {
3911 + "name": "brief_title",
3912 + "type": "text",
3913 + "primaryKey": false,
3914 + "notNull": true
3915 + },
3916 + "official_title": {
3917 + "name": "official_title",
3918 + "type": "text",
3919 + "primaryKey": false,
3920 + "notNull": false
3921 + },
3922 + "acronym": {
3923 + "name": "acronym",
3924 + "type": "text",
3925 + "primaryKey": false,
3926 + "notNull": false
3927 + },
3928 + "study_type": {
3929 + "name": "study_type",
3930 + "type": "text",
3931 + "primaryKey": false,
3932 + "notNull": false
3933 + },
3934 + "phases": {
3935 + "name": "phases",
3936 + "type": "text[]",
3937 + "primaryKey": false,
3938 + "notNull": true,
3939 + "default": "'{}'"
3940 + },
3941 + "overall_status": {
3942 + "name": "overall_status",
3943 + "type": "text",
3944 + "primaryKey": false,
3945 + "notNull": false
3946 + },
3947 + "why_stopped": {
3948 + "name": "why_stopped",
3949 + "type": "text",
3950 + "primaryKey": false,
3951 + "notNull": false
3952 + },
3953 + "start_date": {
3954 + "name": "start_date",
3955 + "type": "text",
3956 + "primaryKey": false,
3957 + "notNull": false
3958 + },
3959 + "primary_completion_date": {
3960 + "name": "primary_completion_date",
3961 + "type": "text",
3962 + "primaryKey": false,
3963 + "notNull": false
3964 + },
3965 + "completion_date": {
3966 + "name": "completion_date",
3967 + "type": "text",
3968 + "primaryKey": false,
3969 + "notNull": false
3970 + },
3971 + "first_posted_date": {
3972 + "name": "first_posted_date",
3973 + "type": "text",
3974 + "primaryKey": false,
3975 + "notNull": false
3976 + },
3977 + "last_update_posted_date": {
3978 + "name": "last_update_posted_date",
3979 + "type": "text",
3980 + "primaryKey": false,
3981 + "notNull": false
3982 + },
3983 + "results_first_posted_date": {
3984 + "name": "results_first_posted_date",
3985 + "type": "text",
3986 + "primaryKey": false,
3987 + "notNull": false
3988 + },
3989 + "has_results": {
3990 + "name": "has_results",
3991 + "type": "boolean",
3992 + "primaryKey": false,
3993 + "notNull": true,
3994 + "default": false
3995 + },
3996 + "enrollment_count": {
3997 + "name": "enrollment_count",
3998 + "type": "integer",
3999 + "primaryKey": false,
4000 + "notNull": false
4001 + },
4002 + "enrollment_type": {
4003 + "name": "enrollment_type",
4004 + "type": "text",
4005 + "primaryKey": false,
4006 + "notNull": false
4007 + },
4008 + "lead_sponsor": {
4009 + "name": "lead_sponsor",
4010 + "type": "text",
4011 + "primaryKey": false,
4012 + "notNull": false
4013 + },
4014 + "lead_sponsor_class": {
4015 + "name": "lead_sponsor_class",
4016 + "type": "text",
4017 + "primaryKey": false,
4018 + "notNull": false
4019 + },
4020 + "collaborators": {
4021 + "name": "collaborators",
4022 + "type": "text[]",
4023 + "primaryKey": false,
4024 + "notNull": true,
4025 + "default": "'{}'"
4026 + },
4027 + "conditions": {
4028 + "name": "conditions",
4029 + "type": "text[]",
4030 + "primaryKey": false,
4031 + "notNull": true,
4032 + "default": "'{}'"
4033 + },
4034 + "keywords": {
4035 + "name": "keywords",
4036 + "type": "text[]",
4037 + "primaryKey": false,
4038 + "notNull": true,
4039 + "default": "'{}'"
4040 + },
4041 + "interventions": {
4042 + "name": "interventions",
4043 + "type": "jsonb",
4044 + "primaryKey": false,
4045 + "notNull": true,
4046 + "default": "'[]'::jsonb"
4047 + },
4048 + "arms": {
4049 + "name": "arms",
4050 + "type": "jsonb",
4051 + "primaryKey": false,
4052 + "notNull": true,
4053 + "default": "'[]'::jsonb"
4054 + },
4055 + "primary_outcomes": {
4056 + "name": "primary_outcomes",
4057 + "type": "jsonb",
4058 + "primaryKey": false,
4059 + "notNull": true,
4060 + "default": "'[]'::jsonb"
4061 + },
4062 + "secondary_outcomes": {
4063 + "name": "secondary_outcomes",
4064 + "type": "jsonb",
4065 + "primaryKey": false,
4066 + "notNull": true,
4067 + "default": "'[]'::jsonb"
4068 + },
4069 + "eligibility": {
4070 + "name": "eligibility",
4071 + "type": "jsonb",
4072 + "primaryKey": false,
4073 + "notNull": true,
4074 + "default": "'{}'::jsonb"
4075 + },
4076 + "sex": {
4077 + "name": "sex",
4078 + "type": "text",
4079 + "primaryKey": false,
4080 + "notNull": false
4081 + },
4082 + "minimum_age": {
4083 + "name": "minimum_age",
4084 + "type": "text",
4085 + "primaryKey": false,
4086 + "notNull": false
4087 + },
4088 + "maximum_age": {
4089 + "name": "maximum_age",
4090 + "type": "text",
4091 + "primaryKey": false,
4092 + "notNull": false
4093 + },
4094 + "countries": {
4095 + "name": "countries",
4096 + "type": "text[]",
4097 + "primaryKey": false,
4098 + "notNull": true,
4099 + "default": "'{}'"
4100 + },
4101 + "locations_count": {
4102 + "name": "locations_count",
4103 + "type": "integer",
4104 + "primaryKey": false,
4105 + "notNull": true,
4106 + "default": 0
4107 + },
4108 + "references": {
4109 + "name": "references",
4110 + "type": "jsonb",
4111 + "primaryKey": false,
4112 + "notNull": true,
4113 + "default": "'[]'::jsonb"
4114 + },
4115 + "brief_summary": {
4116 + "name": "brief_summary",
4117 + "type": "text",
4118 + "primaryKey": false,
4119 + "notNull": false
4120 + },
4121 + "is_oncology": {
4122 + "name": "is_oncology",
4123 + "type": "boolean",
4124 + "primaryKey": false,
4125 + "notNull": true,
4126 + "default": true
4127 + },
4128 + "source_record_id": {
4129 + "name": "source_record_id",
4130 + "type": "integer",
4131 + "primaryKey": false,
4132 + "notNull": false
4133 + },
4134 + "ingest_run_id": {
4135 + "name": "ingest_run_id",
4136 + "type": "text",
4137 + "primaryKey": false,
4138 + "notNull": false
4139 + },
4140 + "created_at": {
4141 + "name": "created_at",
4142 + "type": "timestamp with time zone",
4143 + "primaryKey": false,
4144 + "notNull": true,
4145 + "default": "now()"
4146 + },
4147 + "updated_at": {
4148 + "name": "updated_at",
4149 + "type": "timestamp with time zone",
4150 + "primaryKey": false,
4151 + "notNull": true,
4152 + "default": "now()"
4153 + }
4154 + },
4155 + "indexes": {
4156 + "clinical_trials_nct_uq": {
4157 + "name": "clinical_trials_nct_uq",
4158 + "columns": [
4159 + {
4160 + "expression": "nct_id",
4161 + "isExpression": false,
4162 + "asc": true,
4163 + "nulls": "last"
4164 + }
4165 + ],
4166 + "isUnique": true,
4167 + "concurrently": false,
4168 + "method": "btree",
4169 + "with": {}
4170 + },
4171 + "clinical_trials_status_idx": {
4172 + "name": "clinical_trials_status_idx",
4173 + "columns": [
4174 + {
4175 + "expression": "overall_status",
4176 + "isExpression": false,
4177 + "asc": true,
4178 + "nulls": "last"
4179 + }
4180 + ],
4181 + "isUnique": false,
4182 + "concurrently": false,
4183 + "method": "btree",
4184 + "with": {}
4185 + },
4186 + "clinical_trials_updated_idx": {
4187 + "name": "clinical_trials_updated_idx",
4188 + "columns": [
4189 + {
4190 + "expression": "last_update_posted_date",
4191 + "isExpression": false,
4192 + "asc": true,
4193 + "nulls": "last"
4194 + }
4195 + ],
4196 + "isUnique": false,
4197 + "concurrently": false,
4198 + "method": "btree",
4199 + "with": {}
4200 + },
4201 + "clinical_trials_sponsor_idx": {
4202 + "name": "clinical_trials_sponsor_idx",
4203 + "columns": [
4204 + {
4205 + "expression": "lead_sponsor",
4206 + "isExpression": false,
4207 + "asc": true,
4208 + "nulls": "last"
4209 + }
4210 + ],
4211 + "isUnique": false,
4212 + "concurrently": false,
4213 + "method": "btree",
4214 + "with": {}
4215 + }
4216 + },
4217 + "foreignKeys": {},
4218 + "compositePrimaryKeys": {},
4219 + "uniqueConstraints": {},
4220 + "policies": {},
4221 + "checkConstraints": {},
4222 + "isRLSEnabled": false
4223 + },
4224 + "public.trial_conditions": {
4225 + "name": "trial_conditions",
4226 + "schema": "",
4227 + "columns": {
4228 + "id": {
4229 + "name": "id",
4230 + "type": "bigserial",
4231 + "primaryKey": true,
4232 + "notNull": true
4233 + },
4234 + "trial_id": {
4235 + "name": "trial_id",
4236 + "type": "varchar(32)",
4237 + "primaryKey": false,
4238 + "notNull": true
4239 + },
4240 + "condition_text": {
4241 + "name": "condition_text",
4242 + "type": "text",
4243 + "primaryKey": false,
4244 + "notNull": true
4245 + },
4246 + "normalized": {
4247 + "name": "normalized",
4248 + "type": "text",
4249 + "primaryKey": false,
4250 + "notNull": true
4251 + },
4252 + "cancer_id": {
4253 + "name": "cancer_id",
4254 + "type": "varchar(32)",
4255 + "primaryKey": false,
4256 + "notNull": false
4257 + },
4258 + "match_type": {
4259 + "name": "match_type",
4260 + "type": "text",
4261 + "primaryKey": false,
4262 + "notNull": true,
4263 + "default": "'UNRESOLVED'"
4264 + },
4265 + "confidence": {
4266 + "name": "confidence",
4267 + "type": "real",
4268 + "primaryKey": false,
4269 + "notNull": false
4270 + }
4271 + },
4272 + "indexes": {
4273 + "trial_conditions_uq": {
4274 + "name": "trial_conditions_uq",
4275 + "columns": [
4276 + {
4277 + "expression": "trial_id",
4278 + "isExpression": false,
4279 + "asc": true,
4280 + "nulls": "last"
4281 + },
4282 + {
4283 + "expression": "normalized",
4284 + "isExpression": false,
4285 + "asc": true,
4286 + "nulls": "last"
4287 + }
4288 + ],
4289 + "isUnique": true,
4290 + "concurrently": false,
4291 + "method": "btree",
4292 + "with": {}
4293 + },
4294 + "trial_conditions_cancer_idx": {
4295 + "name": "trial_conditions_cancer_idx",
4296 + "columns": [
4297 + {
4298 + "expression": "cancer_id",
4299 + "isExpression": false,
4300 + "asc": true,
4301 + "nulls": "last"
4302 + }
4303 + ],
4304 + "isUnique": false,
4305 + "concurrently": false,
4306 + "method": "btree",
4307 + "with": {}
4308 + },
4309 + "trial_conditions_norm_idx": {
4310 + "name": "trial_conditions_norm_idx",
4311 + "columns": [
4312 + {
4313 + "expression": "normalized",
4314 + "isExpression": false,
4315 + "asc": true,
4316 + "nulls": "last"
4317 + }
4318 + ],
4319 + "isUnique": false,
4320 + "concurrently": false,
4321 + "method": "btree",
4322 + "with": {}
4323 + }
4324 + },
4325 + "foreignKeys": {},
4326 + "compositePrimaryKeys": {},
4327 + "uniqueConstraints": {},
4328 + "policies": {},
4329 + "checkConstraints": {},
4330 + "isRLSEnabled": false
4331 + },
4332 + "public.trial_interventions": {
4333 + "name": "trial_interventions",
4334 + "schema": "",
4335 + "columns": {
4336 + "id": {
4337 + "name": "id",
4338 + "type": "bigserial",
4339 + "primaryKey": true,
4340 + "notNull": true
4341 + },
4342 + "trial_id": {
4343 + "name": "trial_id",
4344 + "type": "varchar(32)",
4345 + "primaryKey": false,
4346 + "notNull": true
4347 + },
4348 + "name": {
4349 + "name": "name",
4350 + "type": "text",
4351 + "primaryKey": false,
4352 + "notNull": true
4353 + },
4354 + "normalized": {
4355 + "name": "normalized",
4356 + "type": "text",
4357 + "primaryKey": false,
4358 + "notNull": true
4359 + },
4360 + "intervention_type": {
4361 + "name": "intervention_type",
4362 + "type": "text",
4363 + "primaryKey": false,
4364 + "notNull": false
4365 + },
4366 + "drug_id": {
4367 + "name": "drug_id",
4368 + "type": "varchar(32)",
4369 + "primaryKey": false,
4370 + "notNull": false
4371 + },
4372 + "match_type": {
4373 + "name": "match_type",
4374 + "type": "text",
4375 + "primaryKey": false,
4376 + "notNull": true,
4377 + "default": "'UNRESOLVED'"
4378 + }
4379 + },
4380 + "indexes": {
4381 + "trial_interventions_uq": {
4382 + "name": "trial_interventions_uq",
4383 + "columns": [
4384 + {
4385 + "expression": "trial_id",
4386 + "isExpression": false,
4387 + "asc": true,
4388 + "nulls": "last"
4389 + },
4390 + {
4391 + "expression": "normalized",
4392 + "isExpression": false,
4393 + "asc": true,
4394 + "nulls": "last"
4395 + }
4396 + ],
4397 + "isUnique": true,
4398 + "concurrently": false,
4399 + "method": "btree",
4400 + "with": {}
4401 + },
4402 + "trial_interventions_drug_idx": {
4403 + "name": "trial_interventions_drug_idx",
4404 + "columns": [
4405 + {
4406 + "expression": "drug_id",
4407 + "isExpression": false,
4408 + "asc": true,
4409 + "nulls": "last"
4410 + }
4411 + ],
4412 + "isUnique": false,
4413 + "concurrently": false,
4414 + "method": "btree",
4415 + "with": {}
4416 + }
4417 + },
4418 + "foreignKeys": {},
4419 + "compositePrimaryKeys": {},
4420 + "uniqueConstraints": {},
4421 + "policies": {},
4422 + "checkConstraints": {},
4423 + "isRLSEnabled": false
4424 + },
4425 + "public.trial_locations": {
4426 + "name": "trial_locations",
4427 + "schema": "",
4428 + "columns": {
4429 + "id": {
4430 + "name": "id",
4431 + "type": "bigserial",
4432 + "primaryKey": true,
4433 + "notNull": true
4434 + },
4435 + "trial_id": {
4436 + "name": "trial_id",
4437 + "type": "varchar(32)",
4438 + "primaryKey": false,
4439 + "notNull": true
4440 + },
4441 + "facility": {
4442 + "name": "facility",
4443 + "type": "text",
4444 + "primaryKey": false,
4445 + "notNull": false
4446 + },
4447 + "city": {
4448 + "name": "city",
4449 + "type": "text",
4450 + "primaryKey": false,
4451 + "notNull": false
4452 + },
4453 + "state": {
4454 + "name": "state",
4455 + "type": "text",
4456 + "primaryKey": false,
4457 + "notNull": false
4458 + },
4459 + "zip": {
4460 + "name": "zip",
4461 + "type": "text",
4462 + "primaryKey": false,
4463 + "notNull": false
4464 + },
4465 + "country": {
4466 + "name": "country",
4467 + "type": "text",
4468 + "primaryKey": false,
4469 + "notNull": false
4470 + },
4471 + "status": {
4472 + "name": "status",
4473 + "type": "text",
4474 + "primaryKey": false,
4475 + "notNull": false
4476 + },
4477 + "lat": {
4478 + "name": "lat",
4479 + "type": "real",
4480 + "primaryKey": false,
4481 + "notNull": false
4482 + },
4483 + "lng": {
4484 + "name": "lng",
4485 + "type": "real",
4486 + "primaryKey": false,
4487 + "notNull": false
4488 + }
4489 + },
4490 + "indexes": {
4491 + "trial_locations_trial_idx": {
4492 + "name": "trial_locations_trial_idx",
4493 + "columns": [
4494 + {
4495 + "expression": "trial_id",
4496 + "isExpression": false,
4497 + "asc": true,
4498 + "nulls": "last"
4499 + }
4500 + ],
4501 + "isUnique": false,
4502 + "concurrently": false,
4503 + "method": "btree",
4504 + "with": {}
4505 + },
4506 + "trial_locations_country_idx": {
4507 + "name": "trial_locations_country_idx",
4508 + "columns": [
4509 + {
4510 + "expression": "country",
4511 + "isExpression": false,
4512 + "asc": true,
4513 + "nulls": "last"
4514 + }
4515 + ],
4516 + "isUnique": false,
4517 + "concurrently": false,
4518 + "method": "btree",
4519 + "with": {}
4520 + }
4521 + },
4522 + "foreignKeys": {},
4523 + "compositePrimaryKeys": {},
4524 + "uniqueConstraints": {},
4525 + "policies": {},
4526 + "checkConstraints": {},
4527 + "isRLSEnabled": false
4528 + },
4529 + "public.trial_pulse": {
4530 + "name": "trial_pulse",
4531 + "schema": "",
4532 + "columns": {
4533 + "id": {
4534 + "name": "id",
4535 + "type": "bigserial",
4536 + "primaryKey": true,
4537 + "notNull": true
4538 + },
4539 + "day": {
4540 + "name": "day",
4541 + "type": "date",
4542 + "primaryKey": false,
4543 + "notNull": true
4544 + },
4545 + "cancer_id": {
4546 + "name": "cancer_id",
4547 + "type": "varchar(32)",
4548 + "primaryKey": false,
4549 + "notNull": false
4550 + },
4551 + "phase": {
4552 + "name": "phase",
4553 + "type": "text",
4554 + "primaryKey": false,
4555 + "notNull": false
4556 + },
4557 + "new_trials": {
4558 + "name": "new_trials",
4559 + "type": "integer",
4560 + "primaryKey": false,
4561 + "notNull": true
4562 + },
4563 + "updated_at": {
4564 + "name": "updated_at",
4565 + "type": "timestamp with time zone",
4566 + "primaryKey": false,
4567 + "notNull": true,
4568 + "default": "now()"
4569 + }
4570 + },
4571 + "indexes": {
4572 + "trial_pulse_uq": {
4573 + "name": "trial_pulse_uq",
4574 + "columns": [
4575 + {
4576 + "expression": "day",
4577 + "isExpression": false,
4578 + "asc": true,
4579 + "nulls": "last"
4580 + },
4581 + {
4582 + "expression": "cancer_id",
4583 + "isExpression": false,
4584 + "asc": true,
4585 + "nulls": "last"
4586 + },
4587 + {
4588 + "expression": "phase",
4589 + "isExpression": false,
4590 + "asc": true,
4591 + "nulls": "last"
4592 + }
4593 + ],
4594 + "isUnique": true,
4595 + "concurrently": false,
4596 + "method": "btree",
4597 + "with": {}
4598 + }
4599 + },
4600 + "foreignKeys": {},
4601 + "compositePrimaryKeys": {},
4602 + "uniqueConstraints": {},
4603 + "policies": {},
4604 + "checkConstraints": {},
4605 + "isRLSEnabled": false
4606 + },
4607 + "public.literature_counts": {
4608 + "name": "literature_counts",
4609 + "schema": "",
4610 + "columns": {
4611 + "id": {
4612 + "name": "id",
4613 + "type": "bigserial",
4614 + "primaryKey": true,
4615 + "notNull": true
4616 + },
4617 + "cancer_id": {
4618 + "name": "cancer_id",
4619 + "type": "varchar(32)",
4620 + "primaryKey": false,
4621 + "notNull": true
4622 + },
4623 + "window_key": {
4624 + "name": "window_key",
4625 + "type": "text",
4626 + "primaryKey": false,
4627 + "notNull": true
4628 + },
4629 + "window_start": {
4630 + "name": "window_start",
4631 + "type": "text",
4632 + "primaryKey": false,
4633 + "notNull": false
4634 + },
4635 + "window_end": {
4636 + "name": "window_end",
4637 + "type": "text",
4638 + "primaryKey": false,
4639 + "notNull": false
4640 + },
4641 + "query": {
4642 + "name": "query",
4643 + "type": "text",
4644 + "primaryKey": false,
4645 + "notNull": true
4646 + },
4647 + "count": {
4648 + "name": "count",
4649 + "type": "integer",
4650 + "primaryKey": false,
4651 + "notNull": true
4652 + },
4653 + "provenance_id": {
4654 + "name": "provenance_id",
4655 + "type": "integer",
4656 + "primaryKey": false,
4657 + "notNull": true
4658 + },
4659 + "updated_at": {
4660 + "name": "updated_at",
4661 + "type": "timestamp with time zone",
4662 + "primaryKey": false,
4663 + "notNull": true,
4664 + "default": "now()"
4665 + }
4666 + },
4667 + "indexes": {
4668 + "literature_counts_uq": {
4669 + "name": "literature_counts_uq",
4670 + "columns": [
4671 + {
4672 + "expression": "cancer_id",
4673 + "isExpression": false,
4674 + "asc": true,
4675 + "nulls": "last"
4676 + },
4677 + {
4678 + "expression": "window_key",
4679 + "isExpression": false,
4680 + "asc": true,
4681 + "nulls": "last"
4682 + }
4683 + ],
4684 + "isUnique": true,
4685 + "concurrently": false,
4686 + "method": "btree",
4687 + "with": {}
4688 + },
4689 + "literature_counts_window_idx": {
4690 + "name": "literature_counts_window_idx",
4691 + "columns": [
4692 + {
4693 + "expression": "window_key",
4694 + "isExpression": false,
4695 + "asc": true,
4696 + "nulls": "last"
4697 + },
4698 + {
4699 + "expression": "count",
4700 + "isExpression": false,
4701 + "asc": true,
4702 + "nulls": "last"
4703 + }
4704 + ],
4705 + "isUnique": false,
4706 + "concurrently": false,
4707 + "method": "btree",
4708 + "with": {}
4709 + }
4710 + },
4711 + "foreignKeys": {},
4712 + "compositePrimaryKeys": {},
4713 + "uniqueConstraints": {},
4714 + "policies": {},
4715 + "checkConstraints": {},
4716 + "isRLSEnabled": false
4717 + },
4718 + "public.publication_entity_edges": {
4719 + "name": "publication_entity_edges",
4720 + "schema": "",
4721 + "columns": {
4722 + "id": {
4723 + "name": "id",
4724 + "type": "bigserial",
4725 + "primaryKey": true,
4726 + "notNull": true
4727 + },
4728 + "publication_id": {
4729 + "name": "publication_id",
4730 + "type": "varchar(32)",
4731 + "primaryKey": false,
4732 + "notNull": true
4733 + },
4734 + "entity_type": {
4735 + "name": "entity_type",
4736 + "type": "text",
4737 + "primaryKey": false,
4738 + "notNull": true
4739 + },
4740 + "entity_id": {
4741 + "name": "entity_id",
4742 + "type": "text",
4743 + "primaryKey": false,
4744 + "notNull": true
4745 + },
4746 + "method": {
4747 + "name": "method",
4748 + "type": "text",
4749 + "primaryKey": false,
4750 + "notNull": true
4751 + },
4752 + "confidence": {
4753 + "name": "confidence",
4754 + "type": "real",
4755 + "primaryKey": false,
4756 + "notNull": false
4757 + },
4758 + "status": {
4759 + "name": "status",
4760 + "type": "text",
4761 + "primaryKey": false,
4762 + "notNull": true,
4763 + "default": "'candidate'"
4764 + },
4765 + "source_id": {
4766 + "name": "source_id",
4767 + "type": "varchar(32)",
4768 + "primaryKey": false,
4769 + "notNull": false
4770 + },
4771 + "ingest_run_id": {
4772 + "name": "ingest_run_id",
4773 + "type": "text",
4774 + "primaryKey": false,
4775 + "notNull": false
4776 + },
4777 + "created_at": {
4778 + "name": "created_at",
4779 + "type": "timestamp with time zone",
4780 + "primaryKey": false,
4781 + "notNull": true,
4782 + "default": "now()"
4783 + }
4784 + },
4785 + "indexes": {
4786 + "pub_entity_edges_uq": {
4787 + "name": "pub_entity_edges_uq",
4788 + "columns": [
4789 + {
4790 + "expression": "publication_id",
4791 + "isExpression": false,
4792 + "asc": true,
4793 + "nulls": "last"
4794 + },
4795 + {
4796 + "expression": "entity_type",
4797 + "isExpression": false,
4798 + "asc": true,
4799 + "nulls": "last"
4800 + },
4801 + {
4802 + "expression": "entity_id",
4803 + "isExpression": false,
4804 + "asc": true,
4805 + "nulls": "last"
4806 + },
4807 + {
4808 + "expression": "method",
4809 + "isExpression": false,
4810 + "asc": true,
4811 + "nulls": "last"
4812 + }
4813 + ],
4814 + "isUnique": true,
4815 + "concurrently": false,
4816 + "method": "btree",
4817 + "with": {}
4818 + },
4819 + "pub_entity_edges_entity_idx": {
4820 + "name": "pub_entity_edges_entity_idx",
4821 + "columns": [
4822 + {
4823 + "expression": "entity_type",
4824 + "isExpression": false,
4825 + "asc": true,
4826 + "nulls": "last"
4827 + },
4828 + {
4829 + "expression": "entity_id",
4830 + "isExpression": false,
4831 + "asc": true,
4832 + "nulls": "last"
4833 + }
4834 + ],
4835 + "isUnique": false,
4836 + "concurrently": false,
4837 + "method": "btree",
4838 + "with": {}
4839 + }
4840 + },
4841 + "foreignKeys": {},
4842 + "compositePrimaryKeys": {},
4843 + "uniqueConstraints": {},
4844 + "policies": {},
4845 + "checkConstraints": {},
4846 + "isRLSEnabled": false
4847 + },
4848 + "public.publications": {
4849 + "name": "publications",
4850 + "schema": "",
4851 + "columns": {
4852 + "id": {
4853 + "name": "id",
4854 + "type": "varchar(32)",
4855 + "primaryKey": true,
4856 + "notNull": true
4857 + },
4858 + "pmid": {
4859 + "name": "pmid",
4860 + "type": "text",
4861 + "primaryKey": false,
4862 + "notNull": false
4863 + },
4864 + "doi": {
4865 + "name": "doi",
4866 + "type": "text",
4867 + "primaryKey": false,
4868 + "notNull": false
4869 + },
4870 + "pmcid": {
4871 + "name": "pmcid",
4872 + "type": "text",
4873 + "primaryKey": false,
4874 + "notNull": false
4875 + },
4876 + "title": {
4877 + "name": "title",
4878 + "type": "text",
4879 + "primaryKey": false,
4880 + "notNull": true
4881 + },
4882 + "abstract": {
4883 + "name": "abstract",
4884 + "type": "text",
4885 + "primaryKey": false,
4886 + "notNull": false
4887 + },
4888 + "journal": {
4889 + "name": "journal",
4890 + "type": "text",
4891 + "primaryKey": false,
4892 + "notNull": false
4893 + },
4894 + "journal_iso": {
4895 + "name": "journal_iso",
4896 + "type": "text",
4897 + "primaryKey": false,
4898 + "notNull": false
4899 + },
4900 + "pub_date": {
4901 + "name": "pub_date",
4902 + "type": "text",
4903 + "primaryKey": false,
4904 + "notNull": false
4905 + },
4906 + "pub_year": {
4907 + "name": "pub_year",
4908 + "type": "integer",
4909 + "primaryKey": false,
4910 + "notNull": false
4911 + },
4912 + "publication_types": {
4913 + "name": "publication_types",
4914 + "type": "text[]",
4915 + "primaryKey": false,
4916 + "notNull": true,
4917 + "default": "'{}'"
4918 + },
4919 + "mesh_terms": {
4920 + "name": "mesh_terms",
4921 + "type": "jsonb",
4922 + "primaryKey": false,
4923 + "notNull": true,
4924 + "default": "'[]'::jsonb"
4925 + },
4926 + "authors": {
4927 + "name": "authors",
4928 + "type": "jsonb",
4929 + "primaryKey": false,
4930 + "notNull": true,
4931 + "default": "'[]'::jsonb"
4932 + },
4933 + "language": {
4934 + "name": "language",
4935 + "type": "text",
4936 + "primaryKey": false,
4937 + "notNull": false
4938 + },
4939 + "is_preprint": {
4940 + "name": "is_preprint",
4941 + "type": "boolean",
4942 + "primaryKey": false,
4943 + "notNull": true,
4944 + "default": false
4945 + },
4946 + "retracted": {
4947 + "name": "retracted",
4948 + "type": "boolean",
4949 + "primaryKey": false,
4950 + "notNull": true,
4951 + "default": false
4952 + },
4953 + "retraction_notice": {
4954 + "name": "retraction_notice",
4955 + "type": "text",
4956 + "primaryKey": false,
4957 + "notNull": false
4958 + },
4959 + "nct_ids": {
4960 + "name": "nct_ids",
4961 + "type": "text[]",
4962 + "primaryKey": false,
4963 + "notNull": true,
4964 + "default": "'{}'"
4965 + },
4966 + "cited_by_count": {
4967 + "name": "cited_by_count",
4968 + "type": "integer",
4969 + "primaryKey": false,
4970 + "notNull": false
4971 + },
4972 + "source_record_id": {
4973 + "name": "source_record_id",
4974 + "type": "integer",
4975 + "primaryKey": false,
4976 + "notNull": false
4977 + },
4978 + "ingest_run_id": {
4979 + "name": "ingest_run_id",
4980 + "type": "text",
4981 + "primaryKey": false,
4982 + "notNull": false
4983 + },
4984 + "created_at": {
4985 + "name": "created_at",
4986 + "type": "timestamp with time zone",
4987 + "primaryKey": false,
4988 + "notNull": true,
4989 + "default": "now()"
4990 + },
4991 + "updated_at": {
4992 + "name": "updated_at",
4993 + "type": "timestamp with time zone",
4994 + "primaryKey": false,
4995 + "notNull": true,
4996 + "default": "now()"
4997 + }
4998 + },
4999 + "indexes": {
5000 + "publications_pmid_uq": {
5001 + "name": "publications_pmid_uq",
5002 + "columns": [
5003 + {
5004 + "expression": "pmid",
5005 + "isExpression": false,
5006 + "asc": true,
5007 + "nulls": "last"
5008 + }
5009 + ],
5010 + "isUnique": true,
5011 + "concurrently": false,
5012 + "method": "btree",
5013 + "with": {}
5014 + },
5015 + "publications_doi_idx": {
5016 + "name": "publications_doi_idx",
5017 + "columns": [
5018 + {
5019 + "expression": "doi",
5020 + "isExpression": false,
5021 + "asc": true,
5022 + "nulls": "last"
5023 + }
5024 + ],
5025 + "isUnique": false,
5026 + "concurrently": false,
5027 + "method": "btree",
5028 + "with": {}
5029 + },
5030 + "publications_year_idx": {
5031 + "name": "publications_year_idx",
5032 + "columns": [
5033 + {
5034 + "expression": "pub_year",
5035 + "isExpression": false,
5036 + "asc": true,
5037 + "nulls": "last"
5038 + }
5039 + ],
5040 + "isUnique": false,
5041 + "concurrently": false,
5042 + "method": "btree",
5043 + "with": {}
5044 + },
5045 + "publications_retracted_idx": {
5046 + "name": "publications_retracted_idx",
5047 + "columns": [
5048 + {
5049 + "expression": "retracted",
5050 + "isExpression": false,
5051 + "asc": true,
5052 + "nulls": "last"
5053 + }
5054 + ],
5055 + "isUnique": false,
5056 + "concurrently": false,
5057 + "method": "btree",
5058 + "with": {}
5059 + }
5060 + },
5061 + "foreignKeys": {},
5062 + "compositePrimaryKeys": {},
5063 + "uniqueConstraints": {},
5064 + "policies": {},
5065 + "checkConstraints": {},
5066 + "isRLSEnabled": false
5067 + },
5068 + "public.civic_evidence_items": {
5069 + "name": "civic_evidence_items",
5070 + "schema": "",
5071 + "columns": {
5072 + "id": {
5073 + "name": "id",
5074 + "type": "bigserial",
5075 + "primaryKey": true,
5076 + "notNull": true
5077 + },
5078 + "civic_id": {
5079 + "name": "civic_id",
5080 + "type": "integer",
5081 + "primaryKey": false,
5082 + "notNull": true
5083 + },
5084 + "name": {
5085 + "name": "name",
5086 + "type": "text",
5087 + "primaryKey": false,
5088 + "notNull": false
5089 + },
5090 + "molecular_profile_id": {
5091 + "name": "molecular_profile_id",
5092 + "type": "integer",
5093 + "primaryKey": false,
5094 + "notNull": false
5095 + },
5096 + "molecular_profile_name": {
5097 + "name": "molecular_profile_name",
5098 + "type": "text",
5099 + "primaryKey": false,
5100 + "notNull": false
5101 + },
5102 + "gene_symbols": {
5103 + "name": "gene_symbols",
5104 + "type": "text[]",
5105 + "primaryKey": false,
5106 + "notNull": true,
5107 + "default": "'{}'"
5108 + },
5109 + "gene_ids": {
5110 + "name": "gene_ids",
5111 + "type": "text[]",
5112 + "primaryKey": false,
5113 + "notNull": true,
5114 + "default": "'{}'"
5115 + },
5116 + "variant_ids": {
5117 + "name": "variant_ids",
5118 + "type": "text[]",
5119 + "primaryKey": false,
5120 + "notNull": true,
5121 + "default": "'{}'"
5122 + },
5123 + "civic_variant_ids": {
5124 + "name": "civic_variant_ids",
5125 + "type": "integer[]",
5126 + "primaryKey": false,
5127 + "notNull": true,
5128 + "default": "'{}'"
5129 + },
5130 + "disease_name": {
5131 + "name": "disease_name",
5132 + "type": "text",
5133 + "primaryKey": false,
5134 + "notNull": false
5135 + },
5136 + "doid": {
5137 + "name": "doid",
5138 + "type": "text",
5139 + "primaryKey": false,
5140 + "notNull": false
5141 + },
5142 + "cancer_id": {
5143 + "name": "cancer_id",
5144 + "type": "varchar(32)",
5145 + "primaryKey": false,
5146 + "notNull": false
5147 + },
5148 + "cancer_match_type": {
5149 + "name": "cancer_match_type",
5150 + "type": "text",
5151 + "primaryKey": false,
5152 + "notNull": false
5153 + },
5154 + "therapy_names": {
5155 + "name": "therapy_names",
5156 + "type": "text[]",
5157 + "primaryKey": false,
5158 + "notNull": true,
5159 + "default": "'{}'"
5160 + },
5161 + "therapy_ids": {
5162 + "name": "therapy_ids",
5163 + "type": "text[]",
5164 + "primaryKey": false,
5165 + "notNull": true,
5166 + "default": "'{}'"
5167 + },
5168 + "therapy_interaction_type": {
5169 + "name": "therapy_interaction_type",
5170 + "type": "text",
5171 + "primaryKey": false,
5172 + "notNull": false
5173 + },
5174 + "evidence_type": {
5175 + "name": "evidence_type",
5176 + "type": "text",
5177 + "primaryKey": false,
5178 + "notNull": false
5179 + },
5180 + "evidence_level": {
5181 + "name": "evidence_level",
5182 + "type": "text",
5183 + "primaryKey": false,
5184 + "notNull": false
5185 + },
5186 + "evidence_direction": {
5187 + "name": "evidence_direction",
5188 + "type": "text",
5189 + "primaryKey": false,
5190 + "notNull": false
5191 + },
5192 + "significance": {
5193 + "name": "significance",
5194 + "type": "text",
5195 + "primaryKey": false,
5196 + "notNull": false
5197 + },
5198 + "evidence_rating": {
5199 + "name": "evidence_rating",
5200 + "type": "integer",
5201 + "primaryKey": false,
5202 + "notNull": false
5203 + },
5204 + "status": {
5205 + "name": "status",
5206 + "type": "text",
5207 + "primaryKey": false,
5208 + "notNull": false
5209 + },
5210 + "description": {
5211 + "name": "description",
5212 + "type": "text",
5213 + "primaryKey": false,
5214 + "notNull": false
5215 + },
5216 + "pmid": {
5217 + "name": "pmid",
5218 + "type": "text",
5219 + "primaryKey": false,
5220 + "notNull": false
5221 + },
5222 + "source_citation": {
5223 + "name": "source_citation",
5224 + "type": "text",
5225 + "primaryKey": false,
5226 + "notNull": false
5227 + },
5228 + "phenotypes": {
5229 + "name": "phenotypes",
5230 + "type": "text[]",
5231 + "primaryKey": false,
5232 + "notNull": true,
5233 + "default": "'{}'"
5234 + },
5235 + "provenance_id": {
5236 + "name": "provenance_id",
5237 + "type": "integer",
5238 + "primaryKey": false,
5239 + "notNull": true
5240 + },
5241 + "ingest_run_id": {
5242 + "name": "ingest_run_id",
5243 + "type": "text",
5244 + "primaryKey": false,
5245 + "notNull": false
5246 + },
5247 + "updated_at": {
5248 + "name": "updated_at",
5249 + "type": "timestamp with time zone",
5250 + "primaryKey": false,
5251 + "notNull": true,
5252 + "default": "now()"
5253 + }
5254 + },
5255 + "indexes": {
5256 + "civic_evidence_uq": {
5257 + "name": "civic_evidence_uq",
5258 + "columns": [
5259 + {
5260 + "expression": "civic_id",
5261 + "isExpression": false,
5262 + "asc": true,
5263 + "nulls": "last"
5264 + }
5265 + ],
5266 + "isUnique": true,
5267 + "concurrently": false,
5268 + "method": "btree",
5269 + "with": {}
5270 + },
5271 + "civic_evidence_cancer_idx": {
5272 + "name": "civic_evidence_cancer_idx",
5273 + "columns": [
5274 + {
5275 + "expression": "cancer_id",
5276 + "isExpression": false,
5277 + "asc": true,
5278 + "nulls": "last"
5279 + }
5280 + ],
5281 + "isUnique": false,
5282 + "concurrently": false,
5283 + "method": "btree",
5284 + "with": {}
5285 + },
5286 + "civic_evidence_gene_idx": {
5287 + "name": "civic_evidence_gene_idx",
5288 + "columns": [
5289 + {
5290 + "expression": "gene_symbols",
5291 + "isExpression": false,
5292 + "asc": true,
5293 + "nulls": "last"
5294 + }
5295 + ],
5296 + "isUnique": false,
5297 + "concurrently": false,
5298 + "method": "btree",
5299 + "with": {}
5300 + }
5301 + },
5302 + "foreignKeys": {},
5303 + "compositePrimaryKeys": {},
5304 + "uniqueConstraints": {},
5305 + "policies": {},
5306 + "checkConstraints": {},
5307 + "isRLSEnabled": false
5308 + },
5309 + "public.knowledge_edges": {
5310 + "name": "knowledge_edges",
5311 + "schema": "",
5312 + "columns": {
5313 + "id": {
5314 + "name": "id",
5315 + "type": "bigserial",
5316 + "primaryKey": true,
5317 + "notNull": true
5318 + },
5319 + "source_entity_type": {
5320 + "name": "source_entity_type",
5321 + "type": "text",
5322 + "primaryKey": false,
5323 + "notNull": true
5324 + },
5325 + "source_entity_id": {
5326 + "name": "source_entity_id",
5327 + "type": "text",
5328 + "primaryKey": false,
5329 + "notNull": true
5330 + },
5331 + "target_entity_type": {
5332 + "name": "target_entity_type",
5333 + "type": "text",
5334 + "primaryKey": false,
5335 + "notNull": true
5336 + },
5337 + "target_entity_id": {
5338 + "name": "target_entity_id",
5339 + "type": "text",
5340 + "primaryKey": false,
5341 + "notNull": true
5342 + },
5343 + "relationship_type": {
5344 + "name": "relationship_type",
5345 + "type": "text",
5346 + "primaryKey": false,
5347 + "notNull": true
5348 + },
5349 + "cancer_context_ids": {
5350 + "name": "cancer_context_ids",
5351 + "type": "text[]",
5352 + "primaryKey": false,
5353 + "notNull": true,
5354 + "default": "'{}'"
5355 + },
5356 + "predictive": {
5357 + "name": "predictive",
5358 + "type": "boolean",
5359 + "primaryKey": false,
5360 + "notNull": false
5361 + },
5362 + "prognostic": {
5363 + "name": "prognostic",
5364 + "type": "boolean",
5365 + "primaryKey": false,
5366 + "notNull": false
5367 + },
5368 + "diagnostic": {
5369 + "name": "diagnostic",
5370 + "type": "boolean",
5371 + "primaryKey": false,
5372 + "notNull": false
5373 + },
5374 + "predisposing": {
5375 + "name": "predisposing",
5376 + "type": "boolean",
5377 + "primaryKey": false,
5378 + "notNull": false
5379 + },
5380 + "direction": {
5381 + "name": "direction",
5382 + "type": "text",
5383 + "primaryKey": false,
5384 + "notNull": false
5385 + },
5386 + "evidence_level": {
5387 + "name": "evidence_level",
5388 + "type": "text",
5389 + "primaryKey": false,
5390 + "notNull": false
5391 + },
5392 + "evidence_score": {
5393 + "name": "evidence_score",
5394 + "type": "real",
5395 + "primaryKey": false,
5396 + "notNull": false
5397 + },
5398 + "evidence_category": {
5399 + "name": "evidence_category",
5400 + "type": "text",
5401 + "primaryKey": false,
5402 + "notNull": true,
5403 + "default": "'curated_evidence'"
5404 + },
5405 + "status": {
5406 + "name": "status",
5407 + "type": "text",
5408 + "primaryKey": false,
5409 + "notNull": true,
5410 + "default": "'active'"
5411 + },
5412 + "source_id": {
5413 + "name": "source_id",
5414 + "type": "varchar(32)",
5415 + "primaryKey": false,
5416 + "notNull": true
5417 + },
5418 + "source_record_id": {
5419 + "name": "source_record_id",
5420 + "type": "text",
5421 + "primaryKey": false,
5422 + "notNull": false
5423 + },
5424 + "provenance_ids": {
5425 + "name": "provenance_ids",
5426 + "type": "integer[]",
5427 + "primaryKey": false,
5428 + "notNull": true,
5429 + "default": "'{}'"
5430 + },
5431 + "support_count": {
5432 + "name": "support_count",
5433 + "type": "integer",
5434 + "primaryKey": false,
5435 + "notNull": true,
5436 + "default": 1
5437 + },
5438 + "first_seen_at": {
5439 + "name": "first_seen_at",
5440 + "type": "timestamp with time zone",
5441 + "primaryKey": false,
5442 + "notNull": true,
5443 + "default": "now()"
5444 + },
5445 + "last_seen_at": {
5446 + "name": "last_seen_at",
5447 + "type": "timestamp with time zone",
5448 + "primaryKey": false,
5449 + "notNull": true,
5450 + "default": "now()"
5451 + }
5452 + },
5453 + "indexes": {
5454 + "knowledge_edges_uq": {
5455 + "name": "knowledge_edges_uq",
5456 + "columns": [
5457 + {
5458 + "expression": "source_entity_type",
5459 + "isExpression": false,
5460 + "asc": true,
5461 + "nulls": "last"
5462 + },
5463 + {
5464 + "expression": "source_entity_id",
5465 + "isExpression": false,
5466 + "asc": true,
5467 + "nulls": "last"
5468 + },
5469 + {
5470 + "expression": "target_entity_type",
5471 + "isExpression": false,
5472 + "asc": true,
5473 + "nulls": "last"
5474 + },
5475 + {
5476 + "expression": "target_entity_id",
5477 + "isExpression": false,
5478 + "asc": true,
5479 + "nulls": "last"
5480 + },
5481 + {
5482 + "expression": "relationship_type",
5483 + "isExpression": false,
5484 + "asc": true,
5485 + "nulls": "last"
5486 + },
5487 + {
5488 + "expression": "source_id",
5489 + "isExpression": false,
5490 + "asc": true,
5491 + "nulls": "last"
5492 + },
5493 + {
5494 + "expression": "source_record_id",
5495 + "isExpression": false,
5496 + "asc": true,
5497 + "nulls": "last"
5498 + }
5499 + ],
5500 + "isUnique": true,
5501 + "concurrently": false,
5502 + "method": "btree",
5503 + "with": {}
5504 + },
5505 + "knowledge_edges_source_idx": {
5506 + "name": "knowledge_edges_source_idx",
5507 + "columns": [
5508 + {
5509 + "expression": "source_entity_type",
5510 + "isExpression": false,
5511 + "asc": true,
5512 + "nulls": "last"
5513 + },
5514 + {
5515 + "expression": "source_entity_id",
5516 + "isExpression": false,
5517 + "asc": true,
5518 + "nulls": "last"
5519 + },
5520 + {
5521 + "expression": "relationship_type",
5522 + "isExpression": false,
5523 + "asc": true,
5524 + "nulls": "last"
5525 + }
5526 + ],
5527 + "isUnique": false,
5528 + "concurrently": false,
5529 + "method": "btree",
5530 + "with": {}
5531 + },
5532 + "knowledge_edges_target_idx": {
5533 + "name": "knowledge_edges_target_idx",
5534 + "columns": [
5535 + {
5536 + "expression": "target_entity_type",
5537 + "isExpression": false,
5538 + "asc": true,
5539 + "nulls": "last"
5540 + },
5541 + {
5542 + "expression": "target_entity_id",
5543 + "isExpression": false,
5544 + "asc": true,
5545 + "nulls": "last"
5546 + },
5547 + {
5548 + "expression": "relationship_type",
5549 + "isExpression": false,
5550 + "asc": true,
5551 + "nulls": "last"
5552 + }
5553 + ],
5554 + "isUnique": false,
5555 + "concurrently": false,
5556 + "method": "btree",
5557 + "with": {}
5558 + }
5559 + },
5560 + "foreignKeys": {},
5561 + "compositePrimaryKeys": {},
5562 + "uniqueConstraints": {},
5563 + "policies": {},
5564 + "checkConstraints": {},
5565 + "isRLSEnabled": false
5566 + },
5567 + "public.risk_factors": {
5568 + "name": "risk_factors",
5569 + "schema": "",
5570 + "columns": {
5571 + "id": {
5572 + "name": "id",
5573 + "type": "bigserial",
5574 + "primaryKey": true,
5575 + "notNull": true
5576 + },
5577 + "slug": {
5578 + "name": "slug",
5579 + "type": "text",
5580 + "primaryKey": false,
5581 + "notNull": true
5582 + },
5583 + "name": {
5584 + "name": "name",
5585 + "type": "text",
5586 + "primaryKey": false,
5587 + "notNull": true
5588 + },
5589 + "kind": {
5590 + "name": "kind",
5591 + "type": "text",
5592 + "primaryKey": false,
5593 + "notNull": true
5594 + },
5595 + "classification_authority": {
5596 + "name": "classification_authority",
5597 + "type": "text",
5598 + "primaryKey": false,
5599 + "notNull": false
5600 + },
5601 + "classification": {
5602 + "name": "classification",
5603 + "type": "text",
5604 + "primaryKey": false,
5605 + "notNull": false
5606 + },
5607 + "description": {
5608 + "name": "description",
5609 + "type": "text",
5610 + "primaryKey": false,
5611 + "notNull": false
5612 + },
5613 + "created_at": {
5614 + "name": "created_at",
5615 + "type": "timestamp with time zone",
5616 + "primaryKey": false,
5617 + "notNull": true,
5618 + "default": "now()"
5619 + }
5620 + },
5621 + "indexes": {
5622 + "risk_factors_slug_uq": {
5623 + "name": "risk_factors_slug_uq",
5624 + "columns": [
5625 + {
5626 + "expression": "slug",
5627 + "isExpression": false,
5628 + "asc": true,
5629 + "nulls": "last"
5630 + }
5631 + ],
5632 + "isUnique": true,
5633 + "concurrently": false,
5634 + "method": "btree",
5635 + "with": {}
5636 + }
5637 + },
5638 + "foreignKeys": {},
5639 + "compositePrimaryKeys": {},
5640 + "uniqueConstraints": {},
5641 + "policies": {},
5642 + "checkConstraints": {},
5643 + "isRLSEnabled": false
5644 + },
5645 + "public.epidemiology_observations": {
5646 + "name": "epidemiology_observations",
5647 + "schema": "",
5648 + "columns": {
5649 + "id": {
5650 + "name": "id",
5651 + "type": "bigserial",
5652 + "primaryKey": true,
5653 + "notNull": true
5654 + },
5655 + "cancer_id": {
5656 + "name": "cancer_id",
5657 + "type": "varchar(32)",
5658 + "primaryKey": false,
5659 + "notNull": true
5660 + },
5661 + "geography_id": {
5662 + "name": "geography_id",
5663 + "type": "varchar(32)",
5664 + "primaryKey": false,
5665 + "notNull": true
5666 + },
5667 + "year": {
5668 + "name": "year",
5669 + "type": "integer",
5670 + "primaryKey": false,
5671 + "notNull": true
5672 + },
5673 + "year_end": {
5674 + "name": "year_end",
5675 + "type": "integer",
5676 + "primaryKey": false,
5677 + "notNull": false
5678 + },
5679 + "sex": {
5680 + "name": "sex",
5681 + "type": "text",
5682 + "primaryKey": false,
5683 + "notNull": true,
5684 + "default": "'all'"
5685 + },
5686 + "age_group": {
5687 + "name": "age_group",
5688 + "type": "text",
5689 + "primaryKey": false,
5690 + "notNull": true,
5691 + "default": "'all'"
5692 + },
5693 + "metric": {
5694 + "name": "metric",
5695 + "type": "text",
5696 + "primaryKey": false,
5697 + "notNull": true
5698 + },
5699 + "value": {
5700 + "name": "value",
5701 + "type": "double precision",
5702 + "primaryKey": false,
5703 + "notNull": true
5704 + },
5705 + "unit": {
5706 + "name": "unit",
5707 + "type": "text",
5708 + "primaryKey": false,
5709 + "notNull": true
5710 + },
5711 + "lower_ci": {
5712 + "name": "lower_ci",
5713 + "type": "double precision",
5714 + "primaryKey": false,
5715 + "notNull": false
5716 + },
5717 + "upper_ci": {
5718 + "name": "upper_ci",
5719 + "type": "double precision",
5720 + "primaryKey": false,
5721 + "notNull": false
5722 + },
5723 + "standard_population": {
5724 + "name": "standard_population",
5725 + "type": "text",
5726 + "primaryKey": false,
5727 + "notNull": false
5728 + },
5729 + "estimate_type": {
5730 + "name": "estimate_type",
5731 + "type": "text",
5732 + "primaryKey": false,
5733 + "notNull": true,
5734 + "default": "'observed'"
5735 + },
5736 + "site_definition": {
5737 + "name": "site_definition",
5738 + "type": "text",
5739 + "primaryKey": false,
5740 + "notNull": false
5741 + },
5742 + "source_id": {
5743 + "name": "source_id",
5744 + "type": "varchar(32)",
5745 + "primaryKey": false,
5746 + "notNull": true
5747 + },
5748 + "provenance_id": {
5749 + "name": "provenance_id",
5750 + "type": "integer",
5751 + "primaryKey": false,
5752 + "notNull": true
5753 + },
5754 + "ingest_run_id": {
5755 + "name": "ingest_run_id",
5756 + "type": "text",
5757 + "primaryKey": false,
5758 + "notNull": false
5759 + },
5760 + "updated_at": {
5761 + "name": "updated_at",
5762 + "type": "timestamp with time zone",
5763 + "primaryKey": false,
5764 + "notNull": true,
5765 + "default": "now()"
5766 + }
5767 + },
5768 + "indexes": {
5769 + "epi_obs_uq": {
5770 + "name": "epi_obs_uq",
5771 + "columns": [
5772 + {
5773 + "expression": "cancer_id",
5774 + "isExpression": false,
5775 + "asc": true,
5776 + "nulls": "last"
5777 + },
5778 + {
5779 + "expression": "geography_id",
5780 + "isExpression": false,
5781 + "asc": true,
5782 + "nulls": "last"
5783 + },
5784 + {
5785 + "expression": "year",
5786 + "isExpression": false,
5787 + "asc": true,
5788 + "nulls": "last"
5789 + },
5790 + {
5791 + "expression": "sex",
5792 + "isExpression": false,
5793 + "asc": true,
5794 + "nulls": "last"
5795 + },
5796 + {
5797 + "expression": "age_group",
5798 + "isExpression": false,
5799 + "asc": true,
5800 + "nulls": "last"
5801 + },
5802 + {
5803 + "expression": "metric",
5804 + "isExpression": false,
5805 + "asc": true,
5806 + "nulls": "last"
5807 + },
5808 + {
5809 + "expression": "source_id",
5810 + "isExpression": false,
5811 + "asc": true,
5812 + "nulls": "last"
5813 + },
5814 + {
5815 + "expression": "site_definition",
5816 + "isExpression": false,
5817 + "asc": true,
5818 + "nulls": "last"
5819 + }
5820 + ],
5821 + "isUnique": true,
5822 + "concurrently": false,
5823 + "method": "btree",
5824 + "with": {}
5825 + },
5826 + "epi_obs_lookup_idx": {
5827 + "name": "epi_obs_lookup_idx",
5828 + "columns": [
5829 + {
5830 + "expression": "metric",
5831 + "isExpression": false,
5832 + "asc": true,
5833 + "nulls": "last"
5834 + },
5835 + {
5836 + "expression": "geography_id",
5837 + "isExpression": false,
5838 + "asc": true,
5839 + "nulls": "last"
5840 + },
5841 + {
5842 + "expression": "year",
5843 + "isExpression": false,
5844 + "asc": true,
5845 + "nulls": "last"
5846 + },
5847 + {
5848 + "expression": "sex",
5849 + "isExpression": false,
5850 + "asc": true,
5851 + "nulls": "last"
5852 + }
5853 + ],
5854 + "isUnique": false,
5855 + "concurrently": false,
5856 + "method": "btree",
5857 + "with": {}
5858 + },
5859 + "epi_obs_cancer_idx": {
5860 + "name": "epi_obs_cancer_idx",
5861 + "columns": [
5862 + {
5863 + "expression": "cancer_id",
5864 + "isExpression": false,
5865 + "asc": true,
5866 + "nulls": "last"
5867 + },
5868 + {
5869 + "expression": "metric",
5870 + "isExpression": false,
5871 + "asc": true,
5872 + "nulls": "last"
5873 + }
5874 + ],
5875 + "isUnique": false,
5876 + "concurrently": false,
5877 + "method": "btree",
5878 + "with": {}
5879 + }
5880 + },
5881 + "foreignKeys": {},
5882 + "compositePrimaryKeys": {},
5883 + "uniqueConstraints": {},
5884 + "policies": {},
5885 + "checkConstraints": {},
5886 + "isRLSEnabled": false
5887 + },
5888 + "public.survival_observations": {
5889 + "name": "survival_observations",
5890 + "schema": "",
5891 + "columns": {
5892 + "id": {
5893 + "name": "id",
5894 + "type": "bigserial",
5895 + "primaryKey": true,
5896 + "notNull": true
5897 + },
5898 + "cancer_id": {
5899 + "name": "cancer_id",
5900 + "type": "varchar(32)",
5901 + "primaryKey": false,
5902 + "notNull": true
5903 + },
5904 + "geography_id": {
5905 + "name": "geography_id",
5906 + "type": "varchar(32)",
5907 + "primaryKey": false,
5908 + "notNull": false
5909 + },
5910 + "stage": {
5911 + "name": "stage",
5912 + "type": "text",
5913 + "primaryKey": false,
5914 + "notNull": false
5915 + },
5916 + "staging_system": {
5917 + "name": "staging_system",
5918 + "type": "text",
5919 + "primaryKey": false,
5920 + "notNull": false
5921 + },
5922 + "sex": {
5923 + "name": "sex",
5924 + "type": "text",
5925 + "primaryKey": false,
5926 + "notNull": true,
5927 + "default": "'all'"
5928 + },
5929 + "age_group": {
5930 + "name": "age_group",
5931 + "type": "text",
5932 + "primaryKey": false,
5933 + "notNull": true,
5934 + "default": "'all'"
5935 + },
5936 + "diagnosis_period": {
5937 + "name": "diagnosis_period",
5938 + "type": "text",
5939 + "primaryKey": false,
5940 + "notNull": false
5941 + },
5942 + "survival_type": {
5943 + "name": "survival_type",
5944 + "type": "text",
5945 + "primaryKey": false,
5946 + "notNull": true
5947 + },
5948 + "duration_months": {
5949 + "name": "duration_months",
5950 + "type": "integer",
5951 + "primaryKey": false,
5952 + "notNull": true
5953 + },
5954 + "probability": {
5955 + "name": "probability",
5956 + "type": "real",
5957 + "primaryKey": false,
5958 + "notNull": false
5959 + },
5960 + "median_months": {
5961 + "name": "median_months",
5962 + "type": "real",
5963 + "primaryKey": false,
5964 + "notNull": false
5965 + },
5966 + "cohort_size": {
5967 + "name": "cohort_size",
5968 + "type": "integer",
5969 + "primaryKey": false,
5970 + "notNull": false
5971 + },
5972 + "lower_ci": {
5973 + "name": "lower_ci",
5974 + "type": "real",
5975 + "primaryKey": false,
5976 + "notNull": false
5977 + },
5978 + "upper_ci": {
5979 + "name": "upper_ci",
5980 + "type": "real",
5981 + "primaryKey": false,
5982 + "notNull": false
5983 + },
5984 + "method": {
5985 + "name": "method",
5986 + "type": "text",
5987 + "primaryKey": false,
5988 + "notNull": false
5989 + },
5990 + "source_id": {
5991 + "name": "source_id",
5992 + "type": "varchar(32)",
5993 + "primaryKey": false,
5994 + "notNull": true
5995 + },
5996 + "provenance_id": {
5997 + "name": "provenance_id",
5998 + "type": "integer",
5999 + "primaryKey": false,
6000 + "notNull": true
6001 + },
6002 + "ingest_run_id": {
6003 + "name": "ingest_run_id",
6004 + "type": "text",
6005 + "primaryKey": false,
6006 + "notNull": false
6007 + },
6008 + "updated_at": {
6009 + "name": "updated_at",
6010 + "type": "timestamp with time zone",
6011 + "primaryKey": false,
6012 + "notNull": true,
6013 + "default": "now()"
6014 + }
6015 + },
6016 + "indexes": {
6017 + "survival_obs_cancer_idx": {
6018 + "name": "survival_obs_cancer_idx",
6019 + "columns": [
6020 + {
6021 + "expression": "cancer_id",
6022 + "isExpression": false,
6023 + "asc": true,
6024 + "nulls": "last"
6025 + },
6026 + {
6027 + "expression": "survival_type",
6028 + "isExpression": false,
6029 + "asc": true,
6030 + "nulls": "last"
6031 + },
6032 + {
6033 + "expression": "duration_months",
6034 + "isExpression": false,
6035 + "asc": true,
6036 + "nulls": "last"
6037 + }
6038 + ],
6039 + "isUnique": false,
6040 + "concurrently": false,
6041 + "method": "btree",
6042 + "with": {}
6043 + }
6044 + },
6045 + "foreignKeys": {},
6046 + "compositePrimaryKeys": {},
6047 + "uniqueConstraints": {},
6048 + "policies": {},
6049 + "checkConstraints": {},
6050 + "isRLSEnabled": false
6051 + },
6052 + "public.ai_answers": {
6053 + "name": "ai_answers",
6054 + "schema": "",
6055 + "columns": {
6056 + "id": {
6057 + "name": "id",
6058 + "type": "bigserial",
6059 + "primaryKey": true,
6060 + "notNull": true
6061 + },
6062 + "kind": {
6063 + "name": "kind",
6064 + "type": "text",
6065 + "primaryKey": false,
6066 + "notNull": true
6067 + },
6068 + "subject_id": {
6069 + "name": "subject_id",
6070 + "type": "text",
6071 + "primaryKey": false,
6072 + "notNull": false
6073 + },
6074 + "question_hash": {
6075 + "name": "question_hash",
6076 + "type": "text",
6077 + "primaryKey": false,
6078 + "notNull": true
6079 + },
6080 + "question": {
6081 + "name": "question",
6082 + "type": "text",
6083 + "primaryKey": false,
6084 + "notNull": false
6085 + },
6086 + "answer": {
6087 + "name": "answer",
6088 + "type": "jsonb",
6089 + "primaryKey": false,
6090 + "notNull": true
6091 + },
6092 + "model": {
6093 + "name": "model",
6094 + "type": "text",
6095 + "primaryKey": false,
6096 + "notNull": true
6097 + },
6098 + "prompt_version": {
6099 + "name": "prompt_version",
6100 + "type": "text",
6101 + "primaryKey": false,
6102 + "notNull": true
6103 + },
6104 + "source_snapshot": {
6105 + "name": "source_snapshot",
6106 + "type": "jsonb",
6107 + "primaryKey": false,
6108 + "notNull": true,
6109 + "default": "'{}'::jsonb"
6110 + },
6111 + "data_as_of": {
6112 + "name": "data_as_of",
6113 + "type": "timestamp with time zone",
6114 + "primaryKey": false,
6115 + "notNull": true
6116 + },
6117 + "created_at": {
6118 + "name": "created_at",
6119 + "type": "timestamp with time zone",
6120 + "primaryKey": false,
6121 + "notNull": true,
6122 + "default": "now()"
6123 + }
6124 + },
6125 + "indexes": {
6126 + "ai_answers_uq": {
6127 + "name": "ai_answers_uq",
6128 + "columns": [
6129 + {
6130 + "expression": "kind",
6131 + "isExpression": false,
6132 + "asc": true,
6133 + "nulls": "last"
6134 + },
6135 + {
6136 + "expression": "question_hash",
6137 + "isExpression": false,
6138 + "asc": true,
6139 + "nulls": "last"
6140 + },
6141 + {
6142 + "expression": "prompt_version",
6143 + "isExpression": false,
6144 + "asc": true,
6145 + "nulls": "last"
6146 + }
6147 + ],
6148 + "isUnique": true,
6149 + "concurrently": false,
6150 + "method": "btree",
6151 + "with": {}
6152 + }
6153 + },
6154 + "foreignKeys": {},
6155 + "compositePrimaryKeys": {},
6156 + "uniqueConstraints": {},
6157 + "policies": {},
6158 + "checkConstraints": {},
6159 + "isRLSEnabled": false
6160 + },
6161 + "public.api_keys": {
6162 + "name": "api_keys",
6163 + "schema": "",
6164 + "columns": {
6165 + "id": {
6166 + "name": "id",
6167 + "type": "bigserial",
6168 + "primaryKey": true,
6169 + "notNull": true
6170 + },
6171 + "key_hash": {
6172 + "name": "key_hash",
6173 + "type": "text",
6174 + "primaryKey": false,
6175 + "notNull": true
6176 + },
6177 + "prefix": {
6178 + "name": "prefix",
6179 + "type": "text",
6180 + "primaryKey": false,
6181 + "notNull": true
6182 + },
6183 + "label": {
6184 + "name": "label",
6185 + "type": "text",
6186 + "primaryKey": false,
6187 + "notNull": false
6188 + },
6189 + "owner_email": {
6190 + "name": "owner_email",
6191 + "type": "text",
6192 + "primaryKey": false,
6193 + "notNull": false
6194 + },
6195 + "tier": {
6196 + "name": "tier",
6197 + "type": "text",
6198 + "primaryKey": false,
6199 + "notNull": true,
6200 + "default": "'free'"
6201 + },
6202 + "rate_limit_per_minute": {
6203 + "name": "rate_limit_per_minute",
6204 + "type": "integer",
6205 + "primaryKey": false,
6206 + "notNull": true,
6207 + "default": 60
6208 + },
6209 + "active": {
6210 + "name": "active",
6211 + "type": "boolean",
6212 + "primaryKey": false,
6213 + "notNull": true,
6214 + "default": true
6215 + },
6216 + "last_used_at": {
6217 + "name": "last_used_at",
6218 + "type": "timestamp with time zone",
6219 + "primaryKey": false,
6220 + "notNull": false
6221 + },
6222 + "created_at": {
6223 + "name": "created_at",
6224 + "type": "timestamp with time zone",
6225 + "primaryKey": false,
6226 + "notNull": true,
6227 + "default": "now()"
6228 + }
6229 + },
6230 + "indexes": {
6231 + "api_keys_hash_uq": {
6232 + "name": "api_keys_hash_uq",
6233 + "columns": [
6234 + {
6235 + "expression": "key_hash",
6236 + "isExpression": false,
6237 + "asc": true,
6238 + "nulls": "last"
6239 + }
6240 + ],
6241 + "isUnique": true,
6242 + "concurrently": false,
6243 + "method": "btree",
6244 + "with": {}
6245 + }
6246 + },
6247 + "foreignKeys": {},
6248 + "compositePrimaryKeys": {},
6249 + "uniqueConstraints": {},
6250 + "policies": {},
6251 + "checkConstraints": {},
6252 + "isRLSEnabled": false
6253 + },
6254 + "public.entity_counters": {
6255 + "name": "entity_counters",
6256 + "schema": "",
6257 + "columns": {
6258 + "id": {
6259 + "name": "id",
6260 + "type": "bigserial",
6261 + "primaryKey": true,
6262 + "notNull": true
6263 + },
6264 + "entity_type": {
6265 + "name": "entity_type",
6266 + "type": "text",
6267 + "primaryKey": false,
6268 + "notNull": true
6269 + },
6270 + "entity_id": {
6271 + "name": "entity_id",
6272 + "type": "text",
6273 + "primaryKey": false,
6274 + "notNull": true
6275 + },
6276 + "trial_count": {
6277 + "name": "trial_count",
6278 + "type": "integer",
6279 + "primaryKey": false,
6280 + "notNull": true,
6281 + "default": 0
6282 + },
6283 + "active_trial_count": {
6284 + "name": "active_trial_count",
6285 + "type": "integer",
6286 + "primaryKey": false,
6287 + "notNull": true,
6288 + "default": 0
6289 + },
6290 + "recruiting_trial_count": {
6291 + "name": "recruiting_trial_count",
6292 + "type": "integer",
6293 + "primaryKey": false,
6294 + "notNull": true,
6295 + "default": 0
6296 + },
6297 + "phase3_trial_count": {
6298 + "name": "phase3_trial_count",
6299 + "type": "integer",
6300 + "primaryKey": false,
6301 + "notNull": true,
6302 + "default": 0
6303 + },
6304 + "publication_count": {
6305 + "name": "publication_count",
6306 + "type": "integer",
6307 + "primaryKey": false,
6308 + "notNull": true,
6309 + "default": 0
6310 + },
6311 + "publication_count_5y": {
6312 + "name": "publication_count_5y",
6313 + "type": "integer",
6314 + "primaryKey": false,
6315 + "notNull": true,
6316 + "default": 0
6317 + },
6318 + "publication_count_12m": {
6319 + "name": "publication_count_12m",
6320 + "type": "integer",
6321 + "primaryKey": false,
6322 + "notNull": true,
6323 + "default": 0
6324 + },
6325 + "gene_count": {
6326 + "name": "gene_count",
6327 + "type": "integer",
6328 + "primaryKey": false,
6329 + "notNull": true,
6330 + "default": 0
6331 + },
6332 + "variant_count": {
6333 + "name": "variant_count",
6334 + "type": "integer",
6335 + "primaryKey": false,
6336 + "notNull": true,
6337 + "default": 0
6338 + },
6339 + "drug_count": {
6340 + "name": "drug_count",
6341 + "type": "integer",
6342 + "primaryKey": false,
6343 + "notNull": true,
6344 + "default": 0
6345 + },
6346 + "approved_drug_count": {
6347 + "name": "approved_drug_count",
6348 + "type": "integer",
6349 + "primaryKey": false,
6350 + "notNull": true,
6351 + "default": 0
6352 + },
6353 + "evidence_count": {
6354 + "name": "evidence_count",
6355 + "type": "integer",
6356 + "primaryKey": false,
6357 + "notNull": true,
6358 + "default": 0
6359 + },
6360 + "cohort_count": {
6361 + "name": "cohort_count",
6362 + "type": "integer",
6363 + "primaryKey": false,
6364 + "notNull": true,
6365 + "default": 0
6366 + },
6367 + "subtype_count": {
6368 + "name": "subtype_count",
6369 + "type": "integer",
6370 + "primaryKey": false,
6371 + "notNull": true,
6372 + "default": 0
6373 + },
6374 + "descendant_count": {
6375 + "name": "descendant_count",
6376 + "type": "integer",
6377 + "primaryKey": false,
6378 + "notNull": true,
6379 + "default": 0
6380 + },
6381 + "epidemiology_obs_count": {
6382 + "name": "epidemiology_obs_count",
6383 + "type": "integer",
6384 + "primaryKey": false,
6385 + "notNull": true,
6386 + "default": 0
6387 + },
6388 + "survival_obs_count": {
6389 + "name": "survival_obs_count",
6390 + "type": "integer",
6391 + "primaryKey": false,
6392 + "notNull": true,
6393 + "default": 0
6394 + },
6395 + "completeness": {
6396 + "name": "completeness",
6397 + "type": "jsonb",
6398 + "primaryKey": false,
6399 + "notNull": true,
6400 + "default": "'{}'::jsonb"
6401 + },
6402 + "updated_at": {
6403 + "name": "updated_at",
6404 + "type": "timestamp with time zone",
6405 + "primaryKey": false,
6406 + "notNull": true,
6407 + "default": "now()"
6408 + }
6409 + },
6410 + "indexes": {
6411 + "entity_counters_uq": {
6412 + "name": "entity_counters_uq",
6413 + "columns": [
6414 + {
6415 + "expression": "entity_type",
6416 + "isExpression": false,
6417 + "asc": true,
6418 + "nulls": "last"
6419 + },
6420 + {
6421 + "expression": "entity_id",
6422 + "isExpression": false,
6423 + "asc": true,
6424 + "nulls": "last"
6425 + }
6426 + ],
6427 + "isUnique": true,
6428 + "concurrently": false,
6429 + "method": "btree",
6430 + "with": {}
6431 + },
6432 + "entity_counters_trials_idx": {
6433 + "name": "entity_counters_trials_idx",
6434 + "columns": [
6435 + {
6436 + "expression": "entity_type",
6437 + "isExpression": false,
6438 + "asc": true,
6439 + "nulls": "last"
6440 + },
6441 + {
6442 + "expression": "active_trial_count",
6443 + "isExpression": false,
6444 + "asc": true,
6445 + "nulls": "last"
6446 + }
6447 + ],
6448 + "isUnique": false,
6449 + "concurrently": false,
6450 + "method": "btree",
6451 + "with": {}
6452 + }
6453 + },
6454 + "foreignKeys": {},
6455 + "compositePrimaryKeys": {},
6456 + "uniqueConstraints": {},
6457 + "policies": {},
6458 + "checkConstraints": {},
6459 + "isRLSEnabled": false
6460 + },
6461 + "public.metric_definitions": {
6462 + "name": "metric_definitions",
6463 + "schema": "",
6464 + "columns": {
6465 + "id": {
6466 + "name": "id",
6467 + "type": "varchar(32)",
6468 + "primaryKey": true,
6469 + "notNull": true
6470 + },
6471 + "slug": {
6472 + "name": "slug",
6473 + "type": "text",
6474 + "primaryKey": false,
6475 + "notNull": true
6476 + },
6477 + "name": {
6478 + "name": "name",
6479 + "type": "text",
6480 + "primaryKey": false,
6481 + "notNull": true
6482 + },
6483 + "description": {
6484 + "name": "description",
6485 + "type": "text",
6486 + "primaryKey": false,
6487 + "notNull": true
6488 + },
6489 + "formula": {
6490 + "name": "formula",
6491 + "type": "text",
6492 + "primaryKey": false,
6493 + "notNull": true
6494 + },
6495 + "formula_version": {
6496 + "name": "formula_version",
6497 + "type": "text",
6498 + "primaryKey": false,
6499 + "notNull": true
6500 + },
6501 + "unit": {
6502 + "name": "unit",
6503 + "type": "text",
6504 + "primaryKey": false,
6505 + "notNull": true
6506 + },
6507 + "higher_is_worse": {
6508 + "name": "higher_is_worse",
6509 + "type": "boolean",
6510 + "primaryKey": false,
6511 + "notNull": false
6512 + },
6513 + "aggregation": {
6514 + "name": "aggregation",
6515 + "type": "text",
6516 + "primaryKey": false,
6517 + "notNull": false
6518 + },
6519 + "valid_dimensions": {
6520 + "name": "valid_dimensions",
6521 + "type": "text[]",
6522 + "primaryKey": false,
6523 + "notNull": true,
6524 + "default": "'{}'"
6525 + },
6526 + "source_slugs": {
6527 + "name": "source_slugs",
6528 + "type": "text[]",
6529 + "primaryKey": false,
6530 + "notNull": true,
6531 + "default": "'{}'"
6532 + },
6533 + "category": {
6534 + "name": "category",
6535 + "type": "text",
6536 + "primaryKey": false,
6537 + "notNull": true
6538 + },
6539 + "eligibility": {
6540 + "name": "eligibility",
6541 + "type": "jsonb",
6542 + "primaryKey": false,
6543 + "notNull": true,
6544 + "default": "'{}'::jsonb"
6545 + },
6546 + "experimental": {
6547 + "name": "experimental",
6548 + "type": "boolean",
6549 + "primaryKey": false,
6550 + "notNull": true,
6551 + "default": false
6552 + },
6553 + "created_at": {
6554 + "name": "created_at",
6555 + "type": "timestamp with time zone",
6556 + "primaryKey": false,
6557 + "notNull": true,
6558 + "default": "now()"
6559 + },
6560 + "updated_at": {
6561 + "name": "updated_at",
6562 + "type": "timestamp with time zone",
6563 + "primaryKey": false,
6564 + "notNull": true,
6565 + "default": "now()"
6566 + }
6567 + },
6568 + "indexes": {
6569 + "metric_definitions_slug_uq": {
6570 + "name": "metric_definitions_slug_uq",
6571 + "columns": [
6572 + {
6573 + "expression": "slug",
6574 + "isExpression": false,
6575 + "asc": true,
6576 + "nulls": "last"
6577 + }
6578 + ],
6579 + "isUnique": true,
6580 + "concurrently": false,
6581 + "method": "btree",
6582 + "with": {}
6583 + }
6584 + },
6585 + "foreignKeys": {},
6586 + "compositePrimaryKeys": {},
6587 + "uniqueConstraints": {},
6588 + "policies": {},
6589 + "checkConstraints": {},
6590 + "isRLSEnabled": false
6591 + },
6592 + "public.ranking_snapshots": {
6593 + "name": "ranking_snapshots",
6594 + "schema": "",
6595 + "columns": {
6596 + "id": {
6597 + "name": "id",
6598 + "type": "bigserial",
6599 + "primaryKey": true,
6600 + "notNull": true
6601 + },
6602 + "metric_id": {
6603 + "name": "metric_id",
6604 + "type": "varchar(32)",
6605 + "primaryKey": false,
6606 + "notNull": true
6607 + },
6608 + "metric_slug": {
6609 + "name": "metric_slug",
6610 + "type": "text",
6611 + "primaryKey": false,
6612 + "notNull": true
6613 + },
6614 + "scope_key": {
6615 + "name": "scope_key",
6616 + "type": "text",
6617 + "primaryKey": false,
6618 + "notNull": true
6619 + },
6620 + "geography": {
6621 + "name": "geography",
6622 + "type": "text",
6623 + "primaryKey": false,
6624 + "notNull": true,
6625 + "default": "'WORLD'"
6626 + },
6627 + "sex": {
6628 + "name": "sex",
6629 + "type": "text",
6630 + "primaryKey": false,
6631 + "notNull": true,
6632 + "default": "'all'"
6633 + },
6634 + "age_group": {
6635 + "name": "age_group",
6636 + "type": "text",
6637 + "primaryKey": false,
6638 + "notNull": true,
6639 + "default": "'all'"
6640 + },
6641 + "year": {
6642 + "name": "year",
6643 + "type": "integer",
6644 + "primaryKey": false,
6645 + "notNull": false
6646 + },
6647 + "entity_level": {
6648 + "name": "entity_level",
6649 + "type": "text",
6650 + "primaryKey": false,
6651 + "notNull": true,
6652 + "default": "'top'"
6653 + },
6654 + "formula_version": {
6655 + "name": "formula_version",
6656 + "type": "text",
6657 + "primaryKey": false,
6658 + "notNull": true
6659 + },
6660 + "eligible_entities": {
6661 + "name": "eligible_entities",
6662 + "type": "integer",
6663 + "primaryKey": false,
6664 + "notNull": true
6665 + },
6666 + "inputs_hash": {
6667 + "name": "inputs_hash",
6668 + "type": "text",
6669 + "primaryKey": false,
6670 + "notNull": true
6671 + },
6672 + "source_ids": {
6673 + "name": "source_ids",
6674 + "type": "text[]",
6675 + "primaryKey": false,
6676 + "notNull": true,
6677 + "default": "'{}'"
6678 + },
6679 + "is_current": {
6680 + "name": "is_current",
6681 + "type": "boolean",
6682 + "primaryKey": false,
6683 + "notNull": true,
6684 + "default": true
6685 + },
6686 + "generated_at": {
6687 + "name": "generated_at",
6688 + "type": "timestamp with time zone",
6689 + "primaryKey": false,
6690 + "notNull": true,
6691 + "default": "now()"
6692 + }
6693 + },
6694 + "indexes": {
6695 + "ranking_snapshots_lookup_idx": {
6696 + "name": "ranking_snapshots_lookup_idx",
6697 + "columns": [
6698 + {
6699 + "expression": "metric_slug",
6700 + "isExpression": false,
6701 + "asc": true,
6702 + "nulls": "last"
6703 + },
6704 + {
6705 + "expression": "scope_key",
6706 + "isExpression": false,
6707 + "asc": true,
6708 + "nulls": "last"
6709 + },
6710 + {
6711 + "expression": "is_current",
6712 + "isExpression": false,
6713 + "asc": true,
6714 + "nulls": "last"
6715 + }
6716 + ],
6717 + "isUnique": false,
6718 + "concurrently": false,
6719 + "method": "btree",
6720 + "with": {}
6721 + }
6722 + },
6723 + "foreignKeys": {},
6724 + "compositePrimaryKeys": {},
6725 + "uniqueConstraints": {},
6726 + "policies": {},
6727 + "checkConstraints": {},
6728 + "isRLSEnabled": false
6729 + },
6730 + "public.rankings": {
6731 + "name": "rankings",
6732 + "schema": "",
6733 + "columns": {
6734 + "id": {
6735 + "name": "id",
6736 + "type": "bigserial",
6737 + "primaryKey": true,
6738 + "notNull": true
6739 + },
6740 + "snapshot_id": {
6741 + "name": "snapshot_id",
6742 + "type": "integer",
6743 + "primaryKey": false,
6744 + "notNull": true
6745 + },
6746 + "metric_slug": {
6747 + "name": "metric_slug",
6748 + "type": "text",
6749 + "primaryKey": false,
6750 + "notNull": true
6751 + },
6752 + "scope_key": {
6753 + "name": "scope_key",
6754 + "type": "text",
6755 + "primaryKey": false,
6756 + "notNull": true
6757 + },
6758 + "cancer_id": {
6759 + "name": "cancer_id",
6760 + "type": "varchar(32)",
6761 + "primaryKey": false,
6762 + "notNull": true
6763 + },
6764 + "rank": {
6765 + "name": "rank",
6766 + "type": "integer",
6767 + "primaryKey": false,
6768 + "notNull": true
6769 + },
6770 + "eligible_entities": {
6771 + "name": "eligible_entities",
6772 + "type": "integer",
6773 + "primaryKey": false,
6774 + "notNull": true
6775 + },
6776 + "percentile": {
6777 + "name": "percentile",
6778 + "type": "real",
6779 + "primaryKey": false,
6780 + "notNull": true
6781 + },
6782 + "value": {
6783 + "name": "value",
6784 + "type": "double precision",
6785 + "primaryKey": false,
6786 + "notNull": true
6787 + },
6788 + "unit": {
6789 + "name": "unit",
6790 + "type": "text",
6791 + "primaryKey": false,
6792 + "notNull": true
6793 + },
6794 + "confidence": {
6795 + "name": "confidence",
6796 + "type": "text",
6797 + "primaryKey": false,
6798 + "notNull": true,
6799 + "default": "'MEDIUM'"
6800 + },
6801 + "inputs": {
6802 + "name": "inputs",
6803 + "type": "jsonb",
6804 + "primaryKey": false,
6805 + "notNull": true,
6806 + "default": "'{}'::jsonb"
6807 + },
6808 + "breakdown": {
6809 + "name": "breakdown",
6810 + "type": "jsonb",
6811 + "primaryKey": false,
6812 + "notNull": false
6813 + },
6814 + "previous_rank": {
6815 + "name": "previous_rank",
6816 + "type": "integer",
6817 + "primaryKey": false,
6818 + "notNull": false
6819 + },
6820 + "generated_at": {
6821 + "name": "generated_at",
6822 + "type": "timestamp with time zone",
6823 + "primaryKey": false,
6824 + "notNull": true,
6825 + "default": "now()"
6826 + }
6827 + },
6828 + "indexes": {
6829 + "rankings_uq": {
6830 + "name": "rankings_uq",
6831 + "columns": [
6832 + {
6833 + "expression": "snapshot_id",
6834 + "isExpression": false,
6835 + "asc": true,
6836 + "nulls": "last"
6837 + },
6838 + {
6839 + "expression": "cancer_id",
6840 + "isExpression": false,
6841 + "asc": true,
6842 + "nulls": "last"
6843 + }
6844 + ],
6845 + "isUnique": true,
6846 + "concurrently": false,
6847 + "method": "btree",
6848 + "with": {}
6849 + },
6850 + "rankings_cancer_idx": {
6851 + "name": "rankings_cancer_idx",
6852 + "columns": [
6853 + {
6854 + "expression": "cancer_id",
6855 + "isExpression": false,
6856 + "asc": true,
6857 + "nulls": "last"
6858 + },
6859 + {
6860 + "expression": "metric_slug",
6861 + "isExpression": false,
6862 + "asc": true,
6863 + "nulls": "last"
6864 + }
6865 + ],
6866 + "isUnique": false,
6867 + "concurrently": false,
6868 + "method": "btree",
6869 + "with": {}
6870 + },
6871 + "rankings_lookup_idx": {
6872 + "name": "rankings_lookup_idx",
6873 + "columns": [
6874 + {
6875 + "expression": "metric_slug",
6876 + "isExpression": false,
6877 + "asc": true,
6878 + "nulls": "last"
6879 + },
6880 + {
6881 + "expression": "scope_key",
6882 + "isExpression": false,
6883 + "asc": true,
6884 + "nulls": "last"
6885 + },
6886 + {
6887 + "expression": "rank",
6888 + "isExpression": false,
6889 + "asc": true,
6890 + "nulls": "last"
6891 + }
6892 + ],
6893 + "isUnique": false,
6894 + "concurrently": false,
6895 + "method": "btree",
6896 + "with": {}
6897 + }
6898 + },
6899 + "foreignKeys": {},
6900 + "compositePrimaryKeys": {},
6901 + "uniqueConstraints": {},
6902 + "policies": {},
6903 + "checkConstraints": {},
6904 + "isRLSEnabled": false
6905 + }
6906 + },
6907 + "enums": {},
6908 + "schemas": {},
6909 + "sequences": {},
6910 + "roles": {},
6911 + "policies": {},
6912 + "views": {},
6913 + "_meta": {
6914 + "columns": {},
6915 + "schemas": {},
6916 + "tables": {}
6917 + }
6918 +}
\ No newline at end of file
added packages/database/migrations/meta/_journal.json +13 −0
@@ -0,0 +1,13 @@
1 +{
2 + "version": "7",
3 + "dialect": "postgresql",
4 + "entries": [
5 + {
6 + "idx": 0,
7 + "version": "7",
8 + "when": 1788858213248,
9 + "tag": "0000_small_blazing_skull",
10 + "breakpoints": true
11 + }
12 + ]
13 +}
\ No newline at end of file
added packages/database/package.json +37 −0
@@ -0,0 +1,37 @@
1 +{
2 + "name": "@cancerindex/database",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "exports": {
7 + ".": {
8 + "types": "./src/index.ts",
9 + "default": "./src/index.ts"
10 + },
11 + "./schema": {
12 + "types": "./src/schema/index.ts",
13 + "default": "./src/schema/index.ts"
14 + }
15 + },
16 + "scripts": {
17 + "build": "tsc -p tsconfig.json --noEmit",
18 + "typecheck": "tsc -p tsconfig.json --noEmit",
19 + "test": "vitest run --passWithNoTests",
20 + "generate": "drizzle-kit generate",
21 + "migrate": "tsx src/migrate.ts",
22 + "seed": "tsx src/seed.ts",
23 + "studio": "drizzle-kit studio"
24 + },
25 + "dependencies": {
26 + "@cancerindex/shared": "workspace:*",
27 + "drizzle-orm": "^0.45.0",
28 + "postgres": "^3.4.7"
29 + },
30 + "devDependencies": {
31 + "@types/node": "^24.0.0",
32 + "drizzle-kit": "^0.31.0",
33 + "tsx": "^4.20.0",
34 + "typescript": "^5.9.3",
35 + "vitest": "^3.2.0"
36 + }
37 +}
added packages/database/src/client.ts +42 −0
@@ -0,0 +1,42 @@
1 +import postgres from 'postgres';
2 +import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
3 +import * as schema from './schema/index.js';
4 +
5 +export type Database = PostgresJsDatabase<typeof schema>;
6 +
7 +let _sql: ReturnType<typeof postgres> | null = null;
8 +let _db: Database | null = null;
9 +
10 +export interface DbOptions {
11 + url?: string;
12 + max?: number;
13 +}
14 +
15 +/** Lazily created singleton; safe from Next.js server components, Fastify and workers alike. */
16 +export function getSql(opts: DbOptions = {}) {
17 + if (!_sql) {
18 + const url = opts.url ?? process.env.DATABASE_URL ?? 'postgres://localhost:5432/cancerindex';
19 + _sql = postgres(url, {
20 + max: opts.max ?? Number(process.env.DB_POOL_MAX ?? 10),
21 + idle_timeout: 30,
22 + connect_timeout: 10,
23 + prepare: true,
24 + transform: { undefined: null },
25 + onnotice: () => {},
26 + });
27 + }
28 + return _sql;
29 +}
30 +
31 +export function getDb(opts: DbOptions = {}): Database {
32 + if (!_db) _db = drizzle(getSql(opts), { schema, casing: 'snake_case' });
33 + return _db;
34 +}
35 +
36 +export async function closeDb(): Promise<void> {
37 + if (_sql) {
38 + await _sql.end({ timeout: 5 });
39 + _sql = null;
40 + _db = null;
41 + }
42 +}
added packages/database/src/ids.ts +25 −0
@@ -0,0 +1,25 @@
1 +import { sql } from 'drizzle-orm';
2 +import { formatId, type IdNamespace } from '@cancerindex/shared';
3 +import type { Database } from './client.js';
4 +
5 +/**
6 + * Mint stable public identifiers from per-namespace counters (CLAUDE.md §6).
7 + * Atomic upsert-increment; `count` ids are reserved in one round trip.
8 + */
9 +export async function mintIds(db: Database, ns: IdNamespace, count = 1): Promise<string[]> {
10 + if (count < 1) return [];
11 + const rows = await db.execute<{ next: string | number }>(sql`
12 + INSERT INTO id_sequences (namespace, next) VALUES (${ns}, ${count + 1})
13 + ON CONFLICT (namespace) DO UPDATE SET next = id_sequences.next + ${count}
14 + RETURNING next
15 + `);
16 + const next = Number(rows[0]?.next ?? 0);
17 + const first = next - count;
18 + return Array.from({ length: count }, (_, i) => formatId(ns, first + i));
19 +}
20 +
21 +export async function mintId(db: Database, ns: IdNamespace): Promise<string> {
22 + const [id] = await mintIds(db, ns, 1);
23 + if (!id) throw new Error('mint failed');
24 + return id;
25 +}
added packages/database/src/index.ts +5 −0
@@ -0,0 +1,5 @@
1 +export * from './client.js';
2 +export * from './ids.js';
3 +export * as schema from './schema/index.js';
4 +export * from './schema/index.js';
5 +export { runMigrations } from './migrate.js';
added packages/database/src/migrate.ts +40 −0
@@ -0,0 +1,40 @@
1 +import { migrate } from 'drizzle-orm/postgres-js/migrator';
2 +import { fileURLToPath } from 'node:url';
3 +import path from 'node:path';
4 +import { getDb, getSql, closeDb } from './client.js';
5 +
6 +/**
7 + * Runs SQL migrations from ./migrations (CLAUDE.md §284). Extensions are created first because
8 + * drizzle-kit does not manage them; pgvector is optional and only enabled when available.
9 + */
10 +export async function runMigrations(): Promise<void> {
11 + const sql = getSql({ max: 1 });
12 + await sql`CREATE EXTENSION IF NOT EXISTS pg_trgm`;
13 + await sql`CREATE EXTENSION IF NOT EXISTS unaccent`;
14 + const [vec] = await sql`SELECT 1 AS ok FROM pg_available_extensions WHERE name = 'vector'`;
15 + if (vec) await sql`CREATE EXTENSION IF NOT EXISTS vector`;
16 + else console.warn('[migrate] pgvector not available — semantic search disabled until installed');
17 + const here = path.dirname(fileURLToPath(import.meta.url));
18 + await migrate(getDb({ max: 1 }), { migrationsFolder: path.resolve(here, '../migrations') });
19 + // Trigram indexes for fuzzy search (not expressible in drizzle schema).
20 + await sql`CREATE INDEX IF NOT EXISTS cancer_aliases_trgm_idx ON cancer_aliases USING gin (normalized gin_trgm_ops)`;
21 + await sql`CREATE INDEX IF NOT EXISTS cancers_name_trgm_idx ON cancers USING gin (lower(canonical_name) gin_trgm_ops)`;
22 + await sql`CREATE INDEX IF NOT EXISTS genes_symbol_trgm_idx ON genes USING gin (lower(symbol) gin_trgm_ops)`;
23 + await sql`CREATE INDEX IF NOT EXISTS drugs_name_trgm_idx ON drugs USING gin (lower(name) gin_trgm_ops)`;
24 + await sql`CREATE INDEX IF NOT EXISTS trials_title_trgm_idx ON clinical_trials USING gin (lower(brief_title) gin_trgm_ops)`;
25 + await sql`CREATE INDEX IF NOT EXISTS publications_title_trgm_idx ON publications USING gin (lower(title) gin_trgm_ops)`;
26 +}
27 +
28 +const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
29 +if (isMain) {
30 + runMigrations()
31 + .then(async () => {
32 + console.log('[migrate] done');
33 + await closeDb();
34 + })
35 + .catch(async (err) => {
36 + console.error('[migrate] failed', err);
37 + await closeDb();
38 + process.exit(1);
39 + });
40 +}
added packages/database/src/schema/_common.ts +23 −0
@@ -0,0 +1,23 @@
1 +import { timestamp, varchar, customType } from 'drizzle-orm/pg-core';
2 +
3 +export const createdAt = () => timestamp('created_at', { withTimezone: true }).notNull().defaultNow();
4 +export const updatedAt = () => timestamp('updated_at', { withTimezone: true }).notNull().defaultNow();
5 +
6 +/** Public CancerIndex identifier column (CI-CAN-00000001 …). */
7 +export const ciId = (name = 'id') => varchar(name, { length: 32 });
8 +
9 +/** pgvector column (embedding model is stored alongside, never mix models in one column without it). */
10 +export const vector = customType<{ data: number[]; driverData: string; config: { dimensions: number } }>({
11 + dataType(config) {
12 + return `vector(${config?.dimensions ?? 1536})`;
13 + },
14 + toDriver(value) {
15 + return `[${value.join(',')}]`;
16 + },
17 + fromDriver(value) {
18 + return value
19 + .slice(1, -1)
20 + .split(',')
21 + .map((v) => Number(v));
22 + },
23 +});
added packages/database/src/schema/cancers.ts +139 −0
@@ -0,0 +1,139 @@
1 +import { pgTable, text, integer, boolean, bigserial, index, uniqueIndex, real, jsonb } from 'drizzle-orm/pg-core';
2 +import { ciId, createdAt, updatedAt } from './_common.js';
3 +
4 +/** Canonical cancer entity (CLAUDE.md §5). One row per recognized disease concept, never per alias. */
5 +export const cancers = pgTable(
6 + 'cancers',
7 + {
8 + id: ciId().primaryKey(), // CI-CAN-…
9 + slug: text('slug').notNull(),
10 + canonicalName: text('canonical_name').notNull(),
11 + shortName: text('short_name'),
12 + entityType: text('entity_type').notNull().default('cancer'), // cancer | cancer_family | histology | subtype | molecular_subtype | hematologic_malignancy | precursor_condition | other
13 + malignant: boolean('malignant').notNull().default(true),
14 + solidTumor: boolean('solid_tumor').notNull().default(true),
15 + hematologic: boolean('hematologic').notNull().default(false),
16 + pediatricRelevant: boolean('pediatric_relevant').notNull().default(false),
17 + rareCancer: boolean('rare_cancer'), // null = unknown (no incidence data yet)
18 + topLevel: boolean('top_level').notNull().default(false), // member of the mutually exclusive global ranking set (§247)
19 + description: text('description'),
20 + descriptionProvenanceId: integer('description_provenance_id'),
21 + primaryNcitCode: text('primary_ncit_code'),
22 + primaryOncotreeCode: text('primary_oncotree_code'),
23 + depth: integer('depth').notNull().default(0), // depth in the NCIt-derived hierarchy from root "Neoplasm"
24 + status: text('status').notNull().default('active'), // active | deprecated | merged
25 + mergedInto: ciId('merged_into'),
26 + deprecatedReason: text('deprecated_reason'),
27 + classificationVersion: text('classification_version'),
28 + semanticTypes: text('semantic_types').array().notNull().default([]),
29 + createdAt: createdAt(),
30 + updatedAt: updatedAt(),
31 + },
32 + (t) => [
33 + uniqueIndex('cancers_slug_uq').on(t.slug),
34 + uniqueIndex('cancers_ncit_uq').on(t.primaryNcitCode),
35 + index('cancers_name_idx').on(t.canonicalName),
36 + index('cancers_type_idx').on(t.entityType, t.malignant, t.topLevel),
37 + ],
38 +);
39 +
40 +export const cancerAliases = pgTable(
41 + 'cancer_aliases',
42 + {
43 + id: bigserial('id', { mode: 'number' }).primaryKey(),
44 + cancerId: ciId('cancer_id').notNull(),
45 + alias: text('alias').notNull(),
46 + normalized: text('normalized').notNull(),
47 + aliasType: text('alias_type').notNull().default('synonym'), // preferred | synonym | abbreviation | historical | deprecated | display
48 + sourceId: ciId('source_id'),
49 + sourceTerminology: text('source_terminology'), // NCIt synonym source (e.g. CTRP, caDSR) or connector id
50 + language: text('language').notNull().default('en'),
51 + },
52 + (t) => [uniqueIndex('cancer_aliases_uq').on(t.cancerId, t.normalized, t.aliasType), index('cancer_aliases_norm_idx').on(t.normalized)],
53 +);
54 +
55 +/** Multi-dimensional hierarchy (CLAUDE.md §4): several trees coexist. */
56 +export const cancerHierarchy = pgTable(
57 + 'cancer_hierarchy',
58 + {
59 + id: bigserial('id', { mode: 'number' }).primaryKey(),
60 + parentId: ciId('parent_id').notNull(),
61 + childId: ciId('child_id').notNull(),
62 + hierarchyType: text('hierarchy_type').notNull(), // ncit | oncotree | anatomical | histological | molecular | who | icd | seer
63 + sourceId: ciId('source_id'),
64 + },
65 + (t) => [uniqueIndex('cancer_hierarchy_uq').on(t.parentId, t.childId, t.hierarchyType), index('cancer_hierarchy_child_idx').on(t.childId)],
66 +);
67 +
68 +/** Cross-reference codes (CLAUDE.md §220-221, §347): searchable, never buried in JSON. */
69 +export const cancerCodes = pgTable(
70 + 'cancer_codes',
71 + {
72 + id: bigserial('id', { mode: 'number' }).primaryKey(),
73 + cancerId: ciId('cancer_id').notNull(),
74 + system: text('system').notNull(), // ncit | icd10 | icd10cm | icdo_topography | icdo_morphology | doid | oncotree | umls | mesh | mondo | seer_site | efo | orphanet | gdc_project
75 + code: text('code').notNull(),
76 + matchType: text('match_type').notNull().default('EXACT_IDENTIFIER'),
77 + sourceId: ciId('source_id'),
78 + validFrom: text('valid_from'),
79 + validTo: text('valid_to'),
80 + },
81 + (t) => [uniqueIndex('cancer_codes_uq').on(t.cancerId, t.system, t.code), index('cancer_codes_lookup_idx').on(t.system, t.code)],
82 +);
83 +
84 +export const anatomicalSites = pgTable(
85 + 'anatomical_sites',
86 + {
87 + id: ciId().primaryKey(), // CI-ANAT-…
88 + name: text('name').notNull(),
89 + slug: text('slug').notNull(),
90 + ncitCode: text('ncit_code'),
91 + uberonId: text('uberon_id'),
92 + parentId: ciId('parent_id'),
93 + system: text('system'), // e.g. Digestive, Respiratory, Hematopoietic
94 + },
95 + (t) => [uniqueIndex('anatomical_sites_slug_uq').on(t.slug)],
96 +);
97 +
98 +export const cancerAnatomy = pgTable(
99 + 'cancer_anatomy',
100 + {
101 + id: bigserial('id', { mode: 'number' }).primaryKey(),
102 + cancerId: ciId('cancer_id').notNull(),
103 + siteId: ciId('site_id').notNull(),
104 + relation: text('relation').notNull().default('primary'), // primary | metastatic
105 + sourceId: ciId('source_id'),
106 + },
107 + (t) => [uniqueIndex('cancer_anatomy_uq').on(t.cancerId, t.siteId, t.relation)],
108 +);
109 +
110 +/** Canonical geography (CLAUDE.md §117). */
111 +export const geographies = pgTable(
112 + 'geographies',
113 + {
114 + id: ciId().primaryKey(), // CI-GEO-…
115 + slug: text('slug').notNull(),
116 + name: text('name').notNull(),
117 + kind: text('kind').notNull(), // world | region | who_region | country | subdivision
118 + iso2: text('iso2'),
119 + iso3: text('iso3'),
120 + parentId: ciId('parent_id'),
121 + whoRegion: text('who_region'),
122 + population: integer('population'),
123 + populationYear: integer('population_year'),
124 + },
125 + (t) => [uniqueIndex('geographies_slug_uq').on(t.slug), index('geographies_iso3_idx').on(t.iso3)],
126 +);
127 +
128 +/** Cohort definitions (CLAUDE.md §218): attribute combinations that are not taxonomy nodes. */
129 +export const cohortDefinitions = pgTable('cohort_definitions', {
130 + id: bigserial('id', { mode: 'number' }).primaryKey(),
131 + name: text('name').notNull(),
132 + cancerId: ciId('cancer_id').notNull(),
133 + biomarkerIds: text('biomarker_ids').array().notNull().default([]),
134 + variantIds: text('variant_ids').array().notNull().default([]),
135 + stage: text('stage'),
136 + attributes: jsonb('attributes').$type<Record<string, unknown>>().notNull().default({}),
137 + confidence: real('confidence'),
138 + createdAt: createdAt(),
139 +});
added packages/database/src/schema/drugs.ts +84 −0
@@ -0,0 +1,84 @@
1 +import { pgTable, text, integer, bigserial, index, uniqueIndex, boolean, jsonb } from 'drizzle-orm/pg-core';
2 +import { ciId, createdAt, updatedAt } from './_common.js';
3 +
4 +/** Drugs (CLAUDE.md §7, §344): brand names are aliases, never separate molecules. */
5 +export const drugs = pgTable(
6 + 'drugs',
7 + {
8 + id: ciId().primaryKey(), // CI-DRUG-…
9 + slug: text('slug').notNull(),
10 + name: text('name').notNull(), // generic / INN preferred
11 + kind: text('kind'), // small_molecule | monoclonal_antibody | adc | bispecific | car_t | cell_therapy | gene_therapy | vaccine | radiopharmaceutical | chemotherapy | hormonal | immunotherapy | targeted | other
12 + ncitCode: text('ncit_code'),
13 + chemblId: text('chembl_id'),
14 + civicTherapyId: integer('civic_therapy_id'),
15 + drugbankId: text('drugbank_id'),
16 + pubchemCid: text('pubchem_cid'),
17 + unii: text('unii'),
18 + mechanism: text('mechanism'),
19 + targetGeneIds: text('target_gene_ids').array().notNull().default([]),
20 + developmentStatus: text('development_status'),
21 + description: text('description'),
22 + createdAt: createdAt(),
23 + updatedAt: updatedAt(),
24 + },
25 + (t) => [uniqueIndex('drugs_slug_uq').on(t.slug), index('drugs_ncit_idx').on(t.ncitCode), index('drugs_civic_idx').on(t.civicTherapyId), index('drugs_chembl_idx').on(t.chemblId)],
26 +);
27 +
28 +export const drugAliases = pgTable(
29 + 'drug_aliases',
30 + {
31 + id: bigserial('id', { mode: 'number' }).primaryKey(),
32 + drugId: ciId('drug_id').notNull(),
33 + alias: text('alias').notNull(),
34 + normalized: text('normalized').notNull(),
35 + aliasType: text('alias_type').notNull().default('synonym'), // generic | brand | development_code | salt | synonym
36 + sourceId: ciId('source_id'),
37 + },
38 + (t) => [uniqueIndex('drug_aliases_uq').on(t.drugId, t.normalized, t.aliasType), index('drug_aliases_norm_idx').on(t.normalized)],
39 +);
40 +
41 +/** Regimens / combinations (CLAUDE.md §53). */
42 +export const treatmentRegimens = pgTable(
43 + 'treatment_regimens',
44 + {
45 + id: ciId().primaryKey(), // CI-TRT-…
46 + slug: text('slug').notNull(),
47 + name: text('name').notNull(),
48 + componentDrugIds: text('component_drug_ids').array().notNull().default([]),
49 + modality: text('modality').notNull().default('drug_combination'), // drug | drug_combination | surgery | radiotherapy | transplantation | cell_therapy | surveillance
50 + description: text('description'),
51 + createdAt: createdAt(),
52 + },
53 + (t) => [uniqueIndex('treatment_regimens_slug_uq').on(t.slug)],
54 +);
55 +
56 +/** Country-aware regulatory status (CLAUDE.md §13). Never a bare approved=true. */
57 +export const drugApprovals = pgTable(
58 + 'drug_approvals',
59 + {
60 + id: bigserial('id', { mode: 'number' }).primaryKey(),
61 + drugId: ciId('drug_id').notNull(),
62 + cancerId: ciId('cancer_id'),
63 + biomarkerIds: text('biomarker_ids').array().notNull().default([]),
64 + tumorAgnostic: boolean('tumor_agnostic').notNull().default(false),
65 + jurisdiction: text('jurisdiction').notNull(), // US | CA | EU | UK | AU | JP | CH | OTHER
66 + authority: text('authority').notNull(), // FDA | Health Canada | EMA | MHRA | TGA | PMDA | Swissmedic
67 + indication: text('indication').notNull(),
68 + lineOfTherapy: text('line_of_therapy'),
69 + diseaseStage: text('disease_stage'),
70 + approvalType: text('approval_type'),
71 + accelerated: boolean('accelerated'),
72 + conditional: boolean('conditional'),
73 + approvalDate: text('approval_date'),
74 + withdrawalDate: text('withdrawal_date'),
75 + status: text('status').notNull(), // approved | conditional | accelerated | withdrawn | superseded
76 + applicationNumber: text('application_number'),
77 + sourceId: ciId('source_id').notNull(),
78 + provenanceId: integer('provenance_id').notNull(),
79 + raw: jsonb('raw'),
80 + createdAt: createdAt(),
81 + updatedAt: updatedAt(),
82 + },
83 + (t) => [index('drug_approvals_drug_idx').on(t.drugId), index('drug_approvals_cancer_idx').on(t.cancerId)],
84 +);
added packages/database/src/schema/epidemiology.ts +61 −0
@@ -0,0 +1,61 @@
1 +import { pgTable, text, integer, bigserial, index, uniqueIndex, real, doublePrecision } from 'drizzle-orm/pg-core';
2 +import { ciId, updatedAt } from './_common.js';
3 +
4 +/** Time-aware epidemiology observations (CLAUDE.md §71): never overwrite a year with another. */
5 +export const epidemiologyObservations = pgTable(
6 + 'epidemiology_observations',
7 + {
8 + id: bigserial('id', { mode: 'number' }).primaryKey(),
9 + cancerId: ciId('cancer_id').notNull(),
10 + geographyId: ciId('geography_id').notNull(),
11 + year: integer('year').notNull(),
12 + yearEnd: integer('year_end'), // for multi-year aggregates (e.g. 2018-2022)
13 + sex: text('sex').notNull().default('all'), // all | male | female
14 + ageGroup: text('age_group').notNull().default('all'),
15 + metric: text('metric').notNull(), // incidence_count | incidence_rate | as_incidence_rate | mortality_count | mortality_rate | as_mortality_rate | prevalence | prevalence_5y
16 + value: doublePrecision('value').notNull(),
17 + unit: text('unit').notNull(), // count | per_100k
18 + lowerCi: doublePrecision('lower_ci'),
19 + upperCi: doublePrecision('upper_ci'),
20 + standardPopulation: text('standard_population'), // e.g. "US 2000 standard", "World (Segi)"
21 + estimateType: text('estimate_type').notNull().default('observed'), // observed | estimated | projected
22 + siteDefinition: text('site_definition'), // source's own site label / ICD code range
23 + sourceId: ciId('source_id').notNull(),
24 + provenanceId: integer('provenance_id').notNull(),
25 + ingestRunId: text('ingest_run_id'),
26 + updatedAt: updatedAt(),
27 + },
28 + (t) => [
29 + uniqueIndex('epi_obs_uq').on(t.cancerId, t.geographyId, t.year, t.sex, t.ageGroup, t.metric, t.sourceId, t.siteDefinition),
30 + index('epi_obs_lookup_idx').on(t.metric, t.geographyId, t.year, t.sex),
31 + index('epi_obs_cancer_idx').on(t.cancerId, t.metric),
32 + ],
33 +);
34 +
35 +/** Survival requires context (CLAUDE.md §72). */
36 +export const survivalObservations = pgTable(
37 + 'survival_observations',
38 + {
39 + id: bigserial('id', { mode: 'number' }).primaryKey(),
40 + cancerId: ciId('cancer_id').notNull(),
41 + geographyId: ciId('geography_id'),
42 + stage: text('stage'),
43 + stagingSystem: text('staging_system'),
44 + sex: text('sex').notNull().default('all'),
45 + ageGroup: text('age_group').notNull().default('all'),
46 + diagnosisPeriod: text('diagnosis_period'),
47 + survivalType: text('survival_type').notNull(), // overall | relative | cause_specific | progression_free | disease_free | net
48 + durationMonths: integer('duration_months').notNull(),
49 + probability: real('probability'), // 0..1
50 + medianMonths: real('median_months'),
51 + cohortSize: integer('cohort_size'),
52 + lowerCi: real('lower_ci'),
53 + upperCi: real('upper_ci'),
54 + method: text('method'),
55 + sourceId: ciId('source_id').notNull(),
56 + provenanceId: integer('provenance_id').notNull(),
57 + ingestRunId: text('ingest_run_id'),
58 + updatedAt: updatedAt(),
59 + },
60 + (t) => [index('survival_obs_cancer_idx').on(t.cancerId, t.survivalType, t.durationMonths)],
61 +);
added packages/database/src/schema/evidence.ts +89 −0
@@ -0,0 +1,89 @@
1 +import { pgTable, text, integer, bigserial, index, uniqueIndex, boolean, jsonb, real, timestamp } from 'drizzle-orm/pg-core';
2 +import { ciId, createdAt, updatedAt } from './_common.js';
3 +
4 +/** Knowledge graph edge with mandatory context and provenance (CLAUDE.md §28-29, §244). */
5 +export const knowledgeEdges = pgTable(
6 + 'knowledge_edges',
7 + {
8 + id: bigserial('id', { mode: 'number' }).primaryKey(),
9 + sourceEntityType: text('source_entity_type').notNull(), // cancer | gene | variant | drug | biomarker | trial | publication | pathway | risk_factor
10 + sourceEntityId: text('source_entity_id').notNull(),
11 + targetEntityType: text('target_entity_type').notNull(),
12 + targetEntityId: text('target_entity_id').notNull(),
13 + relationshipType: text('relationship_type').notNull(), // HAS_SUBTYPE | OCCURS_IN | ASSOCIATED_WITH | HAS_VARIANT | HAS_BIOMARKER | TREATED_BY | STUDIED_IN | DESCRIBED_BY | TARGETS | APPROVED_FOR | INVESTIGATED_FOR | PREDICTS_RESPONSE_TO | CONFERS_RESISTANCE_TO | PREDISPOSES_TO | PROGNOSTIC_IN | DIAGNOSTIC_OF
14 + cancerContextIds: text('cancer_context_ids').array().notNull().default([]),
15 + predictive: boolean('predictive'),
16 + prognostic: boolean('prognostic'),
17 + diagnostic: boolean('diagnostic'),
18 + predisposing: boolean('predisposing'),
19 + direction: text('direction'), // supports | resistance | sensitivity | neutral | unknown
20 + evidenceLevel: text('evidence_level'), // source-native level (e.g. CIViC A-E) — never re-scaled silently
21 + evidenceScore: real('evidence_score'),
22 + evidenceCategory: text('evidence_category').notNull().default('curated_evidence'), // observed_data | published_evidence | curated_evidence | regulatory_status | clinical_guideline | computed_metric
23 + status: text('status').notNull().default('active'), // active | candidate | superseded | retracted_basis
24 + sourceId: ciId('source_id').notNull(),
25 + sourceRecordId: text('source_record_id'),
26 + provenanceIds: integer('provenance_ids').array().notNull().default([]),
27 + supportCount: integer('support_count').notNull().default(1),
28 + firstSeenAt: timestamp('first_seen_at', { withTimezone: true }).notNull().defaultNow(),
29 + lastSeenAt: timestamp('last_seen_at', { withTimezone: true }).notNull().defaultNow(),
30 + },
31 + (t) => [
32 + uniqueIndex('knowledge_edges_uq').on(t.sourceEntityType, t.sourceEntityId, t.targetEntityType, t.targetEntityId, t.relationshipType, t.sourceId, t.sourceRecordId),
33 + index('knowledge_edges_source_idx').on(t.sourceEntityType, t.sourceEntityId, t.relationshipType),
34 + index('knowledge_edges_target_idx').on(t.targetEntityType, t.targetEntityId, t.relationshipType),
35 + ],
36 +);
37 +
38 +/** CIViC evidence items kept in their native structure (CLAUDE.md §10.9). */
39 +export const civicEvidenceItems = pgTable(
40 + 'civic_evidence_items',
41 + {
42 + id: bigserial('id', { mode: 'number' }).primaryKey(),
43 + civicId: integer('civic_id').notNull(),
44 + name: text('name'),
45 + molecularProfileId: integer('molecular_profile_id'),
46 + molecularProfileName: text('molecular_profile_name'),
47 + geneSymbols: text('gene_symbols').array().notNull().default([]),
48 + geneIds: text('gene_ids').array().notNull().default([]),
49 + variantIds: text('variant_ids').array().notNull().default([]),
50 + civicVariantIds: integer('civic_variant_ids').array().notNull().default([]),
51 + diseaseName: text('disease_name'),
52 + doid: text('doid'),
53 + cancerId: ciId('cancer_id'),
54 + cancerMatchType: text('cancer_match_type'),
55 + therapyNames: text('therapy_names').array().notNull().default([]),
56 + therapyIds: text('therapy_ids').array().notNull().default([]), // CI-DRUG-…
57 + therapyInteractionType: text('therapy_interaction_type'),
58 + evidenceType: text('evidence_type'), // PREDICTIVE | PROGNOSTIC | DIAGNOSTIC | PREDISPOSING | ONCOGENIC | FUNCTIONAL
59 + evidenceLevel: text('evidence_level'), // A-E
60 + evidenceDirection: text('evidence_direction'), // SUPPORTS | DOES_NOT_SUPPORT
61 + significance: text('significance'), // SENSITIVITYRESPONSE | RESISTANCE | …
62 + evidenceRating: integer('evidence_rating'),
63 + status: text('status'), // ACCEPTED | SUBMITTED | REJECTED
64 + description: text('description'),
65 + pmid: text('pmid'),
66 + sourceCitation: text('source_citation'),
67 + phenotypes: text('phenotypes').array().notNull().default([]),
68 + provenanceId: integer('provenance_id').notNull(),
69 + ingestRunId: text('ingest_run_id'),
70 + updatedAt: updatedAt(),
71 + },
72 + (t) => [uniqueIndex('civic_evidence_uq').on(t.civicId), index('civic_evidence_cancer_idx').on(t.cancerId), index('civic_evidence_gene_idx').on(t.geneSymbols)],
73 +);
74 +
75 +/** Risk factors / carcinogens as first-class entities (CLAUDE.md §74, §133). */
76 +export const riskFactors = pgTable(
77 + 'risk_factors',
78 + {
79 + id: bigserial('id', { mode: 'number' }).primaryKey(),
80 + slug: text('slug').notNull(),
81 + name: text('name').notNull(),
82 + kind: text('kind').notNull(), // behavioral | infectious | environmental | occupational | genetic | hormonal | demographic | carcinogen
83 + classificationAuthority: text('classification_authority'),
84 + classification: text('classification'),
85 + description: text('description'),
86 + createdAt: createdAt(),
87 + },
88 + (t) => [uniqueIndex('risk_factors_slug_uq').on(t.slug)],
89 +);
added packages/database/src/schema/genomics.ts +184 −0
@@ -0,0 +1,184 @@
1 +import { pgTable, text, integer, bigserial, index, uniqueIndex, real, jsonb, boolean, timestamp } from 'drizzle-orm/pg-core';
2 +import { ciId, createdAt, updatedAt } from './_common.js';
3 +
4 +/** Genes — HGNC is authoritative for symbols (CLAUDE.md §11, §143). */
5 +export const genes = pgTable(
6 + 'genes',
7 + {
8 + id: ciId().primaryKey(), // CI-GENE-…
9 + hgncId: text('hgnc_id'), // HGNC:11998
10 + symbol: text('symbol').notNull(),
11 + name: text('name'),
12 + locusType: text('locus_type'),
13 + locusGroup: text('locus_group'),
14 + location: text('location'),
15 + chromosome: text('chromosome'),
16 + ensemblGeneId: text('ensembl_gene_id'),
17 + ncbiGeneId: text('ncbi_gene_id'),
18 + omimIds: text('omim_ids').array().notNull().default([]),
19 + uniprotIds: text('uniprot_ids').array().notNull().default([]),
20 + refseqAccession: text('refseq_accession'),
21 + prevSymbols: text('prev_symbols').array().notNull().default([]),
22 + aliasSymbols: text('alias_symbols').array().notNull().default([]),
23 + geneFamilies: text('gene_families').array().notNull().default([]),
24 + status: text('status').notNull().default('Approved'),
25 + isCancerGene: boolean('is_cancer_gene').notNull().default(false), // has ≥1 curated cancer edge (derived)
26 + civicGeneId: integer('civic_gene_id'),
27 + description: text('description'),
28 + createdAt: createdAt(),
29 + updatedAt: updatedAt(),
30 + },
31 + (t) => [uniqueIndex('genes_symbol_uq').on(t.symbol), uniqueIndex('genes_hgnc_uq').on(t.hgncId), index('genes_ensembl_idx').on(t.ensemblGeneId), index('genes_ncbi_idx').on(t.ncbiGeneId)],
32 +);
33 +
34 +export const geneAliases = pgTable(
35 + 'gene_aliases',
36 + {
37 + id: bigserial('id', { mode: 'number' }).primaryKey(),
38 + geneId: ciId('gene_id').notNull(),
39 + alias: text('alias').notNull(),
40 + aliasType: text('alias_type').notNull(), // prev_symbol | alias_symbol | prev_name | alias_name
41 + sourceId: ciId('source_id'),
42 + },
43 + (t) => [uniqueIndex('gene_aliases_uq').on(t.geneId, t.alias, t.aliasType), index('gene_aliases_alias_idx').on(t.alias)],
44 +);
45 +
46 +/** Variants (CLAUDE.md §235-239): coordinates always carry assembly; original + normalized kept. */
47 +export const variants = pgTable(
48 + 'variants',
49 + {
50 + id: ciId().primaryKey(), // CI-VAR-…
51 + slug: text('slug').notNull(),
52 + geneId: ciId('gene_id'),
53 + geneSymbol: text('gene_symbol'),
54 + name: text('name').notNull(), // e.g. "V600E", "Exon 19 Deletion", "Amplification"
55 + variantType: text('variant_type'), // SO-style: SNV | MNV | insertion | deletion | indel | fusion | amplification | deletion_cna | loss_of_heterozygosity | promoter_mutation | splice | expression | epigenetic | structural | other
56 + hgvsG: text('hgvs_g'),
57 + hgvsC: text('hgvs_c'),
58 + hgvsP: text('hgvs_p'),
59 + assembly: text('assembly'), // GRCh37 | GRCh38
60 + chromosome: text('chromosome'),
61 + start: integer('start'),
62 + end: integer('end'),
63 + referenceBases: text('reference_bases'),
64 + alternateBases: text('alternate_bases'),
65 + coordinates: jsonb('coordinates').$type<Array<Record<string, unknown>>>().notNull().default([]), // per-assembly list
66 + clinvarVariationId: text('clinvar_variation_id'),
67 + civicVariantId: integer('civic_variant_id'),
68 + dbsnpIds: text('dbsnp_ids').array().notNull().default([]),
69 + fusionPartners: text('fusion_partners').array().notNull().default([]), // [5' gene, 3' gene]
70 + createdAt: createdAt(),
71 + updatedAt: updatedAt(),
72 + },
73 + (t) => [uniqueIndex('variants_slug_uq').on(t.slug), index('variants_gene_idx').on(t.geneId), index('variants_clinvar_idx').on(t.clinvarVariationId), index('variants_civic_idx').on(t.civicVariantId)],
74 +);
75 +
76 +export const variantAliases = pgTable(
77 + 'variant_aliases',
78 + {
79 + id: bigserial('id', { mode: 'number' }).primaryKey(),
80 + variantId: ciId('variant_id').notNull(),
81 + alias: text('alias').notNull(),
82 + sourceId: ciId('source_id'),
83 + },
84 + (t) => [uniqueIndex('variant_aliases_uq').on(t.variantId, t.alias)],
85 +);
86 +
87 +/** ClinVar interpretations (CLAUDE.md §10.7): structured, never flattened. */
88 +export const variantClinicalSignificance = pgTable(
89 + 'variant_clinical_significance',
90 + {
91 + id: bigserial('id', { mode: 'number' }).primaryKey(),
92 + variantId: ciId('variant_id').notNull(),
93 + clinvarVariationId: text('clinvar_variation_id').notNull(),
94 + clinicalSignificance: text('clinical_significance').notNull(),
95 + reviewStatus: text('review_status'),
96 + starRating: integer('star_rating'),
97 + lastEvaluated: text('last_evaluated'),
98 + conditions: text('conditions').array().notNull().default([]),
99 + conditionCancerIds: text('condition_cancer_ids').array().notNull().default([]),
100 + originSimple: text('origin_simple'),
101 + numberSubmitters: integer('number_submitters'),
102 + provenanceId: integer('provenance_id').notNull(),
103 + ingestRunId: text('ingest_run_id'),
104 + updatedAt: updatedAt(),
105 + },
106 + (t) => [uniqueIndex('variant_clinsig_uq').on(t.clinvarVariationId)],
107 +);
108 +
109 +export const biomarkers = pgTable(
110 + 'biomarkers',
111 + {
112 + id: ciId().primaryKey(), // CI-BIO-…
113 + slug: text('slug').notNull(),
114 + name: text('name').notNull(),
115 + kind: text('kind').notNull(), // gene_mutation | protein_expression | hormone_receptor | immune_marker | msi | tmb | hrd | ctdna | methylation | signature | cell_surface | other
116 + geneId: ciId('gene_id'),
117 + ncitCode: text('ncit_code'),
118 + description: text('description'),
119 + measurement: jsonb('measurement').$type<Record<string, unknown>>().notNull().default({}), // assay/clone/scoring/thresholds (§241-243)
120 + createdAt: createdAt(),
121 + updatedAt: updatedAt(),
122 + },
123 + (t) => [uniqueIndex('biomarkers_slug_uq').on(t.slug)],
124 +);
125 +
126 +/** Genomic studies / cohorts (GDC projects, cBioPortal studies…) — original study IDs preserved. */
127 +export const genomicCohorts = pgTable(
128 + 'genomic_cohorts',
129 + {
130 + id: ciId().primaryKey(), // CI-STUDY-…
131 + sourceId: ciId('source_id').notNull(),
132 + studyId: text('study_id').notNull(), // TCGA-PAAD
133 + name: text('name').notNull(),
134 + program: text('program'),
135 + primarySites: text('primary_sites').array().notNull().default([]),
136 + diseaseTypes: text('disease_types').array().notNull().default([]),
137 + cancerId: ciId('cancer_id'),
138 + cancerMatchType: text('cancer_match_type'),
139 + caseCount: integer('case_count'),
140 + casesWithSsm: integer('cases_with_ssm'), // denominator for mutation frequencies
141 + dataRelease: text('data_release'),
142 + accessLevel: text('access_level').notNull().default('open'),
143 + url: text('url'),
144 + provenanceId: integer('provenance_id'),
145 + updatedAt: updatedAt(),
146 + },
147 + (t) => [uniqueIndex('genomic_cohorts_uq').on(t.sourceId, t.studyId), index('genomic_cohorts_cancer_idx').on(t.cancerId)],
148 +);
149 +
150 +/** Gene alteration frequency per cohort — denominator is mandatory (CLAUDE.md §261-262). */
151 +export const cancerGeneFrequencies = pgTable(
152 + 'cancer_gene_frequencies',
153 + {
154 + id: bigserial('id', { mode: 'number' }).primaryKey(),
155 + cohortId: ciId('cohort_id').notNull(),
156 + cancerId: ciId('cancer_id'),
157 + geneId: ciId('gene_id'),
158 + geneSymbol: text('gene_symbol').notNull(),
159 + alterationType: text('alteration_type').notNull().default('ssm'), // ssm | cnv_gain | cnv_loss | fusion
160 + casesAffected: integer('cases_affected').notNull(),
161 + casesProfiled: integer('cases_profiled').notNull(),
162 + frequency: real('frequency').notNull(),
163 + rank: integer('rank'),
164 + dataRelease: text('data_release'),
165 + provenanceId: integer('provenance_id').notNull(),
166 + updatedAt: updatedAt(),
167 + },
168 + (t) => [uniqueIndex('cancer_gene_freq_uq').on(t.cohortId, t.geneSymbol, t.alterationType), index('cancer_gene_freq_cancer_idx').on(t.cancerId, t.frequency), index('cancer_gene_freq_gene_idx').on(t.geneId)],
169 +);
170 +
171 +export const entityEmbeddings = pgTable(
172 + 'entity_embeddings',
173 + {
174 + id: bigserial('id', { mode: 'number' }).primaryKey(),
175 + entityType: text('entity_type').notNull(),
176 + entityId: text('entity_id').notNull(),
177 + model: text('model').notNull(),
178 + dimensions: integer('dimensions').notNull(),
179 + textHash: text('text_hash').notNull(),
180 + embedding: text('embedding'), // stored via raw SQL cast to vector(n); model recorded on the row
181 + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
182 + },
183 + (t) => [uniqueIndex('entity_embeddings_uq').on(t.entityType, t.entityId, t.model)],
184 +);
added packages/database/src/schema/index.ts +9 −0
@@ -0,0 +1,9 @@
1 +export * from './sources.js';
2 +export * from './cancers.js';
3 +export * from './genomics.js';
4 +export * from './drugs.js';
5 +export * from './trials.js';
6 +export * from './publications.js';
7 +export * from './evidence.js';
8 +export * from './epidemiology.js';
9 +export * from './rankings.js';
added packages/database/src/schema/publications.ts +68 −0
@@ -0,0 +1,68 @@
1 +import { pgTable, text, integer, bigserial, index, uniqueIndex, boolean, jsonb, real } from 'drizzle-orm/pg-core';
2 +import { ciId, createdAt, updatedAt } from './_common.js';
3 +
4 +/** Publications (CLAUDE.md §10.6, §342): one entity per paper across PubMed/DOI/Europe PMC. */
5 +export const publications = pgTable(
6 + 'publications',
7 + {
8 + id: ciId().primaryKey(), // CI-PUB-…
9 + pmid: text('pmid'),
10 + doi: text('doi'),
11 + pmcid: text('pmcid'),
12 + title: text('title').notNull(),
13 + abstract: text('abstract'), // stored only where NLM terms permit (abstract text may carry publisher copyright; we store it for indexing, display truncated with link)
14 + journal: text('journal'),
15 + journalIso: text('journal_iso'),
16 + pubDate: text('pub_date'),
17 + pubYear: integer('pub_year'),
18 + publicationTypes: text('publication_types').array().notNull().default([]),
19 + meshTerms: jsonb('mesh_terms').$type<Array<{ descriptor: string; ui?: string; major: boolean; qualifiers?: string[] }>>().notNull().default([]),
20 + authors: jsonb('authors').$type<Array<{ name: string; affiliation?: string; orcid?: string }>>().notNull().default([]),
21 + language: text('language'),
22 + isPreprint: boolean('is_preprint').notNull().default(false),
23 + retracted: boolean('retracted').notNull().default(false),
24 + retractionNotice: text('retraction_notice'),
25 + nctIds: text('nct_ids').array().notNull().default([]),
26 + citedByCount: integer('cited_by_count'),
27 + sourceRecordId: integer('source_record_id'),
28 + ingestRunId: text('ingest_run_id'),
29 + createdAt: createdAt(),
30 + updatedAt: updatedAt(),
31 + },
32 + (t) => [uniqueIndex('publications_pmid_uq').on(t.pmid), index('publications_doi_idx').on(t.doi), index('publications_year_idx').on(t.pubYear), index('publications_retracted_idx').on(t.retracted)],
33 +);
34 +
35 +/** publication → entity links (CLAUDE.md §165-166, §271): extraction method + status preserved. */
36 +export const publicationEntityEdges = pgTable(
37 + 'publication_entity_edges',
38 + {
39 + id: bigserial('id', { mode: 'number' }).primaryKey(),
40 + publicationId: ciId('publication_id').notNull(),
41 + entityType: text('entity_type').notNull(), // cancer | gene | variant | drug | biomarker | trial
42 + entityId: text('entity_id').notNull(),
43 + method: text('method').notNull(), // mesh | dictionary | registry_reference | civic_curation | ner | llm | curator
44 + confidence: real('confidence'),
45 + status: text('status').notNull().default('candidate'), // candidate | validated | rejected
46 + sourceId: ciId('source_id'),
47 + ingestRunId: text('ingest_run_id'),
48 + createdAt: createdAt(),
49 + },
50 + (t) => [uniqueIndex('pub_entity_edges_uq').on(t.publicationId, t.entityType, t.entityId, t.method), index('pub_entity_edges_entity_idx').on(t.entityType, t.entityId)],
51 +);
52 +
53 +/** Research activity counts per cancer from PubMed queries — the formula (query string) is stored (CLAUDE.md §251). */
54 +export const literatureCounts = pgTable(
55 + 'literature_counts',
56 + {
57 + id: bigserial('id', { mode: 'number' }).primaryKey(),
58 + cancerId: ciId('cancer_id').notNull(),
59 + windowKey: text('window_key').notNull(), // all | 12m | 5y | 10y | y2015 …
60 + windowStart: text('window_start'),
61 + windowEnd: text('window_end'),
62 + query: text('query').notNull(), // exact PubMed query used
63 + count: integer('count').notNull(),
64 + provenanceId: integer('provenance_id').notNull(),
65 + computedAt: updatedAt(),
66 + },
67 + (t) => [uniqueIndex('literature_counts_uq').on(t.cancerId, t.windowKey), index('literature_counts_window_idx').on(t.windowKey, t.count)],
68 +);
added packages/database/src/schema/rankings.ts +138 −0
@@ -0,0 +1,138 @@
1 +import { pgTable, text, integer, bigserial, index, uniqueIndex, real, jsonb, boolean, timestamp, doublePrecision } from 'drizzle-orm/pg-core';
2 +import { ciId, createdAt, updatedAt } from './_common.js';
3 +
4 +/** Metric catalog (CLAUDE.md §250-251): every derived number has a versioned formula. */
5 +export const metricDefinitions = pgTable(
6 + 'metric_definitions',
7 + {
8 + id: ciId().primaryKey(), // CI-METRIC-…
9 + slug: text('slug').notNull(), // active_trials | mortality_incidence_ratio | …
10 + name: text('name').notNull(),
11 + description: text('description').notNull(),
12 + formula: text('formula').notNull(),
13 + formulaVersion: text('formula_version').notNull(), // ci-mir-v1
14 + unit: text('unit').notNull(),
15 + higherIsWorse: boolean('higher_is_worse'),
16 + aggregation: text('aggregation'),
17 + validDimensions: text('valid_dimensions').array().notNull().default([]),
18 + sourceSlugs: text('source_slugs').array().notNull().default([]),
19 + category: text('category').notNull(), // burden | lethality | trend | rarity | treatment | trials | research | molecular | unmet_need | composite
20 + eligibility: jsonb('eligibility').$type<Record<string, unknown>>().notNull().default({}),
21 + experimental: boolean('experimental').notNull().default(false),
22 + createdAt: createdAt(),
23 + updatedAt: updatedAt(),
24 + },
25 + (t) => [uniqueIndex('metric_definitions_slug_uq').on(t.slug)],
26 +);
27 +
28 +/** Ranking snapshot = one metric × one scope × one formula version at one time (CLAUDE.md §33, §184). */
29 +export const rankingSnapshots = pgTable(
30 + 'ranking_snapshots',
31 + {
32 + id: bigserial('id', { mode: 'number' }).primaryKey(),
33 + metricId: ciId('metric_id').notNull(),
34 + metricSlug: text('metric_slug').notNull(),
35 + scopeKey: text('scope_key').notNull(), // e.g. "geo=world|sex=all|age=all|year=2022|level=top"
36 + geography: text('geography').notNull().default('WORLD'),
37 + sex: text('sex').notNull().default('all'),
38 + ageGroup: text('age_group').notNull().default('all'),
39 + year: integer('year'),
40 + entityLevel: text('entity_level').notNull().default('top'), // top | histology | subtype | rare | all
41 + formulaVersion: text('formula_version').notNull(),
42 + eligibleEntities: integer('eligible_entities').notNull(),
43 + inputsHash: text('inputs_hash').notNull(),
44 + sourceIds: text('source_ids').array().notNull().default([]),
45 + isCurrent: boolean('is_current').notNull().default(true),
46 + generatedAt: timestamp('generated_at', { withTimezone: true }).notNull().defaultNow(),
47 + },
48 + (t) => [index('ranking_snapshots_lookup_idx').on(t.metricSlug, t.scopeKey, t.isCurrent)],
49 +);
50 +
51 +export const rankings = pgTable(
52 + 'rankings',
53 + {
54 + id: bigserial('id', { mode: 'number' }).primaryKey(),
55 + snapshotId: integer('snapshot_id').notNull(),
56 + metricSlug: text('metric_slug').notNull(),
57 + scopeKey: text('scope_key').notNull(),
58 + cancerId: ciId('cancer_id').notNull(),
59 + rank: integer('rank').notNull(),
60 + eligibleEntities: integer('eligible_entities').notNull(),
61 + percentile: real('percentile').notNull(),
62 + value: doublePrecision('value').notNull(),
63 + unit: text('unit').notNull(),
64 + confidence: text('confidence').notNull().default('MEDIUM'), // HIGH | MEDIUM | LOW | INSUFFICIENT_DATA
65 + inputs: jsonb('inputs').$type<Record<string, unknown>>().notNull().default({}), // lineage: observation ids, counts, formula inputs
66 + breakdown: jsonb('breakdown').$type<Record<string, number>>(), // composite components
67 + previousRank: integer('previous_rank'),
68 + generatedAt: timestamp('generated_at', { withTimezone: true }).notNull().defaultNow(),
69 + },
70 + (t) => [uniqueIndex('rankings_uq').on(t.snapshotId, t.cancerId), index('rankings_cancer_idx').on(t.cancerId, t.metricSlug), index('rankings_lookup_idx').on(t.metricSlug, t.scopeKey, t.rank)],
71 +);
72 +
73 +/** Precomputed counters per entity, refreshed deterministically from canonical relations (CLAUDE.md §287). */
74 +export const entityCounters = pgTable(
75 + 'entity_counters',
76 + {
77 + id: bigserial('id', { mode: 'number' }).primaryKey(),
78 + entityType: text('entity_type').notNull(),
79 + entityId: text('entity_id').notNull(),
80 + trialCount: integer('trial_count').notNull().default(0),
81 + activeTrialCount: integer('active_trial_count').notNull().default(0),
82 + recruitingTrialCount: integer('recruiting_trial_count').notNull().default(0),
83 + phase3TrialCount: integer('phase3_trial_count').notNull().default(0),
84 + publicationCount: integer('publication_count').notNull().default(0),
85 + publicationCount5y: integer('publication_count_5y').notNull().default(0),
86 + publicationCount12m: integer('publication_count_12m').notNull().default(0),
87 + geneCount: integer('gene_count').notNull().default(0),
88 + variantCount: integer('variant_count').notNull().default(0),
89 + drugCount: integer('drug_count').notNull().default(0),
90 + approvedDrugCount: integer('approved_drug_count').notNull().default(0),
91 + evidenceCount: integer('evidence_count').notNull().default(0),
92 + cohortCount: integer('cohort_count').notNull().default(0),
93 + subtypeCount: integer('subtype_count').notNull().default(0),
94 + descendantCount: integer('descendant_count').notNull().default(0),
95 + epidemiologyObsCount: integer('epidemiology_obs_count').notNull().default(0),
96 + survivalObsCount: integer('survival_obs_count').notNull().default(0),
97 + completeness: jsonb('completeness').$type<Record<string, number>>().notNull().default({}),
98 + computedAt: updatedAt(),
99 + },
100 + (t) => [uniqueIndex('entity_counters_uq').on(t.entityType, t.entityId), index('entity_counters_trials_idx').on(t.entityType, t.activeTrialCount)],
101 +);
102 +
103 +/** Public API keys (CLAUDE.md §190-191). */
104 +export const apiKeys = pgTable(
105 + 'api_keys',
106 + {
107 + id: bigserial('id', { mode: 'number' }).primaryKey(),
108 + keyHash: text('key_hash').notNull(),
109 + prefix: text('prefix').notNull(),
110 + label: text('label'),
111 + ownerEmail: text('owner_email'),
112 + tier: text('tier').notNull().default('free'), // free | research | pro | institutional
113 + rateLimitPerMinute: integer('rate_limit_per_minute').notNull().default(60),
114 + active: boolean('active').notNull().default(true),
115 + lastUsedAt: timestamp('last_used_at', { withTimezone: true }),
116 + createdAt: createdAt(),
117 + },
118 + (t) => [uniqueIndex('api_keys_hash_uq').on(t.keyHash)],
119 +);
120 +
121 +/** Cached AI syntheses (CLAUDE.md §44, §322) — model, prompt version and record snapshot recorded. */
122 +export const aiAnswers = pgTable(
123 + 'ai_answers',
124 + {
125 + id: bigserial('id', { mode: 'number' }).primaryKey(),
126 + kind: text('kind').notNull(), // cancer_summary | ask
127 + subjectId: text('subject_id'),
128 + questionHash: text('question_hash').notNull(),
129 + question: text('question'),
130 + answer: jsonb('answer').$type<Record<string, unknown>>().notNull(),
131 + model: text('model').notNull(),
132 + promptVersion: text('prompt_version').notNull(),
133 + sourceSnapshot: jsonb('source_snapshot').$type<Record<string, unknown>>().notNull().default({}),
134 + dataAsOf: timestamp('data_as_of', { withTimezone: true }).notNull(),
135 + createdAt: createdAt(),
136 + },
137 + (t) => [uniqueIndex('ai_answers_uq').on(t.kind, t.questionHash, t.promptVersion)],
138 +);
added packages/database/src/schema/sources.ts +228 −0
@@ -0,0 +1,228 @@
1 +import { pgTable, text, integer, bigint, bigserial, boolean, jsonb, timestamp, index, uniqueIndex, real, varchar } from 'drizzle-orm/pg-core';
2 +import { ciId, createdAt, updatedAt } from './_common.js';
3 +
4 +/** Per-namespace counters for stable public IDs (never exposed, never reused). */
5 +export const idSequences = pgTable('id_sequences', {
6 + namespace: varchar('namespace', { length: 16 }).primaryKey(),
7 + next: bigint('next', { mode: 'number' }).notNull().default(1),
8 +});
9 +
10 +/** Source registry + license registry (CLAUDE.md §9, §95, §142). Seeded from connector manifests. */
11 +export const sources = pgTable(
12 + 'sources',
13 + {
14 + id: ciId().primaryKey(), // CI-SOURCE-…
15 + slug: text('slug').notNull(), // connector id, e.g. "clinicaltrials"
16 + name: text('name').notNull(),
17 + organization: text('organization'),
18 + category: text('category').notNull(), // terminology | genomics | epidemiology | trials | literature | variants | drugs | regulatory
19 + description: text('description'),
20 + homepage: text('homepage'),
21 + docsUrl: text('docs_url'),
22 + termsUrl: text('terms_url'),
23 + accessType: text('access_type').notNull(), // api|bulk|rss|ftp|graphql|rest|scrape|manual
24 + accessAuth: text('access_auth').notNull(), // none|api_key|oauth|account|controlled
25 + license: text('license'),
26 + licenseStatus: text('license_status').notNull().default('review'), // review | approved | restricted | blocked
27 + commercialUse: text('commercial_use').notNull().default('unknown'), // allowed | restricted | prohibited | unknown
28 + redistribution: text('redistribution').notNull().default('unknown'), // allowed | attribution | restricted | prohibited | unknown
29 + attribution: text('attribution'),
30 + licenseReviewedAt: timestamp('license_reviewed_at', { withTimezone: true }),
31 + approvedForProduction: boolean('approved_for_production').notNull().default(false),
32 + updateFrequency: text('update_frequency'),
33 + supportsIncremental: boolean('supports_incremental').notNull().default(false),
34 + entities: text('entities').array().notNull().default([]),
35 + metrics: text('metrics').array().notNull().default([]),
36 + rateLimit: text('rate_limit'),
37 + status: text('status').notNull().default('planned'), // planned | active | paused | degraded | awaiting_credentials | review | retired
38 + tier: integer('tier').notNull().default(0),
39 + manifest: jsonb('manifest').$type<Record<string, unknown>>().notNull().default({}),
40 + createdAt: createdAt(),
41 + updatedAt: updatedAt(),
42 + },
43 + (t) => [uniqueIndex('sources_slug_uq').on(t.slug)],
44 +);
45 +
46 +/** Every connector execution (CLAUDE.md §24, §176). */
47 +export const ingestRuns = pgTable(
48 + 'ingest_runs',
49 + {
50 + id: text('id').primaryKey(), // ING-CLINICALTRIALS-20260908-000019
51 + connectorId: text('connector_id').notNull(),
52 + sourceId: ciId('source_id').notNull(),
53 + mode: text('mode').notNull().default('incremental'), // full | incremental | backfill | dry_run | probe
54 + status: text('status').notNull().default('running'), // running | succeeded | failed | partial | aborted
55 + startedAt: timestamp('started_at', { withTimezone: true }).notNull().defaultNow(),
56 + finishedAt: timestamp('finished_at', { withTimezone: true }),
57 + durationMs: integer('duration_ms'),
58 + recordsFetched: integer('records_fetched').notNull().default(0),
59 + recordsCreated: integer('records_created').notNull().default(0),
60 + recordsUpdated: integer('records_updated').notNull().default(0),
61 + recordsUnchanged: integer('records_unchanged').notNull().default(0),
62 + recordsRejected: integer('records_rejected').notNull().default(0),
63 + httpRequests: integer('http_requests').notNull().default(0),
64 + httpFailures: integer('http_failures').notNull().default(0),
65 + rateLimitEvents: integer('rate_limit_events').notNull().default(0),
66 + validationFailures: integer('validation_failures').notNull().default(0),
67 + schemaDrift: jsonb('schema_drift').$type<unknown[]>().notNull().default([]),
68 + cursorBefore: jsonb('cursor_before'),
69 + cursorAfter: jsonb('cursor_after'),
70 + error: text('error'),
71 + log: jsonb('log').$type<Array<{ t: string; level: string; msg: string }>>().notNull().default([]),
72 + datasetVersion: text('dataset_version'),
73 + anomaly: text('anomaly'), // set when a destructive update was refused (CLAUDE.md §171)
74 + },
75 + (t) => [index('ingest_runs_connector_idx').on(t.connectorId, t.startedAt)],
76 +);
77 +
78 +export const connectorCursors = pgTable('connector_cursors', {
79 + connectorId: text('connector_id').primaryKey(),
80 + cursor: jsonb('cursor').$type<Record<string, unknown>>().notNull().default({}),
81 + lastSuccessAt: timestamp('last_success_at', { withTimezone: true }),
82 + lastAttemptAt: timestamp('last_attempt_at', { withTimezone: true }),
83 + paused: boolean('paused').notNull().default(false),
84 + health: text('health').notNull().default('unknown'), // healthy | degraded | failing | review | awaiting_credentials | unknown
85 + healthDetail: text('health_detail'),
86 + updatedAt: updatedAt(),
87 +});
88 +
89 +/** Schema-drift detection: observed field names/types per connector entity (CLAUDE.md §25). */
90 +export const connectorFieldStats = pgTable(
91 + 'connector_field_stats',
92 + {
93 + id: bigserial('id', { mode: 'number' }).primaryKey(),
94 + connectorId: text('connector_id').notNull(),
95 + entity: text('entity').notNull(),
96 + field: text('field').notNull(),
97 + types: text('types').array().notNull().default([]),
98 + seenCount: integer('seen_count').notNull().default(0),
99 + nullCount: integer('null_count').notNull().default(0),
100 + firstSeenRun: text('first_seen_run'),
101 + lastSeenRun: text('last_seen_run'),
102 + updatedAt: updatedAt(),
103 + },
104 + (t) => [uniqueIndex('connector_field_stats_uq').on(t.connectorId, t.entity, t.field)],
105 +);
106 +
107 +/** Source-native records (idempotency key = source + entity + source record id; CLAUDE.md §91-92). */
108 +export const sourceRecords = pgTable(
109 + 'source_records',
110 + {
111 + id: bigserial('id', { mode: 'number' }).primaryKey(),
112 + sourceId: ciId('source_id').notNull(),
113 + entityKind: text('entity_kind').notNull(),
114 + sourceRecordId: text('source_record_id').notNull(),
115 + payloadHash: text('payload_hash').notNull(),
116 + rawPath: text('raw_path'), // data lake path /raw/{source}/{date}/{entity}/{id}.json.gz
117 + status: text('status').notNull().default('active'), // active | deprecated | retracted | withdrawn | source_missing
118 + firstSeenRun: text('first_seen_run'),
119 + lastSeenRun: text('last_seen_run'),
120 + retrievedAt: timestamp('retrieved_at', { withTimezone: true }).notNull().defaultNow(),
121 + sourceUpdatedAt: timestamp('source_updated_at', { withTimezone: true }),
122 + canonicalType: text('canonical_type'),
123 + canonicalId: text('canonical_id'),
124 + createdAt: createdAt(),
125 + updatedAt: updatedAt(),
126 + },
127 + (t) => [
128 + uniqueIndex('source_records_uq').on(t.sourceId, t.entityKind, t.sourceRecordId),
129 + index('source_records_canonical_idx').on(t.canonicalType, t.canonicalId),
130 + ],
131 +);
132 +
133 +/** Provenance record (CLAUDE.md §2). Referenced by observations, edges, descriptions, rankings. */
134 +export const provenance = pgTable(
135 + 'provenance',
136 + {
137 + id: bigserial('id', { mode: 'number' }).primaryKey(),
138 + publicId: ciId('public_id'), // CI-PROV-… (minted lazily when exposed)
139 + sourceId: ciId('source_id').notNull(),
140 + sourceRecordId: text('source_record_id'),
141 + sourceUrl: text('source_url'),
142 + dataset: text('dataset'),
143 + datasetVersion: text('dataset_version'),
144 + publicationId: ciId('publication_id'),
145 + pmid: text('pmid'),
146 + doi: text('doi'),
147 + retrievedAt: timestamp('retrieved_at', { withTimezone: true }).notNull(),
148 + publishedAt: text('published_at'),
149 + updatedAtSource: text('updated_at_source'),
150 + geography: text('geography'),
151 + population: text('population'),
152 + cohortSize: integer('cohort_size'),
153 + methodology: text('methodology'),
154 + evidenceType: text('evidence_type').notNull(),
155 + accessLevel: text('access_level').notNull().default('open'),
156 + confidence: real('confidence'),
157 + license: text('license'),
158 + ingestRunId: text('ingest_run_id'),
159 + createdAt: createdAt(),
160 + },
161 + (t) => [index('provenance_source_idx').on(t.sourceId, t.sourceRecordId), index('provenance_pmid_idx').on(t.pmid)],
162 +);
163 +
164 +/** Unknown disease/drug labels are never discarded (CLAUDE.md §222). */
165 +export const unresolvedLabels = pgTable(
166 + 'unresolved_labels',
167 + {
168 + id: bigserial('id', { mode: 'number' }).primaryKey(),
169 + sourceId: ciId('source_id').notNull(),
170 + entityKind: text('entity_kind').notNull(), // cancer | drug | gene | biomarker
171 + sourceText: text('source_text').notNull(),
172 + normalized: text('normalized').notNull(),
173 + context: jsonb('context').$type<Record<string, unknown>>().notNull().default({}),
174 + count: integer('count').notNull().default(1),
175 + status: text('status').notNull().default('open'), // open | mapped | rejected | ignored
176 + suggestedId: text('suggested_id'),
177 + suggestedMatchType: text('suggested_match_type'),
178 + suggestedScore: real('suggested_score'),
179 + resolvedId: text('resolved_id'),
180 + resolvedBy: text('resolved_by'),
181 + createdAt: createdAt(),
182 + updatedAt: updatedAt(),
183 + },
184 + (t) => [uniqueIndex('unresolved_labels_uq').on(t.sourceId, t.entityKind, t.normalized), index('unresolved_labels_count_idx').on(t.status, t.count)],
185 +);
186 +
187 +/** Entity change history (CLAUDE.md §96). */
188 +export const changeEvents = pgTable(
189 + 'change_events',
190 + {
191 + id: bigserial('id', { mode: 'number' }).primaryKey(),
192 + entityType: text('entity_type').notNull(),
193 + entityId: text('entity_id').notNull(),
194 + kind: text('kind').notNull(), // created | updated | trial_added | approval_added | ranking_changed | merged | deprecated …
195 + summary: text('summary').notNull(),
196 + before: jsonb('before'),
197 + after: jsonb('after'),
198 + ingestRunId: text('ingest_run_id'),
199 + createdAt: createdAt(),
200 + },
201 + (t) => [index('change_events_entity_idx').on(t.entityType, t.entityId, t.createdAt)],
202 +);
203 +
204 +/** Curator/admin audit log (CLAUDE.md §348). */
205 +export const auditLog = pgTable('audit_log', {
206 + id: bigserial('id', { mode: 'number' }).primaryKey(),
207 + actor: text('actor').notNull(),
208 + action: text('action').notNull(),
209 + entityType: text('entity_type'),
210 + entityId: text('entity_id'),
211 + before: jsonb('before'),
212 + after: jsonb('after'),
213 + reason: text('reason'),
214 + createdAt: createdAt(),
215 +});
216 +
217 +/** Entity merge queue + audit (CLAUDE.md §70). Reversible. */
218 +export const entityMerges = pgTable('entity_merges', {
219 + id: bigserial('id', { mode: 'number' }).primaryKey(),
220 + entityType: text('entity_type').notNull(),
221 + keepId: text('keep_id').notNull(),
222 + mergeId: text('merge_id').notNull(),
223 + evidence: jsonb('evidence').$type<Record<string, unknown>>().notNull().default({}),
224 + status: text('status').notNull().default('proposed'), // proposed | merged | kept_separate | reverted
225 + decidedBy: text('decided_by'),
226 + decidedAt: timestamp('decided_at', { withTimezone: true }),
227 + createdAt: createdAt(),
228 +});
added packages/database/src/schema/trials.ts +115 −0
@@ -0,0 +1,115 @@
1 +import { pgTable, text, integer, bigserial, index, uniqueIndex, boolean, jsonb, real, date } from 'drizzle-orm/pg-core';
2 +import { ciId, createdAt, updatedAt } from './_common.js';
3 +
4 +/** ClinicalTrials.gov studies (CLAUDE.md §10.5). */
5 +export const clinicalTrials = pgTable(
6 + 'clinical_trials',
7 + {
8 + id: ciId().primaryKey(), // CI-TRIAL-…
9 + nctId: text('nct_id').notNull(),
10 + briefTitle: text('brief_title').notNull(),
11 + officialTitle: text('official_title'),
12 + acronym: text('acronym'),
13 + studyType: text('study_type'), // INTERVENTIONAL | OBSERVATIONAL | EXPANDED_ACCESS
14 + phases: text('phases').array().notNull().default([]), // EARLY_PHASE1 | PHASE1 | PHASE2 | PHASE3 | PHASE4 | NA
15 + overallStatus: text('overall_status'), // RECRUITING | ACTIVE_NOT_RECRUITING | COMPLETED | …
16 + whyStopped: text('why_stopped'),
17 + startDate: text('start_date'),
18 + primaryCompletionDate: text('primary_completion_date'),
19 + completionDate: text('completion_date'),
20 + firstPostedDate: text('first_posted_date'),
21 + lastUpdatePostedDate: text('last_update_posted_date'),
22 + resultsFirstPostedDate: text('results_first_posted_date'),
23 + hasResults: boolean('has_results').notNull().default(false),
24 + enrollmentCount: integer('enrollment_count'),
25 + enrollmentType: text('enrollment_type'),
26 + leadSponsor: text('lead_sponsor'),
27 + leadSponsorClass: text('lead_sponsor_class'),
28 + collaborators: text('collaborators').array().notNull().default([]),
29 + conditions: text('conditions').array().notNull().default([]), // raw free text
30 + keywords: text('keywords').array().notNull().default([]),
31 + interventions: jsonb('interventions').$type<Array<{ type: string; name: string; description?: string; otherNames?: string[] }>>().notNull().default([]),
32 + arms: jsonb('arms').$type<Array<Record<string, unknown>>>().notNull().default([]),
33 + primaryOutcomes: jsonb('primary_outcomes').$type<Array<Record<string, unknown>>>().notNull().default([]),
34 + secondaryOutcomes: jsonb('secondary_outcomes').$type<Array<Record<string, unknown>>>().notNull().default([]),
35 + eligibility: jsonb('eligibility').$type<Record<string, unknown>>().notNull().default({}),
36 + sex: text('sex'),
37 + minimumAge: text('minimum_age'),
38 + maximumAge: text('maximum_age'),
39 + countries: text('countries').array().notNull().default([]),
40 + locationsCount: integer('locations_count').notNull().default(0),
41 + references: jsonb('references').$type<Array<{ pmid?: string; type?: string; citation?: string }>>().notNull().default([]),
42 + briefSummary: text('brief_summary'),
43 + isOncology: boolean('is_oncology').notNull().default(true),
44 + sourceRecordId: integer('source_record_id'),
45 + ingestRunId: text('ingest_run_id'),
46 + createdAt: createdAt(),
47 + updatedAt: updatedAt(),
48 + },
49 + (t) => [
50 + uniqueIndex('clinical_trials_nct_uq').on(t.nctId),
51 + index('clinical_trials_status_idx').on(t.overallStatus),
52 + index('clinical_trials_updated_idx').on(t.lastUpdatePostedDate),
53 + index('clinical_trials_sponsor_idx').on(t.leadSponsor),
54 + ],
55 +);
56 +
57 +/** trial → cancer mapping from free-text conditions (CLAUDE.md §10.5, §221). */
58 +export const trialConditions = pgTable(
59 + 'trial_conditions',
60 + {
61 + id: bigserial('id', { mode: 'number' }).primaryKey(),
62 + trialId: ciId('trial_id').notNull(),
63 + conditionText: text('condition_text').notNull(),
64 + normalized: text('normalized').notNull(),
65 + cancerId: ciId('cancer_id'),
66 + matchType: text('match_type').notNull().default('UNRESOLVED'),
67 + confidence: real('confidence'),
68 + },
69 + (t) => [uniqueIndex('trial_conditions_uq').on(t.trialId, t.normalized), index('trial_conditions_cancer_idx').on(t.cancerId), index('trial_conditions_norm_idx').on(t.normalized)],
70 +);
71 +
72 +export const trialInterventions = pgTable(
73 + 'trial_interventions',
74 + {
75 + id: bigserial('id', { mode: 'number' }).primaryKey(),
76 + trialId: ciId('trial_id').notNull(),
77 + name: text('name').notNull(),
78 + normalized: text('normalized').notNull(),
79 + interventionType: text('intervention_type'), // DRUG | BIOLOGICAL | DEVICE | PROCEDURE | RADIATION | BEHAVIORAL | OTHER
80 + drugId: ciId('drug_id'),
81 + matchType: text('match_type').notNull().default('UNRESOLVED'),
82 + },
83 + (t) => [uniqueIndex('trial_interventions_uq').on(t.trialId, t.normalized), index('trial_interventions_drug_idx').on(t.drugId)],
84 +);
85 +
86 +export const trialLocations = pgTable(
87 + 'trial_locations',
88 + {
89 + id: bigserial('id', { mode: 'number' }).primaryKey(),
90 + trialId: ciId('trial_id').notNull(),
91 + facility: text('facility'),
92 + city: text('city'),
93 + state: text('state'),
94 + zip: text('zip'),
95 + country: text('country'),
96 + status: text('status'),
97 + lat: real('lat'),
98 + lng: real('lng'),
99 + },
100 + (t) => [index('trial_locations_trial_idx').on(t.trialId), index('trial_locations_country_idx').on(t.country)],
101 +);
102 +
103 +/** Daily trial-creation pulse (derived; CLAUDE.md §332). */
104 +export const trialPulse = pgTable(
105 + 'trial_pulse',
106 + {
107 + id: bigserial('id', { mode: 'number' }).primaryKey(),
108 + day: date('day').notNull(),
109 + cancerId: ciId('cancer_id'),
110 + phase: text('phase'),
111 + newTrials: integer('new_trials').notNull(),
112 + computedAt: updatedAt(),
113 + },
114 + (t) => [uniqueIndex('trial_pulse_uq').on(t.day, t.cancerId, t.phase)],
115 +);
added packages/database/src/seed-data/geographies.ts +101 −0
@@ -0,0 +1,101 @@
1 +/** Canonical geography seed (CLAUDE.md §117). ISO 3166-1; WHO regions per WHO membership lists. */
2 +export interface GeographySeed {
3 + slug: string;
4 + name: string;
5 + kind: 'world' | 'region' | 'who_region' | 'country' | 'subdivision';
6 + iso2?: string;
7 + iso3?: string;
8 + parentSlug?: string;
9 + whoRegion?: string;
10 +}
11 +
12 +const WHO_REGIONS: Array<[string, string]> = [
13 + ['who-afro', 'WHO African Region'],
14 + ['who-amro', 'WHO Region of the Americas'],
15 + ['who-searo', 'WHO South-East Asia Region'],
16 + ['who-euro', 'WHO European Region'],
17 + ['who-emro', 'WHO Eastern Mediterranean Region'],
18 + ['who-wpro', 'WHO Western Pacific Region'],
19 +];
20 +
21 +// [iso2, iso3, name, whoRegion]
22 +const COUNTRIES: Array<[string, string, string, string]> = [
23 + ['US', 'USA', 'United States', 'who-amro'],
24 + ['CA', 'CAN', 'Canada', 'who-amro'],
25 + ['MX', 'MEX', 'Mexico', 'who-amro'],
26 + ['BR', 'BRA', 'Brazil', 'who-amro'],
27 + ['AR', 'ARG', 'Argentina', 'who-amro'],
28 + ['CL', 'CHL', 'Chile', 'who-amro'],
29 + ['CO', 'COL', 'Colombia', 'who-amro'],
30 + ['PE', 'PER', 'Peru', 'who-amro'],
31 + ['CU', 'CUB', 'Cuba', 'who-amro'],
32 + ['GB', 'GBR', 'United Kingdom', 'who-euro'],
33 + ['IE', 'IRL', 'Ireland', 'who-euro'],
34 + ['FR', 'FRA', 'France', 'who-euro'],
35 + ['DE', 'DEU', 'Germany', 'who-euro'],
36 + ['IT', 'ITA', 'Italy', 'who-euro'],
37 + ['ES', 'ESP', 'Spain', 'who-euro'],
38 + ['PT', 'PRT', 'Portugal', 'who-euro'],
39 + ['NL', 'NLD', 'Netherlands', 'who-euro'],
40 + ['BE', 'BEL', 'Belgium', 'who-euro'],
41 + ['CH', 'CHE', 'Switzerland', 'who-euro'],
42 + ['AT', 'AUT', 'Austria', 'who-euro'],
43 + ['SE', 'SWE', 'Sweden', 'who-euro'],
44 + ['NO', 'NOR', 'Norway', 'who-euro'],
45 + ['DK', 'DNK', 'Denmark', 'who-euro'],
46 + ['FI', 'FIN', 'Finland', 'who-euro'],
47 + ['IS', 'ISL', 'Iceland', 'who-euro'],
48 + ['PL', 'POL', 'Poland', 'who-euro'],
49 + ['CZ', 'CZE', 'Czechia', 'who-euro'],
50 + ['HU', 'HUN', 'Hungary', 'who-euro'],
51 + ['GR', 'GRC', 'Greece', 'who-euro'],
52 + ['RO', 'ROU', 'Romania', 'who-euro'],
53 + ['UA', 'UKR', 'Ukraine', 'who-euro'],
54 + ['RU', 'RUS', 'Russian Federation', 'who-euro'],
55 + ['TR', 'TUR', 'Türkiye', 'who-euro'],
56 + ['IL', 'ISR', 'Israel', 'who-euro'],
57 + ['EG', 'EGY', 'Egypt', 'who-emro'],
58 + ['SA', 'SAU', 'Saudi Arabia', 'who-emro'],
59 + ['IR', 'IRN', 'Iran', 'who-emro'],
60 + ['PK', 'PAK', 'Pakistan', 'who-emro'],
61 + ['MA', 'MAR', 'Morocco', 'who-emro'],
62 + ['NG', 'NGA', 'Nigeria', 'who-afro'],
63 + ['ZA', 'ZAF', 'South Africa', 'who-afro'],
64 + ['KE', 'KEN', 'Kenya', 'who-afro'],
65 + ['ET', 'ETH', 'Ethiopia', 'who-afro'],
66 + ['GH', 'GHA', 'Ghana', 'who-afro'],
67 + ['UG', 'UGA', 'Uganda', 'who-afro'],
68 + ['TZ', 'TZA', 'Tanzania', 'who-afro'],
69 + ['DZ', 'DZA', 'Algeria', 'who-afro'],
70 + ['IN', 'IND', 'India', 'who-searo'],
71 + ['BD', 'BGD', 'Bangladesh', 'who-searo'],
72 + ['ID', 'IDN', 'Indonesia', 'who-searo'],
73 + ['TH', 'THA', 'Thailand', 'who-searo'],
74 + ['NP', 'NPL', 'Nepal', 'who-searo'],
75 + ['LK', 'LKA', 'Sri Lanka', 'who-searo'],
76 + ['CN', 'CHN', 'China', 'who-wpro'],
77 + ['JP', 'JPN', 'Japan', 'who-wpro'],
78 + ['KR', 'KOR', 'Republic of Korea', 'who-wpro'],
79 + ['AU', 'AUS', 'Australia', 'who-wpro'],
80 + ['NZ', 'NZL', 'New Zealand', 'who-wpro'],
81 + ['SG', 'SGP', 'Singapore', 'who-wpro'],
82 + ['MY', 'MYS', 'Malaysia', 'who-wpro'],
83 + ['PH', 'PHL', 'Philippines', 'who-wpro'],
84 + ['VN', 'VNM', 'Viet Nam', 'who-wpro'],
85 +];
86 +
87 +const US_STATES: Array<[string, string]> = [
88 + ['AL', 'Alabama'], ['AK', 'Alaska'], ['AZ', 'Arizona'], ['AR', 'Arkansas'], ['CA', 'California'], ['CO', 'Colorado'], ['CT', 'Connecticut'], ['DE', 'Delaware'], ['DC', 'District of Columbia'], ['FL', 'Florida'], ['GA', 'Georgia'], ['HI', 'Hawaii'], ['ID', 'Idaho'], ['IL', 'Illinois'], ['IN', 'Indiana'], ['IA', 'Iowa'], ['KS', 'Kansas'], ['KY', 'Kentucky'], ['LA', 'Louisiana'], ['ME', 'Maine'], ['MD', 'Maryland'], ['MA', 'Massachusetts'], ['MI', 'Michigan'], ['MN', 'Minnesota'], ['MS', 'Mississippi'], ['MO', 'Missouri'], ['MT', 'Montana'], ['NE', 'Nebraska'], ['NV', 'Nevada'], ['NH', 'New Hampshire'], ['NJ', 'New Jersey'], ['NM', 'New Mexico'], ['NY', 'New York'], ['NC', 'North Carolina'], ['ND', 'North Dakota'], ['OH', 'Ohio'], ['OK', 'Oklahoma'], ['OR', 'Oregon'], ['PA', 'Pennsylvania'], ['RI', 'Rhode Island'], ['SC', 'South Carolina'], ['SD', 'South Dakota'], ['TN', 'Tennessee'], ['TX', 'Texas'], ['UT', 'Utah'], ['VT', 'Vermont'], ['VA', 'Virginia'], ['WA', 'Washington'], ['WV', 'West Virginia'], ['WI', 'Wisconsin'], ['WY', 'Wyoming'],
89 +];
90 +
91 +const CA_PROVINCES: Array<[string, string]> = [
92 + ['AB', 'Alberta'], ['BC', 'British Columbia'], ['MB', 'Manitoba'], ['NB', 'New Brunswick'], ['NL', 'Newfoundland and Labrador'], ['NS', 'Nova Scotia'], ['NT', 'Northwest Territories'], ['NU', 'Nunavut'], ['ON', 'Ontario'], ['PE', 'Prince Edward Island'], ['QC', 'Quebec'], ['SK', 'Saskatchewan'], ['YT', 'Yukon'],
93 +];
94 +
95 +export const GEOGRAPHY_SEED: GeographySeed[] = [
96 + { slug: 'world', name: 'World', kind: 'world' },
97 + ...WHO_REGIONS.map(([slug, name]) => ({ slug, name, kind: 'who_region' as const, parentSlug: 'world' })),
98 + ...COUNTRIES.map(([iso2, iso3, name, who]) => ({ slug: name.toLowerCase().replace(/[^a-z0-9]+/g, '-'), name, kind: 'country' as const, iso2, iso3, parentSlug: who, whoRegion: who })),
99 + ...US_STATES.map(([code, name]) => ({ slug: `us-${code.toLowerCase()}`, name, kind: 'subdivision' as const, iso2: `US-${code}`, parentSlug: 'united-states', whoRegion: 'who-amro' })),
100 + ...CA_PROVINCES.map(([code, name]) => ({ slug: `ca-${code.toLowerCase()}`, name, kind: 'subdivision' as const, iso2: `CA-${code}`, parentSlug: 'canada', whoRegion: 'who-amro' })),
101 +];
added packages/database/src/seed-data/metrics.ts +285 −0
@@ -0,0 +1,285 @@
1 +/**
2 + * Metric catalog (CLAUDE.md §250-252, §352). Every derived number shown on the site references
3 + * one of these definitions by slug; the formula text and version are displayed on /methodology.
4 + *
5 + * Composite scores (CI-IMPACT etc.) are intentionally absent from Phase 1 (§353).
6 + */
7 +export interface MetricSeed {
8 + slug: string;
9 + name: string;
10 + description: string;
11 + formula: string;
12 + formulaVersion: string;
13 + unit: string;
14 + higherIsWorse: boolean | null;
15 + aggregation: string | null;
16 + validDimensions: string[];
17 + sourceSlugs: string[];
18 + category: string;
19 + eligibility: Record<string, unknown>;
20 + experimental: boolean;
21 +}
22 +
23 +export const METRIC_CATALOG: MetricSeed[] = [
24 + // --- Burden (epidemiology observations) ---
25 + {
26 + slug: 'incidence_count',
27 + name: 'Annual new cases',
28 + description: 'Number of new cases diagnosed in the reference year for the population scope. Registry counts are observed; national/global figures may be estimates and are labeled as such.',
29 + formula: 'epidemiology_observations.value WHERE metric = incidence_count',
30 + formulaVersion: 'ci-incidence-count-v1',
31 + unit: 'count',
32 + higherIsWorse: true,
33 + aggregation: 'none',
34 + validDimensions: ['geography', 'sex', 'age_group', 'year'],
35 + sourceSlugs: ['cdc-wonder', 'seer', 'iarc-globocan'],
36 + category: 'burden',
37 + eligibility: { requires: ['incidence_count'], entityLevel: 'top' },
38 + experimental: false,
39 + },
40 + {
41 + slug: 'mortality_count',
42 + name: 'Annual deaths',
43 + description: 'Number of deaths with this cancer as underlying cause in the reference year for the population scope.',
44 + formula: 'epidemiology_observations.value WHERE metric = mortality_count',
45 + formulaVersion: 'ci-mortality-count-v1',
46 + unit: 'count',
47 + higherIsWorse: true,
48 + aggregation: 'none',
49 + validDimensions: ['geography', 'sex', 'age_group', 'year'],
50 + sourceSlugs: ['cdc-wonder', 'seer', 'iarc-globocan'],
51 + category: 'burden',
52 + eligibility: { requires: ['mortality_count'], entityLevel: 'top' },
53 + experimental: false,
54 + },
55 + {
56 + slug: 'as_incidence_rate',
57 + name: 'Age-standardized incidence rate',
58 + description: 'Incidence per 100,000 person-years, age-standardized to the standard population declared by the source. Rates standardized to different standard populations are never compared in one ranking.',
59 + formula: 'epidemiology_observations.value WHERE metric = as_incidence_rate (standard_population fixed per scope)',
60 + formulaVersion: 'ci-asir-v1',
61 + unit: 'per_100k',
62 + higherIsWorse: true,
63 + aggregation: 'none',
64 + validDimensions: ['geography', 'sex', 'year'],
65 + sourceSlugs: ['cdc-wonder', 'seer', 'iarc-globocan'],
66 + category: 'burden',
67 + eligibility: { requires: ['as_incidence_rate'], entityLevel: 'top' },
68 + experimental: false,
69 + },
70 + {
71 + slug: 'as_mortality_rate',
72 + name: 'Age-standardized mortality rate',
73 + description: 'Deaths per 100,000 person-years, age-standardized to the standard population declared by the source.',
74 + formula: 'epidemiology_observations.value WHERE metric = as_mortality_rate (standard_population fixed per scope)',
75 + formulaVersion: 'ci-asmr-v1',
76 + unit: 'per_100k',
77 + higherIsWorse: true,
78 + aggregation: 'none',
79 + validDimensions: ['geography', 'sex', 'year'],
80 + sourceSlugs: ['cdc-wonder', 'seer', 'iarc-globocan'],
81 + category: 'burden',
82 + eligibility: { requires: ['as_mortality_rate'], entityLevel: 'top' },
83 + experimental: false,
84 + },
85 + // --- Lethality ---
86 + {
87 + slug: 'mortality_incidence_ratio',
88 + name: 'Mortality-to-incidence ratio',
89 + description: 'Deaths divided by new cases in the same population, year and sex. A crude proxy of lethality; it is not a survival probability and is affected by incidence trends and registration completeness.',
90 + formula: 'mortality_count / incidence_count (same geography, year, sex, source family)',
91 + formulaVersion: 'ci-mir-v1',
92 + unit: 'ratio',
93 + higherIsWorse: true,
94 + aggregation: 'none',
95 + validDimensions: ['geography', 'sex', 'year'],
96 + sourceSlugs: ['cdc-wonder', 'seer', 'iarc-globocan'],
97 + category: 'lethality',
98 + eligibility: { requires: ['incidence_count', 'mortality_count'], minIncidence: 100, entityLevel: 'top' },
99 + experimental: false,
100 + },
101 + {
102 + slug: 'five_year_survival',
103 + name: '5-year relative survival',
104 + description: '5-year relative survival for the diagnosis period and population declared by the source. Population survival statistics do not predict an individual outcome.',
105 + formula: 'survival_observations.probability WHERE survival_type = relative AND duration_months = 60 AND stage IS NULL',
106 + formulaVersion: 'ci-5ys-v1',
107 + unit: 'probability',
108 + higherIsWorse: false,
109 + aggregation: 'none',
110 + validDimensions: ['geography', 'sex', 'diagnosis_period'],
111 + sourceSlugs: ['seer'],
112 + category: 'lethality',
113 + eligibility: { requires: ['five_year_relative_survival'], minCohort: 50, entityLevel: 'top' },
114 + experimental: false,
115 + },
116 + // --- Clinical research (ClinicalTrials.gov) ---
117 + {
118 + slug: 'active_trials',
119 + name: 'Active clinical trials',
120 + description: 'Interventional studies registered on ClinicalTrials.gov whose overall status is RECRUITING, NOT_YET_RECRUITING, ENROLLING_BY_INVITATION or ACTIVE_NOT_RECRUITING and whose conditions map to this cancer or one of its descendants in the NCIt hierarchy.',
121 + formula: 'COUNT(DISTINCT trial) FROM trial_conditions JOIN clinical_trials WHERE cancer_id IN descendants(cancer) AND study_type = INTERVENTIONAL AND overall_status IN (RECRUITING, NOT_YET_RECRUITING, ENROLLING_BY_INVITATION, ACTIVE_NOT_RECRUITING)',
122 + formulaVersion: 'ci-active-trials-v1',
123 + unit: 'count',
124 + higherIsWorse: false,
125 + aggregation: 'descendants',
126 + validDimensions: ['entity_level'],
127 + sourceSlugs: ['clinicaltrials'],
128 + category: 'trials',
129 + eligibility: { entityLevel: 'any' },
130 + experimental: false,
131 + },
132 + {
133 + slug: 'recruiting_trials',
134 + name: 'Recruiting clinical trials',
135 + description: 'Interventional studies currently RECRUITING whose conditions map to this cancer or its descendants.',
136 + formula: 'COUNT(DISTINCT trial) … overall_status = RECRUITING AND study_type = INTERVENTIONAL',
137 + formulaVersion: 'ci-recruiting-trials-v1',
138 + unit: 'count',
139 + higherIsWorse: false,
140 + aggregation: 'descendants',
141 + validDimensions: ['entity_level'],
142 + sourceSlugs: ['clinicaltrials'],
143 + category: 'trials',
144 + eligibility: { entityLevel: 'any' },
145 + experimental: false,
146 + },
147 + {
148 + slug: 'phase3_trials',
149 + name: 'Active Phase III trials',
150 + description: 'Active interventional studies with PHASE3 among their phases, mapped to this cancer or its descendants.',
151 + formula: 'COUNT(DISTINCT trial) … active statuses AND PHASE3 = ANY(phases)',
152 + formulaVersion: 'ci-phase3-trials-v1',
153 + unit: 'count',
154 + higherIsWorse: false,
155 + aggregation: 'descendants',
156 + validDimensions: ['entity_level'],
157 + sourceSlugs: ['clinicaltrials'],
158 + category: 'trials',
159 + eligibility: { entityLevel: 'any' },
160 + experimental: false,
161 + },
162 + // --- Research activity (PubMed) ---
163 + {
164 + slug: 'publications_5y',
165 + name: 'Publications, last 5 years',
166 + description: 'PubMed records matching the cancer’s MeSH-anchored query (stored verbatim with the count) with a publication date in the last 5 full years plus the current year.',
167 + formula: 'literature_counts.count WHERE window_key = 5y (query stored per row)',
168 + formulaVersion: 'ci-pubs-5y-v1',
169 + unit: 'count',
170 + higherIsWorse: false,
171 + aggregation: 'none',
172 + validDimensions: ['entity_level'],
173 + sourceSlugs: ['pubmed'],
174 + category: 'research',
175 + eligibility: { entityLevel: 'any', requiresQuery: true },
176 + experimental: false,
177 + },
178 + {
179 + slug: 'publications_12m',
180 + name: 'Publications, last 12 months',
181 + description: 'PubMed records matching the cancer’s query with a publication date in the last 12 months.',
182 + formula: 'literature_counts.count WHERE window_key = 12m',
183 + formulaVersion: 'ci-pubs-12m-v1',
184 + unit: 'count',
185 + higherIsWorse: false,
186 + aggregation: 'none',
187 + validDimensions: ['entity_level'],
188 + sourceSlugs: ['pubmed'],
189 + category: 'research',
190 + eligibility: { entityLevel: 'any', requiresQuery: true },
191 + experimental: false,
192 + },
193 + {
194 + slug: 'publication_growth',
195 + name: 'Publication growth',
196 + description: 'Ratio of publications in the last 12 months to the average annual publications over the preceding 5-year window. Values above 1 indicate accelerating literature.',
197 + formula: 'publications_12m / (publications_5y_prior / 5)',
198 + formulaVersion: 'ci-pub-growth-v1',
199 + unit: 'ratio',
200 + higherIsWorse: null,
201 + aggregation: 'none',
202 + validDimensions: ['entity_level'],
203 + sourceSlugs: ['pubmed'],
204 + category: 'trend',
205 + eligibility: { entityLevel: 'any', minPublications5y: 50 },
206 + experimental: false,
207 + },
208 + // --- Molecular knowledge ---
209 + {
210 + slug: 'curated_evidence_items',
211 + name: 'Curated clinical evidence items',
212 + description: 'Accepted CIViC evidence items whose disease maps to this cancer or its descendants.',
213 + formula: 'COUNT(civic_evidence_items) WHERE status = ACCEPTED AND cancer_id IN descendants(cancer)',
214 + formulaVersion: 'ci-civic-evidence-v1',
215 + unit: 'count',
216 + higherIsWorse: false,
217 + aggregation: 'descendants',
218 + validDimensions: ['entity_level'],
219 + sourceSlugs: ['civic'],
220 + category: 'molecular',
221 + eligibility: { entityLevel: 'any' },
222 + experimental: false,
223 + },
224 + {
225 + slug: 'associated_genes',
226 + name: 'Genes with curated or cohort evidence',
227 + description: 'Distinct genes linked to this cancer (or descendants) by an accepted CIViC evidence item or by a GDC cohort frequency of at least 5% with ≥ 20 affected cases.',
228 + formula: 'COUNT(DISTINCT gene) FROM (civic accepted evidence ∪ gdc frequency ≥ 0.05 AND cases_affected ≥ 20)',
229 + formulaVersion: 'ci-genes-v1',
230 + unit: 'count',
231 + higherIsWorse: false,
232 + aggregation: 'descendants',
233 + validDimensions: ['entity_level'],
234 + sourceSlugs: ['civic', 'gdc'],
235 + category: 'molecular',
236 + eligibility: { entityLevel: 'any' },
237 + experimental: false,
238 + },
239 + {
240 + slug: 'genomic_cohorts',
241 + name: 'Public genomic cohorts',
242 + description: 'Open-access genomic studies (GDC projects) whose disease maps to this cancer or its descendants.',
243 + formula: 'COUNT(genomic_cohorts) WHERE cancer_id IN descendants(cancer)',
244 + formulaVersion: 'ci-cohorts-v1',
245 + unit: 'count',
246 + higherIsWorse: false,
247 + aggregation: 'descendants',
248 + validDimensions: ['entity_level'],
249 + sourceSlugs: ['gdc'],
250 + category: 'molecular',
251 + eligibility: { entityLevel: 'any' },
252 + experimental: false,
253 + },
254 + // --- Gap indexes (derived; require burden) ---
255 + {
256 + slug: 'trial_gap',
257 + name: 'Trial Gap Index',
258 + description: 'Burden percentile minus active-trial percentile within the same scope. Positive values flag cancers with high mortality burden but comparatively few active trials. A quantitative signal, not an accusation (CLAUDE.md §324).',
259 + formula: 'percentile(mortality_count) − percentile(active_trials)',
260 + formulaVersion: 'ci-trial-gap-v1',
261 + unit: 'percentile_points',
262 + higherIsWorse: true,
263 + aggregation: 'none',
264 + validDimensions: ['geography', 'year'],
265 + sourceSlugs: ['clinicaltrials', 'cdc-wonder', 'seer', 'iarc-globocan'],
266 + category: 'unmet_need',
267 + eligibility: { requires: ['mortality_count', 'active_trials'], entityLevel: 'top' },
268 + experimental: false,
269 + },
270 + {
271 + slug: 'research_gap',
272 + name: 'Research Gap Index',
273 + description: 'Burden percentile minus research-activity percentile (publications, last 5 years) within the same scope. Positive values flag cancers with high mortality burden but comparatively little literature.',
274 + formula: 'percentile(mortality_count) − percentile(publications_5y)',
275 + formulaVersion: 'ci-research-gap-v1',
276 + unit: 'percentile_points',
277 + higherIsWorse: true,
278 + aggregation: 'none',
279 + validDimensions: ['geography', 'year'],
280 + sourceSlugs: ['pubmed', 'cdc-wonder', 'seer', 'iarc-globocan'],
281 + category: 'unmet_need',
282 + eligibility: { requires: ['mortality_count', 'publications_5y'], entityLevel: 'top' },
283 + experimental: false,
284 + },
285 +];
added packages/database/src/seed.ts +49 −0
@@ -0,0 +1,49 @@
1 +import { fileURLToPath } from 'node:url';
2 +import path from 'node:path';
3 +import { eq, sql } from 'drizzle-orm';
4 +import { getDb, closeDb } from './client.js';
5 +import { geographies, metricDefinitions } from './schema/index.js';
6 +import { mintId } from './ids.js';
7 +import { METRIC_CATALOG } from './seed-data/metrics.js';
8 +import { GEOGRAPHY_SEED } from './seed-data/geographies.js';
9 +
10 +/**
11 + * Seeds only system data (CLAUDE.md §358): metric definitions and canonical geographies.
12 + * Scientific data is never seeded — it comes from connectors. Sources are synced from connector
13 + * manifests by `pnpm cix sources:sync` (lives in the root CLI to avoid a package cycle).
14 + */
15 +export async function seed(): Promise<void> {
16 + const db = getDb({ max: 2 });
17 + for (const m of METRIC_CATALOG) {
18 + const [existing] = await db.select({ id: metricDefinitions.id }).from(metricDefinitions).where(eq(metricDefinitions.slug, m.slug)).limit(1);
19 + const values = { ...m, updatedAt: new Date() };
20 + if (existing) await db.update(metricDefinitions).set(values).where(eq(metricDefinitions.id, existing.id));
21 + else await db.insert(metricDefinitions).values({ id: await mintId(db, 'METRIC'), ...m });
22 + }
23 + const bySlug = new Map<string, string>();
24 + for (const g of GEOGRAPHY_SEED) {
25 + const [existing] = await db.select({ id: geographies.id }).from(geographies).where(eq(geographies.slug, g.slug)).limit(1);
26 + const parentId = g.parentSlug ? bySlug.get(g.parentSlug) ?? null : null;
27 + if (existing) {
28 + await db.update(geographies).set({ name: g.name, kind: g.kind, iso2: g.iso2 ?? null, iso3: g.iso3 ?? null, parentId, whoRegion: g.whoRegion ?? null }).where(eq(geographies.id, existing.id));
29 + bySlug.set(g.slug, existing.id);
30 + } else {
31 + const id = await mintId(db, 'GEO');
32 + await db.insert(geographies).values({ id, slug: g.slug, name: g.name, kind: g.kind, iso2: g.iso2 ?? null, iso3: g.iso3 ?? null, parentId, whoRegion: g.whoRegion ?? null });
33 + bySlug.set(g.slug, id);
34 + }
35 + }
36 + const [{ n }] = (await db.execute<{ n: string }>(sql`SELECT count(*)::text AS n FROM metric_definitions`)) as unknown as [{ n: string }];
37 + console.log(`[seed] metrics=${n} geographies=${GEOGRAPHY_SEED.length}`);
38 +}
39 +
40 +const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
41 +if (isMain) {
42 + seed()
43 + .then(() => closeDb())
44 + .catch(async (e) => {
45 + console.error('[seed] failed', e);
46 + await closeDb();
47 + process.exit(1);
48 + });
49 +}
added packages/database/tsconfig.json +8 −0
@@ -0,0 +1,8 @@
1 +{
2 + "extends": "../../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": ".",
5 + "noEmit": true
6 + },
7 + "include": ["src", "drizzle.config.ts"]
8 +}
added packages/ontology/package.json +27 −0
@@ -0,0 +1,27 @@
1 +{
2 + "name": "@cancerindex/ontology",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "exports": {
7 + ".": {
8 + "types": "./src/index.ts",
9 + "default": "./src/index.ts"
10 + }
11 + },
12 + "scripts": {
13 + "build": "tsc -p tsconfig.json --noEmit",
14 + "typecheck": "tsc -p tsconfig.json --noEmit",
15 + "test": "vitest run --passWithNoTests"
16 + },
17 + "dependencies": {
18 + "@cancerindex/database": "workspace:*",
19 + "@cancerindex/shared": "workspace:*",
20 + "drizzle-orm": "^0.45.0"
21 + },
22 + "devDependencies": {
23 + "@types/node": "^24.0.0",
24 + "typescript": "^5.9.3",
25 + "vitest": "^3.2.0"
26 + }
27 +}
added packages/ontology/src/index.ts +3 −0
@@ -0,0 +1,3 @@
1 +export * from './qualifiers.js';
2 +export * from './reconcile.js';
3 +export * from './top-level.js';
added packages/ontology/src/qualifiers.ts +76 −0
@@ -0,0 +1,76 @@
1 +/**
2 + * NCIt neoplasm concepts that are *qualified disease states* rather than taxonomy nodes
3 + * (CLAUDE.md §217-218: "Do not create a unique canonical cancer entity for every arbitrary
4 + * combination"). Stage / recurrence / resectability / laterality / treatment-history qualified
5 + * concepts are retained as source records and mapped to their base disease, but are not canonical
6 + * cancers.
7 + *
8 + * Rules are deterministic and tested (test/qualifiers.test.ts). When in doubt a concept is KEPT
9 + * (a false negative costs an extra node; a false positive loses a real disease entity).
10 + */
11 +
12 +/** Words that mark a clinical state, never a disease identity. */
13 +const STATE_WORDS =
14 + /\b(stage\s?[0iv]+[abcs]?\d?|stages?\s[0iv]+|recurrent|refractory|relapsed|resectable|unresectable|borderline resectable|localized|locally advanced|advanced|metastatic|progressive|residual|persistent|newly diagnosed|untreated|previously treated|previously untreated|treatment[- ]naive|bilateral|unilateral|synchronous|metachronous|multifocal|multicentric|unifocal|occult|extensive[- ]stage|limited[- ]stage|in remission|in relapse|in complete remission|minimal residual disease|post[- ]?transplant(ation)?|aggravated|complicated|uncomplicated|with (unknown|known) primary site|of unknown primary origin)\b/i;
15 +
16 +/** Staging-system suffixes: "… AJCC v8", "… FIGO Stage IIIC". */
17 +const STAGING_SUFFIX = /\s(by\s)?(ajcc|figo|uicc|inrg|inss|cog|enneking|lugano|ann arbor|binet|rai|durie[- ]salmon|r?-?iss|tnm)(\sv\d+)?(\sstage\s?[0iv]+[abc]?)?$/i;
18 +
19 +/** Laterality prefixes only count when followed by an organ-level disease. */
20 +const LATERALITY_PREFIX = /^(left|right)\s/i;
21 +
22 +/**
23 + * Recognized disease names that contain a state word as part of their identity.
24 + * Kept deliberately short: the regexes above are the primary mechanism.
25 + */
26 +const KEEP_EXACT = new Set(
27 + [
28 + 'Progressive Multifocal Leukoencephalopathy',
29 + 'Persistent Mullerian Duct Syndrome',
30 + 'Advanced Sclerosing Systemic Mastocytosis',
31 + 'Residual Neuroblastoma',
32 + 'Cancer of Unknown Primary Origin',
33 + 'Carcinoma of Unknown Primary Origin',
34 + 'Neoplasm of Unknown Primary Origin',
35 + 'Adenocarcinoma of Unknown Primary Origin',
36 + 'Squamous Cell Carcinoma of Unknown Primary Origin',
37 + 'Neuroendocrine Carcinoma of Unknown Primary Origin',
38 + 'Melanoma of Unknown Primary Origin',
39 + 'Metastatic Malignant Neoplasm of Unknown Primary Origin',
40 + 'Multifocal Motor Neuropathy',
41 + 'Multicentric Castleman Disease',
42 + 'Idiopathic Multicentric Castleman Disease',
43 + 'HHV8-Associated Multicentric Castleman Disease',
44 + 'Multicentric Reticulohistiocytosis',
45 + 'Multifocal Langerhans Cell Histiocytosis',
46 + 'Unifocal Langerhans Cell Histiocytosis',
47 + 'Multifocal Osteosarcoma',
48 + 'Synchronous Multiple Primary Malignant Neoplasm',
49 + 'Metachronous Multiple Primary Malignant Neoplasm',
50 + 'Occult Breast Carcinoma',
51 + 'Occult Primary Malignant Neoplasm',
52 + ].map((s) => s.toLowerCase()),
53 +);
54 +
55 +/** True when a preferred name denotes a qualified state rather than a disease taxonomy node. */
56 +export function isQualifiedState(preferredName: string): boolean {
57 + const name = preferredName.trim().toLowerCase();
58 + if (KEEP_EXACT.has(name)) return false;
59 + if (STAGING_SUFFIX.test(name)) return true;
60 + if (STATE_WORDS.test(name)) return true;
61 + if (LATERALITY_PREFIX.test(name)) return true;
62 + return false;
63 +}
64 +
65 +/** Strip qualifiers to find the base disease label (used to attach qualified states to their disease). */
66 +export function baseDiseaseLabel(preferredName: string): string {
67 + let s = preferredName.trim();
68 + for (let i = 0; i < 5; i++) {
69 + const before = s;
70 + s = s.replace(STAGING_SUFFIX, '');
71 + s = s.replace(/^(stage\s?[0iv]+[abcs]?\d?|recurrent|refractory|relapsed|resectable|unresectable|borderline resectable|localized|locally advanced|advanced|metastatic|progressive|residual|persistent|newly diagnosed|untreated|previously treated|previously untreated|treatment[- ]naive|bilateral|unilateral|left|right|synchronous|metachronous|multifocal|multicentric|unifocal|occult|extensive[- ]stage|limited[- ]stage)\s+/i, '');
72 + s = s.replace(/\s+(stage\s?[0iv]+[abcs]?\d?|in remission|in relapse|in complete remission)$/i, '');
73 + if (s === before) break;
74 + }
75 + return s.trim();
76 +}
added packages/ontology/src/reconcile.ts +106 −0
@@ -0,0 +1,106 @@
1 +import { and, eq, inArray, sql } from 'drizzle-orm';
2 +import { normalizeLabel, uninvertMeshTerm, type MatchType } from '@cancerindex/shared';
3 +import { type Database, cancerAliases, cancerCodes, cancers } from '@cancerindex/database';
4 +
5 +export interface CancerMatch {
6 + cancerId: string;
7 + matchType: MatchType;
8 + confidence: number;
9 + via: string; // human-readable explanation
10 +}
11 +
12 +/**
13 + * Cancer label reconciliation (CLAUDE.md §69): identifiers → curated aliases → normalized strings.
14 + * Never uses embeddings or an LLM to merge. Returns null (→ unresolved queue) when unsure.
15 + */
16 +export class CancerResolver {
17 + private aliasCache = new Map<string, { cancerId: string; aliasType: string }[]>();
18 + private codeCache = new Map<string, string>();
19 +
20 + constructor(private readonly db: Database) {}
21 +
22 + /** Preload alias + code lookups in memory for bulk reconciliation. */
23 + async warm(): Promise<void> {
24 + const rows = await this.db
25 + .select({ cancerId: cancerAliases.cancerId, normalized: cancerAliases.normalized, aliasType: cancerAliases.aliasType })
26 + .from(cancerAliases)
27 + .innerJoin(cancers, eq(cancers.id, cancerAliases.cancerId))
28 + .where(eq(cancers.status, 'active'));
29 + this.aliasCache.clear();
30 + for (const r of rows) {
31 + const arr = this.aliasCache.get(r.normalized) ?? [];
32 + arr.push({ cancerId: r.cancerId, aliasType: r.aliasType });
33 + this.aliasCache.set(r.normalized, arr);
34 + }
35 + const codes = await this.db.select({ cancerId: cancerCodes.cancerId, system: cancerCodes.system, code: cancerCodes.code }).from(cancerCodes).where(inArray(cancerCodes.matchType, ['EXACT_IDENTIFIER', 'CURATED_EXACT', 'ONTOLOGY_EXACT']));
36 + this.codeCache.clear();
37 + for (const c of codes) this.codeCache.set(`${c.system}:${c.code}`, c.cancerId);
38 + }
39 +
40 + get warmed(): boolean {
41 + return this.aliasCache.size > 0;
42 + }
43 +
44 + /** Resolve by external identifier (NCIt code, DOID, OncoTree code, UMLS CUI, MeSH UI…). */
45 + byCode(system: string, code: string): CancerMatch | null {
46 + const id = this.codeCache.get(`${system}:${code}`);
47 + return id ? { cancerId: id, matchType: 'EXACT_IDENTIFIER', confidence: 1, via: `${system}:${code}` } : null;
48 + }
49 +
50 + /** Resolve a free-text label. Ambiguous alias hits (≠ 1 distinct cancer) are returned as null. */
51 + byLabel(label: string, opts: { allowMeshInversion?: boolean } = {}): CancerMatch | null {
52 + const candidates = [label];
53 + if (opts.allowMeshInversion && label.includes(',')) candidates.push(uninvertMeshTerm(label));
54 + // Common registry phrasings: "Cancer of the X" → "X cancer"; "Carcinoma of X" → "X carcinoma"
55 + const m = /^(cancer|carcinoma|neoplasm|tumou?r|sarcoma|lymphoma|leukemia|adenocarcinoma|melanoma) of (the )?(.+)$/i.exec(label);
56 + if (m) candidates.push(`${m[3]} ${m[1]}`);
57 + for (const c of candidates) {
58 + const norm = normalizeLabel(c);
59 + if (!norm) continue;
60 + const hits = this.aliasCache.get(norm);
61 + if (!hits || hits.length === 0) continue;
62 + const distinct = [...new Set(hits.map((h) => h.cancerId))];
63 + if (distinct.length === 1) {
64 + const preferred = hits.some((h) => h.aliasType === 'preferred');
65 + return { cancerId: distinct[0]!, matchType: preferred ? 'ONTOLOGY_EXACT' : 'ALIAS', confidence: preferred ? 0.98 : 0.9, via: `alias "${c}"` };
66 + }
67 + // Ambiguous: prefer the cancer where this string is the preferred name.
68 + const pref = hits.filter((h) => h.aliasType === 'preferred');
69 + const prefDistinct = [...new Set(pref.map((h) => h.cancerId))];
70 + if (prefDistinct.length === 1) return { cancerId: prefDistinct[0]!, matchType: 'ONTOLOGY_EXACT', confidence: 0.9, via: `preferred name "${c}" (alias also used by ${distinct.length - 1} other concept(s))` };
71 + return null;
72 + }
73 + return null;
74 + }
75 +
76 + /** Try identifiers first, then labels (CLAUDE.md §143, §221). */
77 + resolve(input: { codes?: Array<{ system: string; code: string }>; labels?: string[]; allowMeshInversion?: boolean }): CancerMatch | null {
78 + for (const c of input.codes ?? []) {
79 + const hit = this.byCode(c.system, c.code);
80 + if (hit) return hit;
81 + }
82 + for (const l of input.labels ?? []) {
83 + const hit = this.byLabel(l, { allowMeshInversion: input.allowMeshInversion });
84 + if (hit) return hit;
85 + }
86 + return null;
87 + }
88 +
89 + /** Fuzzy suggestion for the curation queue only (never auto-accepted). */
90 + async suggest(label: string): Promise<{ cancerId: string; name: string; score: number } | null> {
91 + const norm = normalizeLabel(label);
92 + if (norm.length < 4) return null;
93 + const rows = await this.db.execute<{ cancer_id: string; canonical_name: string; score: number }>(sql`
94 + SELECT a.cancer_id, c.canonical_name, similarity(a.normalized, ${norm}) AS score
95 + FROM cancer_aliases a JOIN cancers c ON c.id = a.cancer_id
96 + WHERE c.status = 'active' AND a.normalized % ${norm}
97 + ORDER BY score DESC LIMIT 1`);
98 + const r = rows[0];
99 + return r && r.score >= 0.6 ? { cancerId: r.cancer_id, name: r.canonical_name, score: Number(r.score) } : null;
100 + }
101 +}
102 +
103 +export async function loadCancerIdBySlug(db: Database, slug: string): Promise<string | null> {
104 + const [row] = await db.select({ id: cancers.id }).from(cancers).where(and(eq(cancers.slug, slug), eq(cancers.status, 'active'))).limit(1);
105 + return row?.id ?? null;
106 +}
added packages/ontology/src/top-level.ts +75 −0
@@ -0,0 +1,75 @@
1 +/**
2 + * Explicitly curated metadata (CLAUDE.md §8 allows curated metadata; §246-247 require a
3 + * mutually exclusive top-level set for the global master ranking).
4 + *
5 + * The top-level set follows the GLOBOCAN / ICD-10 site groups so that registry burden estimates
6 + * can be attached without double counting. Each entry maps a site group to its NCIt anchor concept
7 + * (codes verified against the EVS REST API on 2026-09-08, NCIt 26.08e) and to the ICD-10 range
8 + * used by registries. Codes are recorded on `cancer_codes` with match type CURATED_EXACT.
9 + *
10 + * This list is *not* the taxonomy — it is a ranking scope definition (entity_level = "top").
11 + */
12 +export interface TopLevelCancer {
13 + key: string;
14 + name: string; // display name (registry phrasing); the canonical NCIt name is kept on the entity
15 + ncit: string;
16 + icd10: string[]; // ICD-10 code ranges, e.g. "C33-C34"
17 + hematologic?: boolean;
18 + globocanCode?: number; // GLOBOCAN cancer code (reference only; ingestion is gated by license review)
19 + notes?: string;
20 +}
21 +
22 +export const TOP_LEVEL_CANCERS: TopLevelCancer[] = [
23 + { key: 'lip-oral-cavity', name: 'Lip and Oral Cavity Cancer', ncit: 'C9316', icd10: ['C00-C06'], globocanCode: 1 },
24 + { key: 'salivary-gland', name: 'Salivary Gland Cancer', ncit: 'C3811', icd10: ['C07-C08'], globocanCode: 2 },
25 + { key: 'oropharynx', name: 'Oropharyngeal Cancer', ncit: 'C7398', icd10: ['C09-C10'], globocanCode: 3 },
26 + { key: 'nasopharynx', name: 'Nasopharyngeal Cancer', ncit: 'C9321', icd10: ['C11'], globocanCode: 4 },
27 + { key: 'hypopharynx', name: 'Hypopharyngeal Cancer', ncit: 'C7190', icd10: ['C12-C13'], globocanCode: 5 },
28 + { key: 'esophagus', name: 'Esophageal Cancer', ncit: 'C7478', icd10: ['C15'], globocanCode: 6 },
29 + { key: 'stomach', name: 'Stomach Cancer', ncit: 'C9331', icd10: ['C16'], globocanCode: 7 },
30 + { key: 'colon', name: 'Colon Cancer', ncit: 'C9242', icd10: ['C18'], globocanCode: 8 },
31 + { key: 'rectum', name: 'Rectal Cancer', ncit: 'C7418', icd10: ['C19-C20'], globocanCode: 9 },
32 + { key: 'anus', name: 'Anal Cancer', ncit: 'C7379', icd10: ['C21'], globocanCode: 10 },
33 + { key: 'liver', name: 'Liver Cancer', ncit: 'C190593', icd10: ['C22'], globocanCode: 11, notes: 'ICD-10 C22 includes intrahepatic bile ducts' },
34 + { key: 'gallbladder', name: 'Gallbladder Cancer', ncit: 'C7481', icd10: ['C23-C24'], globocanCode: 12, notes: 'ICD-10 C23-C24 includes extrahepatic biliary tract; NCIt anchor is gallbladder' },
35 + { key: 'pancreas', name: 'Pancreatic Cancer', ncit: 'C9005', icd10: ['C25'], globocanCode: 13 },
36 + { key: 'larynx', name: 'Laryngeal Cancer', ncit: 'C7484', icd10: ['C32'], globocanCode: 14 },
37 + { key: 'lung', name: 'Lung Cancer', ncit: 'C7377', icd10: ['C33-C34'], globocanCode: 15 },
38 + { key: 'melanoma-skin', name: 'Melanoma of Skin', ncit: 'C3510', icd10: ['C43'], globocanCode: 16 },
39 + { key: 'non-melanoma-skin', name: 'Non-Melanoma Skin Cancer', ncit: 'C4914', icd10: ['C44'], globocanCode: 17, notes: 'registries differ on inclusion (CLAUDE.md §210)' },
40 + { key: 'mesothelioma', name: 'Mesothelioma', ncit: 'C4456', icd10: ['C45'], globocanCode: 18 },
41 + { key: 'kaposi-sarcoma', name: 'Kaposi Sarcoma', ncit: 'C9087', icd10: ['C46'], globocanCode: 19 },
42 + { key: 'breast', name: 'Breast Cancer', ncit: 'C9335', icd10: ['C50'], globocanCode: 20 },
43 + { key: 'vulva', name: 'Vulvar Cancer', ncit: 'C7502', icd10: ['C51'], globocanCode: 21 },
44 + { key: 'vagina', name: 'Vaginal Cancer', ncit: 'C7410', icd10: ['C52'], globocanCode: 22 },
45 + { key: 'cervix', name: 'Cervical Cancer', ncit: 'C9311', icd10: ['C53'], globocanCode: 23 },
46 + { key: 'corpus-uteri', name: 'Uterine Corpus Cancer', ncit: 'C3556', icd10: ['C54'], globocanCode: 24 },
47 + { key: 'ovary', name: 'Ovarian Cancer', ncit: 'C7431', icd10: ['C56'], globocanCode: 25 },
48 + { key: 'penis', name: 'Penile Cancer', ncit: 'C7547', icd10: ['C60'], globocanCode: 26 },
49 + { key: 'prostate', name: 'Prostate Cancer', ncit: 'C7378', icd10: ['C61'], globocanCode: 27 },
50 + { key: 'testis', name: 'Testicular Cancer', ncit: 'C7251', icd10: ['C62'], globocanCode: 28 },
51 + { key: 'kidney', name: 'Kidney Cancer', ncit: 'C7548', icd10: ['C64-C65'], globocanCode: 29 },
52 + { key: 'bladder', name: 'Bladder Cancer', ncit: 'C9334', icd10: ['C67'], globocanCode: 30 },
53 + { key: 'brain-cns', name: 'Brain and Central Nervous System Cancer', ncit: 'C4627', icd10: ['C70-C72'], globocanCode: 31 },
54 + { key: 'thyroid', name: 'Thyroid Cancer', ncit: 'C7510', icd10: ['C73'], globocanCode: 32 },
55 + { key: 'hodgkin-lymphoma', name: 'Hodgkin Lymphoma', ncit: 'C9357', icd10: ['C81'], hematologic: true, globocanCode: 33 },
56 + { key: 'non-hodgkin-lymphoma', name: 'Non-Hodgkin Lymphoma', ncit: 'C3211', icd10: ['C82-C86', 'C96'], hematologic: true, globocanCode: 34 },
57 + { key: 'multiple-myeloma', name: 'Multiple Myeloma', ncit: 'C3242', icd10: ['C88', 'C90'], hematologic: true, globocanCode: 35 },
58 + { key: 'leukemia', name: 'Leukemia', ncit: 'C3161', icd10: ['C91-C95'], hematologic: true, globocanCode: 36 },
59 +];
60 +
61 +export const TOP_LEVEL_BY_NCIT = new Map(TOP_LEVEL_CANCERS.map((t) => [t.ncit, t]));
62 +
63 +/** NCIt roots used to build the multi-dimensional hierarchy (verified 2026-09-08). */
64 +export const NCIT_ROOTS = {
65 + neoplasm: 'C3262',
66 + malignantNeoplasm: 'C9305',
67 + neoplasmBySite: 'C3263',
68 + neoplasmByMorphology: 'C4741',
69 + carcinoma: 'C2916',
70 + malignantSolidNeoplasm: 'C132146',
71 + hematopoieticLymphoidCellNeoplasm: 'C27134',
72 + hematopoieticLymphaticSystemNeoplasm: 'C35813',
73 + childhoodMalignantNeoplasm: 'C4005',
74 + nervousSystemNeoplasm: 'C3268',
75 +} as const;
added packages/ontology/test/qualifiers.test.ts +55 −0
@@ -0,0 +1,55 @@
1 +import { describe, expect, it } from 'vitest';
2 +import { isQualifiedState, baseDiseaseLabel } from '../src/qualifiers.js';
3 +
4 +describe('isQualifiedState', () => {
5 + it('flags stage / recurrence / resectability concepts', () => {
6 + for (const n of [
7 + 'Stage IV Pancreatic Cancer AJCC v8',
8 + 'Stage IIIA Lung Cancer AJCC v7',
9 + 'Recurrent Glioblastoma',
10 + 'Refractory Acute Myeloid Leukemia',
11 + 'Relapsed Multiple Myeloma',
12 + 'Unresectable Hepatocellular Carcinoma',
13 + 'Locally Advanced Rectal Carcinoma',
14 + 'Metastatic Breast Carcinoma',
15 + 'Newly Diagnosed Glioblastoma',
16 + 'Pancreatic Adenocarcinoma Stage II',
17 + 'Bilateral Breast Carcinoma',
18 + 'Recurrent Childhood Acute Lymphoblastic Leukemia',
19 + ])
20 + expect(isQualifiedState(n), n).toBe(true);
21 + });
22 + it('keeps recognized diseases whose names start with a qualifier-like word', () => {
23 + for (const n of [
24 + 'Anaplastic Astrocytoma',
25 + 'Anaplastic Large Cell Lymphoma',
26 + 'Undifferentiated Pleomorphic Sarcoma',
27 + 'Invasive Breast Carcinoma',
28 + 'Childhood Acute Lymphoblastic Leukemia',
29 + 'Adult T-Cell Leukemia/Lymphoma',
30 + 'Primary Myelofibrosis',
31 + 'Primary Central Nervous System Lymphoma',
32 + 'Therapy-Related Acute Myeloid Leukemia',
33 + 'Triple-Negative Breast Carcinoma',
34 + 'EGFR-Mutant Lung Adenocarcinoma',
35 + 'KRAS G12C-Mutant Non-Small Cell Lung Carcinoma',
36 + 'High Grade Serous Ovarian Carcinoma',
37 + 'Low Grade Glioma',
38 + 'Castration-Resistant Prostate Carcinoma',
39 + 'Pancreatic Ductal Adenocarcinoma',
40 + 'Glioblastoma',
41 + 'Diffuse Large B-Cell Lymphoma',
42 + 'Acute Myeloid Leukemia with NPM1 Mutation',
43 + 'Secondary Acute Myeloid Leukemia',
44 + ])
45 + expect(isQualifiedState(n), n).toBe(false);
46 + });
47 +});
48 +
49 +describe('baseDiseaseLabel', () => {
50 + it('strips stage and recurrence qualifiers', () => {
51 + expect(baseDiseaseLabel('Stage IV Pancreatic Cancer AJCC v8')).toBe('Pancreatic Cancer');
52 + expect(baseDiseaseLabel('Recurrent Metastatic Breast Carcinoma')).toBe('Breast Carcinoma');
53 + expect(baseDiseaseLabel('Glioblastoma')).toBe('Glioblastoma');
54 + });
55 +});
added packages/ontology/tsconfig.json +8 −0
@@ -0,0 +1,8 @@
1 +{
2 + "extends": "../../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": ".",
5 + "noEmit": true
6 + },
7 + "include": ["src", "test"]
8 +}
added packages/ontology/vitest.config.ts +3 −0
@@ -0,0 +1,3 @@
1 +import { defineConfig } from 'vitest/config';
2 +
3 +export default defineConfig({ test: { include: ['src/**/*.test.ts', 'test/**/*.test.ts'] } });
added packages/ranking/package.json +28 −0
@@ -0,0 +1,28 @@
1 +{
2 + "name": "@cancerindex/ranking",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "exports": {
7 + ".": {
8 + "types": "./src/index.ts",
9 + "default": "./src/index.ts"
10 + }
11 + },
12 + "scripts": {
13 + "build": "tsc -p tsconfig.json --noEmit",
14 + "typecheck": "tsc -p tsconfig.json --noEmit",
15 + "test": "vitest run --passWithNoTests"
16 + },
17 + "dependencies": {
18 + "@cancerindex/database": "workspace:*",
19 + "@cancerindex/ontology": "workspace:*",
20 + "@cancerindex/shared": "workspace:*",
21 + "drizzle-orm": "^0.45.0"
22 + },
23 + "devDependencies": {
24 + "@types/node": "^24.0.0",
25 + "typescript": "^5.9.3",
26 + "vitest": "^3.2.0"
27 + }
28 +}
added packages/ranking/src/counters.ts +140 −0
@@ -0,0 +1,140 @@
1 +import { sql } from 'drizzle-orm';
2 +import type { Database } from '@cancerindex/database';
3 +
4 +const ACTIVE_STATUSES = ['RECRUITING', 'NOT_YET_RECRUITING', 'ENROLLING_BY_INVITATION', 'ACTIVE_NOT_RECRUITING'];
5 +
6 +/**
7 + * Recompute entity_counters for every active cancer from canonical relations (CLAUDE.md §287).
8 + * Trial / evidence / cohort counts aggregate over the NCIt-hierarchy descendants of each cancer
9 + * (a lung-cancer trial mapped to "Lung Adenocarcinoma" counts for "Lung Cancer" too).
10 + * Deterministic: a full rebuild in one SQL transaction.
11 + */
12 +export async function refreshCounters(db: Database): Promise<number> {
13 + await db.execute(sql`
14 + CREATE TEMP TABLE IF NOT EXISTS _desc (ancestor varchar(32), descendant varchar(32)) ON COMMIT DROP;
15 + `);
16 + return db.transaction(async (tx) => {
17 + await tx.execute(sql`CREATE TEMP TABLE _desc (ancestor varchar(32), descendant varchar(32)) ON COMMIT DROP`);
18 + await tx.execute(sql`
19 + INSERT INTO _desc
20 + WITH RECURSIVE d AS (
21 + SELECT id AS ancestor, id AS descendant, 0 AS depth FROM cancers WHERE status = 'active'
22 + UNION
23 + SELECT d.ancestor, h.child_id, d.depth + 1 FROM d JOIN cancer_hierarchy h ON h.parent_id = d.descendant
24 + WHERE d.depth < 12
25 + )
26 + SELECT DISTINCT ancestor, descendant FROM d
27 + `);
28 + await tx.execute(sql`CREATE INDEX ON _desc (descendant)`);
29 + await tx.execute(sql`CREATE INDEX ON _desc (ancestor)`);
30 + const rows = await tx.execute<{ n: string }>(sql`
31 + WITH trial_map AS (
32 + SELECT DISTINCT d.ancestor AS cancer_id, t.id AS trial_id, t.overall_status, t.phases, t.study_type
33 + FROM trial_conditions tc JOIN _desc d ON d.descendant = tc.cancer_id JOIN clinical_trials t ON t.id = tc.trial_id
34 + WHERE tc.cancer_id IS NOT NULL
35 + ),
36 + trials AS (
37 + SELECT cancer_id,
38 + count(*) AS trial_count,
39 + count(*) FILTER (WHERE study_type = 'INTERVENTIONAL' AND overall_status = ANY(${ACTIVE_STATUSES}::text[])) AS active_trial_count,
40 + count(*) FILTER (WHERE study_type = 'INTERVENTIONAL' AND overall_status = 'RECRUITING') AS recruiting_trial_count,
41 + count(*) FILTER (WHERE study_type = 'INTERVENTIONAL' AND overall_status = ANY(${ACTIVE_STATUSES}::text[]) AND 'PHASE3' = ANY(phases)) AS phase3_trial_count
42 + FROM trial_map GROUP BY cancer_id
43 + ),
44 + pubs AS (
45 + SELECT cancer_id,
46 + max(count) FILTER (WHERE window_key = 'all') AS publication_count,
47 + max(count) FILTER (WHERE window_key = '5y') AS publication_count_5y,
48 + max(count) FILTER (WHERE window_key = '12m') AS publication_count_12m
49 + FROM literature_counts GROUP BY cancer_id
50 + ),
51 + evid AS (
52 + SELECT d.ancestor AS cancer_id, count(DISTINCT e.civic_id) AS evidence_count,
53 + count(DISTINCT g) FILTER (WHERE g IS NOT NULL) AS civic_gene_count,
54 + count(DISTINCT v) FILTER (WHERE v IS NOT NULL) AS variant_count,
55 + count(DISTINCT t) FILTER (WHERE t IS NOT NULL) AS drug_count
56 + FROM civic_evidence_items e JOIN _desc d ON d.descendant = e.cancer_id
57 + LEFT JOIN LATERAL unnest(e.gene_ids) g ON true
58 + LEFT JOIN LATERAL unnest(e.variant_ids) v ON true
59 + LEFT JOIN LATERAL unnest(e.therapy_ids) t ON true
60 + WHERE e.status = 'ACCEPTED' GROUP BY d.ancestor
61 + ),
62 + cohorts AS (
63 + SELECT d.ancestor AS cancer_id, count(DISTINCT c.id) AS cohort_count,
64 + count(DISTINCT f.gene_symbol) FILTER (WHERE f.frequency >= 0.05 AND f.cases_affected >= 20) AS gdc_gene_count
65 + FROM genomic_cohorts c JOIN _desc d ON d.descendant = c.cancer_id
66 + LEFT JOIN cancer_gene_frequencies f ON f.cohort_id = c.id
67 + GROUP BY d.ancestor
68 + ),
69 + appr AS (
70 + SELECT d.ancestor AS cancer_id, count(DISTINCT a.drug_id) AS approved_drug_count
71 + FROM drug_approvals a JOIN _desc d ON d.descendant = a.cancer_id WHERE a.status IN ('approved','accelerated','conditional') GROUP BY d.ancestor
72 + ),
73 + epi AS (
74 + SELECT cancer_id, count(*) AS n FROM epidemiology_observations GROUP BY cancer_id
75 + ),
76 + surv AS (
77 + SELECT cancer_id, count(*) AS n FROM survival_observations GROUP BY cancer_id
78 + ),
79 + tree AS (
80 + SELECT ancestor AS cancer_id, count(*) - 1 AS descendant_count FROM _desc GROUP BY ancestor
81 + ),
82 + kids AS (
83 + SELECT parent_id AS cancer_id, count(DISTINCT child_id) AS subtype_count FROM cancer_hierarchy GROUP BY parent_id
84 + ),
85 + ins AS (
86 + INSERT INTO entity_counters (entity_type, entity_id, trial_count, active_trial_count, recruiting_trial_count, phase3_trial_count,
87 + publication_count, publication_count_5y, publication_count_12m, gene_count, variant_count, drug_count, approved_drug_count,
88 + evidence_count, cohort_count, subtype_count, descendant_count, epidemiology_obs_count, survival_obs_count, completeness, computed_at)
89 + SELECT 'cancer', c.id,
90 + COALESCE(t.trial_count,0), COALESCE(t.active_trial_count,0), COALESCE(t.recruiting_trial_count,0), COALESCE(t.phase3_trial_count,0),
91 + COALESCE(p.publication_count,0), COALESCE(p.publication_count_5y,0), COALESCE(p.publication_count_12m,0),
92 + GREATEST(COALESCE(e.civic_gene_count,0), COALESCE(g.gdc_gene_count,0)), COALESCE(e.variant_count,0), COALESCE(e.drug_count,0), COALESCE(a.approved_drug_count,0),
93 + COALESCE(e.evidence_count,0), COALESCE(g.cohort_count,0), COALESCE(k.subtype_count,0), COALESCE(tr.descendant_count,0), COALESCE(ep.n,0), COALESCE(sv.n,0),
94 + jsonb_build_object(
95 + 'epidemiology', CASE WHEN COALESCE(ep.n,0) > 0 THEN 1 ELSE 0 END,
96 + 'survival', CASE WHEN COALESCE(sv.n,0) > 0 THEN 1 ELSE 0 END,
97 + 'trials', CASE WHEN COALESCE(t.trial_count,0) > 0 THEN 1 ELSE 0 END,
98 + 'literature', CASE WHEN COALESCE(p.publication_count,0) > 0 THEN 1 ELSE 0 END,
99 + 'genomics', CASE WHEN COALESCE(g.cohort_count,0) > 0 THEN 1 ELSE 0 END,
100 + 'evidence', CASE WHEN COALESCE(e.evidence_count,0) > 0 THEN 1 ELSE 0 END,
101 + 'therapies', CASE WHEN COALESCE(a.approved_drug_count,0) > 0 THEN 1 ELSE 0 END
102 + ), now()
103 + FROM cancers c
104 + LEFT JOIN trials t ON t.cancer_id = c.id
105 + LEFT JOIN pubs p ON p.cancer_id = c.id
106 + LEFT JOIN evid e ON e.cancer_id = c.id
107 + LEFT JOIN cohorts g ON g.cancer_id = c.id
108 + LEFT JOIN appr a ON a.cancer_id = c.id
109 + LEFT JOIN epi ep ON ep.cancer_id = c.id
110 + LEFT JOIN surv sv ON sv.cancer_id = c.id
111 + LEFT JOIN tree tr ON tr.cancer_id = c.id
112 + LEFT JOIN kids k ON k.cancer_id = c.id
113 + WHERE c.status = 'active'
114 + ON CONFLICT (entity_type, entity_id) DO UPDATE SET
115 + trial_count = EXCLUDED.trial_count, active_trial_count = EXCLUDED.active_trial_count, recruiting_trial_count = EXCLUDED.recruiting_trial_count,
116 + phase3_trial_count = EXCLUDED.phase3_trial_count, publication_count = EXCLUDED.publication_count, publication_count_5y = EXCLUDED.publication_count_5y,
117 + publication_count_12m = EXCLUDED.publication_count_12m, gene_count = EXCLUDED.gene_count, variant_count = EXCLUDED.variant_count, drug_count = EXCLUDED.drug_count,
118 + approved_drug_count = EXCLUDED.approved_drug_count, evidence_count = EXCLUDED.evidence_count, cohort_count = EXCLUDED.cohort_count, subtype_count = EXCLUDED.subtype_count,
119 + descendant_count = EXCLUDED.descendant_count, epidemiology_obs_count = EXCLUDED.epidemiology_obs_count, survival_obs_count = EXCLUDED.survival_obs_count,
120 + completeness = EXCLUDED.completeness, computed_at = now()
121 + RETURNING 1
122 + )
123 + SELECT count(*)::text AS n FROM ins
124 + `);
125 + // Gene-level counters (cancers linked, evidence items) for gene pages.
126 + await tx.execute(sql`
127 + INSERT INTO entity_counters (entity_type, entity_id, evidence_count, variant_count, drug_count, trial_count, computed_at)
128 + SELECT 'gene', g.id,
129 + (SELECT count(*) FROM civic_evidence_items e WHERE e.status = 'ACCEPTED' AND g.id = ANY(e.gene_ids)),
130 + (SELECT count(*) FROM variants v WHERE v.gene_id = g.id),
131 + (SELECT count(DISTINCT t) FROM civic_evidence_items e, unnest(e.therapy_ids) t WHERE e.status = 'ACCEPTED' AND g.id = ANY(e.gene_ids)),
132 + 0, now()
133 + FROM genes g
134 + WHERE EXISTS (SELECT 1 FROM civic_evidence_items e WHERE g.id = ANY(e.gene_ids)) OR EXISTS (SELECT 1 FROM variants v WHERE v.gene_id = g.id) OR EXISTS (SELECT 1 FROM cancer_gene_frequencies f WHERE f.gene_id = g.id)
135 + ON CONFLICT (entity_type, entity_id) DO UPDATE SET evidence_count = EXCLUDED.evidence_count, variant_count = EXCLUDED.variant_count, drug_count = EXCLUDED.drug_count, computed_at = now()
136 + `);
137 + await tx.execute(sql`UPDATE genes g SET is_cancer_gene = EXISTS (SELECT 1 FROM civic_evidence_items e WHERE e.status = 'ACCEPTED' AND g.id = ANY(e.gene_ids)) OR EXISTS (SELECT 1 FROM cancer_gene_frequencies f WHERE f.gene_id = g.id AND f.frequency >= 0.05 AND f.cases_affected >= 20)`);
138 + return Number(rows[0]?.n ?? 0);
139 + });
140 +}
added packages/ranking/src/engine.ts +173 −0
@@ -0,0 +1,173 @@
1 +import { and, eq, sql } from 'drizzle-orm';
2 +import { type Database, metricDefinitions, rankingSnapshots, rankings, entityCounters, cancers } from '@cancerindex/database';
3 +import { rankEntities, inputsHash, percentiles, type RankInput } from './rank.js';
4 +
5 +export interface Scope {
6 + geography: string; // WORLD | ISO3 | slug
7 + sex: 'all' | 'male' | 'female';
8 + ageGroup: string;
9 + year: number | null;
10 + entityLevel: 'top' | 'all';
11 +}
12 +
13 +export function scopeKey(s: Scope): string {
14 + return `geo=${s.geography}|sex=${s.sex}|age=${s.ageGroup}|year=${s.year ?? 'latest'}|level=${s.entityLevel}`;
15 +}
16 +
17 +export interface RankingResult {
18 + metricSlug: string;
19 + scopeKey: string;
20 + eligible: number;
21 + snapshotId: number;
22 +}
23 +
24 +/**
25 + * Ranking engine (CLAUDE.md §30-35, §245-247, §288). Every snapshot stores scope, formula version,
26 + * an inputs hash and per-row lineage (`inputs`) so "Why #4?" and TRACE can be answered.
27 + *
28 + * Phase 1 ships count-based metrics (trials, literature, molecular) for both entity levels and the
29 + * burden/lethality/gap metrics only where epidemiology observations exist for the scope.
30 + */
31 +export async function computeAllRankings(db: Database): Promise<RankingResult[]> {
32 + const results: RankingResult[] = [];
33 + const defs = await db.select().from(metricDefinitions);
34 + const byslug = new Map(defs.map((d) => [d.slug, d]));
35 +
36 + const countMetrics: Array<[string, keyof typeof entityCounters.$inferSelect]> = [
37 + ['active_trials', 'activeTrialCount'],
38 + ['recruiting_trials', 'recruitingTrialCount'],
39 + ['phase3_trials', 'phase3TrialCount'],
40 + ['publications_5y', 'publicationCount5y'],
41 + ['publications_12m', 'publicationCount12m'],
42 + ['curated_evidence_items', 'evidenceCount'],
43 + ['associated_genes', 'geneCount'],
44 + ['genomic_cohorts', 'cohortCount'],
45 + ];
46 +
47 + for (const level of ['top', 'all'] as const) {
48 + const scope: Scope = { geography: 'WORLD', sex: 'all', ageGroup: 'all', year: null, entityLevel: level };
49 + const counters = await db
50 + .select({ id: cancers.id, c: entityCounters })
51 + .from(cancers)
52 + .innerJoin(entityCounters, and(eq(entityCounters.entityType, 'cancer'), eq(entityCounters.entityId, cancers.id)))
53 + .where(level === 'top' ? and(eq(cancers.status, 'active'), eq(cancers.topLevel, true)) : and(eq(cancers.status, 'active'), eq(cancers.malignant, true)));
54 + for (const [slug, field] of countMetrics) {
55 + const def = byslug.get(slug);
56 + if (!def) continue;
57 + const items: RankInput[] = counters
58 + .map((r) => ({ id: r.id, value: Number(r.c[field] ?? 0), inputs: { counter: field, computedAt: r.c.computedAt } }))
59 + .filter((i) => (slug.startsWith('publications') ? i.value > 0 : true)); // publications: only cancers with a stored PubMed query
60 + if (items.length === 0) continue;
61 + const snap = await persistSnapshot(db, def, scope, items, { descending: true, sourceIds: def.sourceSlugs });
62 + results.push(snap);
63 + }
64 + // Publication growth (derived from two windows).
65 + const growthDef = byslug.get('publication_growth');
66 + if (growthDef) {
67 + const rows = await db.execute<{ cancer_id: string; m12: number; y5: number }>(sql`
68 + SELECT cancer_id, max(count) FILTER (WHERE window_key = '12m') AS m12, max(count) FILTER (WHERE window_key = '5y_prior') AS y5
69 + FROM literature_counts GROUP BY cancer_id`);
70 + const ids = new Set(counters.map((c) => c.id));
71 + const items: RankInput[] = rows
72 + .filter((r) => ids.has(r.cancer_id) && r.y5 != null && r.m12 != null && Number(r.y5) >= 50)
73 + .map((r) => ({ id: r.cancer_id, value: Number(r.m12) / (Number(r.y5) / 5), inputs: { publications_12m: Number(r.m12), publications_5y_prior: Number(r.y5), formula: growthDef.formula } }));
74 + if (items.length) results.push(await persistSnapshot(db, growthDef, scope, items, { descending: true, sourceIds: ['pubmed'] }));
75 + }
76 + }
77 +
78 + // Burden / lethality / gaps per geography-year where observations exist (top level only, §246-247).
79 + const scopes = await db.execute<{ geography_id: string; slug: string; iso3: string | null; year: number; sex: string; source_id: string; n: number }>(sql`
80 + SELECT o.geography_id, g.slug, g.iso3, o.year, o.sex, o.source_id, count(*) AS n
81 + FROM epidemiology_observations o JOIN geographies g ON g.id = o.geography_id JOIN cancers c ON c.id = o.cancer_id
82 + WHERE c.top_level AND o.age_group = 'all' AND o.metric IN ('incidence_count','mortality_count','as_incidence_rate','as_mortality_rate')
83 + GROUP BY o.geography_id, g.slug, g.iso3, o.year, o.sex, o.source_id HAVING count(*) >= 10`);
84 + for (const s of scopes) {
85 + const scope: Scope = { geography: s.iso3 ?? s.slug.toUpperCase(), sex: s.sex as Scope['sex'], ageGroup: 'all', year: Number(s.year), entityLevel: 'top' };
86 + const obs = await db.execute<{ cancer_id: string; metric: string; value: number; id: number; estimate_type: string; site_definition: string | null }>(sql`
87 + SELECT o.cancer_id, o.metric, o.value, o.id, o.estimate_type, o.site_definition FROM epidemiology_observations o JOIN cancers c ON c.id = o.cancer_id
88 + WHERE c.top_level AND o.geography_id = ${s.geography_id} AND o.year = ${s.year} AND o.sex = ${s.sex} AND o.age_group = 'all' AND o.source_id = ${s.source_id}`);
89 + const byMetric = new Map<string, Map<string, { value: number; id: number; estimate_type: string; site: string | null }>>();
90 + for (const o of obs) {
91 + if (!byMetric.has(o.metric)) byMetric.set(o.metric, new Map());
92 + byMetric.get(o.metric)!.set(o.cancer_id, { value: Number(o.value), id: o.id, estimate_type: o.estimate_type, site: o.site_definition });
93 + }
94 + for (const metric of ['incidence_count', 'mortality_count', 'as_incidence_rate', 'as_mortality_rate']) {
95 + const def = byslug.get(metric);
96 + const m = byMetric.get(metric);
97 + if (!def || !m) continue;
98 + const items: RankInput[] = [...m.entries()].map(([id, v]) => ({ id, value: v.value, confidence: v.estimate_type === 'observed' ? 'HIGH' : 'MEDIUM', inputs: { observationId: v.id, estimateType: v.estimate_type, siteDefinition: v.site, sourceId: s.source_id } }));
99 + results.push(await persistSnapshot(db, def, scope, items, { descending: true, sourceIds: [s.source_id] }));
100 + }
101 + // Mortality-to-incidence ratio
102 + const mirDef = byslug.get('mortality_incidence_ratio');
103 + const inc = byMetric.get('incidence_count');
104 + const mort = byMetric.get('mortality_count');
105 + if (mirDef && inc && mort) {
106 + const items: RankInput[] = [];
107 + for (const [id, i] of inc) {
108 + const d = mort.get(id);
109 + if (!d || i.value < 100) continue;
110 + items.push({ id, value: d.value / i.value, confidence: i.value >= 1000 ? 'HIGH' : 'MEDIUM', inputs: { incidenceObservationId: i.id, mortalityObservationId: d.id, incidence: i.value, deaths: d.value, formula: mirDef.formula } });
111 + }
112 + if (items.length) results.push(await persistSnapshot(db, mirDef, scope, items, { descending: true, sourceIds: [s.source_id] }));
113 + }
114 + // Gap indexes: burden percentile − activity percentile
115 + if (mort) {
116 + const burdenPct = percentiles([...mort.entries()].map(([id, v]) => ({ id, value: v.value })));
117 + const counters = await db.select({ id: entityCounters.entityId, trials: entityCounters.activeTrialCount, pubs: entityCounters.publicationCount5y }).from(entityCounters).where(eq(entityCounters.entityType, 'cancer'));
118 + const cmap = new Map(counters.map((c) => [c.id, c]));
119 + for (const [slug, field] of [
120 + ['trial_gap', 'trials'],
121 + ['research_gap', 'pubs'],
122 + ] as const) {
123 + const def = byslug.get(slug);
124 + if (!def) continue;
125 + const ids = [...mort.keys()].filter((id) => cmap.has(id) && (field === 'trials' || (cmap.get(id)!.pubs ?? 0) > 0));
126 + if (ids.length < 5) continue;
127 + const actPct = percentiles(ids.map((id) => ({ id, value: Number(cmap.get(id)![field] ?? 0) })));
128 + const items: RankInput[] = ids.map((id) => ({ id, value: (burdenPct.get(id) ?? 0) - (actPct.get(id) ?? 0), inputs: { burdenPercentile: burdenPct.get(id), activityPercentile: actPct.get(id), deaths: mort.get(id)!.value, activity: Number(cmap.get(id)![field] ?? 0), formula: def.formula } }));
129 + results.push(await persistSnapshot(db, def, scope, items, { descending: true, sourceIds: [s.source_id, ...def.sourceSlugs] }));
130 + }
131 + }
132 + }
133 + return results;
134 +}
135 +
136 +async function persistSnapshot(db: Database, def: typeof metricDefinitions.$inferSelect, scope: Scope, items: RankInput[], opts: { descending: boolean; sourceIds: string[] }): Promise<RankingResult> {
137 + const key = scopeKey(scope);
138 + const ranked = rankEntities(items, { descending: def.higherIsWorse === false ? opts.descending : opts.descending });
139 + const hash = await inputsHash(items);
140 + return db.transaction(async (tx) => {
141 + // Previous rank for change explanation (§311)
142 + const prev = await tx.execute<{ cancer_id: string; rank: number }>(sql`
143 + SELECT r.cancer_id, r.rank FROM rankings r JOIN ranking_snapshots s ON s.id = r.snapshot_id
144 + WHERE s.metric_slug = ${def.slug} AND s.scope_key = ${key} AND s.is_current`);
145 + const prevMap = new Map(prev.map((p) => [p.cancer_id, Number(p.rank)]));
146 + await tx.update(rankingSnapshots).set({ isCurrent: false }).where(and(eq(rankingSnapshots.metricSlug, def.slug), eq(rankingSnapshots.scopeKey, key), eq(rankingSnapshots.isCurrent, true)));
147 + const [snap] = await tx
148 + .insert(rankingSnapshots)
149 + .values({ metricId: def.id, metricSlug: def.slug, scopeKey: key, geography: scope.geography, sex: scope.sex, ageGroup: scope.ageGroup, year: scope.year, entityLevel: scope.entityLevel, formulaVersion: def.formulaVersion, eligibleEntities: ranked.length, inputsHash: hash, sourceIds: opts.sourceIds, isCurrent: true })
150 + .returning({ id: rankingSnapshots.id });
151 + const snapshotId = snap!.id;
152 + const chunk = 500;
153 + for (let i = 0; i < ranked.length; i += chunk) {
154 + await tx.insert(rankings).values(
155 + ranked.slice(i, i + chunk).map((r) => ({
156 + snapshotId,
157 + metricSlug: def.slug,
158 + scopeKey: key,
159 + cancerId: r.id,
160 + rank: r.rank,
161 + eligibleEntities: r.eligible,
162 + percentile: r.percentile,
163 + value: r.value,
164 + unit: def.unit,
165 + confidence: r.confidence ?? 'MEDIUM',
166 + inputs: r.inputs ?? {},
167 + previousRank: prevMap.get(r.id) ?? null,
168 + })),
169 + );
170 + }
171 + return { metricSlug: def.slug, scopeKey: key, eligible: ranked.length, snapshotId };
172 + });
173 +}
added packages/ranking/src/index.ts +4 −0
@@ -0,0 +1,4 @@
1 +export * from './rank.js';
2 +export * from './counters.js';
3 +export * from './engine.js';
4 +export * from './trace.js';
added packages/ranking/src/rank.ts +50 −0
@@ -0,0 +1,50 @@
1 +/**
2 + * Pure ranking primitives (CLAUDE.md §155: deterministic given fixed inputs).
3 + */
4 +export interface RankInput {
5 + id: string;
6 + value: number;
7 + /** Optional confidence label carried through to the output. */
8 + confidence?: 'HIGH' | 'MEDIUM' | 'LOW' | 'INSUFFICIENT_DATA';
9 + inputs?: Record<string, unknown>;
10 +}
11 +
12 +export interface RankOutput extends RankInput {
13 + rank: number;
14 + percentile: number; // 0..100, 100 = most extreme in the ranking direction
15 + eligible: number;
16 +}
17 +
18 +/**
19 + * Rank entities. `higherIsWorse === null` means "higher is first" (descending) by convention.
20 + * Ties share the same rank (competition ranking: 1,2,2,4). Percentile = share of eligible entities
21 + * ranked at or below this entity in the ranking direction.
22 + */
23 +export function rankEntities(items: RankInput[], opts: { descending?: boolean } = {}): RankOutput[] {
24 + const desc = opts.descending ?? true;
25 + const valid = items.filter((i) => Number.isFinite(i.value));
26 + const sorted = [...valid].sort((a, b) => (desc ? b.value - a.value : a.value - b.value) || a.id.localeCompare(b.id));
27 + const n = sorted.length;
28 + const out: RankOutput[] = [];
29 + let rank = 0;
30 + for (let i = 0; i < n; i++) {
31 + const cur = sorted[i]!;
32 + if (i === 0 || cur.value !== sorted[i - 1]!.value) rank = i + 1;
33 + out.push({ ...cur, rank, eligible: n, percentile: n === 1 ? 100 : Math.round(((n - rank) / (n - 1)) * 1000) / 10 });
34 + }
35 + return out;
36 +}
37 +
38 +/** Percentile of each entity for a metric (0..100, higher value → higher percentile). */
39 +export function percentiles(items: RankInput[]): Map<string, number> {
40 + const ranked = rankEntities(items, { descending: true });
41 + return new Map(ranked.map((r) => [r.id, r.percentile]));
42 +}
43 +
44 +/** Stable hash of ranking inputs so a snapshot is reproducible/auditable. */
45 +export async function inputsHash(items: RankInput[]): Promise<string> {
46 + const { createHash } = await import('node:crypto');
47 + const h = createHash('sha256');
48 + for (const i of [...items].sort((a, b) => a.id.localeCompare(b.id))) h.update(`${i.id}=${i.value};`);
49 + return h.digest('hex').slice(0, 24);
50 +}
added packages/ranking/src/trace.ts +51 −0
@@ -0,0 +1,51 @@
1 +import { sql } from 'drizzle-orm';
2 +import type { Database } from '@cancerindex/database';
3 +
4 +/**
5 + * Lineage trace (CLAUDE.md §252-253): ranked value → inputs → observation → provenance → raw record.
6 + * Supported: rankings/<id>, epidemiology_observations/<id>, cancer_gene_frequencies/<id>, literature_counts/<id>.
7 + */
8 +export async function traceValue(db: Database, table: string, id: string): Promise<Record<string, unknown>> {
9 + const out: Record<string, unknown> = { table, id };
10 + const prov = async (pid: number | null | undefined) => {
11 + if (!pid) return null;
12 + const [p] = await db.execute<Record<string, unknown>>(sql`SELECT p.*, s.name AS source_name, s.slug AS source_slug, s.license FROM provenance p JOIN sources s ON s.id = p.source_id WHERE p.id = ${pid}`);
13 + if (!p) return null;
14 + const [rec] = await db.execute<Record<string, unknown>>(sql`SELECT id, entity_kind, source_record_id, raw_path, payload_hash, retrieved_at, status FROM source_records WHERE source_id = ${p.source_id as string} AND source_record_id = ${(p.source_record_id as string) ?? ''} LIMIT 1`);
15 + return { provenance: p, sourceRecord: rec ?? null };
16 + };
17 + switch (table) {
18 + case 'rankings': {
19 + const [r] = await db.execute<Record<string, unknown>>(sql`SELECT r.*, s.formula_version, s.inputs_hash, s.generated_at AS snapshot_generated_at, m.formula, m.name AS metric_name FROM rankings r JOIN ranking_snapshots s ON s.id = r.snapshot_id JOIN metric_definitions m ON m.slug = r.metric_slug WHERE r.id = ${Number(id)}`);
20 + if (!r) return { ...out, error: 'not found' };
21 + out.ranking = r;
22 + const inputs = (r.inputs ?? {}) as Record<string, unknown>;
23 + for (const k of ['observationId', 'incidenceObservationId', 'mortalityObservationId']) {
24 + if (inputs[k]) out[k] = await traceValue(db, 'epidemiology_observations', String(inputs[k]));
25 + }
26 + return out;
27 + }
28 + case 'epidemiology_observations': {
29 + const [o] = await db.execute<Record<string, unknown>>(sql`SELECT * FROM epidemiology_observations WHERE id = ${Number(id)}`);
30 + if (!o) return { ...out, error: 'not found' };
31 + return { ...out, observation: o, lineage: await prov(o.provenance_id as number) };
32 + }
33 + case 'cancer_gene_frequencies': {
34 + const [o] = await db.execute<Record<string, unknown>>(sql`SELECT * FROM cancer_gene_frequencies WHERE id = ${Number(id)}`);
35 + if (!o) return { ...out, error: 'not found' };
36 + return { ...out, frequency: o, lineage: await prov(o.provenance_id as number) };
37 + }
38 + case 'literature_counts': {
39 + const [o] = await db.execute<Record<string, unknown>>(sql`SELECT * FROM literature_counts WHERE id = ${Number(id)}`);
40 + if (!o) return { ...out, error: 'not found' };
41 + return { ...out, literatureCount: o, lineage: await prov(o.provenance_id as number) };
42 + }
43 + case 'survival_observations': {
44 + const [o] = await db.execute<Record<string, unknown>>(sql`SELECT * FROM survival_observations WHERE id = ${Number(id)}`);
45 + if (!o) return { ...out, error: 'not found' };
46 + return { ...out, observation: o, lineage: await prov(o.provenance_id as number) };
47 + }
48 + default:
49 + return { ...out, error: 'unsupported table' };
50 + }
51 +}
added packages/ranking/test/rank.test.ts +33 −0
@@ -0,0 +1,33 @@
1 +import { describe, expect, it } from 'vitest';
2 +import { rankEntities, percentiles, inputsHash } from '../src/rank.js';
3 +
4 +describe('rankEntities', () => {
5 + it('is deterministic and handles ties with competition ranking', () => {
6 + const items = [
7 + { id: 'b', value: 10 },
8 + { id: 'a', value: 10 },
9 + { id: 'c', value: 5 },
10 + { id: 'd', value: 1 },
11 + ];
12 + const r = rankEntities(items);
13 + expect(r.map((x) => [x.id, x.rank])).toEqual([
14 + ['a', 1],
15 + ['b', 1],
16 + ['c', 3],
17 + ['d', 4],
18 + ]);
19 + expect(r[0]!.percentile).toBe(100);
20 + expect(r[3]!.percentile).toBe(0);
21 + expect(rankEntities([...items].reverse())).toEqual(r);
22 + });
23 + it('drops non-finite values and ranks ascending when requested', () => {
24 + const r = rankEntities([{ id: 'x', value: NaN }, { id: 'y', value: 2 }, { id: 'z', value: 1 }], { descending: false });
25 + expect(r.map((x) => x.id)).toEqual(['z', 'y']);
26 + expect(r[0]!.eligible).toBe(2);
27 + });
28 + it('percentiles and hash are stable', async () => {
29 + const items = [{ id: 'a', value: 3 }, { id: 'b', value: 1 }];
30 + expect(percentiles(items).get('a')).toBe(100);
31 + expect(await inputsHash(items)).toBe(await inputsHash([...items].reverse()));
32 + });
33 +});
added packages/ranking/tsconfig.json +8 −0
@@ -0,0 +1,8 @@
1 +{
2 + "extends": "../../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": ".",
5 + "noEmit": true
6 + },
7 + "include": ["src", "test"]
8 +}
added packages/ranking/vitest.config.ts +3 −0
@@ -0,0 +1,3 @@
1 +import { defineConfig } from 'vitest/config';
2 +
3 +export default defineConfig({ test: { include: ['src/**/*.test.ts', 'test/**/*.test.ts'] } });
added packages/shared/package.json +26 −0
@@ -0,0 +1,26 @@
1 +{
2 + "name": "@cancerindex/shared",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "exports": {
7 + ".": {
8 + "types": "./src/index.ts",
9 + "default": "./src/index.ts"
10 + }
11 + },
12 + "scripts": {
13 + "build": "tsc -p tsconfig.json --noEmit",
14 + "typecheck": "tsc -p tsconfig.json --noEmit",
15 + "test": "vitest run --passWithNoTests"
16 + },
17 + "dependencies": {
18 + "pino": "^9.7.0",
19 + "zod": "^4.0.0"
20 + },
21 + "devDependencies": {
22 + "@types/node": "^24.0.0",
23 + "typescript": "^5.9.3",
24 + "vitest": "^3.2.0"
25 + }
26 +}
added packages/shared/src/env.ts +16 −0
@@ -0,0 +1,16 @@
1 +import path from 'node:path';
2 +
3 +export function dataDir(): string {
4 + return path.resolve(process.env.CI_DATA_DIR ?? path.join(process.cwd(), 'data'));
5 +}
6 +
7 +export function isProduction(): boolean {
8 + return process.env.NODE_ENV === 'production';
9 +}
10 +
11 +export function envInt(name: string, fallback: number): number {
12 + const v = process.env[name];
13 + if (!v) return fallback;
14 + const n = Number(v);
15 + return Number.isFinite(n) ? n : fallback;
16 +}
added packages/shared/src/ids.ts +70 −0
@@ -0,0 +1,70 @@
1 +/**
2 + * CancerIndex stable public identifiers (CLAUDE.md §6).
3 + *
4 + * CI-CAN-00000001 cancer entity
5 + * CI-GENE-00000001 gene
6 + * CI-VAR-00000001 variant
7 + * CI-DRUG-00000001 drug
8 + * CI-TRIAL-00000001 clinical trial
9 + * CI-PUB-00000001 publication
10 + * CI-BIO-00000001 biomarker
11 + * CI-STUDY-00000001 genomic study / cohort
12 + * CI-METRIC-00000001 metric definition
13 + * CI-SOURCE-00000001 source
14 + * CI-ORG-00000001 organization
15 + * CI-TRT-00000001 treatment / regimen
16 + * CI-ANAT-00000001 anatomical site
17 + * CI-GEO-00000001 geography
18 + * CI-PROV-00000001 provenance record
19 + *
20 + * IDs are minted from a database sequence per namespace (see @cancerindex/database `mintId`) and
21 + * are never reused. Database auto-increment integers are never exposed publicly.
22 + */
23 +export const ID_NAMESPACES = [
24 + 'CAN',
25 + 'GENE',
26 + 'VAR',
27 + 'DRUG',
28 + 'TRIAL',
29 + 'PUB',
30 + 'BIO',
31 + 'STUDY',
32 + 'METRIC',
33 + 'SOURCE',
34 + 'ORG',
35 + 'TRT',
36 + 'ANAT',
37 + 'GEO',
38 + 'PROV',
39 + 'EDGE',
40 + 'RANK',
41 +] as const;
42 +export type IdNamespace = (typeof ID_NAMESPACES)[number];
43 +
44 +export const ID_WIDTH = 8;
45 +
46 +export function formatId(ns: IdNamespace, n: number): string {
47 + if (!Number.isInteger(n) || n < 1) throw new Error(`invalid id number ${n}`);
48 + return `CI-${ns}-${String(n).padStart(ID_WIDTH, '0')}`;
49 +}
50 +
51 +const ID_RE = /^CI-([A-Z]+)-(\d{8,})$/;
52 +
53 +export function parseId(id: string): { ns: IdNamespace; n: number } | null {
54 + const m = ID_RE.exec(id);
55 + if (!m) return null;
56 + const ns = m[1] as IdNamespace;
57 + if (!ID_NAMESPACES.includes(ns)) return null;
58 + return { ns, n: Number(m[2]) };
59 +}
60 +
61 +export function isCiId(id: string, ns?: IdNamespace): boolean {
62 + const p = parseId(id);
63 + return !!p && (ns ? p.ns === ns : true);
64 +}
65 +
66 +/** Ingest run identifiers: ING-CLINICALTRIALS-20260908-000019 (CLAUDE.md §176). */
67 +export function formatRunId(connectorId: string, date: Date, seq: number): string {
68 + const d = date.toISOString().slice(0, 10).replace(/-/g, '');
69 + return `ING-${connectorId.toUpperCase().replace(/[^A-Z0-9]/g, '')}-${d}-${String(seq).padStart(6, '0')}`;
70 +}
added packages/shared/src/index.ts +5 −0
@@ -0,0 +1,5 @@
1 +export * from './ids.js';
2 +export * from './provenance.js';
3 +export * from './normalize.js';
4 +export * from './logger.js';
5 +export * from './env.js';
added packages/shared/src/logger.ts +9 −0
@@ -0,0 +1,9 @@
1 +import pino from 'pino';
2 +
3 +export const logger = pino({
4 + level: process.env.LOG_LEVEL ?? 'info',
5 + base: { service: process.env.CI_SERVICE ?? 'cancerindex' },
6 + timestamp: pino.stdTimeFunctions.isoTime,
7 +});
8 +
9 +export type Logger = typeof logger;
added packages/shared/src/normalize.ts +40 −0
@@ -0,0 +1,40 @@
1 +/**
2 + * String normalization used by reconciliation (CLAUDE.md §69). Deterministic, no LLM.
3 + */
4 +
5 +const GREEK: Record<string, string> = { α: 'alpha', β: 'beta', γ: 'gamma', δ: 'delta', κ: 'kappa', λ: 'lambda', μ: 'mu' };
6 +
7 +export function stripDiacritics(s: string): string {
8 + return s.normalize('NFKD').replace(/[̀-ͯ]/g, '');
9 +}
10 +
11 +/** Lowercase, ASCII, punctuation → space, collapse whitespace. Keeps digits (G12C, HER2). */
12 +export function normalizeLabel(input: string): string {
13 + let s = stripDiacritics(input).toLowerCase();
14 + s = s.replace(/[αβγδκλμ]/g, (c) => GREEK[c] ?? c);
15 + s = s.replace(/['’`"]/g, '');
16 + s = s.replace(/[^a-z0-9]+/g, ' ');
17 + s = s.replace(/\b(nos|unspecified|not otherwise specified)\b/g, ' ');
18 + s = s.replace(/\bcarcinomas\b/g, 'carcinoma').replace(/\bcancers\b/g, 'cancer').replace(/\btumou?rs\b/g, 'tumor').replace(/\btumour\b/g, 'tumor').replace(/\bneoplasms\b/g, 'neoplasm').replace(/\bsarcomas\b/g, 'sarcoma').replace(/\blymphomas\b/g, 'lymphoma').replace(/\bleukaemia\b/g, 'leukemia').replace(/\bleukemias\b/g, 'leukemia');
19 + return s.replace(/\s+/g, ' ').trim();
20 +}
21 +
22 +/** "Carcinoma, Non-Small-Cell Lung" (MeSH inverted form) → "Non-Small-Cell Lung Carcinoma". */
23 +export function uninvertMeshTerm(term: string): string {
24 + const parts = term.split(',').map((p) => p.trim());
25 + if (parts.length < 2) return term;
26 + return [...parts.slice(1), parts[0]].join(' ');
27 +}
28 +
29 +/** URL slug for entity routes. */
30 +export function slugify(input: string): string {
31 + return stripDiacritics(input)
32 + .toLowerCase()
33 + .replace(/[^a-z0-9]+/g, '-')
34 + .replace(/^-+|-+$/g, '')
35 + .slice(0, 120);
36 +}
37 +
38 +export function sha256Hex(data: string | Uint8Array): Promise<string> {
39 + return import('node:crypto').then(({ createHash }) => createHash('sha256').update(data).digest('hex'));
40 +}
added packages/shared/src/provenance.ts +85 −0
@@ -0,0 +1,85 @@
1 +import { z } from 'zod';
2 +
3 +/** CLAUDE.md §2 — every imported fact carries provenance; every derived value is reproducible. */
4 +export const EvidenceType = z.enum([
5 + 'registry',
6 + 'clinical_trial',
7 + 'meta_analysis',
8 + 'systematic_review',
9 + 'cohort',
10 + 'case_control',
11 + 'case_series',
12 + 'case_report',
13 + 'preclinical',
14 + 'regulatory',
15 + 'guideline',
16 + 'expert_curation',
17 + 'database',
18 + 'computed',
19 +]);
20 +export type EvidenceType = z.infer<typeof EvidenceType>;
21 +
22 +export const AccessLevel = z.enum(['open', 'registration_required', 'controlled', 'licensed']);
23 +export type AccessLevel = z.infer<typeof AccessLevel>;
24 +
25 +export const ProvenanceInput = z.object({
26 + sourceId: z.string(),
27 + sourceRecordId: z.string().optional(),
28 + sourceUrl: z.string().url().optional(),
29 + dataset: z.string().optional(),
30 + datasetVersion: z.string().optional(),
31 + publicationId: z.string().optional(),
32 + pmid: z.string().optional(),
33 + doi: z.string().optional(),
34 + retrievedAt: z.string().datetime(),
35 + publishedAt: z.string().optional(),
36 + updatedAt: z.string().optional(),
37 + geography: z.string().optional(),
38 + population: z.string().optional(),
39 + cohortSize: z.number().int().nonnegative().optional(),
40 + methodology: z.string().optional(),
41 + evidenceType: EvidenceType,
42 + accessLevel: AccessLevel,
43 + confidence: z.number().min(0).max(1).optional(),
44 + license: z.string().optional(),
45 + ingestRunId: z.string().optional(),
46 +});
47 +export type ProvenanceInput = z.infer<typeof ProvenanceInput>;
48 +
49 +/** Data layer a value belongs to (CLAUDE.md §2 — layers stay separable). */
50 +export const DataLayer = z.enum(['raw', 'normalized', 'canonical', 'derived', 'ranked', 'ai_synthesis']);
51 +export type DataLayer = z.infer<typeof DataLayer>;
52 +
53 +/** Display category for scientific safety (CLAUDE.md §3). */
54 +export const ClaimCategory = z.enum([
55 + 'observed_data',
56 + 'published_evidence',
57 + 'curated_evidence',
58 + 'regulatory_status',
59 + 'clinical_guideline',
60 + 'computed_metric',
61 + 'ai_generated_synthesis',
62 +]);
63 +export type ClaimCategory = z.infer<typeof ClaimCategory>;
64 +
65 +/** Entity mapping confidence (CLAUDE.md §221). */
66 +export const MatchType = z.enum([
67 + 'EXACT_IDENTIFIER',
68 + 'CURATED_EXACT',
69 + 'ONTOLOGY_EXACT',
70 + 'CURATED_BROADER',
71 + 'CURATED_NARROWER',
72 + 'ALIAS',
73 + 'PROBABILISTIC',
74 + 'UNRESOLVED',
75 +]);
76 +export type MatchType = z.infer<typeof MatchType>;
77 +
78 +/** Derived statistic envelope (CLAUDE.md §2). */
79 +export interface DerivedValue {
80 + metric: string;
81 + value: number;
82 + computed: true;
83 + formulaVersion: string;
84 + inputs: string[];
85 +}
added packages/shared/tsconfig.json +9 −0
@@ -0,0 +1,9 @@
1 +{
2 + "extends": "../../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": "src",
5 + "outDir": "dist",
6 + "noEmit": true
7 + },
8 + "include": ["src"]
9 +}
added pnpm-lock.yaml +2023 −0
@@ -0,0 +1,2023 @@
1 +lockfileVersion: '9.0'
2 +
3 +settings:
4 + autoInstallPeers: true
5 + excludeLinksFromLockfile: false
6 +
7 +importers:
8 +
9 + .:
10 + dependencies:
11 + '@cancerindex/connectors':
12 + specifier: workspace:*
13 + version: link:packages/connectors
14 + '@cancerindex/database':
15 + specifier: workspace:*
16 + version: link:packages/database
17 + '@cancerindex/ontology':
18 + specifier: workspace:*
19 + version: link:packages/ontology
20 + '@cancerindex/ranking':
21 + specifier: workspace:*
22 + version: link:packages/ranking
23 + '@cancerindex/shared':
24 + specifier: workspace:*
25 + version: link:packages/shared
26 + drizzle-orm:
27 + specifier: ^0.45.0
28 + version: 0.45.2(postgres@3.4.9)
29 + postgres:
30 + specifier: ^3.4.7
31 + version: 3.4.9
32 + devDependencies:
33 + '@types/node':
34 + specifier: ^24.0.0
35 + version: 24.13.3
36 + tsx:
37 + specifier: ^4.20.0
38 + version: 4.23.13
39 + typescript:
40 + specifier: ^5.9.3
41 + version: 5.9.3
42 + vitest:
43 + specifier: ^3.2.0
44 + version: 3.2.7(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
45 +
46 + packages/connectors:
47 + dependencies:
48 + '@cancerindex/database':
49 + specifier: workspace:*
50 + version: link:../database
51 + '@cancerindex/ontology':
52 + specifier: workspace:*
53 + version: link:../ontology
54 + '@cancerindex/shared':
55 + specifier: workspace:*
56 + version: link:../shared
57 + drizzle-orm:
58 + specifier: ^0.45.0
59 + version: 0.45.2(postgres@3.4.9)
60 + fast-xml-parser:
61 + specifier: ^5.2.0
62 + version: 5.11.1
63 + postgres:
64 + specifier: ^3.4.7
65 + version: 3.4.9
66 + yaml:
67 + specifier: ^2.8.0
68 + version: 2.9.0
69 + zod:
70 + specifier: ^4.0.0
71 + version: 4.5.4
72 + devDependencies:
73 + '@types/node':
74 + specifier: ^24.0.0
75 + version: 24.13.3
76 + typescript:
77 + specifier: ^5.9.3
78 + version: 5.9.3
79 + vitest:
80 + specifier: ^3.2.0
81 + version: 3.2.7(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
82 +
83 + packages/database:
84 + dependencies:
85 + '@cancerindex/shared':
86 + specifier: workspace:*
87 + version: link:../shared
88 + drizzle-orm:
89 + specifier: ^0.45.0
90 + version: 0.45.2(postgres@3.4.9)
91 + postgres:
92 + specifier: ^3.4.7
93 + version: 3.4.9
94 + devDependencies:
95 + '@types/node':
96 + specifier: ^24.0.0
97 + version: 24.13.3
98 + drizzle-kit:
99 + specifier: ^0.31.0
100 + version: 0.31.10
101 + tsx:
102 + specifier: ^4.20.0
103 + version: 4.23.13
104 + typescript:
105 + specifier: ^5.9.3
106 + version: 5.9.3
107 + vitest:
108 + specifier: ^3.2.0
109 + version: 3.2.7(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
110 +
111 + packages/ontology:
112 + dependencies:
113 + '@cancerindex/database':
114 + specifier: workspace:*
115 + version: link:../database
116 + '@cancerindex/shared':
117 + specifier: workspace:*
118 + version: link:../shared
119 + drizzle-orm:
120 + specifier: ^0.45.0
121 + version: 0.45.2(postgres@3.4.9)
122 + devDependencies:
123 + '@types/node':
124 + specifier: ^24.0.0
125 + version: 24.13.3
126 + typescript:
127 + specifier: ^5.9.3
128 + version: 5.9.3
129 + vitest:
130 + specifier: ^3.2.0
131 + version: 3.2.7(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
132 +
133 + packages/ranking:
134 + dependencies:
135 + '@cancerindex/database':
136 + specifier: workspace:*
137 + version: link:../database
138 + '@cancerindex/ontology':
139 + specifier: workspace:*
140 + version: link:../ontology
141 + '@cancerindex/shared':
142 + specifier: workspace:*
143 + version: link:../shared
144 + drizzle-orm:
145 + specifier: ^0.45.0
146 + version: 0.45.2(postgres@3.4.9)
147 + devDependencies:
148 + '@types/node':
149 + specifier: ^24.0.0
150 + version: 24.13.3
151 + typescript:
152 + specifier: ^5.9.3
153 + version: 5.9.3
154 + vitest:
155 + specifier: ^3.2.0
156 + version: 3.2.7(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
157 +
158 + packages/shared:
159 + dependencies:
160 + pino:
161 + specifier: ^9.7.0
162 + version: 9.14.0
163 + zod:
164 + specifier: ^4.0.0
165 + version: 4.5.4
166 + devDependencies:
167 + '@types/node':
168 + specifier: ^24.0.0
169 + version: 24.13.3
170 + typescript:
171 + specifier: ^5.9.3
172 + version: 5.9.3
173 + vitest:
174 + specifier: ^3.2.0
175 + version: 3.2.7(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
176 +
177 +packages:
178 +
179 + '@drizzle-team/brocli@0.10.2':
180 + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
181 +
182 + '@esbuild-kit/core-utils@3.3.2':
183 + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
184 + deprecated: 'Merged into tsx: https://tsx.hirok.io'
185 +
186 + '@esbuild-kit/esm-loader@2.6.5':
187 + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
188 + deprecated: 'Merged into tsx: https://tsx.hirok.io'
189 +
190 + '@esbuild/aix-ppc64@0.25.12':
191 + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
192 + engines: {node: '>=18'}
193 + cpu: [ppc64]
194 + os: [aix]
195 +
196 + '@esbuild/aix-ppc64@0.28.2':
197 + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
198 + engines: {node: '>=18'}
199 + cpu: [ppc64]
200 + os: [aix]
201 +
202 + '@esbuild/android-arm64@0.18.20':
203 + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
204 + engines: {node: '>=12'}
205 + cpu: [arm64]
206 + os: [android]
207 +
208 + '@esbuild/android-arm64@0.25.12':
209 + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
210 + engines: {node: '>=18'}
211 + cpu: [arm64]
212 + os: [android]
213 +
214 + '@esbuild/android-arm64@0.28.2':
215 + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==}
216 + engines: {node: '>=18'}
217 + cpu: [arm64]
218 + os: [android]
219 +
220 + '@esbuild/android-arm@0.18.20':
221 + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
222 + engines: {node: '>=12'}
223 + cpu: [arm]
224 + os: [android]
225 +
226 + '@esbuild/android-arm@0.25.12':
227 + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
228 + engines: {node: '>=18'}
229 + cpu: [arm]
230 + os: [android]
231 +
232 + '@esbuild/android-arm@0.28.2':
233 + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==}
234 + engines: {node: '>=18'}
235 + cpu: [arm]
236 + os: [android]
237 +
238 + '@esbuild/android-x64@0.18.20':
239 + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
240 + engines: {node: '>=12'}
241 + cpu: [x64]
242 + os: [android]
243 +
244 + '@esbuild/android-x64@0.25.12':
245 + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
246 + engines: {node: '>=18'}
247 + cpu: [x64]
248 + os: [android]
249 +
250 + '@esbuild/android-x64@0.28.2':
251 + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==}
252 + engines: {node: '>=18'}
253 + cpu: [x64]
254 + os: [android]
255 +
256 + '@esbuild/darwin-arm64@0.18.20':
257 + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
258 + engines: {node: '>=12'}
259 + cpu: [arm64]
260 + os: [darwin]
261 +
262 + '@esbuild/darwin-arm64@0.25.12':
263 + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
264 + engines: {node: '>=18'}
265 + cpu: [arm64]
266 + os: [darwin]
267 +
268 + '@esbuild/darwin-arm64@0.28.2':
269 + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==}
270 + engines: {node: '>=18'}
271 + cpu: [arm64]
272 + os: [darwin]
273 +
274 + '@esbuild/darwin-x64@0.18.20':
275 + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
276 + engines: {node: '>=12'}
277 + cpu: [x64]
278 + os: [darwin]
279 +
280 + '@esbuild/darwin-x64@0.25.12':
281 + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
282 + engines: {node: '>=18'}
283 + cpu: [x64]
284 + os: [darwin]
285 +
286 + '@esbuild/darwin-x64@0.28.2':
287 + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==}
288 + engines: {node: '>=18'}
289 + cpu: [x64]
290 + os: [darwin]
291 +
292 + '@esbuild/freebsd-arm64@0.18.20':
293 + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
294 + engines: {node: '>=12'}
295 + cpu: [arm64]
296 + os: [freebsd]
297 +
298 + '@esbuild/freebsd-arm64@0.25.12':
299 + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
300 + engines: {node: '>=18'}
301 + cpu: [arm64]
302 + os: [freebsd]
303 +
304 + '@esbuild/freebsd-arm64@0.28.2':
305 + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==}
306 + engines: {node: '>=18'}
307 + cpu: [arm64]
308 + os: [freebsd]
309 +
310 + '@esbuild/freebsd-x64@0.18.20':
311 + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
312 + engines: {node: '>=12'}
313 + cpu: [x64]
314 + os: [freebsd]
315 +
316 + '@esbuild/freebsd-x64@0.25.12':
317 + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
318 + engines: {node: '>=18'}
319 + cpu: [x64]
320 + os: [freebsd]
321 +
322 + '@esbuild/freebsd-x64@0.28.2':
323 + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==}
324 + engines: {node: '>=18'}
325 + cpu: [x64]
326 + os: [freebsd]
327 +
328 + '@esbuild/linux-arm64@0.18.20':
329 + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
330 + engines: {node: '>=12'}
331 + cpu: [arm64]
332 + os: [linux]
333 +
334 + '@esbuild/linux-arm64@0.25.12':
335 + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
336 + engines: {node: '>=18'}
337 + cpu: [arm64]
338 + os: [linux]
339 +
340 + '@esbuild/linux-arm64@0.28.2':
341 + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==}
342 + engines: {node: '>=18'}
343 + cpu: [arm64]
344 + os: [linux]
345 +
346 + '@esbuild/linux-arm@0.18.20':
347 + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
348 + engines: {node: '>=12'}
349 + cpu: [arm]
350 + os: [linux]
351 +
352 + '@esbuild/linux-arm@0.25.12':
353 + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
354 + engines: {node: '>=18'}
355 + cpu: [arm]
356 + os: [linux]
357 +
358 + '@esbuild/linux-arm@0.28.2':
359 + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==}
360 + engines: {node: '>=18'}
361 + cpu: [arm]
362 + os: [linux]
363 +
364 + '@esbuild/linux-ia32@0.18.20':
365 + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
366 + engines: {node: '>=12'}
367 + cpu: [ia32]
368 + os: [linux]
369 +
370 + '@esbuild/linux-ia32@0.25.12':
371 + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
372 + engines: {node: '>=18'}
373 + cpu: [ia32]
374 + os: [linux]
375 +
376 + '@esbuild/linux-ia32@0.28.2':
377 + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==}
378 + engines: {node: '>=18'}
379 + cpu: [ia32]
380 + os: [linux]
381 +
382 + '@esbuild/linux-loong64@0.18.20':
383 + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
384 + engines: {node: '>=12'}
385 + cpu: [loong64]
386 + os: [linux]
387 +
388 + '@esbuild/linux-loong64@0.25.12':
389 + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
390 + engines: {node: '>=18'}
391 + cpu: [loong64]
392 + os: [linux]
393 +
394 + '@esbuild/linux-loong64@0.28.2':
395 + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==}
396 + engines: {node: '>=18'}
397 + cpu: [loong64]
398 + os: [linux]
399 +
400 + '@esbuild/linux-mips64el@0.18.20':
401 + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
402 + engines: {node: '>=12'}
403 + cpu: [mips64el]
404 + os: [linux]
405 +
406 + '@esbuild/linux-mips64el@0.25.12':
407 + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
408 + engines: {node: '>=18'}
409 + cpu: [mips64el]
410 + os: [linux]
411 +
412 + '@esbuild/linux-mips64el@0.28.2':
413 + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==}
414 + engines: {node: '>=18'}
415 + cpu: [mips64el]
416 + os: [linux]
417 +
418 + '@esbuild/linux-ppc64@0.18.20':
419 + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
420 + engines: {node: '>=12'}
421 + cpu: [ppc64]
422 + os: [linux]
423 +
424 + '@esbuild/linux-ppc64@0.25.12':
425 + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
426 + engines: {node: '>=18'}
427 + cpu: [ppc64]
428 + os: [linux]
429 +
430 + '@esbuild/linux-ppc64@0.28.2':
431 + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==}
432 + engines: {node: '>=18'}
433 + cpu: [ppc64]
434 + os: [linux]
435 +
436 + '@esbuild/linux-riscv64@0.18.20':
437 + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
438 + engines: {node: '>=12'}
439 + cpu: [riscv64]
440 + os: [linux]
441 +
442 + '@esbuild/linux-riscv64@0.25.12':
443 + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
444 + engines: {node: '>=18'}
445 + cpu: [riscv64]
446 + os: [linux]
447 +
448 + '@esbuild/linux-riscv64@0.28.2':
449 + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==}
450 + engines: {node: '>=18'}
451 + cpu: [riscv64]
452 + os: [linux]
453 +
454 + '@esbuild/linux-s390x@0.18.20':
455 + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
456 + engines: {node: '>=12'}
457 + cpu: [s390x]
458 + os: [linux]
459 +
460 + '@esbuild/linux-s390x@0.25.12':
461 + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
462 + engines: {node: '>=18'}
463 + cpu: [s390x]
464 + os: [linux]
465 +
466 + '@esbuild/linux-s390x@0.28.2':
467 + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==}
468 + engines: {node: '>=18'}
469 + cpu: [s390x]
470 + os: [linux]
471 +
472 + '@esbuild/linux-x64@0.18.20':
473 + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
474 + engines: {node: '>=12'}
475 + cpu: [x64]
476 + os: [linux]
477 +
478 + '@esbuild/linux-x64@0.25.12':
479 + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
480 + engines: {node: '>=18'}
481 + cpu: [x64]
482 + os: [linux]
483 +
484 + '@esbuild/linux-x64@0.28.2':
485 + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==}
486 + engines: {node: '>=18'}
487 + cpu: [x64]
488 + os: [linux]
489 +
490 + '@esbuild/netbsd-arm64@0.25.12':
491 + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
492 + engines: {node: '>=18'}
493 + cpu: [arm64]
494 + os: [netbsd]
495 +
496 + '@esbuild/netbsd-arm64@0.28.2':
497 + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
498 + engines: {node: '>=18'}
499 + cpu: [arm64]
500 + os: [netbsd]
501 +
502 + '@esbuild/netbsd-x64@0.18.20':
503 + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
504 + engines: {node: '>=12'}
505 + cpu: [x64]
506 + os: [netbsd]
507 +
508 + '@esbuild/netbsd-x64@0.25.12':
509 + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
510 + engines: {node: '>=18'}
511 + cpu: [x64]
512 + os: [netbsd]
513 +
514 + '@esbuild/netbsd-x64@0.28.2':
515 + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==}
516 + engines: {node: '>=18'}
517 + cpu: [x64]
518 + os: [netbsd]
519 +
520 + '@esbuild/openbsd-arm64@0.25.12':
521 + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
522 + engines: {node: '>=18'}
523 + cpu: [arm64]
524 + os: [openbsd]
525 +
526 + '@esbuild/openbsd-arm64@0.28.2':
527 + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
528 + engines: {node: '>=18'}
529 + cpu: [arm64]
530 + os: [openbsd]
531 +
532 + '@esbuild/openbsd-x64@0.18.20':
533 + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
534 + engines: {node: '>=12'}
535 + cpu: [x64]
536 + os: [openbsd]
537 +
538 + '@esbuild/openbsd-x64@0.25.12':
539 + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
540 + engines: {node: '>=18'}
541 + cpu: [x64]
542 + os: [openbsd]
543 +
544 + '@esbuild/openbsd-x64@0.28.2':
545 + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==}
546 + engines: {node: '>=18'}
547 + cpu: [x64]
548 + os: [openbsd]
549 +
550 + '@esbuild/openharmony-arm64@0.25.12':
551 + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
552 + engines: {node: '>=18'}
553 + cpu: [arm64]
554 + os: [openharmony]
555 +
556 + '@esbuild/openharmony-arm64@0.28.2':
557 + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
558 + engines: {node: '>=18'}
559 + cpu: [arm64]
560 + os: [openharmony]
561 +
562 + '@esbuild/sunos-x64@0.18.20':
563 + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
564 + engines: {node: '>=12'}
565 + cpu: [x64]
566 + os: [sunos]
567 +
568 + '@esbuild/sunos-x64@0.25.12':
569 + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
570 + engines: {node: '>=18'}
571 + cpu: [x64]
572 + os: [sunos]
573 +
574 + '@esbuild/sunos-x64@0.28.2':
575 + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==}
576 + engines: {node: '>=18'}
577 + cpu: [x64]
578 + os: [sunos]
579 +
580 + '@esbuild/win32-arm64@0.18.20':
581 + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
582 + engines: {node: '>=12'}
583 + cpu: [arm64]
584 + os: [win32]
585 +
586 + '@esbuild/win32-arm64@0.25.12':
587 + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
588 + engines: {node: '>=18'}
589 + cpu: [arm64]
590 + os: [win32]
591 +
592 + '@esbuild/win32-arm64@0.28.2':
593 + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==}
594 + engines: {node: '>=18'}
595 + cpu: [arm64]
596 + os: [win32]
597 +
598 + '@esbuild/win32-ia32@0.18.20':
599 + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
600 + engines: {node: '>=12'}
601 + cpu: [ia32]
602 + os: [win32]
603 +
604 + '@esbuild/win32-ia32@0.25.12':
605 + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
606 + engines: {node: '>=18'}
607 + cpu: [ia32]
608 + os: [win32]
609 +
610 + '@esbuild/win32-ia32@0.28.2':
611 + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==}
612 + engines: {node: '>=18'}
613 + cpu: [ia32]
614 + os: [win32]
615 +
616 + '@esbuild/win32-x64@0.18.20':
617 + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
618 + engines: {node: '>=12'}
619 + cpu: [x64]
620 + os: [win32]
621 +
622 + '@esbuild/win32-x64@0.25.12':
623 + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
624 + engines: {node: '>=18'}
625 + cpu: [x64]
626 + os: [win32]
627 +
628 + '@esbuild/win32-x64@0.28.2':
629 + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==}
630 + engines: {node: '>=18'}
631 + cpu: [x64]
632 + os: [win32]
633 +
634 + '@jridgewell/sourcemap-codec@1.6.0':
635 + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
636 +
637 + '@napi-rs/lzma-linux-x64-gnu@1.5.1':
638 + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==}
639 + engines: {node: ^22.20 || ^24.12 || >=25}
640 + cpu: [x64]
641 + os: [linux]
642 + libc: [glibc]
643 +
644 + '@nodable/entities@3.0.0':
645 + resolution: {integrity: sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==}
646 +
647 + '@pinojs/redact@0.4.0':
648 + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
649 +
650 + '@rollup/rollup-android-arm-eabi@4.63.1':
651 + resolution: {integrity: sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==}
652 + cpu: [arm]
653 + os: [android]
654 +
655 + '@rollup/rollup-android-arm64@4.63.1':
656 + resolution: {integrity: sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==}
657 + cpu: [arm64]
658 + os: [android]
659 +
660 + '@rollup/rollup-darwin-arm64@4.63.1':
661 + resolution: {integrity: sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==}
662 + cpu: [arm64]
663 + os: [darwin]
664 +
665 + '@rollup/rollup-darwin-x64@4.63.1':
666 + resolution: {integrity: sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==}
667 + cpu: [x64]
668 + os: [darwin]
669 +
670 + '@rollup/rollup-freebsd-arm64@4.63.1':
671 + resolution: {integrity: sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==}
672 + cpu: [arm64]
673 + os: [freebsd]
674 +
675 + '@rollup/rollup-freebsd-x64@4.63.1':
676 + resolution: {integrity: sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==}
677 + cpu: [x64]
678 + os: [freebsd]
679 +
680 + '@rollup/rollup-linux-arm-gnueabihf@4.63.1':
681 + resolution: {integrity: sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==}
682 + cpu: [arm]
683 + os: [linux]
684 + libc: [glibc]
685 +
686 + '@rollup/rollup-linux-arm-musleabihf@4.63.1':
687 + resolution: {integrity: sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==}
688 + cpu: [arm]
689 + os: [linux]
690 + libc: [musl]
691 +
692 + '@rollup/rollup-linux-arm64-gnu@4.63.1':
693 + resolution: {integrity: sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==}
694 + cpu: [arm64]
695 + os: [linux]
696 + libc: [glibc]
697 +
698 + '@rollup/rollup-linux-arm64-musl@4.63.1':
699 + resolution: {integrity: sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==}
700 + cpu: [arm64]
701 + os: [linux]
702 + libc: [musl]
703 +
704 + '@rollup/rollup-linux-loong64-gnu@4.63.1':
705 + resolution: {integrity: sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==}
706 + cpu: [loong64]
707 + os: [linux]
708 + libc: [glibc]
709 +
710 + '@rollup/rollup-linux-loong64-musl@4.63.1':
711 + resolution: {integrity: sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==}
712 + cpu: [loong64]
713 + os: [linux]
714 + libc: [musl]
715 +
716 + '@rollup/rollup-linux-ppc64-gnu@4.63.1':
717 + resolution: {integrity: sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==}
718 + cpu: [ppc64]
719 + os: [linux]
720 + libc: [glibc]
721 +
722 + '@rollup/rollup-linux-ppc64-musl@4.63.1':
723 + resolution: {integrity: sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==}
724 + cpu: [ppc64]
725 + os: [linux]
726 + libc: [musl]
727 +
728 + '@rollup/rollup-linux-riscv64-gnu@4.63.1':
729 + resolution: {integrity: sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==}
730 + cpu: [riscv64]
731 + os: [linux]
732 + libc: [glibc]
733 +
734 + '@rollup/rollup-linux-riscv64-musl@4.63.1':
735 + resolution: {integrity: sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==}
736 + cpu: [riscv64]
737 + os: [linux]
738 + libc: [musl]
739 +
740 + '@rollup/rollup-linux-s390x-gnu@4.63.1':
741 + resolution: {integrity: sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==}
742 + cpu: [s390x]
743 + os: [linux]
744 + libc: [glibc]
745 +
746 + '@rollup/rollup-linux-x64-gnu@4.63.1':
747 + resolution: {integrity: sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==}
748 + cpu: [x64]
749 + os: [linux]
750 + libc: [glibc]
751 +
752 + '@rollup/rollup-linux-x64-musl@4.63.1':
753 + resolution: {integrity: sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==}
754 + cpu: [x64]
755 + os: [linux]
756 + libc: [musl]
757 +
758 + '@rollup/rollup-openbsd-x64@4.63.1':
759 + resolution: {integrity: sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==}
760 + cpu: [x64]
761 + os: [openbsd]
762 +
763 + '@rollup/rollup-openharmony-arm64@4.63.1':
764 + resolution: {integrity: sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==}
765 + cpu: [arm64]
766 + os: [openharmony]
767 +
768 + '@rollup/rollup-win32-arm64-msvc@4.63.1':
769 + resolution: {integrity: sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==}
770 + cpu: [arm64]
771 + os: [win32]
772 +
773 + '@rollup/rollup-win32-ia32-msvc@4.63.1':
774 + resolution: {integrity: sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==}
775 + cpu: [ia32]
776 + os: [win32]
777 +
778 + '@rollup/rollup-win32-x64-gnu@4.63.1':
779 + resolution: {integrity: sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==}
780 + cpu: [x64]
781 + os: [win32]
782 +
783 + '@rollup/rollup-win32-x64-msvc@4.63.1':
784 + resolution: {integrity: sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==}
785 + cpu: [x64]
786 + os: [win32]
787 +
788 + '@types/chai@5.2.3':
789 + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
790 +
791 + '@types/deep-eql@4.0.2':
792 + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
793 +
794 + '@types/estree@1.0.9':
795 + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
796 +
797 + '@types/node@24.13.3':
798 + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==}
799 +
800 + '@vitest/expect@3.2.7':
801 + resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==}
802 +
803 + '@vitest/mocker@3.2.7':
804 + resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==}
805 + peerDependencies:
806 + msw: ^2.4.9
807 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
808 + peerDependenciesMeta:
809 + msw:
810 + optional: true
811 + vite:
812 + optional: true
813 +
814 + '@vitest/pretty-format@3.2.7':
815 + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==}
816 +
817 + '@vitest/runner@3.2.7':
818 + resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==}
819 +
820 + '@vitest/snapshot@3.2.7':
821 + resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==}
822 +
823 + '@vitest/spy@3.2.7':
824 + resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==}
825 +
826 + '@vitest/utils@3.2.7':
827 + resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==}
828 +
829 + anynum@1.0.1:
830 + resolution: {integrity: sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==}
831 +
832 + assertion-error@2.0.1:
833 + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
834 + engines: {node: '>=12'}
835 +
836 + atomic-sleep@1.0.0:
837 + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
838 + engines: {node: '>=8.0.0'}
839 +
840 + buffer-from@1.1.2:
841 + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
842 +
843 + cac@6.7.14:
844 + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
845 + engines: {node: '>=8'}
846 +
847 + chai@5.3.3:
848 + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
849 + engines: {node: '>=18'}
850 +
851 + check-error@2.1.3:
852 + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
853 + engines: {node: '>= 16'}
854 +
855 + debug@4.4.3:
856 + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
857 + engines: {node: '>=6.0'}
858 + peerDependencies:
859 + supports-color: '*'
860 + peerDependenciesMeta:
861 + supports-color:
862 + optional: true
863 +
864 + deep-eql@5.0.2:
865 + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
866 + engines: {node: '>=6'}
867 +
868 + drizzle-kit@0.31.10:
869 + resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==}
870 + hasBin: true
871 +
872 + drizzle-orm@0.45.2:
873 + resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==}
874 + peerDependencies:
875 + '@aws-sdk/client-rds-data': '>=3'
876 + '@cloudflare/workers-types': '>=4'
877 + '@electric-sql/pglite': '>=0.2.0'
878 + '@libsql/client': '>=0.10.0'
879 + '@libsql/client-wasm': '>=0.10.0'
880 + '@neondatabase/serverless': '>=0.10.0'
881 + '@op-engineering/op-sqlite': '>=2'
882 + '@opentelemetry/api': ^1.4.1
883 + '@planetscale/database': '>=1.13'
884 + '@prisma/client': '*'
885 + '@tidbcloud/serverless': '*'
886 + '@types/better-sqlite3': '*'
887 + '@types/pg': '*'
888 + '@types/sql.js': '*'
889 + '@upstash/redis': '>=1.34.7'
890 + '@vercel/postgres': '>=0.8.0'
891 + '@xata.io/client': '*'
892 + better-sqlite3: '>=7'
893 + bun-types: '*'
894 + expo-sqlite: '>=14.0.0'
895 + gel: '>=2'
896 + knex: '*'
897 + kysely: '*'
898 + mysql2: '>=2'
899 + pg: '>=8'
900 + postgres: '>=3'
901 + prisma: '*'
902 + sql.js: '>=1'
903 + sqlite3: '>=5'
904 + peerDependenciesMeta:
905 + '@aws-sdk/client-rds-data':
906 + optional: true
907 + '@cloudflare/workers-types':
908 + optional: true
909 + '@electric-sql/pglite':
910 + optional: true
911 + '@libsql/client':
912 + optional: true
913 + '@libsql/client-wasm':
914 + optional: true
915 + '@neondatabase/serverless':
916 + optional: true
917 + '@op-engineering/op-sqlite':
918 + optional: true
919 + '@opentelemetry/api':
920 + optional: true
921 + '@planetscale/database':
922 + optional: true
923 + '@prisma/client':
924 + optional: true
925 + '@tidbcloud/serverless':
926 + optional: true
927 + '@types/better-sqlite3':
928 + optional: true
929 + '@types/pg':
930 + optional: true
931 + '@types/sql.js':
932 + optional: true
933 + '@upstash/redis':
934 + optional: true
935 + '@vercel/postgres':
936 + optional: true
937 + '@xata.io/client':
938 + optional: true
939 + better-sqlite3:
940 + optional: true
941 + bun-types:
942 + optional: true
943 + expo-sqlite:
944 + optional: true
945 + gel:
946 + optional: true
947 + knex:
948 + optional: true
949 + kysely:
950 + optional: true
951 + mysql2:
952 + optional: true
953 + pg:
954 + optional: true
955 + postgres:
956 + optional: true
957 + prisma:
958 + optional: true
959 + sql.js:
960 + optional: true
961 + sqlite3:
962 + optional: true
963 +
964 + es-module-lexer@1.7.0:
965 + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
966 +
967 + esbuild@0.18.20:
968 + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
969 + engines: {node: '>=12'}
970 + hasBin: true
971 +
972 + esbuild@0.25.12:
973 + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
974 + engines: {node: '>=18'}
975 + hasBin: true
976 +
977 + esbuild@0.28.2:
978 + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
979 + engines: {node: '>=18'}
980 + hasBin: true
981 +
982 + estree-walker@3.0.3:
983 + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
984 +
985 + expect-type@1.4.0:
986 + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
987 + engines: {node: '>=12.0.0'}
988 +
989 + fast-xml-builder@1.3.1:
990 + resolution: {integrity: sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==}
991 +
992 + fast-xml-parser@5.11.1:
993 + resolution: {integrity: sha512-TBw6K/fxoQGGjCmZDw9w/ZwP3uDcnTM4YH/g+PFRWr8sbe5idXtxNN6vITh4+1ruCZaho6uBFurElsA7F0zzgw==}
994 + hasBin: true
995 +
996 + fdir@6.5.0:
997 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
998 + engines: {node: '>=12.0.0'}
999 + peerDependencies:
1000 + picomatch: ^3 || ^4
1001 + peerDependenciesMeta:
1002 + picomatch:
1003 + optional: true
1004 +
1005 + fsevents@2.3.3:
1006 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
1007 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
1008 + os: [darwin]
1009 +
1010 + get-tsconfig@4.14.3:
1011 + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==}
1012 +
1013 + is-unsafe@2.0.2:
1014 + resolution: {integrity: sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==}
1015 +
1016 + js-tokens@9.0.1:
1017 + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
1018 +
1019 + loupe@3.2.1:
1020 + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
1021 +
1022 + magic-string@0.30.21:
1023 + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
1024 +
1025 + ms@2.1.3:
1026 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
1027 +
1028 + nanoid@3.3.18:
1029 + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
1030 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
1031 + hasBin: true
1032 +
1033 + on-exit-leak-free@2.1.2:
1034 + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
1035 + engines: {node: '>=14.0.0'}
1036 +
1037 + path-expression-matcher@1.6.2:
1038 + resolution: {integrity: sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==}
1039 + engines: {node: '>=14.0.0'}
1040 +
1041 + pathe@2.0.3:
1042 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
1043 +
1044 + pathval@2.0.1:
1045 + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
1046 + engines: {node: '>= 14.16'}
1047 +
1048 + picocolors@1.1.1:
1049 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
1050 +
1051 + picomatch@4.0.7:
1052 + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
1053 + engines: {node: '>=12'}
1054 +
1055 + pino-abstract-transport@2.0.0:
1056 + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
1057 +
1058 + pino-std-serializers@7.1.0:
1059 + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}
1060 +
1061 + pino@9.14.0:
1062 + resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==}
1063 + hasBin: true
1064 +
1065 + postcss@8.5.28:
1066 + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
1067 + engines: {node: ^10 || ^12 || >=14}
1068 +
1069 + postgres@3.4.9:
1070 + resolution: {integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==}
1071 + engines: {node: '>=12'}
1072 +
1073 + process-warning@5.1.0:
1074 + resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==}
1075 +
1076 + quick-format-unescaped@4.0.4:
1077 + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
1078 +
1079 + real-require@0.2.0:
1080 + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
1081 + engines: {node: '>= 12.13.0'}
1082 +
1083 + resolve-pkg-maps@1.0.0:
1084 + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
1085 +
1086 + rollup@4.63.1:
1087 + resolution: {integrity: sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==}
1088 + engines: {node: '>=18.0.0', npm: '>=8.0.0'}
1089 + hasBin: true
1090 +
1091 + safe-stable-stringify@2.5.0:
1092 + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
1093 + engines: {node: '>=10'}
1094 +
1095 + siginfo@2.0.0:
1096 + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
1097 +
1098 + sonic-boom@4.2.1:
1099 + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
1100 +
1101 + source-map-js@1.2.1:
1102 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
1103 + engines: {node: '>=0.10.0'}
1104 +
1105 + source-map-support@0.5.21:
1106 + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
1107 +
1108 + source-map@0.6.1:
1109 + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
1110 + engines: {node: '>=0.10.0'}
1111 +
1112 + split2@4.2.0:
1113 + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
1114 + engines: {node: '>= 10.x'}
1115 +
1116 + stackback@0.0.2:
1117 + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
1118 +
1119 + std-env@3.10.0:
1120 + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
1121 +
1122 + strip-literal@3.1.0:
1123 + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
1124 +
1125 + strnum@2.4.2:
1126 + resolution: {integrity: sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==}
1127 +
1128 + thread-stream@3.2.0:
1129 + resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==}
1130 +
1131 + tinybench@2.9.0:
1132 + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
1133 +
1134 + tinyexec@0.3.2:
1135 + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
1136 +
1137 + tinyglobby@0.2.17:
1138 + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
1139 + engines: {node: '>=12.0.0'}
1140 +
1141 + tinypool@1.1.1:
1142 + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
1143 + engines: {node: ^18.0.0 || >=20.0.0}
1144 +
1145 + tinyrainbow@2.0.0:
1146 + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
1147 + engines: {node: '>=14.0.0'}
1148 +
1149 + tinyspy@4.0.6:
1150 + resolution: {integrity: sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==}
1151 + engines: {node: '>=14.0.0'}
1152 +
1153 + tsx@4.23.13:
1154 + resolution: {integrity: sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==}
1155 + engines: {node: '>=18.0.0'}
1156 + hasBin: true
1157 +
1158 + typescript@5.9.3:
1159 + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
1160 + engines: {node: '>=14.17'}
1161 + hasBin: true
1162 +
1163 + undici-types@7.18.2:
1164 + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
1165 +
1166 + vite-node@3.2.4:
1167 + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
1168 + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
1169 + hasBin: true
1170 +
1171 + vite@7.3.6:
1172 + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==}
1173 + engines: {node: ^20.19.0 || >=22.12.0}
1174 + hasBin: true
1175 + peerDependencies:
1176 + '@types/node': ^20.19.0 || >=22.12.0
1177 + jiti: '>=1.21.0'
1178 + less: ^4.0.0
1179 + lightningcss: ^1.21.0
1180 + sass: ^1.70.0
1181 + sass-embedded: ^1.70.0
1182 + stylus: '>=0.54.8'
1183 + sugarss: ^5.0.0
1184 + terser: ^5.16.0
1185 + tsx: ^4.8.1
1186 + yaml: ^2.4.2
1187 + peerDependenciesMeta:
1188 + '@types/node':
1189 + optional: true
1190 + jiti:
1191 + optional: true
1192 + less:
1193 + optional: true
1194 + lightningcss:
1195 + optional: true
1196 + sass:
1197 + optional: true
1198 + sass-embedded:
1199 + optional: true
1200 + stylus:
1201 + optional: true
1202 + sugarss:
1203 + optional: true
1204 + terser:
1205 + optional: true
1206 + tsx:
1207 + optional: true
1208 + yaml:
1209 + optional: true
1210 +
1211 + vitest@3.2.7:
1212 + resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==}
1213 + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
1214 + hasBin: true
1215 + peerDependencies:
1216 + '@edge-runtime/vm': '*'
1217 + '@types/debug': ^4.1.12
1218 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
1219 + '@vitest/browser': 3.2.7
1220 + '@vitest/ui': 3.2.7
1221 + happy-dom: '*'
1222 + jsdom: '*'
1223 + peerDependenciesMeta:
1224 + '@edge-runtime/vm':
1225 + optional: true
1226 + '@types/debug':
1227 + optional: true
1228 + '@types/node':
1229 + optional: true
1230 + '@vitest/browser':
1231 + optional: true
1232 + '@vitest/ui':
1233 + optional: true
1234 + happy-dom:
1235 + optional: true
1236 + jsdom:
1237 + optional: true
1238 +
1239 + why-is-node-running@2.3.0:
1240 + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
1241 + engines: {node: '>=8'}
1242 + hasBin: true
1243 +
1244 + xml-naming@0.3.0:
1245 + resolution: {integrity: sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==}
1246 + engines: {node: '>=16.0.0'}
1247 +
1248 + yaml@2.9.0:
1249 + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
1250 + engines: {node: '>= 14.6'}
1251 + hasBin: true
1252 +
1253 + zod@4.5.4:
1254 + resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==}
1255 +
1256 +snapshots:
1257 +
1258 + '@drizzle-team/brocli@0.10.2': {}
1259 +
1260 + '@esbuild-kit/core-utils@3.3.2':
1261 + dependencies:
1262 + esbuild: 0.18.20
1263 + source-map-support: 0.5.21
1264 +
1265 + '@esbuild-kit/esm-loader@2.6.5':
1266 + dependencies:
1267 + '@esbuild-kit/core-utils': 3.3.2
1268 + get-tsconfig: 4.14.3
1269 +
1270 + '@esbuild/aix-ppc64@0.25.12':
1271 + optional: true
1272 +
1273 + '@esbuild/aix-ppc64@0.28.2':
1274 + optional: true
1275 +
1276 + '@esbuild/android-arm64@0.18.20':
1277 + optional: true
1278 +
1279 + '@esbuild/android-arm64@0.25.12':
1280 + optional: true
1281 +
1282 + '@esbuild/android-arm64@0.28.2':
1283 + optional: true
1284 +
1285 + '@esbuild/android-arm@0.18.20':
1286 + optional: true
1287 +
1288 + '@esbuild/android-arm@0.25.12':
1289 + optional: true
1290 +
1291 + '@esbuild/android-arm@0.28.2':
1292 + optional: true
1293 +
1294 + '@esbuild/android-x64@0.18.20':
1295 + optional: true
1296 +
1297 + '@esbuild/android-x64@0.25.12':
1298 + optional: true
1299 +
1300 + '@esbuild/android-x64@0.28.2':
1301 + optional: true
1302 +
1303 + '@esbuild/darwin-arm64@0.18.20':
1304 + optional: true
1305 +
1306 + '@esbuild/darwin-arm64@0.25.12':
1307 + optional: true
1308 +
1309 + '@esbuild/darwin-arm64@0.28.2':
1310 + optional: true
1311 +
1312 + '@esbuild/darwin-x64@0.18.20':
1313 + optional: true
1314 +
1315 + '@esbuild/darwin-x64@0.25.12':
1316 + optional: true
1317 +
1318 + '@esbuild/darwin-x64@0.28.2':
1319 + optional: true
1320 +
1321 + '@esbuild/freebsd-arm64@0.18.20':
1322 + optional: true
1323 +
1324 + '@esbuild/freebsd-arm64@0.25.12':
1325 + optional: true
1326 +
1327 + '@esbuild/freebsd-arm64@0.28.2':
1328 + optional: true
1329 +
1330 + '@esbuild/freebsd-x64@0.18.20':
1331 + optional: true
1332 +
1333 + '@esbuild/freebsd-x64@0.25.12':
1334 + optional: true
1335 +
1336 + '@esbuild/freebsd-x64@0.28.2':
1337 + optional: true
1338 +
1339 + '@esbuild/linux-arm64@0.18.20':
1340 + optional: true
1341 +
1342 + '@esbuild/linux-arm64@0.25.12':
1343 + optional: true
1344 +
1345 + '@esbuild/linux-arm64@0.28.2':
1346 + optional: true
1347 +
1348 + '@esbuild/linux-arm@0.18.20':
1349 + optional: true
1350 +
1351 + '@esbuild/linux-arm@0.25.12':
1352 + optional: true
1353 +
1354 + '@esbuild/linux-arm@0.28.2':
1355 + optional: true
1356 +
1357 + '@esbuild/linux-ia32@0.18.20':
1358 + optional: true
1359 +
1360 + '@esbuild/linux-ia32@0.25.12':
1361 + optional: true
1362 +
1363 + '@esbuild/linux-ia32@0.28.2':
1364 + optional: true
1365 +
1366 + '@esbuild/linux-loong64@0.18.20':
1367 + optional: true
1368 +
1369 + '@esbuild/linux-loong64@0.25.12':
1370 + optional: true
1371 +
1372 + '@esbuild/linux-loong64@0.28.2':
1373 + optional: true
1374 +
1375 + '@esbuild/linux-mips64el@0.18.20':
1376 + optional: true
1377 +
1378 + '@esbuild/linux-mips64el@0.25.12':
1379 + optional: true
1380 +
1381 + '@esbuild/linux-mips64el@0.28.2':
1382 + optional: true
1383 +
1384 + '@esbuild/linux-ppc64@0.18.20':
1385 + optional: true
1386 +
1387 + '@esbuild/linux-ppc64@0.25.12':
1388 + optional: true
1389 +
1390 + '@esbuild/linux-ppc64@0.28.2':
1391 + optional: true
1392 +
1393 + '@esbuild/linux-riscv64@0.18.20':
1394 + optional: true
1395 +
1396 + '@esbuild/linux-riscv64@0.25.12':
1397 + optional: true
1398 +
1399 + '@esbuild/linux-riscv64@0.28.2':
1400 + optional: true
1401 +
1402 + '@esbuild/linux-s390x@0.18.20':
1403 + optional: true
1404 +
1405 + '@esbuild/linux-s390x@0.25.12':
1406 + optional: true
1407 +
1408 + '@esbuild/linux-s390x@0.28.2':
1409 + optional: true
1410 +
1411 + '@esbuild/linux-x64@0.18.20':
1412 + optional: true
1413 +
1414 + '@esbuild/linux-x64@0.25.12':
1415 + optional: true
1416 +
1417 + '@esbuild/linux-x64@0.28.2':
1418 + optional: true
1419 +
1420 + '@esbuild/netbsd-arm64@0.25.12':
1421 + optional: true
1422 +
1423 + '@esbuild/netbsd-arm64@0.28.2':
1424 + optional: true
1425 +
1426 + '@esbuild/netbsd-x64@0.18.20':
1427 + optional: true
1428 +
1429 + '@esbuild/netbsd-x64@0.25.12':
1430 + optional: true
1431 +
1432 + '@esbuild/netbsd-x64@0.28.2':
1433 + optional: true
1434 +
1435 + '@esbuild/openbsd-arm64@0.25.12':
1436 + optional: true
1437 +
1438 + '@esbuild/openbsd-arm64@0.28.2':
1439 + optional: true
1440 +
1441 + '@esbuild/openbsd-x64@0.18.20':
1442 + optional: true
1443 +
1444 + '@esbuild/openbsd-x64@0.25.12':
1445 + optional: true
1446 +
1447 + '@esbuild/openbsd-x64@0.28.2':
1448 + optional: true
1449 +
1450 + '@esbuild/openharmony-arm64@0.25.12':
1451 + optional: true
1452 +
1453 + '@esbuild/openharmony-arm64@0.28.2':
1454 + optional: true
1455 +
1456 + '@esbuild/sunos-x64@0.18.20':
1457 + optional: true
1458 +
1459 + '@esbuild/sunos-x64@0.25.12':
1460 + optional: true
1461 +
1462 + '@esbuild/sunos-x64@0.28.2':
1463 + optional: true
1464 +
1465 + '@esbuild/win32-arm64@0.18.20':
1466 + optional: true
1467 +
1468 + '@esbuild/win32-arm64@0.25.12':
1469 + optional: true
1470 +
1471 + '@esbuild/win32-arm64@0.28.2':
1472 + optional: true
1473 +
1474 + '@esbuild/win32-ia32@0.18.20':
1475 + optional: true
1476 +
1477 + '@esbuild/win32-ia32@0.25.12':
1478 + optional: true
1479 +
1480 + '@esbuild/win32-ia32@0.28.2':
1481 + optional: true
1482 +
1483 + '@esbuild/win32-x64@0.18.20':
1484 + optional: true
1485 +
1486 + '@esbuild/win32-x64@0.25.12':
1487 + optional: true
1488 +
1489 + '@esbuild/win32-x64@0.28.2':
1490 + optional: true
1491 +
1492 + '@jridgewell/sourcemap-codec@1.6.0': {}
1493 +
1494 + '@napi-rs/lzma-linux-x64-gnu@1.5.1':
1495 + optional: true
1496 +
1497 + '@nodable/entities@3.0.0': {}
1498 +
1499 + '@pinojs/redact@0.4.0': {}
1500 +
1501 + '@rollup/rollup-android-arm-eabi@4.63.1':
1502 + optional: true
1503 +
1504 + '@rollup/rollup-android-arm64@4.63.1':
1505 + optional: true
1506 +
1507 + '@rollup/rollup-darwin-arm64@4.63.1':
1508 + optional: true
1509 +
1510 + '@rollup/rollup-darwin-x64@4.63.1':
1511 + optional: true
1512 +
1513 + '@rollup/rollup-freebsd-arm64@4.63.1':
1514 + optional: true
1515 +
1516 + '@rollup/rollup-freebsd-x64@4.63.1':
1517 + optional: true
1518 +
1519 + '@rollup/rollup-linux-arm-gnueabihf@4.63.1':
1520 + optional: true
1521 +
1522 + '@rollup/rollup-linux-arm-musleabihf@4.63.1':
1523 + optional: true
1524 +
1525 + '@rollup/rollup-linux-arm64-gnu@4.63.1':
1526 + optional: true
1527 +
1528 + '@rollup/rollup-linux-arm64-musl@4.63.1':
1529 + optional: true
1530 +
1531 + '@rollup/rollup-linux-loong64-gnu@4.63.1':
1532 + optional: true
1533 +
1534 + '@rollup/rollup-linux-loong64-musl@4.63.1':
1535 + optional: true
1536 +
1537 + '@rollup/rollup-linux-ppc64-gnu@4.63.1':
1538 + optional: true
1539 +
1540 + '@rollup/rollup-linux-ppc64-musl@4.63.1':
1541 + optional: true
1542 +
1543 + '@rollup/rollup-linux-riscv64-gnu@4.63.1':
1544 + optional: true
1545 +
1546 + '@rollup/rollup-linux-riscv64-musl@4.63.1':
1547 + optional: true
1548 +
1549 + '@rollup/rollup-linux-s390x-gnu@4.63.1':
1550 + optional: true
1551 +
1552 + '@rollup/rollup-linux-x64-gnu@4.63.1':
1553 + optional: true
1554 +
1555 + '@rollup/rollup-linux-x64-musl@4.63.1':
1556 + optional: true
1557 +
1558 + '@rollup/rollup-openbsd-x64@4.63.1':
1559 + optional: true
1560 +
1561 + '@rollup/rollup-openharmony-arm64@4.63.1':
1562 + optional: true
1563 +
1564 + '@rollup/rollup-win32-arm64-msvc@4.63.1':
1565 + optional: true
1566 +
1567 + '@rollup/rollup-win32-ia32-msvc@4.63.1':
1568 + optional: true
1569 +
1570 + '@rollup/rollup-win32-x64-gnu@4.63.1':
1571 + optional: true
1572 +
1573 + '@rollup/rollup-win32-x64-msvc@4.63.1':
1574 + optional: true
1575 +
1576 + '@types/chai@5.2.3':
1577 + dependencies:
1578 + '@types/deep-eql': 4.0.2
1579 + assertion-error: 2.0.1
1580 +
1581 + '@types/deep-eql@4.0.2': {}
1582 +
1583 + '@types/estree@1.0.9': {}
1584 +
1585 + '@types/node@24.13.3':
1586 + dependencies:
1587 + undici-types: 7.18.2
1588 +
1589 + '@vitest/expect@3.2.7':
1590 + dependencies:
1591 + '@types/chai': 5.2.3
1592 + '@vitest/spy': 3.2.7
1593 + '@vitest/utils': 3.2.7
1594 + chai: 5.3.3
1595 + tinyrainbow: 2.0.0
1596 +
1597 + '@vitest/mocker@3.2.7(vite@7.3.6(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0))':
1598 + dependencies:
1599 + '@vitest/spy': 3.2.7
1600 + estree-walker: 3.0.3
1601 + magic-string: 0.30.21
1602 + optionalDependencies:
1603 + vite: 7.3.6(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
1604 +
1605 + '@vitest/pretty-format@3.2.7':
1606 + dependencies:
1607 + tinyrainbow: 2.0.0
1608 +
1609 + '@vitest/runner@3.2.7':
1610 + dependencies:
1611 + '@vitest/utils': 3.2.7
1612 + pathe: 2.0.3
1613 + strip-literal: 3.1.0
1614 +
1615 + '@vitest/snapshot@3.2.7':
1616 + dependencies:
1617 + '@vitest/pretty-format': 3.2.7
1618 + magic-string: 0.30.21
1619 + pathe: 2.0.3
1620 +
1621 + '@vitest/spy@3.2.7':
1622 + dependencies:
1623 + tinyspy: 4.0.6
1624 +
1625 + '@vitest/utils@3.2.7':
1626 + dependencies:
1627 + '@vitest/pretty-format': 3.2.7
1628 + loupe: 3.2.1
1629 + tinyrainbow: 2.0.0
1630 +
1631 + anynum@1.0.1: {}
1632 +
1633 + assertion-error@2.0.1: {}
1634 +
1635 + atomic-sleep@1.0.0: {}
1636 +
1637 + buffer-from@1.1.2: {}
1638 +
1639 + cac@6.7.14: {}
1640 +
1641 + chai@5.3.3:
1642 + dependencies:
1643 + assertion-error: 2.0.1
1644 + check-error: 2.1.3
1645 + deep-eql: 5.0.2
1646 + loupe: 3.2.1
1647 + pathval: 2.0.1
1648 +
1649 + check-error@2.1.3: {}
1650 +
1651 + debug@4.4.3:
1652 + dependencies:
1653 + ms: 2.1.3
1654 +
1655 + deep-eql@5.0.2: {}
1656 +
1657 + drizzle-kit@0.31.10:
1658 + dependencies:
1659 + '@drizzle-team/brocli': 0.10.2
1660 + '@esbuild-kit/esm-loader': 2.6.5
1661 + esbuild: 0.25.12
1662 + tsx: 4.23.13
1663 +
1664 + drizzle-orm@0.45.2(postgres@3.4.9):
1665 + optionalDependencies:
1666 + postgres: 3.4.9
1667 +
1668 + es-module-lexer@1.7.0: {}
1669 +
1670 + esbuild@0.18.20:
1671 + optionalDependencies:
1672 + '@esbuild/android-arm': 0.18.20
1673 + '@esbuild/android-arm64': 0.18.20
1674 + '@esbuild/android-x64': 0.18.20
1675 + '@esbuild/darwin-arm64': 0.18.20
1676 + '@esbuild/darwin-x64': 0.18.20
1677 + '@esbuild/freebsd-arm64': 0.18.20
1678 + '@esbuild/freebsd-x64': 0.18.20
1679 + '@esbuild/linux-arm': 0.18.20
1680 + '@esbuild/linux-arm64': 0.18.20
1681 + '@esbuild/linux-ia32': 0.18.20
1682 + '@esbuild/linux-loong64': 0.18.20
1683 + '@esbuild/linux-mips64el': 0.18.20
1684 + '@esbuild/linux-ppc64': 0.18.20
1685 + '@esbuild/linux-riscv64': 0.18.20
1686 + '@esbuild/linux-s390x': 0.18.20
1687 + '@esbuild/linux-x64': 0.18.20
1688 + '@esbuild/netbsd-x64': 0.18.20
1689 + '@esbuild/openbsd-x64': 0.18.20
1690 + '@esbuild/sunos-x64': 0.18.20
1691 + '@esbuild/win32-arm64': 0.18.20
1692 + '@esbuild/win32-ia32': 0.18.20
1693 + '@esbuild/win32-x64': 0.18.20
1694 +
1695 + esbuild@0.25.12:
1696 + optionalDependencies:
1697 + '@esbuild/aix-ppc64': 0.25.12
1698 + '@esbuild/android-arm': 0.25.12
1699 + '@esbuild/android-arm64': 0.25.12
1700 + '@esbuild/android-x64': 0.25.12
1701 + '@esbuild/darwin-arm64': 0.25.12
1702 + '@esbuild/darwin-x64': 0.25.12
1703 + '@esbuild/freebsd-arm64': 0.25.12
1704 + '@esbuild/freebsd-x64': 0.25.12
1705 + '@esbuild/linux-arm': 0.25.12
1706 + '@esbuild/linux-arm64': 0.25.12
1707 + '@esbuild/linux-ia32': 0.25.12
1708 + '@esbuild/linux-loong64': 0.25.12
1709 + '@esbuild/linux-mips64el': 0.25.12
1710 + '@esbuild/linux-ppc64': 0.25.12
1711 + '@esbuild/linux-riscv64': 0.25.12
1712 + '@esbuild/linux-s390x': 0.25.12
1713 + '@esbuild/linux-x64': 0.25.12
1714 + '@esbuild/netbsd-arm64': 0.25.12
1715 + '@esbuild/netbsd-x64': 0.25.12
1716 + '@esbuild/openbsd-arm64': 0.25.12
1717 + '@esbuild/openbsd-x64': 0.25.12
1718 + '@esbuild/openharmony-arm64': 0.25.12
1719 + '@esbuild/sunos-x64': 0.25.12
1720 + '@esbuild/win32-arm64': 0.25.12
1721 + '@esbuild/win32-ia32': 0.25.12
1722 + '@esbuild/win32-x64': 0.25.12
1723 +
1724 + esbuild@0.28.2:
1725 + optionalDependencies:
1726 + '@esbuild/aix-ppc64': 0.28.2
1727 + '@esbuild/android-arm': 0.28.2
1728 + '@esbuild/android-arm64': 0.28.2
1729 + '@esbuild/android-x64': 0.28.2
1730 + '@esbuild/darwin-arm64': 0.28.2
1731 + '@esbuild/darwin-x64': 0.28.2
1732 + '@esbuild/freebsd-arm64': 0.28.2
1733 + '@esbuild/freebsd-x64': 0.28.2
1734 + '@esbuild/linux-arm': 0.28.2
1735 + '@esbuild/linux-arm64': 0.28.2
1736 + '@esbuild/linux-ia32': 0.28.2
1737 + '@esbuild/linux-loong64': 0.28.2
1738 + '@esbuild/linux-mips64el': 0.28.2
1739 + '@esbuild/linux-ppc64': 0.28.2
1740 + '@esbuild/linux-riscv64': 0.28.2
1741 + '@esbuild/linux-s390x': 0.28.2
1742 + '@esbuild/linux-x64': 0.28.2
1743 + '@esbuild/netbsd-arm64': 0.28.2
1744 + '@esbuild/netbsd-x64': 0.28.2
1745 + '@esbuild/openbsd-arm64': 0.28.2
1746 + '@esbuild/openbsd-x64': 0.28.2
1747 + '@esbuild/openharmony-arm64': 0.28.2
1748 + '@esbuild/sunos-x64': 0.28.2
1749 + '@esbuild/win32-arm64': 0.28.2
1750 + '@esbuild/win32-ia32': 0.28.2
1751 + '@esbuild/win32-x64': 0.28.2
1752 +
1753 + estree-walker@3.0.3:
1754 + dependencies:
1755 + '@types/estree': 1.0.9
1756 +
1757 + expect-type@1.4.0: {}
1758 +
1759 + fast-xml-builder@1.3.1:
1760 + dependencies:
1761 + path-expression-matcher: 1.6.2
1762 + xml-naming: 0.3.0
1763 +
1764 + fast-xml-parser@5.11.1:
1765 + dependencies:
1766 + '@nodable/entities': 3.0.0
1767 + fast-xml-builder: 1.3.1
1768 + is-unsafe: 2.0.2
1769 + path-expression-matcher: 1.6.2
1770 + strnum: 2.4.2
1771 + xml-naming: 0.3.0
1772 +
1773 + fdir@6.5.0(picomatch@4.0.7):
1774 + optionalDependencies:
1775 + picomatch: 4.0.7
1776 +
1777 + fsevents@2.3.3:
1778 + optional: true
1779 +
1780 + get-tsconfig@4.14.3:
1781 + dependencies:
1782 + resolve-pkg-maps: 1.0.0
1783 +
1784 + is-unsafe@2.0.2: {}
1785 +
1786 + js-tokens@9.0.1: {}
1787 +
1788 + loupe@3.2.1: {}
1789 +
1790 + magic-string@0.30.21:
1791 + dependencies:
1792 + '@jridgewell/sourcemap-codec': 1.6.0
1793 +
1794 + ms@2.1.3: {}
1795 +
1796 + nanoid@3.3.18: {}
1797 +
1798 + on-exit-leak-free@2.1.2: {}
1799 +
1800 + path-expression-matcher@1.6.2: {}
1801 +
1802 + pathe@2.0.3: {}
1803 +
1804 + pathval@2.0.1: {}
1805 +
1806 + picocolors@1.1.1: {}
1807 +
1808 + picomatch@4.0.7: {}
1809 +
1810 + pino-abstract-transport@2.0.0:
1811 + dependencies:
1812 + split2: 4.2.0
1813 +
1814 + pino-std-serializers@7.1.0: {}
1815 +
1816 + pino@9.14.0:
1817 + dependencies:
1818 + '@pinojs/redact': 0.4.0
1819 + atomic-sleep: 1.0.0
1820 + on-exit-leak-free: 2.1.2
1821 + pino-abstract-transport: 2.0.0
1822 + pino-std-serializers: 7.1.0
1823 + process-warning: 5.1.0
1824 + quick-format-unescaped: 4.0.4
1825 + real-require: 0.2.0
1826 + safe-stable-stringify: 2.5.0
1827 + sonic-boom: 4.2.1
1828 + thread-stream: 3.2.0
1829 +
1830 + postcss@8.5.28:
1831 + dependencies:
1832 + nanoid: 3.3.18
1833 + picocolors: 1.1.1
1834 + source-map-js: 1.2.1
1835 +
1836 + postgres@3.4.9: {}
1837 +
1838 + process-warning@5.1.0: {}
1839 +
1840 + quick-format-unescaped@4.0.4: {}
1841 +
1842 + real-require@0.2.0: {}
1843 +
1844 + resolve-pkg-maps@1.0.0: {}
1845 +
1846 + rollup@4.63.1:
1847 + dependencies:
1848 + '@types/estree': 1.0.9
1849 + optionalDependencies:
1850 + '@napi-rs/lzma-linux-x64-gnu': 1.5.1
1851 + '@rollup/rollup-android-arm-eabi': 4.63.1
1852 + '@rollup/rollup-android-arm64': 4.63.1
1853 + '@rollup/rollup-darwin-arm64': 4.63.1
1854 + '@rollup/rollup-darwin-x64': 4.63.1
1855 + '@rollup/rollup-freebsd-arm64': 4.63.1
1856 + '@rollup/rollup-freebsd-x64': 4.63.1
1857 + '@rollup/rollup-linux-arm-gnueabihf': 4.63.1
1858 + '@rollup/rollup-linux-arm-musleabihf': 4.63.1
1859 + '@rollup/rollup-linux-arm64-gnu': 4.63.1
1860 + '@rollup/rollup-linux-arm64-musl': 4.63.1
1861 + '@rollup/rollup-linux-loong64-gnu': 4.63.1
1862 + '@rollup/rollup-linux-loong64-musl': 4.63.1
1863 + '@rollup/rollup-linux-ppc64-gnu': 4.63.1
1864 + '@rollup/rollup-linux-ppc64-musl': 4.63.1
1865 + '@rollup/rollup-linux-riscv64-gnu': 4.63.1
1866 + '@rollup/rollup-linux-riscv64-musl': 4.63.1
1867 + '@rollup/rollup-linux-s390x-gnu': 4.63.1
1868 + '@rollup/rollup-linux-x64-gnu': 4.63.1
1869 + '@rollup/rollup-linux-x64-musl': 4.63.1
1870 + '@rollup/rollup-openbsd-x64': 4.63.1
1871 + '@rollup/rollup-openharmony-arm64': 4.63.1
1872 + '@rollup/rollup-win32-arm64-msvc': 4.63.1
1873 + '@rollup/rollup-win32-ia32-msvc': 4.63.1
1874 + '@rollup/rollup-win32-x64-gnu': 4.63.1
1875 + '@rollup/rollup-win32-x64-msvc': 4.63.1
1876 + fsevents: 2.3.3
1877 +
1878 + safe-stable-stringify@2.5.0: {}
1879 +
1880 + siginfo@2.0.0: {}
1881 +
1882 + sonic-boom@4.2.1:
1883 + dependencies:
1884 + atomic-sleep: 1.0.0
1885 +
1886 + source-map-js@1.2.1: {}
1887 +
1888 + source-map-support@0.5.21:
1889 + dependencies:
1890 + buffer-from: 1.1.2
1891 + source-map: 0.6.1
1892 +
1893 + source-map@0.6.1: {}
1894 +
1895 + split2@4.2.0: {}
1896 +
1897 + stackback@0.0.2: {}
1898 +
1899 + std-env@3.10.0: {}
1900 +
1901 + strip-literal@3.1.0:
1902 + dependencies:
1903 + js-tokens: 9.0.1
1904 +
1905 + strnum@2.4.2:
1906 + dependencies:
1907 + anynum: 1.0.1
1908 +
1909 + thread-stream@3.2.0:
1910 + dependencies:
1911 + real-require: 0.2.0
1912 +
1913 + tinybench@2.9.0: {}
1914 +
1915 + tinyexec@0.3.2: {}
1916 +
1917 + tinyglobby@0.2.17:
1918 + dependencies:
1919 + fdir: 6.5.0(picomatch@4.0.7)
1920 + picomatch: 4.0.7
1921 +
1922 + tinypool@1.1.1: {}
1923 +
1924 + tinyrainbow@2.0.0: {}
1925 +
1926 + tinyspy@4.0.6: {}
1927 +
1928 + tsx@4.23.13:
1929 + dependencies:
1930 + esbuild: 0.28.2
1931 + optionalDependencies:
1932 + fsevents: 2.3.3
1933 +
1934 + typescript@5.9.3: {}
1935 +
1936 + undici-types@7.18.2: {}
1937 +
1938 + vite-node@3.2.4(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0):
1939 + dependencies:
1940 + cac: 6.7.14
1941 + debug: 4.4.3
1942 + es-module-lexer: 1.7.0
1943 + pathe: 2.0.3
1944 + vite: 7.3.6(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
1945 + transitivePeerDependencies:
1946 + - '@types/node'
1947 + - jiti
1948 + - less
1949 + - lightningcss
1950 + - sass
1951 + - sass-embedded
1952 + - stylus
1953 + - sugarss
1954 + - supports-color
1955 + - terser
1956 + - tsx
1957 + - yaml
1958 +
1959 + vite@7.3.6(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0):
1960 + dependencies:
1961 + esbuild: 0.28.2
1962 + fdir: 6.5.0(picomatch@4.0.7)
1963 + picomatch: 4.0.7
1964 + postcss: 8.5.28
1965 + rollup: 4.63.1
1966 + tinyglobby: 0.2.17
1967 + optionalDependencies:
1968 + '@types/node': 24.13.3
1969 + fsevents: 2.3.3
1970 + tsx: 4.23.13
1971 + yaml: 2.9.0
1972 +
1973 + vitest@3.2.7(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0):
1974 + dependencies:
1975 + '@types/chai': 5.2.3
1976 + '@vitest/expect': 3.2.7
1977 + '@vitest/mocker': 3.2.7(vite@7.3.6(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0))
1978 + '@vitest/pretty-format': 3.2.7
1979 + '@vitest/runner': 3.2.7
1980 + '@vitest/snapshot': 3.2.7
1981 + '@vitest/spy': 3.2.7
1982 + '@vitest/utils': 3.2.7
1983 + chai: 5.3.3
1984 + debug: 4.4.3
1985 + expect-type: 1.4.0
1986 + magic-string: 0.30.21
1987 + pathe: 2.0.3
1988 + picomatch: 4.0.7
1989 + std-env: 3.10.0
1990 + tinybench: 2.9.0
1991 + tinyexec: 0.3.2
1992 + tinyglobby: 0.2.17
1993 + tinypool: 1.1.1
1994 + tinyrainbow: 2.0.0
1995 + vite: 7.3.6(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
1996 + vite-node: 3.2.4(@types/node@24.13.3)(tsx@4.23.13)(yaml@2.9.0)
1997 + why-is-node-running: 2.3.0
1998 + optionalDependencies:
1999 + '@types/node': 24.13.3
2000 + transitivePeerDependencies:
2001 + - jiti
2002 + - less
2003 + - lightningcss
2004 + - msw
2005 + - sass
2006 + - sass-embedded
2007 + - stylus
2008 + - sugarss
2009 + - supports-color
2010 + - terser
2011 + - tsx
2012 + - yaml
2013 +
2014 + why-is-node-running@2.3.0:
2015 + dependencies:
2016 + siginfo: 2.0.0
2017 + stackback: 0.0.2
2018 +
2019 + xml-naming@0.3.0: {}
2020 +
2021 + yaml@2.9.0: {}
2022 +
2023 + zod@4.5.4: {}
added pnpm-workspace.yaml +4 −0
@@ -0,0 +1,4 @@
1 +packages:
2 + - apps/*
3 + - packages/*
4 + - workers
added scripts/ci.ts +136 −0
@@ -0,0 +1,136 @@
1 +#!/usr/bin/env tsx
2 +/**
3 + * CancerIndex operator CLI.
4 + *
5 + * pnpm cix connectors list connectors + health
6 + * pnpm cix run <id> [--mode full|incremental|dry_run] [--max-records N] [--max-minutes M] [--reset-cursor]
7 + * pnpm cix run-all [--max-minutes M] run every active connector in registry order
8 + * pnpm cix health <id> source liveness probe
9 + * pnpm cix sources:sync manifests → sources table
10 + * pnpm cix counters recompute entity counters
11 + * pnpm cix rank [--scope …] recompute ranking snapshots
12 + * pnpm cix stats table counts
13 + * pnpm cix trace <table> <id> lineage of a value (rankings/epidemiology/frequency)
14 + */
15 +import { existsSync } from 'node:fs';
16 +import path from 'node:path';
17 +import { sql } from 'drizzle-orm';
18 +
19 +for (const candidate of [path.resolve(process.cwd(), '.env')]) {
20 + if (existsSync(candidate)) {
21 + try {
22 + process.loadEnvFile(candidate);
23 + } catch {
24 + /* ignore */
25 + }
26 + }
27 +}
28 +
29 +const [, , cmd, ...rest] = process.argv;
30 +const flags: Record<string, string | boolean> = {};
31 +const positional: string[] = [];
32 +for (let i = 0; i < rest.length; i++) {
33 + const a = rest[i]!;
34 + if (a.startsWith('--')) {
35 + const k = a.slice(2);
36 + const next = rest[i + 1];
37 + if (next && !next.startsWith('--')) {
38 + flags[k] = next;
39 + i++;
40 + } else flags[k] = true;
41 + } else positional.push(a);
42 +}
43 +
44 +async function main() {
45 + const { getDb, closeDb } = await import('@cancerindex/database');
46 + const db = getDb();
47 + try {
48 + switch (cmd) {
49 + case 'connectors': {
50 + const { CONNECTORS } = await import('@cancerindex/connectors');
51 + const rows = await db.execute<{ connector_id: string; health: string; last_success_at: string | null; cursor: unknown }>(sql`SELECT connector_id, health, last_success_at, cursor FROM connector_cursors`);
52 + const byId = new Map(rows.map((r) => [r.connector_id, r]));
53 + for (const c of CONNECTORS) {
54 + const h = byId.get(c.manifest.id);
55 + console.log(`${c.manifest.id.padEnd(18)} tier${c.manifest.tier} ${c.manifest.status.padEnd(20)} license=${c.manifest.licenseStatus.padEnd(10)} health=${(h?.health ?? 'never-run').padEnd(12)} last_ok=${h?.last_success_at ?? '-'}`);
56 + }
57 + break;
58 + }
59 + case 'sources:sync': {
60 + const { syncSources, PLANNED_MANIFESTS } = await import('@cancerindex/connectors');
61 + const r = await syncSources(db, PLANNED_MANIFESTS);
62 + console.log(`[sources] created=${r.created} updated=${r.updated}`);
63 + break;
64 + }
65 + case 'health': {
66 + const { getConnector, RunContext } = await import('@cancerindex/connectors');
67 + const c = getConnector(positional[0] ?? '');
68 + if (!c) throw new Error(`unknown connector ${positional[0]}`);
69 + const [src] = await db.execute<{ id: string }>(sql`SELECT id FROM sources WHERE slug = ${c.manifest.id}`);
70 + const ctx = new RunContext(db, c.manifest, src?.id ?? 'CI-SOURCE-00000000', 'probe', { maxMinutes: 1 }, 0);
71 + console.log(await c.healthCheck(ctx));
72 + break;
73 + }
74 + case 'run': {
75 + const { getConnector, runConnector } = await import('@cancerindex/connectors');
76 + const c = getConnector(positional[0] ?? '');
77 + if (!c) throw new Error(`unknown connector ${positional[0]}`);
78 + const r = await runConnector(db, c, {
79 + mode: (flags.mode as never) ?? undefined,
80 + maxRecords: flags['max-records'] ? Number(flags['max-records']) : undefined,
81 + maxMinutes: flags['max-minutes'] ? Number(flags['max-minutes']) : undefined,
82 + resetCursor: !!flags['reset-cursor'],
83 + });
84 + console.log(`[run] ${r.runId} ${r.status}`, r.counters);
85 + if (r.status === 'failed') process.exitCode = 1;
86 + break;
87 + }
88 + case 'run-all': {
89 + const { CONNECTORS, runConnector } = await import('@cancerindex/connectors');
90 + for (const c of CONNECTORS) {
91 + if (c.manifest.status !== 'active') {
92 + console.log(`[skip] ${c.manifest.id} (${c.manifest.status})`);
93 + continue;
94 + }
95 + const r = await runConnector(db, c, { maxMinutes: flags['max-minutes'] ? Number(flags['max-minutes']) : undefined });
96 + console.log(`[run] ${r.runId} ${r.status}`, r.counters);
97 + }
98 + break;
99 + }
100 + case 'counters': {
101 + const { refreshCounters } = await import('@cancerindex/ranking');
102 + const n = await refreshCounters(db);
103 + console.log(`[counters] refreshed ${n} entities`);
104 + break;
105 + }
106 + case 'rank': {
107 + const { computeAllRankings } = await import('@cancerindex/ranking');
108 + const res = await computeAllRankings(db);
109 + for (const r of res) console.log(`[rank] ${r.metricSlug} ${r.scopeKey} eligible=${r.eligible}`);
110 + break;
111 + }
112 + case 'stats': {
113 + const tables = ['sources', 'ingest_runs', 'source_records', 'provenance', 'cancers', 'cancer_aliases', 'cancer_hierarchy', 'cancer_codes', 'genes', 'variants', 'drugs', 'clinical_trials', 'trial_conditions', 'publications', 'literature_counts', 'civic_evidence_items', 'genomic_cohorts', 'cancer_gene_frequencies', 'epidemiology_observations', 'survival_observations', 'knowledge_edges', 'unresolved_labels', 'rankings', 'ranking_snapshots'];
114 + for (const t of tables) {
115 + const [r] = await db.execute<{ n: string }>(sql.raw(`SELECT count(*)::text AS n FROM ${t}`));
116 + console.log(`${t.padEnd(28)} ${r?.n}`);
117 + }
118 + break;
119 + }
120 + case 'trace': {
121 + const { traceValue } = await import('@cancerindex/ranking');
122 + console.log(JSON.stringify(await traceValue(db, positional[0] ?? '', positional[1] ?? ''), null, 2));
123 + break;
124 + }
125 + default:
126 + console.log('usage: pnpm cix <connectors|run|run-all|health|sources:sync|counters|rank|stats|trace>');
127 + }
128 + } finally {
129 + await closeDb();
130 + }
131 +}
132 +
133 +main().catch((e) => {
134 + console.error(e);
135 + process.exit(1);
136 +});
added tsconfig.base.json +22 −0
@@ -0,0 +1,22 @@
1 +{
2 + "compilerOptions": {
3 + "target": "ES2023",
4 + "lib": ["ES2023"],
5 + "module": "NodeNext",
6 + "moduleResolution": "NodeNext",
7 + "strict": true,
8 + "noUncheckedIndexedAccess": true,
9 + "noImplicitOverride": true,
10 + "noFallthroughCasesInSwitch": true,
11 + "exactOptionalPropertyTypes": false,
12 + "esModuleInterop": true,
13 + "skipLibCheck": true,
14 + "resolveJsonModule": true,
15 + "declaration": true,
16 + "declarationMap": true,
17 + "sourceMap": true,
18 + "isolatedModules": true,
19 + "verbatimModuleSyntax": true,
20 + "forceConsistentCasingInFileNames": true
21 + }
22 +}
added vitest.workspace.ts +1 −0
@@ -0,0 +1 @@
1 +export default ['packages/*', 'apps/*', 'workers'];
2