spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1import { describe, expect, it } from 'vitest';2import { TRIAL_INTEL_ACTIVE_STATUSES, TRIAL_INTEL_THRESHOLDS, TRIAL_INTELLIGENCE_FORMULA_VERSION, burdenNormalized, growthWindows, growthYoy, hhi, terminationShare } from './trial-intelligence.js';34describe('growthWindows', () => {5 it('builds two adjacent, non-overlapping 12-month windows ending on asOf (exclusive)', () => {6 const w = growthWindows('2026-09-11');7 expect(w).toEqual({ asOf: '2026-09-11', new12m: { from: '2025-09-11', to: '2026-09-11' }, prior12m: { from: '2024-09-11', to: '2025-09-11' } });8 expect(w.prior12m.to).toBe(w.new12m.from);9 });10 it('clamps month-end days (leap day, 31st)', () => {11 expect(growthWindows('2024-02-29').new12m.from).toBe('2023-02-28');12 expect(growthWindows('2025-03-31').prior12m.from).toBe('2023-03-31');13 expect(growthWindows('2025-05-31').new12m.from).toBe('2024-05-31');14 expect(growthWindows('2025-12-31').new12m.from).toBe('2024-12-31');15 });16 it('rejects malformed dates', () => {17 expect(() => growthWindows('2026-9-1')).toThrow();18 expect(() => growthWindows('nope')).toThrow();19 });20});2122describe('growthYoy', () => {23 it('computes (new − prior) / prior', () => {24 expect(growthYoy(120, 100)).toBeCloseTo(0.2);25 expect(growthYoy(80, 100)).toBeCloseTo(-0.2);26 expect(growthYoy(100, 100)).toBe(0);27 });28 it('is null under the prior-window threshold (default 20)', () => {29 expect(growthYoy(50, 19)).toBeNull();30 expect(growthYoy(50, 20)).toBeCloseTo(1.5);31 expect(growthYoy(5, 0)).toBeNull();32 expect(growthYoy(5, 2, 2)).toBeCloseTo(1.5);33 });34 it('threshold matches the persisted constant', () => {35 expect(TRIAL_INTEL_THRESHOLDS.growthMinPriorTrials).toBe(20);36 });37});3839describe('hhi', () => {40 it('is 1 for a single holder and 1/n for n equal holders', () => {41 expect(hhi([42])).toBe(1);42 expect(hhi([10, 10, 10, 10])).toBeCloseTo(0.25);43 });44 it('is Σ share² for an uneven distribution', () => {45 // shares 0.5, 0.3, 0.2 → 0.25 + 0.09 + 0.0446 expect(hhi([50, 30, 20])).toBeCloseTo(0.38);47 });48 it('is null when empty or all zero, ignores negative / non-finite counts', () => {49 expect(hhi([])).toBeNull();50 expect(hhi([0, 0])).toBeNull();51 expect(hhi([5, -1, Number.NaN, 5])).toBeCloseTo(0.5);52 });53});5455describe('terminationShare', () => {56 it('computes (terminated + withdrawn) / (completed + terminated + withdrawn)', () => {57 expect(terminationShare(70, 20, 10)).toBeCloseTo(0.3);58 expect(terminationShare(100, 0, 0)).toBe(0);59 });60 it('is null when the terminal denominator is under 30', () => {61 expect(terminationShare(20, 5, 4)).toBeNull();62 expect(terminationShare(20, 5, 5)).toBeCloseTo(1 / 3);63 expect(terminationShare(0, 0, 0)).toBeNull();64 });65 it('threshold matches the persisted constant', () => {66 expect(TRIAL_INTEL_THRESHOLDS.terminationMinTerminalTrials).toBe(30);67 });68});6970describe('burdenNormalized', () => {71 it('computes trials per 1,000 deaths and per 100,000 cases', () => {72 const r = burdenNormalized(500, 125_000, 250_000);73 expect(r.per1000Deaths).toBeCloseTo(4);74 expect(r.per100kCases).toBeCloseTo(200);75 });76 it('is null when deaths are missing or under 100', () => {77 expect(burdenNormalized(10, null, 1000)).toEqual({ per1000Deaths: null, per100kCases: null });78 expect(burdenNormalized(10, 99, 1000)).toEqual({ per1000Deaths: null, per100kCases: null });79 expect(burdenNormalized(10, 100, 1000).per1000Deaths).toBeCloseTo(100);80 });81 it('leaves the incidence ratio null when incidence is missing or zero', () => {82 expect(burdenNormalized(10, 1000, null).per100kCases).toBeNull();83 expect(burdenNormalized(10, 1000, 0).per100kCases).toBeNull();84 });85 it('zero active trials gives zero, never null (a true zero)', () => {86 expect(burdenNormalized(0, 1000, 2000)).toEqual({ per1000Deaths: 0, per100kCases: 0 });87 });88});8990describe('constants', () => {91 it('formula version and active statuses match entity_counters', () => {92 expect(TRIAL_INTELLIGENCE_FORMULA_VERSION).toBe('ci-trial-intel-v1');93 expect([...TRIAL_INTEL_ACTIVE_STATUSES]).toEqual(['RECRUITING', 'NOT_YET_RECRUITING', 'ENROLLING_BY_INVITATION', 'ACTIVE_NOT_RECRUITING']);94 expect(TRIAL_INTEL_THRESHOLDS.burdenGeography).toBe('USA');95 expect(TRIAL_INTEL_THRESHOLDS.terminationSince).toBe('2010-01-01');96 });97});98