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.

4.9 KB
1// How a ClinicalTrials.gov v2 study is read into flat rows.
2
3/** The parts of a study record this plugin reads, as the API answers them. */
4export interface Study {
5 protocolSection?: {
6 identificationModule?: { nctId?: string; briefTitle?: string; officialTitle?: string }
7 statusModule?: {
8 overallStatus?: string
9 startDateStruct?: { date?: string; type?: string }
10 primaryCompletionDateStruct?: { date?: string; type?: string }
11 completionDateStruct?: { date?: string; type?: string }
12 studyFirstPostDateStruct?: { date?: string }
13 resultsFirstPostDateStruct?: { date?: string }
14 lastUpdatePostDateStruct?: { date?: string }
15 }
16 sponsorCollaboratorsModule?: {
17 leadSponsor?: { name?: string; class?: string }
18 collaborators?: { name?: string }[]
19 }
20 descriptionModule?: { briefSummary?: string }
21 conditionsModule?: { conditions?: string[] }
22 designModule?: {
23 studyType?: string
24 phases?: string[]
25 designInfo?: { allocation?: string; primaryPurpose?: string; maskingInfo?: { masking?: string } }
26 enrollmentInfo?: { count?: number; type?: string }
27 }
28 armsInterventionsModule?: {
29 armGroups?: { label?: string; type?: string; interventionNames?: string[] }[]
30 interventions?: { type?: string; name?: string }[]
31 }
32 outcomesModule?: { primaryOutcomes?: { measure?: string; timeFrame?: string }[] }
33 eligibilityModule?: {
34 eligibilityCriteria?: string
35 sex?: string
36 minimumAge?: string
37 maximumAge?: string
38 healthyVolunteers?: boolean
39 }
40 contactsLocationsModule?: { locations?: unknown[] }
41 }
42 hasResults?: boolean
43}
44
45export type Row = Record<string, string | number | boolean | null>
46
47/** One flat row for a search result. */
48export function summaryRow(study: Study): Row {
49 const p = study.protocolSection ?? {}
50 const id = p.identificationModule ?? {}
51 const status = p.statusModule ?? {}
52 const sponsors = p.sponsorCollaboratorsModule ?? {}
53 const design = p.designModule ?? {}
54 const nctId = id.nctId ?? ''
55 return {
56 nctId,
57 briefTitle: id.briefTitle ?? null,
58 leadSponsor: sponsors.leadSponsor?.name ?? null,
59 sponsorClass: sponsors.leadSponsor?.class ?? null,
60 collaborators: join(sponsors.collaborators?.map((c) => c.name)),
61 phases: join(design.phases),
62 overallStatus: status.overallStatus ?? null,
63 startDate: status.startDateStruct?.date ?? null,
64 primaryCompletionDate: status.primaryCompletionDateStruct?.date ?? null,
65 primaryCompletionType: status.primaryCompletionDateStruct?.type ?? null,
66 completionDate: status.completionDateStruct?.date ?? null,
67 conditions: join(p.conditionsModule?.conditions),
68 interventions: join(p.armsInterventionsModule?.interventions?.map((i) => (i.type ? `${i.type}: ${i.name}` : i.name))),
69 enrollment: design.enrollmentInfo?.count ?? null,
70 studyType: design.studyType ?? null,
71 hasResults: study.hasResults ?? false,
72 lastUpdatePostDate: status.lastUpdatePostDateStruct?.date ?? null,
73 url: `https://clinicaltrials.gov/study/${nctId}`,
74 }
75}
76
77/** The summary row plus the key details of one study. */
78export function detailRow(study: Study): Row {
79 const p = study.protocolSection ?? {}
80 const status = p.statusModule ?? {}
81 const design = p.designModule ?? {}
82 const elig = p.eligibilityModule ?? {}
83 return {
84 ...summaryRow(study),
85 officialTitle: p.identificationModule?.officialTitle ?? null,
86 briefSummary: p.descriptionModule?.briefSummary ?? null,
87 primaryOutcomes: join(
88 p.outcomesModule?.primaryOutcomes?.map((o) => (o.timeFrame ? `${o.measure} [${o.timeFrame}]` : o.measure)),
89 ' | ',
90 ),
91 arms: join(
92 p.armsInterventionsModule?.armGroups?.map(
93 (a) => `${a.label}${a.type ? ` (${a.type})` : ''}${a.interventionNames?.length ? `: ${a.interventionNames.join(', ')}` : ''}`,
94 ),
95 ' | ',
96 ),
97 allocation: design.designInfo?.allocation ?? null,
98 masking: design.designInfo?.maskingInfo?.masking ?? null,
99 primaryPurpose: design.designInfo?.primaryPurpose ?? null,
100 enrollmentType: design.enrollmentInfo?.type ?? null,
101 sex: elig.sex ?? null,
102 minimumAge: elig.minimumAge ?? null,
103 maximumAge: elig.maximumAge ?? null,
104 healthyVolunteers: elig.healthyVolunteers ?? null,
105 eligibilityCriteria: elig.eligibilityCriteria ? elig.eligibilityCriteria.slice(0, 4000) : null,
106 locationsCount: p.contactsLocationsModule?.locations?.length ?? 0,
107 startDateType: status.startDateStruct?.type ?? null,
108 completionDateType: status.completionDateStruct?.type ?? null,
109 studyFirstPostDate: status.studyFirstPostDateStruct?.date ?? null,
110 resultsFirstPostDate: status.resultsFirstPostDateStruct?.date ?? null,
111 }
112}
113
114/** A list as one string, or null when empty. */
115function join(items: (string | undefined)[] | undefined, separator = '; '): string | null {
116 const kept = (items ?? []).filter((s): s is string => Boolean(s))
117 return kept.length ? kept.join(separator) : null
118}