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%
7.0 KB · 139 lines typescript
Raw Blame History
1import { pgTable, text, integer, bigserial, index, uniqueIndex, real, jsonb, boolean, timestamp, doublePrecision } from 'drizzle-orm/pg-core';2import { ciId, createdAt, updatedAt } from './_common.js';34/** Metric catalog (CLAUDE.md §250-251): every derived number has a versioned formula. */5export 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-v114    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 | composite20    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);2728/** Ranking snapshot = one metric × one scope × one formula version at one time (CLAUDE.md §33, §184). */29export 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 | all41    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);5051export 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_DATA65    inputs: jsonb('inputs').$type<Record<string, unknown>>().notNull().default({}), // lineage: observation ids, counts, formula inputs66    breakdown: jsonb('breakdown').$type<Record<string, number>>(), // composite components67    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);7273/** Precomputed counters per entity, refreshed deterministically from canonical relations (CLAUDE.md §287). */74export 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);102103/** Public API keys (CLAUDE.md §190-191). */104export 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 | institutional113    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);120121/** Cached AI syntheses (CLAUDE.md §44, §322) — model, prompt version and record snapshot recorded. */122export const aiAnswers = pgTable(123  'ai_answers',124  {125    id: bigserial('id', { mode: 'number' }).primaryKey(),126    kind: text('kind').notNull(), // cancer_summary | ask127    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);139