import { describe, expect, it } from 'vitest'; import { DRUG_PIPELINE_FORMULA_VERSION, PIPELINE_STAGES, maxPhase, phaseLabel, stageFor } from './drug-pipeline.js'; describe('drug pipeline — highest phase', () => { it('ranks registry phases and treats EARLY_PHASE1 as phase 1', () => { expect(maxPhase([['PHASE2'], ['PHASE3']])).toBe('PHASE3'); expect(maxPhase([['PHASE2', 'PHASE3']])).toBe('PHASE3'); expect(maxPhase([['PHASE1', 'PHASE2']])).toBe('PHASE2'); expect(maxPhase([['PHASE4'], ['NA']])).toBe('PHASE4'); expect(maxPhase([['EARLY_PHASE1']])).toBe('EARLY_PHASE1'); expect(maxPhase([['EARLY_PHASE1'], ['PHASE1']])).toBe('PHASE1'); expect(maxPhase([['NA'], []])).toBe('NA'); expect(maxPhase([[], ['UNKNOWN_LABEL']])).toBeNull(); expect(maxPhase([])).toBeNull(); }); it('phaseLabel mirrors maxPhase for SQL-side ranks', () => { expect(phaseLabel(4, false)).toBe('PHASE4'); expect(phaseLabel(3, true)).toBe('PHASE3'); expect(phaseLabel(2, false)).toBe('PHASE2'); expect(phaseLabel(1, true)).toBe('PHASE1'); expect(phaseLabel(1, false)).toBe('EARLY_PHASE1'); expect(phaseLabel(0, false)).toBe('NA'); expect(phaseLabel(null, false)).toBeNull(); }); }); describe('drug pipeline — stage rule', () => { it('approval beats everything; withdrawn only when every approval is withdrawn/superseded', () => { expect(stageFor({ approvedLike: 1, withdrawnLike: 0, totalTrials: 0, maxPhase: null })).toBe('approved'); expect(stageFor({ approvedLike: 1, withdrawnLike: 3, totalTrials: 12, maxPhase: 'PHASE3' })).toBe('approved'); expect(stageFor({ approvedLike: 0, withdrawnLike: 2, totalTrials: 40, maxPhase: 'PHASE4' })).toBe('withdrawn'); }); it('otherwise the highest phase among interventional trials decides', () => { expect(stageFor({ approvedLike: 0, withdrawnLike: 0, totalTrials: 3, maxPhase: 'PHASE4' })).toBe('phase4'); expect(stageFor({ approvedLike: 0, withdrawnLike: 0, totalTrials: 3, maxPhase: 'PHASE3' })).toBe('phase3'); expect(stageFor({ approvedLike: 0, withdrawnLike: 0, totalTrials: 3, maxPhase: 'PHASE2' })).toBe('phase2'); expect(stageFor({ approvedLike: 0, withdrawnLike: 0, totalTrials: 3, maxPhase: 'PHASE1' })).toBe('phase1'); expect(stageFor({ approvedLike: 0, withdrawnLike: 0, totalTrials: 3, maxPhase: 'EARLY_PHASE1' })).toBe('phase1'); expect(stageFor({ approvedLike: 0, withdrawnLike: 0, totalTrials: 3, maxPhase: 'NA' })).toBe('phase_not_stated'); expect(stageFor({ approvedLike: 0, withdrawnLike: 0, totalTrials: 3, maxPhase: null })).toBe('phase_not_stated'); }); it('no trials and no approvals → no row', () => { expect(stageFor({ approvedLike: 0, withdrawnLike: 0, totalTrials: 0, maxPhase: null })).toBeNull(); }); it('every stage the rule can return is a declared stage; the formula is versioned', () => { const inputs = [ { approvedLike: 1, withdrawnLike: 0, totalTrials: 0, maxPhase: null }, { approvedLike: 0, withdrawnLike: 1, totalTrials: 0, maxPhase: null }, ...['PHASE4', 'PHASE3', 'PHASE2', 'PHASE1', 'EARLY_PHASE1', 'NA'].map((p) => ({ approvedLike: 0, withdrawnLike: 0, totalTrials: 1, maxPhase: p })), ]; for (const i of inputs) expect(PIPELINE_STAGES).toContain(stageFor(i)); expect(DRUG_PIPELINE_FORMULA_VERSION).toMatch(/^ci-drug-pipeline-v\d+$/); }); });