// How a ClinicalTrials.gov v2 study is read into flat rows. /** The parts of a study record this plugin reads, as the API answers them. */ export interface Study { protocolSection?: { identificationModule?: { nctId?: string; briefTitle?: string; officialTitle?: string } statusModule?: { overallStatus?: string startDateStruct?: { date?: string; type?: string } primaryCompletionDateStruct?: { date?: string; type?: string } completionDateStruct?: { date?: string; type?: string } studyFirstPostDateStruct?: { date?: string } resultsFirstPostDateStruct?: { date?: string } lastUpdatePostDateStruct?: { date?: string } } sponsorCollaboratorsModule?: { leadSponsor?: { name?: string; class?: string } collaborators?: { name?: string }[] } descriptionModule?: { briefSummary?: string } conditionsModule?: { conditions?: string[] } designModule?: { studyType?: string phases?: string[] designInfo?: { allocation?: string; primaryPurpose?: string; maskingInfo?: { masking?: string } } enrollmentInfo?: { count?: number; type?: string } } armsInterventionsModule?: { armGroups?: { label?: string; type?: string; interventionNames?: string[] }[] interventions?: { type?: string; name?: string }[] } outcomesModule?: { primaryOutcomes?: { measure?: string; timeFrame?: string }[] } eligibilityModule?: { eligibilityCriteria?: string sex?: string minimumAge?: string maximumAge?: string healthyVolunteers?: boolean } contactsLocationsModule?: { locations?: unknown[] } } hasResults?: boolean } export type Row = Record /** One flat row for a search result. */ export function summaryRow(study: Study): Row { const p = study.protocolSection ?? {} const id = p.identificationModule ?? {} const status = p.statusModule ?? {} const sponsors = p.sponsorCollaboratorsModule ?? {} const design = p.designModule ?? {} const nctId = id.nctId ?? '' return { nctId, briefTitle: id.briefTitle ?? null, leadSponsor: sponsors.leadSponsor?.name ?? null, sponsorClass: sponsors.leadSponsor?.class ?? null, collaborators: join(sponsors.collaborators?.map((c) => c.name)), phases: join(design.phases), overallStatus: status.overallStatus ?? null, startDate: status.startDateStruct?.date ?? null, primaryCompletionDate: status.primaryCompletionDateStruct?.date ?? null, primaryCompletionType: status.primaryCompletionDateStruct?.type ?? null, completionDate: status.completionDateStruct?.date ?? null, conditions: join(p.conditionsModule?.conditions), interventions: join(p.armsInterventionsModule?.interventions?.map((i) => (i.type ? `${i.type}: ${i.name}` : i.name))), enrollment: design.enrollmentInfo?.count ?? null, studyType: design.studyType ?? null, hasResults: study.hasResults ?? false, lastUpdatePostDate: status.lastUpdatePostDateStruct?.date ?? null, url: `https://clinicaltrials.gov/study/${nctId}`, } } /** The summary row plus the key details of one study. */ export function detailRow(study: Study): Row { const p = study.protocolSection ?? {} const status = p.statusModule ?? {} const design = p.designModule ?? {} const elig = p.eligibilityModule ?? {} return { ...summaryRow(study), officialTitle: p.identificationModule?.officialTitle ?? null, briefSummary: p.descriptionModule?.briefSummary ?? null, primaryOutcomes: join( p.outcomesModule?.primaryOutcomes?.map((o) => (o.timeFrame ? `${o.measure} [${o.timeFrame}]` : o.measure)), ' | ', ), arms: join( p.armsInterventionsModule?.armGroups?.map( (a) => `${a.label}${a.type ? ` (${a.type})` : ''}${a.interventionNames?.length ? `: ${a.interventionNames.join(', ')}` : ''}`, ), ' | ', ), allocation: design.designInfo?.allocation ?? null, masking: design.designInfo?.maskingInfo?.masking ?? null, primaryPurpose: design.designInfo?.primaryPurpose ?? null, enrollmentType: design.enrollmentInfo?.type ?? null, sex: elig.sex ?? null, minimumAge: elig.minimumAge ?? null, maximumAge: elig.maximumAge ?? null, healthyVolunteers: elig.healthyVolunteers ?? null, eligibilityCriteria: elig.eligibilityCriteria ? elig.eligibilityCriteria.slice(0, 4000) : null, locationsCount: p.contactsLocationsModule?.locations?.length ?? 0, startDateType: status.startDateStruct?.type ?? null, completionDateType: status.completionDateStruct?.type ?? null, studyFirstPostDate: status.studyFirstPostDateStruct?.date ?? null, resultsFirstPostDate: status.resultsFirstPostDateStruct?.date ?? null, } } /** A list as one string, or null when empty. */ function join(items: (string | undefined)[] | undefined, separator = '; '): string | null { const kept = (items ?? []).filter((s): s is string => Boolean(s)) return kept.length ? kept.join(separator) : null }