Jaspers Hub

sj-jsprai / clinicaltrials

pluginReviewed★ 0↓ 1

ClinicalTrials.gov: Searches and retrieves clinical trial records from the public ClinicalTrials.gov API v2, no key required.

3.8 KB
1import { definePlugin, defineSource } from '@jaspers-ai/sdk'
2import { z } from 'zod'
3import { getJson, HOST, searchQuery } from './client'
4import { detailRow, summaryRow, type Study } from './rows'
5
6// ClinicalTrials.gov API v2: search studies and read one study, no key.
7
8const PHASES = ['EARLY_PHASE1', 'PHASE1', 'PHASE2', 'PHASE3', 'PHASE4', 'NA'] as const
9const STATUSES = [
10 'RECRUITING',
11 'NOT_YET_RECRUITING',
12 'ACTIVE_NOT_RECRUITING',
13 'ENROLLING_BY_INVITATION',
14 'COMPLETED',
15 'SUSPENDED',
16 'TERMINATED',
17 'WITHDRAWN',
18 'UNKNOWN',
19] as const
20const date = z
21 .string()
22 .regex(/^\d{4}(-\d{2}(-\d{2})?)?$/)
23 .optional()
24
25const searchStudies = defineSource({
26 description:
27 'Search ClinicalTrials.gov studies by condition, intervention/drug, sponsor, free text, phase, overall status, and start or primary completion date range. One row per study: nctId, briefTitle, leadSponsor, collaborators, phases, overallStatus, startDate, primaryCompletionDate (with its ACTUAL/ESTIMATED type), completionDate, conditions, interventions, enrollment, studyType, lastUpdatePostDate, url. meta has totalCount and nextPageToken (pass it back as pageToken for the next page). For catalysts: sponsor, phases [PHASE3], statuses [RECRUITING, ACTIVE_NOT_RECRUITING], primaryCompletionFrom today, sort PrimaryCompletionDate:asc.',
28 hosts: [HOST],
29 input: z.object({
30 condition: z.string().optional().describe('Condition or disease (query.cond), like "non-small cell lung cancer".'),
31 intervention: z.string().optional().describe('Intervention or drug name (query.intr), like "pembrolizumab".'),
32 sponsor: z.string().optional().describe('Sponsor or collaborator (query.spons), like "Pfizer".'),
33 term: z.string().optional().describe('Free-text search over the whole record (query.term).'),
34 phases: z.array(z.enum(PHASES)).optional().describe('Phases to match, any of them: PHASE1, PHASE2, PHASE3, PHASE4, EARLY_PHASE1, NA.'),
35 statuses: z.array(z.enum(STATUSES)).optional().describe('Overall statuses to match (filter.overallStatus).'),
36 startFrom: date.describe('Earliest start date, YYYY-MM-DD (or YYYY-MM).'),
37 startTo: date.describe('Latest start date, YYYY-MM-DD.'),
38 primaryCompletionFrom: date.describe('Earliest primary completion date, YYYY-MM-DD.'),
39 primaryCompletionTo: date.describe('Latest primary completion date, YYYY-MM-DD.'),
40 sort: z
41 .string()
42 .optional()
43 .describe('Sort as Field:asc or Field:desc, like LastUpdatePostDate:desc, StartDate:desc, PrimaryCompletionDate:asc; a bare field sorts ascending. Default is relevance.'),
44 pageSize: z.number().int().min(1).max(1000).default(50).describe('Studies per page, up to 1000.'),
45 pageToken: z.string().optional().describe('meta.nextPageToken from the previous page.'),
46 }),
47 async run(args, ctx) {
48 const page = (await getJson(ctx, '/studies', searchQuery(args))) as {
49 studies?: Study[]
50 totalCount?: number
51 nextPageToken?: string
52 }
53 return {
54 rows: (page.studies ?? []).map(summaryRow),
55 totalCount: page.totalCount ?? null,
56 nextPageToken: page.nextPageToken ?? null,
57 }
58 },
59})
60
61const getStudy = defineSource({
62 description:
63 "One ClinicalTrials.gov study by NCT ID: the search row's fields plus officialTitle, briefSummary, primaryOutcomes, arms, interventions, eligibility (sex, ages, healthy volunteers, criteria), locationsCount, hasResults, studyFirstPostDate, resultsFirstPostDate.",
64 hosts: [HOST],
65 input: z.object({
66 nctId: z.string().regex(/^NCT\d{8}$/i).describe('The NCT ID, like NCT04368728.'),
67 }),
68 async run(args, ctx) {
69 const study = (await getJson(ctx, `/studies/${args.nctId.toUpperCase()}`, new URLSearchParams({ format: 'json' }))) as Study
70 return [detailRow(study)]
71 },
72})
73
74export default definePlugin({
75 id: 'clinicaltrials',
76 sources: { 'search-studies': searchStudies, 'get-study': getStudy },
77})