diff --git a/spec/clinicaltrialsgov.spec.ts b/spec/clinicaltrialsgov.spec.ts index c7574cc..c29ca02 100644 --- a/spec/clinicaltrialsgov.spec.ts +++ b/spec/clinicaltrialsgov.spec.ts @@ -938,7 +938,7 @@ describe('ClinicalTrialsGovService', () => { }); describe('#updateResearchStudy', () => { - it('forwards to updateResearchStudyWithClinicalStudy', () => { + it('forwards to createResearchStudyFromClinicalStudy', () => { const service = createMemoryCTGovService(); const testResearchStudy = createResearchStudy('test'); const testClinicalStudy = createClinicalStudy(); diff --git a/spec/index.spec.ts b/spec/index.spec.ts index 466ac75..e33fcbf 100644 --- a/spec/index.spec.ts +++ b/spec/index.spec.ts @@ -6,7 +6,7 @@ describe('index', () => { expect(ctms.ResearchStudy).toBeDefined(); expect(ctms.ClinicalTrialMatchingService).toBeDefined(); expect(ctms.ClinicalTrialsGovAPI).toBeDefined(); - expect(ctms.updateResearchStudyWithClinicalStudy).toBeDefined(); + expect(ctms.createResearchStudyFromClinicalStudy).toBeDefined(); expect(ctms.CodeMapper).toBeDefined(); expect(ctms.CodeSystemEnum).toBeDefined(); expect(ctms.ClientError).toBeDefined(); diff --git a/spec/study-trial-converter.spec.ts b/spec/study-trial-converter.spec.ts index 53e1102..b8f5a39 100644 --- a/spec/study-trial-converter.spec.ts +++ b/spec/study-trial-converter.spec.ts @@ -1,6 +1,6 @@ import { Address, FhirResource, Location, ResearchStudy, PlanDefinition } from 'fhir/r4'; import { getContainedResource, ResearchStudy as ResearchStudyObj } from '../src/research-study'; -import { updateResearchStudyWithClinicalStudy } from '../src/study-fhir-converter'; +import { createResearchStudyFromClinicalStudy } from '../src/study-fhir-converter'; import { DateType, DesignAllocation, @@ -28,7 +28,7 @@ describe('filling out a partial trial', () => { let updatedTrial: ResearchStudy; beforeAll(async function () { - updatedTrial = updateResearchStudyWithClinicalStudy(study, sampleStudy as Study); + updatedTrial = createResearchStudyFromClinicalStudy(sampleStudy as Study, study); }); it('fills in inclusion criteria', () => { @@ -66,11 +66,11 @@ describe('filling out a partial trial', () => { } }); - it('does not overwrite existing categories', () => { + it('overwrites existing categories', () => { const researchStudy = new ResearchStudyObj('id'); researchStudy.category = [{ text: 'Study Type: Do Not Replace' }]; - updateResearchStudyWithClinicalStudy(researchStudy, { + createResearchStudyFromClinicalStudy({ protocolSection: { designModule: { studyType: StudyType.INTERVENTIONAL, @@ -86,7 +86,7 @@ describe('filling out a partial trial', () => { } } } - }); + }, researchStudy); expect(researchStudy.category).toBeDefined(); if (researchStudy.category) { @@ -95,7 +95,7 @@ describe('filling out a partial trial', () => { expect(categories).toHaveSize(7); expect(categories).toEqual( jasmine.arrayContaining([ - 'Study Type: Do Not Replace', + 'Study Type: Interventional', 'Intervention Model: Parallel', 'Primary Purpose: Treatment', 'Masking: None', @@ -107,25 +107,6 @@ describe('filling out a partial trial', () => { } }); - it('will retain old categories if not part of standard study design', () => { - const researchStudy = new ResearchStudyObj('id'); - // Empty category but there is an object there for the sake of this test. - researchStudy.category = [{}]; - - updateResearchStudyWithClinicalStudy(researchStudy, { - protocolSection: { - designModule: { - studyType: StudyType.INTERVENTIONAL - } - } - }); - - expect(researchStudy.category).toBeDefined(); - if (researchStudy.category) { - expect(researchStudy.category).toHaveSize(2); - } - }); - it('fills in arms', () => { expect(updatedTrial.arm).toBeDefined(); if (updatedTrial.arm) { @@ -191,8 +172,7 @@ describe('filling out a partial trial', () => { }); it('fills in interventions even without arms', () => { - const researchStudy = new ResearchStudyObj('id'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + const result = createResearchStudyFromClinicalStudy({ protocolSection: { armsInterventionsModule: { interventions: [ @@ -231,8 +211,7 @@ describe('filling out a partial trial', () => { }); it('fills in interventions with description and subtitle', () => { - const researchStudy = new ResearchStudyObj('id'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + const result = createResearchStudyFromClinicalStudy({ protocolSection: { armsInterventionsModule: { interventions: [ @@ -273,8 +252,7 @@ describe('filling out a partial trial', () => { }); it('falls back on the intervention model description if no intervention model is given', () => { - const researchStudy = new ResearchStudyObj('id'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + const result = createResearchStudyFromClinicalStudy({ protocolSection: { designModule: { designInfo: { @@ -287,8 +265,7 @@ describe('filling out a partial trial', () => { }); it('fills in period', () => { - const researchStudy = new ResearchStudyObj('id'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + const result = createResearchStudyFromClinicalStudy({ protocolSection: { statusModule: { startDateStruct: { @@ -314,8 +291,7 @@ describe('filling out a partial trial', () => { }); it('fills in start of period even without end', () => { - const researchStudy = new ResearchStudyObj('id'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + const result = createResearchStudyFromClinicalStudy({ protocolSection: { statusModule: { startDateStruct: { @@ -336,8 +312,7 @@ describe('filling out a partial trial', () => { }); it('fills in end of period even without start', () => { - const researchStudy = new ResearchStudyObj('id'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + const result = createResearchStudyFromClinicalStudy({ protocolSection: { statusModule: { completionDateStruct: { @@ -358,8 +333,7 @@ describe('filling out a partial trial', () => { }); it('does not fill in period if not a real date', () => { - const researchStudy = new ResearchStudyObj('id'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + const result = createResearchStudyFromClinicalStudy({ protocolSection: { statusModule: { startDateStruct: { @@ -382,8 +356,7 @@ describe('filling out a partial trial', () => { }); it('fills out the status', () => { - const actual = updateResearchStudyWithClinicalStudy( - { resourceType: 'ResearchStudy', status: 'active' }, + const actual = createResearchStudyFromClinicalStudy( { protocolSection: { statusModule: { @@ -395,9 +368,8 @@ describe('filling out a partial trial', () => { expect(actual.status).toEqual('completed'); }); - it('leaves status alone if unavailable', () => { - const actual = updateResearchStudyWithClinicalStudy( - { resourceType: 'ResearchStudy', status: 'active' }, + it('defaults to active if status is unavailable', () => { + const actual = createResearchStudyFromClinicalStudy( { // Lie about types protocolSection: { @@ -412,8 +384,7 @@ describe('filling out a partial trial', () => { }); it('fills out conditions', () => { - const actual = updateResearchStudyWithClinicalStudy( - { resourceType: 'ResearchStudy', status: 'active' }, + const actual = createResearchStudyFromClinicalStudy( { protocolSection: { conditionsModule: { @@ -431,8 +402,7 @@ describe('filling out a partial trial', () => { }); it('fills in contact', () => { - const researchStudy = new ResearchStudyObj('id'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + const result = createResearchStudyFromClinicalStudy({ protocolSection: { contactsLocationsModule: { centralContacts: [ @@ -476,8 +446,7 @@ describe('filling out a partial trial', () => { }); it('fills in contacts even with missing information', () => { - const researchStudy = new ResearchStudyObj('id'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + const result = createResearchStudyFromClinicalStudy({ protocolSection: { contactsLocationsModule: { centralContacts: [ @@ -512,10 +481,10 @@ describe('filling out a partial trial', () => { } }); - it('does not overwrite site data', () => { + it('overwrites site data', () => { const researchStudy = new ResearchStudyObj('id'); - const location = researchStudy.addSite('Example'); - const result = updateResearchStudyWithClinicalStudy(researchStudy, { + researchStudy.addSite('Example'); + const result = createResearchStudyFromClinicalStudy({ protocolSection: { contactsLocationsModule: { locations: [ @@ -527,38 +496,14 @@ describe('filling out a partial trial', () => { } }); expect(result.site).toBeDefined(); - const sites = result.site; - if (sites) { - expect(sites.length).toEqual(1); - if (sites[0]) { - expect(sites[0].reference).toEqual('#' + location.id); - if (location.id) { - const actualLocation = getContainedResource(result, location.id); - expect(actualLocation).not.toBeNull(); - if (actualLocation) { - expect(actualLocation.resourceType).toEqual('Location'); - expect((actualLocation as Location).name).toEqual('Example'); - } - } else { - fail('location.id not defined'); - } - } else { - fail('sites[0] undefined'); - } - } + // FIXME: Verify that the sites are from thte test data }); - it('does not alter a filled out trial', () => { + it('replaces the data in a filled out study', () => { // Clone the trial in the dumbest but also most sensible way const exampleStudy: ResearchStudy = JSON.parse(JSON.stringify(trialFilled)); - updateResearchStudyWithClinicalStudy(exampleStudy, sampleStudy as Study); - // Currently active gets overwritten intentioanlly, so set the example - // back to its original value even if it changed - // (Note that the "as" *should* verify that the underlying JSON value is - // in fact valid at compile time. I think.) - exampleStudy.status = trialFilled.status as ResearchStudy['status']; - // Nothing should have changed - expect(exampleStudy).toEqual(trialFilled as ResearchStudy); + createResearchStudyFromClinicalStudy(sampleStudy as Study, exampleStudy); + // TODO: Figure out the data that should have changed }); function expectTelecom(location: Location, type: 'phone' | 'email', expectedValue: string | null) { @@ -622,8 +567,7 @@ describe('filling out a partial trial', () => { } it('fills out sites as expected', () => { - const result = updateResearchStudyWithClinicalStudy( - { resourceType: 'ResearchStudy', status: 'active' }, + const result = createResearchStudyFromClinicalStudy( { protocolSection: { contactsLocationsModule: { @@ -702,7 +646,7 @@ describe('filling out a partial trial', () => { }); function expectEmptyResearchStudy(researchStudy: ResearchStudy): void { - // Technically this is just checking fields updateResearchStudyWithClinicalStudy may change + // Technically this is just checking fields createResearchStudyFromClinicalStudy may change expect(researchStudy.contained).withContext('contained').not.toBeDefined(); expect(researchStudy.enrollment).withContext('enrollment').not.toBeDefined(); expect(researchStudy.description).withContext('description').not.toBeDefined(); @@ -716,11 +660,11 @@ describe('filling out a partial trial', () => { it("handles JSON with missing data (doesn't crash)", () => { let researchStudy: ResearchStudy; // According to the schema, literally everything is optional, so an empty object should "work" - researchStudy = updateResearchStudyWithClinicalStudy(new ResearchStudyObj('id'), {}); + researchStudy = createResearchStudyFromClinicalStudy({}); // Expect nothing to have changed expectEmptyResearchStudy(researchStudy); // Some partial JSON - researchStudy = updateResearchStudyWithClinicalStudy(new ResearchStudyObj('id'), { + researchStudy = createResearchStudyFromClinicalStudy({ protocolSection: { eligibilityModule: { genderBased: false, @@ -731,7 +675,7 @@ describe('filling out a partial trial', () => { }); expectEmptyResearchStudy(researchStudy); // Some bad JSON that shouldn't cause issues - researchStudy = updateResearchStudyWithClinicalStudy(new ResearchStudyObj('id'), { + researchStudy = createResearchStudyFromClinicalStudy({ protocolSection: { contactsLocationsModule: { locations: [undefined as unknown as StudyLocation] diff --git a/src/clinicaltrialsgov.ts b/src/clinicaltrialsgov.ts index 0b78540..b139c64 100644 --- a/src/clinicaltrialsgov.ts +++ b/src/clinicaltrialsgov.ts @@ -20,7 +20,7 @@ import { debuglog } from 'util'; import { ResearchStudy } from 'fhir/r4'; import { SearchBundleEntry as SearchSetEntry } from './searchset'; import { ClinicalTrialsGovAPI, Study } from './clinicaltrialsgov-api'; -import { updateResearchStudyWithClinicalStudy } from './study-fhir-converter'; +import { createResearchStudyFromClinicalStudy } from './study-fhir-converter'; import * as sqlite from 'sqlite'; import * as sqlite3 from 'sqlite3'; @@ -690,7 +690,7 @@ export class ClinicalTrialsGovService { * @param clinicalStudy the clinical study to update it with */ updateResearchStudy(researchStudy: ResearchStudy, clinicalStudy: Study): void { - updateResearchStudyWithClinicalStudy(researchStudy, clinicalStudy); + createResearchStudyFromClinicalStudy(clinicalStudy, researchStudy); } /** diff --git a/src/index.ts b/src/index.ts index 38bf1ad..a08df53 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ export { CodeMapper, CodeSystemEnum } from './codeMapper'; export * from './mcodeextractor'; export { Study } from './ctg-api'; export { ClinicalTrialsGovAPI } from './clinicaltrialsgov-api'; -export { updateResearchStudyWithClinicalStudy } from './study-fhir-converter'; +export { createResearchStudyFromClinicalStudy } from './study-fhir-converter'; // The export { v } from "mod" forms do not appear to work for types yet, so // they have to be imported and then exported... diff --git a/src/study-fhir-converter.ts b/src/study-fhir-converter.ts index 8860bf2..00cfad9 100644 --- a/src/study-fhir-converter.ts +++ b/src/study-fhir-converter.ts @@ -81,93 +81,86 @@ function convertArrayToCodeableConcept(trialConditions: string[]): CodeableConce } /** - * Updates a research study with data from a clinical study off the ClinicalTrials.gov website. This will only update - * fields that do not have data, it will not overwrite any existing data. * - * Mapping as defined by https://www.hl7.org/fhir/researchstudy-mappings.html#clinicaltrials-gov - * - * @param result the research study to update - * @param study the clinical study to use to update + * @param study the study to base the result on + * @param result if given, keeps whatever data is already in the result that + * isn't in the ClinicalTrials.gov study object (all other data will be + * overwritten on the passed object) + * @returns the resulting study, either a new object if result was + * null/undefined, or the passed ResearchStudy */ -export function updateResearchStudyWithClinicalStudy(result: ResearchStudy, study: Study): ResearchStudy { +export function createResearchStudyFromClinicalStudy(study: Study, result?: ResearchStudy): ResearchStudy { + if (!result) { + result = { + resourceType: 'ResearchStudy', + status: 'active' + }; + } + const protocolSection = study.protocolSection; // If there is no protocol section, we can't do anything. if (!protocolSection) { return result; } - if (!result.enrollment) { - const eligibility = protocolSection.eligibilityModule; - if (eligibility) { - const criteria = eligibility.eligibilityCriteria; - if (criteria) { - const group: Group = { resourceType: 'Group', id: 'group' + result.id, type: 'person', actual: false }; - const reference = addContainedResource(result, group); - reference.display = criteria; - result.enrollment = [reference]; - } - } - } - if (!result.description) { - const briefSummary = protocolSection.descriptionModule?.briefSummary; - if (briefSummary) { - result.description = briefSummary; + const eligibility = protocolSection.eligibilityModule; + if (eligibility) { + const criteria = eligibility.eligibilityCriteria; + if (criteria) { + const group: Group = { resourceType: 'Group', id: 'group' + result.id, type: 'person', actual: false }; + const reference = addContainedResource(result, group); + reference.display = criteria; + result.enrollment = [reference]; } } + const briefSummary = protocolSection.descriptionModule?.briefSummary; + if (briefSummary) { + result.description = briefSummary; + } - if (!result.phase) { - const phase = protocolSection.designModule?.phases; - if (phase && phase.length > 0) { - // For now, just grab whatever the first phase is - // TODO: handle the somewhat weirder "phase-1-phase-2" items - const code = convertToResearchStudyPhase(phase[0]); - if (code) { - const display = code.replace(/-/g, ' '); - result.phase = { - coding: [ - { - system: 'http://terminology.hl7.org/CodeSystem/research-study-phase', - code: code, - display: display - } - ], - text: display - }; - } + const phase = protocolSection.designModule?.phases; + if (phase && phase.length > 0) { + // For now, just grab whatever the first phase is + // TODO: handle the somewhat weirder "phase-1-phase-2" items + const code = convertToResearchStudyPhase(phase[0]); + if (code) { + const display = code.replace(/-/g, ' '); + result.phase = { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/research-study-phase', + code: code, + display: display + } + ], + text: display + }; } } // ------- Category // Since we may not have all of the Study design in the result, we need to do a merge of data - const categories: CodeableConcept[] = result.category ? result.category : []; - - // We need to determine what categories have already been declared. - const types = categories.map((item) => { - const sep = item.text?.split(':'); - return sep ? sep[0] : ''; - }); + const categories: CodeableConcept[] = []; const studyType = study.protocolSection?.designModule?.studyType; - if (studyType && !types.includes('Study Type')) { + if (studyType) { categories.push({ text: 'Study Type: ' + convertToTitleCase(studyType) }); } const designInfo = protocolSection.designModule?.designInfo; if (designInfo) { - if (!types.includes('Intervention Model')) { - if (designInfo.interventionModel) { - categories.push({ text: 'Intervention Model: ' + convertToTitleCase(designInfo.interventionModel) }); - } else if (designInfo.interventionModelDescription) { - categories.push({ text: 'Intervention Model: ' + designInfo.interventionModelDescription }); - } + if (designInfo.interventionModel) { + categories.push({ text: 'Intervention Model: ' + convertToTitleCase(designInfo.interventionModel) }); + } else if (designInfo.interventionModelDescription) { + categories.push({ text: 'Intervention Model: ' + designInfo.interventionModelDescription }); } - if (designInfo.primaryPurpose && !types.includes('Primary Purpose')) { + if (designInfo.primaryPurpose) { categories.push({ text: 'Primary Purpose: ' + convertToTitleCase(designInfo.primaryPurpose) }); } const maskingInfo = designInfo.maskingInfo; - if (maskingInfo && !types.includes('Masking')) { + if (maskingInfo) { // It's unclear exactly how to convert this const masking = maskingInfo.masking ? convertToTitleCase(maskingInfo.masking) : maskingInfo.maskingDescription; if (masking) { @@ -175,15 +168,15 @@ export function updateResearchStudyWithClinicalStudy(result: ResearchStudy, stud } } - if (designInfo.allocation && !types.includes('Allocation')) { + if (designInfo.allocation) { categories.push({ text: 'Allocation: ' + convertToTitleCase(designInfo.allocation) }); } - if (designInfo.timePerspective && !types.includes('Time Perspective')) { + if (designInfo.timePerspective) { categories.push({ text: 'Time Perspective: ' + convertToTitleCase(designInfo.timePerspective) }); } - if (designInfo.observationalModel && !types.includes('Observation Model')) { + if (designInfo.observationalModel) { categories.push({ text: 'Observation Model: ' + convertToTitleCase(designInfo.observationalModel) }); } } @@ -191,128 +184,98 @@ export function updateResearchStudyWithClinicalStudy(result: ResearchStudy, stud if (categories.length >= 1) result.category = categories; // ------- Category - // Right now, the default value for a research study is "active". If CT.G - // knows better, then allow it to override that. - if (!result.status || result.status == 'active') { - const overallStatus = protocolSection.statusModule?.lastKnownStatus; - if (overallStatus) { - const status = convertClincalStudyStatusToFHIRStatus(overallStatus); - if (typeof status !== 'undefined') result.status = status; - } + const overallStatus = protocolSection.statusModule?.lastKnownStatus; + if (overallStatus) { + const status = convertClincalStudyStatusToFHIRStatus(overallStatus); + if (typeof status !== 'undefined') result.status = status; } - if (!result.condition) { - if (protocolSection.conditionsModule?.conditions) { - result.condition = convertArrayToCodeableConcept(protocolSection.conditionsModule?.conditions); - } + if (protocolSection.conditionsModule?.conditions) { + result.condition = convertArrayToCodeableConcept(protocolSection.conditionsModule?.conditions); } - if (!result.site) { - const locations = protocolSection.contactsLocationsModule?.locations; - if (locations) { - let index = 0; - for (const location of locations) { - if (typeof location === 'object' && location != null) { - const fhirLocation: Location = { resourceType: 'Location', id: 'location-' + index++ }; - if (location.status) fhirLocation.status = convertRecruitmentStatusToLocationStatus(location.status); - if (location.facility) fhirLocation.name = location.facility; - if (location.city && location.country) { - // Also add the address information - fhirLocation.address = { use: 'work', city: location.city, country: location.country }; - if (location.state) { - fhirLocation.address.state = location.state; - } - if (location.zip) { - fhirLocation.address.postalCode = location.zip; - } + const locations = protocolSection.contactsLocationsModule?.locations; + if (locations) { + let index = 0; + for (const location of locations) { + if (typeof location === 'object' && location != null) { + const fhirLocation: Location = { resourceType: 'Location', id: 'location-' + index++ }; + if (location.status) fhirLocation.status = convertRecruitmentStatusToLocationStatus(location.status); + if (location.facility) fhirLocation.name = location.facility; + if (location.city && location.country) { + // Also add the address information + fhirLocation.address = { use: 'work', city: location.city, country: location.country }; + if (location.state) { + fhirLocation.address.state = location.state; } - // If we have an exact GPS coordinate, add that, too - const geoPoint = location.geoPoint; - if (geoPoint && typeof geoPoint.lat === 'number' && typeof geoPoint.lon === 'number') { - fhirLocation.position = { - longitude: geoPoint.lon, - latitude: geoPoint.lat - }; + if (location.zip) { + fhirLocation.address.postalCode = location.zip; } - if (location.contacts) { - for (const contact of location.contacts) { - if (contact.email) { - addToContainer(fhirLocation, 'telecom', { - system: 'email', - value: contact.email, - use: 'work' - }); - } - if (contact.phone) { - addToContainer(fhirLocation, 'telecom', { - system: 'phone', - value: contact.phone, - use: 'work' - }); - } + } + // If we have an exact GPS coordinate, add that, too + const geoPoint = location.geoPoint; + if (geoPoint && typeof geoPoint.lat === 'number' && typeof geoPoint.lon === 'number') { + fhirLocation.position = { + longitude: geoPoint.lon, + latitude: geoPoint.lat + }; + } + if (location.contacts) { + for (const contact of location.contacts) { + if (contact.email) { + addToContainer(fhirLocation, 'telecom', { + system: 'email', + value: contact.email, + use: 'work' + }); + } + if (contact.phone) { + addToContainer(fhirLocation, 'telecom', { + system: 'phone', + value: contact.phone, + use: 'work' + }); } } - addToContainer(result, 'site', addContainedResource(result, fhirLocation)); } + addToContainer(result, 'site', addContainedResource(result, fhirLocation)); } } } - if (!result.arm) { - const armGroups = protocolSection.armsInterventionsModule?.armGroups; - if (armGroups) { - for (const studyArm of armGroups) { - const label = studyArm.label; - if (label) { - const arm: ResearchStudyArm = { - name: label, - ...(studyArm.type && { - type: { - coding: [ - { - // It's unclear if there is any coding system for this, so for now, make it look like FHIR - code: studyArm.type.replace(/_/g, '-').toLowerCase(), - display: convertToTitleCase(studyArm.type) - } - ], - text: convertToTitleCase(studyArm.type) - } - }), - ...(studyArm.description && { description: studyArm.description }) - }; + const armGroups = protocolSection.armsInterventionsModule?.armGroups; + if (armGroups) { + for (const studyArm of armGroups) { + const label = studyArm.label; + if (label) { + const arm: ResearchStudyArm = { + name: label, + ...(studyArm.type && { + type: { + coding: [ + { + // It's unclear if there is any coding system for this, so for now, make it look like FHIR + code: studyArm.type.replace(/_/g, '-').toLowerCase(), + display: convertToTitleCase(studyArm.type) + } + ], + text: convertToTitleCase(studyArm.type) + } + }), + ...(studyArm.description && { description: studyArm.description }) + }; - addToContainer(result, 'arm', arm); - } + addToContainer(result, 'arm', arm); } } } - if (!result.protocol) { - const interventions = protocolSection.armsInterventionsModule?.interventions; - if (interventions) { - let index = 0; - for (const intervention of interventions) { - if (intervention.armGroupLabels) { - for (const armGroupLabel of intervention.armGroupLabels) { - let plan: PlanDefinition = { resourceType: 'PlanDefinition', status: 'unknown', id: 'plan-' + index++ }; - - plan = { - ...plan, - ...(intervention.description && { description: intervention.description }), - ...(intervention.name && { title: intervention.name }), - ...(intervention.otherNames && - intervention.otherNames.length > 0 && { subtitle: intervention.otherNames[0] }), - ...(intervention.type && { type: { text: convertToTitleCase(intervention.type) } }), - ...{ subjectCodeableConcept: { text: armGroupLabel } } - }; - - addToContainer( - result, - 'protocol', - addContainedResource(result, plan) - ); - } - } else { + const interventions = protocolSection.armsInterventionsModule?.interventions; + if (interventions) { + let index = 0; + for (const intervention of interventions) { + if (intervention.armGroupLabels) { + for (const armGroupLabel of intervention.armGroupLabels) { let plan: PlanDefinition = { resourceType: 'PlanDefinition', status: 'unknown', id: 'plan-' + index++ }; plan = { @@ -321,57 +284,66 @@ export function updateResearchStudyWithClinicalStudy(result: ResearchStudy, stud ...(intervention.name && { title: intervention.name }), ...(intervention.otherNames && intervention.otherNames.length > 0 && { subtitle: intervention.otherNames[0] }), - ...(intervention.type && { type: { text: convertToTitleCase(intervention.type) } }) + ...(intervention.type && { type: { text: convertToTitleCase(intervention.type) } }), + ...{ subjectCodeableConcept: { text: armGroupLabel } } }; addToContainer(result, 'protocol', addContainedResource(result, plan)); } + } else { + let plan: PlanDefinition = { resourceType: 'PlanDefinition', status: 'unknown', id: 'plan-' + index++ }; + + plan = { + ...plan, + ...(intervention.description && { description: intervention.description }), + ...(intervention.name && { title: intervention.name }), + ...(intervention.otherNames && + intervention.otherNames.length > 0 && { subtitle: intervention.otherNames[0] }), + ...(intervention.type && { type: { text: convertToTitleCase(intervention.type) } }) + }; + + addToContainer(result, 'protocol', addContainedResource(result, plan)); } } } - if (!result.contact) { - const contacts = protocolSection.contactsLocationsModule?.centralContacts; - - if (contacts) { - for (const contact of contacts) { - if (contact != undefined) { - const contactName = contact.name; - if (contactName) { - const fhirContact: ContactDetail = { name: contactName }; - if (contact.email) { - addToContainer(fhirContact, 'telecom', { - system: 'email', - value: contact.email, - use: 'work' - }); - } - if (contact.phone) { - addToContainer(fhirContact, 'telecom', { - system: 'phone', - value: contact.phone, - use: 'work' - }); - } - addToContainer(result, 'contact', fhirContact); + const contacts = protocolSection.contactsLocationsModule?.centralContacts; + + if (contacts) { + for (const contact of contacts) { + if (contact != undefined) { + const contactName = contact.name; + if (contactName) { + const fhirContact: ContactDetail = { name: contactName }; + if (contact.email) { + addToContainer(fhirContact, 'telecom', { + system: 'email', + value: contact.email, + use: 'work' + }); } + if (contact.phone) { + addToContainer(fhirContact, 'telecom', { + system: 'phone', + value: contact.phone, + use: 'work' + }); + } + addToContainer(result, 'contact', fhirContact); } } } } - - if (!result.period) { - const startDate = protocolSection.statusModule?.startDateStruct?.date; - const completionDate = protocolSection.statusModule?.completionDateStruct?.date; - if (startDate || completionDate) { - // Set the period object as appropriate - const period = { - ...(startDate && isFhirDate(startDate) && { start: startDate }), - ...(completionDate && isFhirDate(completionDate) && { end: completionDate }) - }; - - if (Object.keys(period).length != 0) result.period = period; - } + const startDate = protocolSection.statusModule?.startDateStruct?.date; + const completionDate = protocolSection.statusModule?.completionDateStruct?.date; + if (startDate || completionDate) { + // Set the period object as appropriate + const period = { + ...(startDate && isFhirDate(startDate) && { start: startDate }), + ...(completionDate && isFhirDate(completionDate) && { end: completionDate }) + }; + + if (Object.keys(period).length != 0) result.period = period; } return result;