import { listTrialIntelligence } from '@/lib/queries/trial-intelligence'; import { sourceInfoById } from '@/lib/queries/provenance'; import { SITE_URL } from '@/lib/site'; import { isoDate, toDate } from '@/lib/format'; import { csvCell } from '@/lib/trial-intel'; export const dynamic = 'force-dynamic'; /** * GET /api/export/trial-intelligence.csv?level=top|all * The full `trial_intelligence` layer for one entity level as CSV (attribution header rows first). */ export async function GET(req: Request) { const url = new URL(req.url); const levelRaw = url.searchParams.get('level') ?? 'top'; if (levelRaw !== 'top' && levelRaw !== 'all') return new Response('invalid level (top|all)', { status: 400 }); const level = levelRaw; const rows = await listTrialIntelligence(level); if (rows.length === 0) return new Response('trial intelligence not computed on this environment', { status: 404 }); const burdenIds = [...new Set(rows.map((r) => r.burden_source_id).filter((s): s is string => !!s))]; const src = await sourceInfoById(burdenIds); const burdenSources = burdenIds.map((id) => src.get(id)?.name ?? id).join('; '); const computedAt = rows.reduce((m, r) => { const d = toDate(r.computed_at); return d && (!m || d > m) ? d : m; }, null); const first = rows[0]!; const th = JSON.stringify(first.inputs.thresholds ?? {}); const windows = JSON.stringify(first.inputs.windows ?? {}); const header = [ `# CancerIndex trial intelligence export — entity level: ${level}`, `# Source: CancerIndex (${SITE_URL}) — derived data, CC BY 4.0. Underlying records: ClinicalTrials.gov (U.S. National Library of Medicine, public domain)${burdenSources ? `; burden denominators: ${burdenSources}` : ''}.`, `# formula_version: ${first.formula_version} · study_type: INTERVENTIONAL · aggregation: entity + NCIt descendants (depth ≤ 12) · active statuses: ${(first.inputs.activeStatuses as string[] | undefined)?.join('|') ?? ''}`, `# thresholds: ${th} · growth windows: ${windows} · computed_at: ${computedAt?.toISOString() ?? ''}`, `# Every value is a computed metric (claim category computed_metric). Multinational studies contribute to several countries. Registrant-reported statuses; a TERMINATED status does not imply a negative result.`, ]; const cols = [ 'cancer_id', 'slug', 'canonical_name', 'top_level', 'entity_level', 'total_trials', 'active_trials', 'recruiting_trials', 'phase1_active', 'phase2_active', 'phase3_active', 'phase3_recruiting', 'phase4_active', 'completed_trials', 'terminated_trials', 'withdrawn_trials', 'suspended_trials', 'with_results', 'new_trials_12m', 'new_trials_prior_12m', 'trial_growth_yoy', 'avg_enrollment', 'median_enrollment', 'total_enrollment_active', 'distinct_sponsors', 'industry_share', 'sponsor_hhi', 'top_sponsor', 'top_sponsor_share', 'distinct_countries', 'us_share', 'top_country', 'top_country_share', 'country_hhi', 'termination_share', 'why_stopped_breakdown_json', 'trials_per_1000_deaths', 'trials_per_100k_cases', 'burden_geography', 'burden_year', 'burden_source_id', 'burden_source', 'formula_version', 'computed_at', 'inputs_json', ]; const lines = rows.map((r) => [ r.cancer_id, r.cancer_slug, r.cancer_name, r.top_level, r.entity_level, r.total_trials, r.active_trials, r.recruiting_trials, r.phase1_active, r.phase2_active, r.phase3_active, r.phase3_recruiting, r.phase4_active, r.completed_trials, r.terminated_trials, r.withdrawn_trials, r.suspended_trials, r.with_results, r.new_trials_12m, r.new_trials_prior_12m, r.trial_growth_yoy, r.avg_enrollment, r.median_enrollment, r.total_enrollment_active, r.distinct_sponsors, r.industry_share, r.sponsor_hhi, r.top_sponsor, r.top_sponsor_share, r.distinct_countries, r.us_share, r.top_country, r.top_country_share, r.country_hhi, r.termination_share, r.why_stopped_breakdown, r.trials_per_1000_deaths, r.trials_per_100k_cases, r.burden_geography, r.burden_year, r.burden_source_id, r.burden_source_slug, r.formula_version, toDate(r.computed_at)?.toISOString() ?? '', r.inputs, ] .map(csvCell) .join(','), ); const body = [...header, cols.join(','), ...lines].join('\n') + '\n'; const fname = `cancerindex-trial-intelligence-${level}-${isoDate(computedAt)}.csv`; return new Response(body, { headers: { 'Content-Type': 'text/csv; charset=utf-8', 'Content-Disposition': `attachment; filename="${fname}"`, 'Cache-Control': 'public, max-age=300' } }); }