SPB Git

spb/cancerindex

Public

Split research-gap routes, trial-sites stub, unit labels for new metrics

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Sep 11, 2026) parent 7a72991 Browse files

8 changed files +56 −18

modified apps/api/src/app.ts +2 −0
@@ -30,6 +30,7 @@ import { graphRoutes } from './routes/graph.js';
30 import { epidemiologyRoutes } from './routes/epidemiology.js'; 30 import { epidemiologyRoutes } from './routes/epidemiology.js';
31 import { approvalRoutes } from './routes/approvals.js'; 31 import { approvalRoutes } from './routes/approvals.js';
32 import { intelligenceRoutes } from './routes/intelligence.js'; 32 import { intelligenceRoutes } from './routes/intelligence.js';
33+import { researchGapRoutes } from './routes/research-gap.js';
33 34
34 export interface BuildOptions { 35 export interface BuildOptions {
35 db?: Database; 36 db?: Database;
@@ -164,6 +165,7 @@ export async function buildApp(opts: BuildOptions = {}): Promise<FastifyInstance
164 await v1.register(approvalRoutes); 165 await v1.register(approvalRoutes);
165 await v1.register(graphRoutes); 166 await v1.register(graphRoutes);
166 await v1.register(intelligenceRoutes); 167 await v1.register(intelligenceRoutes);
168+ await v1.register(researchGapRoutes);
167 await v1.register(adminRoutes, { prefix: '/admin' }); 169 await v1.register(adminRoutes, { prefix: '/admin' });
168 }, 170 },
169 { prefix: '/v1' }, 171 { prefix: '/v1' },
modified apps/api/src/routes/intelligence.ts +5 −13
@@ -1,18 +1,10 @@
1 import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod'; 1 import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
2 2
3 /** 3 /**
4− * Intelligence routes (SPEC §10-11, §34): `GET /trials/intelligence` (per-cancer trial metrics), 4+ * Trial-intelligence routes (SPEC §10-11): `GET /trials/intelligence` (per-cancer trial metrics
5− * `GET /trials/sites` (country/city aggregates for the map), `GET /research-gap` (components and 5+ * with formula version and inputs), `GET /trials/sites` (country / city aggregates for the map).
6− * ratios per scope). Filled by the Trial Intelligence and Research Gap work packages 6+ * Filled by the Trial Intelligence and Trial Map work packages.
7− * (each adds its own routes to this file; keep route groups in separate functions).
8 */ 7 */
9−export const intelligenceRoutes: FastifyPluginAsyncZod = async (app) => { 8+export const intelligenceRoutes: FastifyPluginAsyncZod = async (_app) => {
10− await app.register(trialIntelligenceRoutes); 9+ /* routes added by the trial-intelligence / trial-map work packages */
11− await app.register(researchGapRoutes);
12 }; 10 };
13−
14−/** Filled by the Trial Intelligence work package. */
15−export const trialIntelligenceRoutes: FastifyPluginAsyncZod = async (_app) => {};
16−
17−/** Filled by the Research Gap work package. */
18−export const researchGapRoutes: FastifyPluginAsyncZod = async (_app) => {};
added apps/api/src/routes/research-gap.ts +10 −0
@@ -0,0 +1,10 @@
1+import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
2+
3+/**
4+ * Research Gap routes (SPEC §34, §113): `GET /research-gap` → components (deaths, trials,
5+ * publications, shares, log-ratios) per cancer for one burden scope; `GET /research-gap/scopes`.
6+ * Filled by the Research Gap work package.
7+ */
8+export const researchGapRoutes: FastifyPluginAsyncZod = async (_app) => {
9+ /* routes added by the research-gap work package */
10+};
modified apps/web/src/lib/format.ts +12 −0
@@ -40,6 +40,12 @@ export function fmtValue(value: number | string | null | undefined, unit: string
40 return fmtPct(v, 1); 40 return fmtPct(v, 1);
41 case 'percentile_points': 41 case 'percentile_points':
42 return `${v > 0 ? '+' : ''}${fmtNum(v, 1)}`; 42 return `${v > 0 ? '+' : ''}${fmtNum(v, 1)}`;
43+ case 'log2_ratio':
44+ return `${v > 0 ? '+' : ''}${fmtNum(v, 2)}`;
45+ case 'per_1000_deaths':
46+ return fmtNum(v, 1);
47+ case 'index':
48+ return fmtNum(v, 3);
43 default: 49 default:
44 return Math.abs(v) >= 1000 ? fmtInt(v) : fmtNum(v, 2); 50 return Math.abs(v) >= 1000 ? fmtInt(v) : fmtNum(v, 2);
45 } 51 }
@@ -57,6 +63,12 @@ export function unitLabel(unit: string | null | undefined): string {
57 return '%'; 63 return '%';
58 case 'percentile_points': 64 case 'percentile_points':
59 return 'percentile points'; 65 return 'percentile points';
66+ case 'per_1000_deaths':
67+ return 'per 1,000 deaths';
68+ case 'log2_ratio':
69+ return 'log₂ ratio';
70+ case 'index':
71+ return 'index (0–1)';
60 default: 72 default:
61 return unit ?? ''; 73 return unit ?? '';
62 } 74 }
modified packages/ranking/src/index.ts +1 −0
@@ -6,3 +6,4 @@ export * from './intelligence.js';
6 export * from './trial-intelligence.js'; 6 export * from './trial-intelligence.js';
7 export * from './drug-pipeline.js'; 7 export * from './drug-pipeline.js';
8 export * from './research-gap.js'; 8 export * from './research-gap.js';
9+export * from './trial-sites.js';
modified packages/ranking/src/intelligence.ts +8 −1
@@ -1,5 +1,6 @@
1 import type { Database } from '@cancerindex/database'; 1 import type { Database } from '@cancerindex/database';
2 import { computeTrialIntelligence } from './trial-intelligence.js'; 2 import { computeTrialIntelligence } from './trial-intelligence.js';
3+import { computeTrialSiteCounts } from './trial-sites.js';
3 import { computeDrugPipeline } from './drug-pipeline.js'; 4 import { computeDrugPipeline } from './drug-pipeline.js';
4 import { computeResearchGap } from './research-gap.js'; 5 import { computeResearchGap } from './research-gap.js';
5 6
@@ -23,11 +24,17 @@ export async function computeIntelligence(db: Database, log: (msg: string, extra
23 try { 24 try {
24 const r = await computeTrialIntelligence(db); 25 const r = await computeTrialIntelligence(db);
25 out.trialIntelligenceRows = r.rows; 26 out.trialIntelligenceRows = r.rows;
26− out.trialSiteCountryRows = r.siteCountryRows;
27 log('trial intelligence computed', { ...r }); 27 log('trial intelligence computed', { ...r });
28 } catch (e) { 28 } catch (e) {
29 errors.push(`trial-intelligence: ${(e as Error).message}`); 29 errors.push(`trial-intelligence: ${(e as Error).message}`);
30 } 30 }
31+ try {
32+ const r = await computeTrialSiteCounts(db);
33+ out.trialSiteCountryRows = r.rows;
34+ log('trial site country counts computed', { ...r });
35+ } catch (e) {
36+ errors.push(`trial-sites: ${(e as Error).message}`);
37+ }
31 try { 38 try {
32 const r = await computeDrugPipeline(db); 39 const r = await computeDrugPipeline(db);
33 out.drugPipelineRows = r.rows; 40 out.drugPipelineRows = r.rows;
modified packages/ranking/src/trial-intelligence.ts +2 −4
@@ -1,17 +1,15 @@
1 import type { Database } from '@cancerindex/database'; 1 import type { Database } from '@cancerindex/database';
2 2
3 export const TRIAL_INTELLIGENCE_FORMULA_VERSION = 'ci-trial-intel-v1'; 3 export const TRIAL_INTELLIGENCE_FORMULA_VERSION = 'ci-trial-intel-v1';
4−export const TRIAL_SITE_COUNTRY_FORMULA_VERSION = 'ci-trial-sites-v1';
5 4
6 export interface TrialIntelligenceResult { 5 export interface TrialIntelligenceResult {
7 rows: number; 6 rows: number;
8− siteCountryRows: number;
9 } 7 }
10 8
11 /** 9 /**
12 * STUB — implemented by the Clinical Trial Intelligence work package. 10 * STUB — implemented by the Clinical Trial Intelligence work package.
13− * Recomputes `trial_intelligence` (per cancer × entity level) and `trial_site_country_counts`. 11+ * Recomputes `trial_intelligence` (per cancer × entity level).
14 */ 12 */
15 export async function computeTrialIntelligence(_db: Database): Promise<TrialIntelligenceResult> { 13 export async function computeTrialIntelligence(_db: Database): Promise<TrialIntelligenceResult> {
16− return { rows: 0, siteCountryRows: 0 }; 14+ return { rows: 0 };
17 } 15 }
added packages/ranking/src/trial-sites.ts +16 −0
@@ -0,0 +1,16 @@
1+import type { Database } from '@cancerindex/database';
2+
3+export const TRIAL_SITE_COUNTRY_FORMULA_VERSION = 'ci-trial-sites-v1';
4+
5+export interface TrialSiteCountsResult {
6+ rows: number;
7+}
8+
9+/**
10+ * STUB — implemented by the Trial Map work package.
11+ * Recomputes `trial_site_country_counts` (country aggregates of trial sites for all oncology trials
12+ * and for each top-level cancer, any phase / per phase, all statuses / recruiting only).
13+ */
14+export async function computeTrialSiteCounts(_db: Database): Promise<TrialSiteCountsResult> {
15+ return { rows: 0 };
16+}
17
Menu

Theme