diff --git a/src/common-model/entities.ts b/src/common-model/entities.ts index 7ec0d83e..bc53ea75 100644 --- a/src/common-model/entities.ts +++ b/src/common-model/entities.ts @@ -17,8 +17,8 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import { z as zod } from 'zod'; import { entities as dictionaryEntities } from '@overturebio-stack/lectern-client'; +import { z as zod } from 'zod'; // this is temporary to keep code compiling until surgery is ready in dictionary, to be removed in favor of // the surgery in ClinicalEntitySchemaNames @@ -101,10 +101,11 @@ export type ClinicalFields = | PrimaryDiagnosisFieldsEnum | FamilyHistoryFieldsEnum | TreatmentFieldsEnum - | TherapyRxNormFields + | TherapyRxNormFieldsEnum + | TherapyDrugFieldsEnum | RadiationFieldsEnum | SurgeryFieldsEnum - | CommonTherapyFields + | CommonTherapyFieldsEnum | ExposureFieldsEnum | ComorbidityFieldsEnum | BiomarkerFieldsEnum; @@ -201,12 +202,18 @@ export enum TreatmentFieldsEnum { treatment_duration = 'treatment_duration', } -export enum TherapyRxNormFields { +export enum TherapyRxNormFieldsEnum { drug_name = 'drug_name', drug_rxnormid = 'drug_rxnormcui', } -export enum CommonTherapyFields { +export enum TherapyDrugFieldsEnum { + drug_database = 'drug_database', + drug_id = 'drug_id', + drug_term = 'drug_term', +} + +export enum CommonTherapyFieldsEnum { program_id = 'program_id', submitter_donor_id = 'submitter_donor_id', submitter_treatment_id = 'submitter_treatment_id', @@ -223,7 +230,7 @@ export enum RadiationFieldsEnum { reference_radiation_treatment_id = 'reference_radiation_treatment_id', } -export enum ImmunotherapyFields { +export enum ImmunotherapyFieldsEnum { immunotherapy_type = 'immunotherapy_type', } @@ -282,29 +289,29 @@ export const ClinicalUniqueIdentifier: TypeEntitySchemaNameToIndenfiterType = { [ClinicalEntitySchemaNames.FOLLOW_UP]: FollowupFieldsEnum.submitter_follow_up_id, [ClinicalEntitySchemaNames.TREATMENT]: TreatmentFieldsEnum.submitter_treatment_id, [ClinicalEntitySchemaNames.CHEMOTHERAPY]: [ - CommonTherapyFields.submitter_donor_id, - CommonTherapyFields.submitter_treatment_id, - TherapyRxNormFields.drug_rxnormid, + CommonTherapyFieldsEnum.submitter_donor_id, + CommonTherapyFieldsEnum.submitter_treatment_id, + TherapyRxNormFieldsEnum.drug_rxnormid, ], [ClinicalEntitySchemaNames.IMMUNOTHERAPY]: [ - CommonTherapyFields.submitter_donor_id, - CommonTherapyFields.submitter_treatment_id, - TherapyRxNormFields.drug_rxnormid, + CommonTherapyFieldsEnum.submitter_donor_id, + CommonTherapyFieldsEnum.submitter_treatment_id, + TherapyRxNormFieldsEnum.drug_rxnormid, ], [SURGERY_SCHEMA_NAME]: [ - CommonTherapyFields.submitter_donor_id, - CommonTherapyFields.submitter_treatment_id, + CommonTherapyFieldsEnum.submitter_donor_id, + CommonTherapyFieldsEnum.submitter_treatment_id, SpecimenFieldsEnum.submitter_specimen_id, ], [ClinicalEntitySchemaNames.RADIATION]: [ - CommonTherapyFields.submitter_donor_id, - CommonTherapyFields.submitter_treatment_id, + CommonTherapyFieldsEnum.submitter_donor_id, + CommonTherapyFieldsEnum.submitter_treatment_id, RadiationFieldsEnum.radiation_therapy_modality, ], [ClinicalEntitySchemaNames.HORMONE_THERAPY]: [ - CommonTherapyFields.submitter_donor_id, - CommonTherapyFields.submitter_treatment_id, - TherapyRxNormFields.drug_rxnormid, + CommonTherapyFieldsEnum.submitter_donor_id, + CommonTherapyFieldsEnum.submitter_treatment_id, + TherapyRxNormFieldsEnum.drug_rxnormid, ], [ClinicalEntitySchemaNames.COMORBIDITY]: [ ComorbidityFieldsEnum.submitter_donor_id, diff --git a/src/submission/submission-entities.ts b/src/submission/submission-entities.ts index c1381abd..851636fa 100644 --- a/src/submission/submission-entities.ts +++ b/src/submission/submission-entities.ts @@ -17,24 +17,24 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import { DeepReadonly } from 'deep-freeze'; import { entities as dictionaryEntities } from '@overturebio-stack/lectern-client'; +import { DeepReadonly } from 'deep-freeze'; import { + BiomarkerFieldsEnum, ClinicalEntitySchemaNames, + ClinicalTherapyType, + CommonTherapyFieldsEnum, + ComorbidityFieldsEnum, DonorFieldsEnum, - SpecimenFieldsEnum, - PrimaryDiagnosisFieldsEnum, + ExposureFieldsEnum, + FamilyHistoryFieldsEnum, FollowupFieldsEnum, - TreatmentFieldsEnum, - TherapyRxNormFields, - CommonTherapyFields, + ImmunotherapyFieldsEnum, + PrimaryDiagnosisFieldsEnum, RadiationFieldsEnum, - ClinicalTherapyType, - ImmunotherapyFields, - FamilyHistoryFieldsEnum, - ExposureFieldsEnum, - ComorbidityFieldsEnum, - BiomarkerFieldsEnum, + SpecimenFieldsEnum, + TherapyRxNormFieldsEnum, + TreatmentFieldsEnum, } from '../common-model/entities'; /** @@ -170,6 +170,7 @@ export enum DataValidationErrors { INVALID_LOST_TO_FOLLOW_UP_ID = 'INVALID_LOST_TO_FOLLOW_UP_ID', INVALID_SUBMISSION_AFTER_LOST_TO_FOLLOW_UP = 'INVALID_SUBMISSION_AFTER_LOST_TO_FOLLOW_UP', INVALID_DIAGNOSIS_AFTER_LOST_TO_FOLLOW_UP = 'INVALID_DIAGNOSIS_AFTER_LOST_TO_FOLLOW_UP', + INVALID_DRUG_INFO = 'INVALID_DRUG_INFO', } export type RegistrationStat = Array<{ @@ -406,19 +407,19 @@ export const ClinicalEntityToEnumFieldsMap: Record = { diff --git a/src/submission/submission-error-messages.ts b/src/submission/submission-error-messages.ts index 723dbd0c..42e0c9a9 100644 --- a/src/submission/submission-error-messages.ts +++ b/src/submission/submission-error-messages.ts @@ -17,8 +17,8 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import { SubmissionBatchErrorTypes } from './submission-entities'; import _ from 'lodash'; +import { SubmissionBatchErrorTypes } from './submission-entities'; const ERROR_MESSAGES: { [key: string]: (errorData: any) => string } = { /* ***************** * @@ -140,6 +140,7 @@ const ERROR_MESSAGES: { [key: string]: (errorData: any) => string } = { info: { lost_to_followup_diagnosis_id, lost_to_followup_age, submitter_primary_diagnosis_id }, }) => `A clinical event that occurs after the donor was lost to follow up cannot be submitted. The donor was indicated to be lost to follow up at age ${lost_to_followup_age} after their primary diagnosis ("submitter_primary_diagnosis_id" = "${lost_to_followup_diagnosis_id}"), but a new primary diagnosis ("${submitter_primary_diagnosis_id}") that started after the donor was lost to follow up has been submitted. If the donor was found later on, then update the "lost_to_followup_after_clinical_event_id" field to be empty.`, + INVALID_DRUG_INFO: () => 'Please provide the missing drug information.', }; const BATCH_ERROR_MESSAGES: Record string> = { diff --git a/src/submission/submission-service.ts b/src/submission/submission-service.ts index 8990be22..0b443e75 100644 --- a/src/submission/submission-service.ts +++ b/src/submission/submission-service.ts @@ -30,14 +30,14 @@ import { Specimen, } from '../clinical/clinical-entities'; import { - donorDao, DONOR_DOCUMENT_FIELDS, FindByProgramAndSubmitterFilter, + donorDao, } from '../clinical/donor-repo'; import { ClinicalEntitySchemaNames, DonorFieldsEnum, - TherapyRxNormFields, + TherapyRxNormFieldsEnum, } from '../common-model/entities'; import * as dictionaryManager from '../dictionary/manager'; import { schemaFilter } from '../exception/property-exceptions/validation'; @@ -79,12 +79,12 @@ import { RegistrationStat, RegistrationStats, RevalidateClinicalSubmissionCommand, + SUBMISSION_STATE, SampleRegistrationFieldsEnum, SubmissionBatchError, SubmissionBatchErrorTypes, SubmissionValidationError, SubmissionValidationUpdate, - SUBMISSION_STATE, SubmittedRegistrationRecord, ValidateSubmissionResult, } from './submission-entities'; @@ -971,9 +971,9 @@ export namespace operations { } const recordWithRxNormPopulated = _.cloneDeep(r) as Record; // we replace the drug name to normalize the case - recordWithRxNormPopulated[TherapyRxNormFields.drug_name] = + recordWithRxNormPopulated[TherapyRxNormFieldsEnum.drug_name] = rxnormConceptLookupResult.rxNormRecord.str; - recordWithRxNormPopulated[TherapyRxNormFields.drug_rxnormid] = + recordWithRxNormPopulated[TherapyRxNormFieldsEnum.drug_rxnormid] = rxnormConceptLookupResult.rxNormRecord.rxcui; return { record: recordWithRxNormPopulated as dictionaryEntities.TypedDataRecord, @@ -989,12 +989,12 @@ export namespace operations { error: SubmissionValidationError | undefined; }> { // if the id is provided we do a look up and double check against the name (if provided) - if (isNotAbsent(therapyRecord[TherapyRxNormFields.drug_rxnormid] as string)) { - const rxcui = therapyRecord[TherapyRxNormFields.drug_rxnormid] as string; + if (isNotAbsent(therapyRecord[TherapyRxNormFieldsEnum.drug_rxnormid] as string)) { + const rxcui = therapyRecord[TherapyRxNormFieldsEnum.drug_rxnormid] as string; const rxRecords = await dbRxNormService.lookupByRxcui(rxcui.trim()); if (_.isEmpty(rxRecords)) { const error: SubmissionValidationError = { - fieldName: TherapyRxNormFields.drug_rxnormid, + fieldName: TherapyRxNormFieldsEnum.drug_rxnormid, index: index, info: {}, message: validationErrorMessage(DataValidationErrors.THERAPY_RXNORM_RXCUI_NOT_FOUND), @@ -1009,7 +1009,7 @@ export namespace operations { const matchingRecord = rxRecords.find( (rx) => rx.str.toLowerCase().trim() == - (therapyRecord[TherapyRxNormFields.drug_name] as string).toLowerCase().trim(), + (therapyRecord[TherapyRxNormFieldsEnum.drug_name] as string).toLowerCase().trim(), ); if (matchingRecord != undefined) { @@ -1021,7 +1021,7 @@ export namespace operations { const foundNames = rxRecords.map((r) => r.str); const error: SubmissionValidationError = { - fieldName: TherapyRxNormFields.drug_rxnormid, + fieldName: TherapyRxNormFieldsEnum.drug_rxnormid, index: index, info: {}, message: validationErrorMessage(DataValidationErrors.THERAPY_RXNORM_DRUG_NAME_INVALID, { @@ -1038,7 +1038,7 @@ export namespace operations { // nothing was provided // this shouldn't happen unless the schema is not validating correctly const error: SubmissionValidationError = { - fieldName: TherapyRxNormFields.drug_rxnormid, + fieldName: TherapyRxNormFieldsEnum.drug_rxnormid, index: index, info: {}, message: `an rxcui id or drug name is required`, diff --git a/src/submission/validation-clinical/surgery.ts b/src/submission/validation-clinical/surgery.ts index f55e9e9a..b7c1965c 100644 --- a/src/submission/validation-clinical/surgery.ts +++ b/src/submission/validation-clinical/surgery.ts @@ -22,21 +22,21 @@ import { isEqual, omit } from 'lodash'; import _ from 'mongoose-sequence'; import { Donor, Therapy } from '../../clinical/clinical-entities'; import { - DataValidationErrors, - SubmissionValidationError, - SubmissionValidationOutput, - SubmittedClinicalRecord, -} from '../submission-entities'; + ClinicalEntitySchemaNames, + CommonTherapyFieldsEnum, + DonorFieldsEnum, + SurgeryFieldsEnum, +} from '../../common-model/entities'; import { findClinicalObjects, getSingleClinicalObjectFromDonor, } from '../../common-model/functions'; import { - ClinicalEntitySchemaNames, - CommonTherapyFields, - DonorFieldsEnum, - SurgeryFieldsEnum, -} from '../../common-model/entities'; + DataValidationErrors, + SubmissionValidationError, + SubmissionValidationOutput, + SubmittedClinicalRecord, +} from '../submission-entities'; import { checkTreatmentHasCorrectTypeForTherapy, getTreatment } from './therapy'; import * as utils from './utils'; @@ -112,8 +112,8 @@ export const validate = async ( DataValidationErrors.DUPLICATE_SURGERY_WHEN_SPECIMEN_NOT_SUBMITTED, DonorFieldsEnum.submitter_donor_id, { - submitter_donor_id: therapyRecord[CommonTherapyFields.submitter_donor_id], - submitter_treatment_id: therapyRecord[CommonTherapyFields.submitter_treatment_id], + submitter_donor_id: therapyRecord[CommonTherapyFieldsEnum.submitter_donor_id], + submitter_treatment_id: therapyRecord[CommonTherapyFieldsEnum.submitter_treatment_id], }, ), ); @@ -147,8 +147,8 @@ function validateSurgeryByDonorAndTreatment( DataValidationErrors.SURGERY_TYPES_NOT_EQUAL, SurgeryFieldsEnum.submitter_specimen_id, { - submitter_donor_id: therapyRecord[CommonTherapyFields.submitter_donor_id], - submitter_treatment_id: therapyRecord[CommonTherapyFields.submitter_treatment_id], + submitter_donor_id: therapyRecord[CommonTherapyFieldsEnum.submitter_donor_id], + submitter_treatment_id: therapyRecord[CommonTherapyFieldsEnum.submitter_treatment_id], surgery_type: therapyRecord[SurgeryFieldsEnum.surgery_type], }, ); @@ -160,12 +160,12 @@ function findSubmittedSurgeryByDonorAndTreatment( existenDonor: DeepReadonly, therapyRecord: DeepReadonly, ) { - const submitterDonorId = therapyRecord[CommonTherapyFields.submitter_donor_id]; - const sumitterTreatmentId = therapyRecord[CommonTherapyFields.submitter_treatment_id]; + const submitterDonorId = therapyRecord[CommonTherapyFieldsEnum.submitter_donor_id]; + const sumitterTreatmentId = therapyRecord[CommonTherapyFieldsEnum.submitter_treatment_id]; return getSingleClinicalObjectFromDonor(existenDonor, ClinicalEntitySchemaNames.SURGERY, { clinicalInfo: { - [CommonTherapyFields.submitter_donor_id]: submitterDonorId as string, - [CommonTherapyFields.submitter_treatment_id]: sumitterTreatmentId as string, + [CommonTherapyFieldsEnum.submitter_donor_id]: submitterDonorId as string, + [CommonTherapyFieldsEnum.submitter_treatment_id]: sumitterTreatmentId as string, }, }) as DeepReadonly; } @@ -174,12 +174,12 @@ function findSurgeryInCurrentSubmission( mergedDonor: Donor, therapyRecord: DeepReadonly, ) { - const submitterDonorId = therapyRecord[CommonTherapyFields.submitter_donor_id]; - const sumitterTreatmentId = therapyRecord[CommonTherapyFields.submitter_treatment_id]; + const submitterDonorId = therapyRecord[CommonTherapyFieldsEnum.submitter_donor_id]; + const sumitterTreatmentId = therapyRecord[CommonTherapyFieldsEnum.submitter_treatment_id]; const sugeries = findClinicalObjects(mergedDonor, ClinicalEntitySchemaNames.SURGERY, { clinicalInfo: { - [CommonTherapyFields.submitter_donor_id]: submitterDonorId as string, - [CommonTherapyFields.submitter_treatment_id]: sumitterTreatmentId as string, + [CommonTherapyFieldsEnum.submitter_donor_id]: submitterDonorId as string, + [CommonTherapyFieldsEnum.submitter_treatment_id]: sumitterTreatmentId as string, }, }) as DeepReadonly[]; diff --git a/src/submission/validation-clinical/therapy.ts b/src/submission/validation-clinical/therapy.ts index 1c7a45b8..ebafef73 100644 --- a/src/submission/validation-clinical/therapy.ts +++ b/src/submission/validation-clinical/therapy.ts @@ -19,26 +19,28 @@ import { DeepReadonly } from 'deep-freeze'; import _ from 'lodash'; -import * as utils from './utils'; -import { - SubmissionValidationError, - SubmittedClinicalRecord, - DataValidationErrors, - SubmissionValidationOutput, - ClinicalSubmissionRecordsByDonorIdMap, -} from '../submission-entities'; +import { ClinicalInfo, Donor, Treatment } from '../../clinical/clinical-entities'; +import { donorDao } from '../../clinical/donor-repo'; +import { ClinicalDataQuery } from '../../clinical/types'; import { ClinicalEntitySchemaNames, + ClinicalFields, ClinicalTherapyType, RadiationFieldsEnum, + TherapyDrugFieldsEnum, TreatmentFieldsEnum, } from '../../common-model/entities'; import { getSingleClinicalObjectFromDonor } from '../../common-model/functions'; -import { donorDao } from '../../clinical/donor-repo'; -import { ClinicalInfo, Donor, Treatment } from '../../clinical/clinical-entities'; -import { ClinicalDataQuery } from '../../clinical/types'; import featureFlags from '../../feature-flags'; import { isValueEqual } from '../../utils'; +import { + ClinicalSubmissionRecordsByDonorIdMap, + DataValidationErrors, + SubmissionValidationError, + SubmissionValidationOutput, + SubmittedClinicalRecord, +} from '../submission-entities'; +import * as utils from './utils'; export const validate = async ( therapyRecord: DeepReadonly, @@ -68,6 +70,27 @@ export const validate = async ( errors = [...errors, ...crossFileErrors]; } + // Validation for Treatment Drug Fields + const drugTreatmentTypes = [ + 'chemotherapy', + 'hormonal therapy', + 'hormone_therapy', + 'immunotherapy', + ]; + const { treatment_type } = treatment.clinicalInfo; + const therapyType = treatment.therapies.find((therapy) => + isValueEqual(therapy.clinicalInfo, therapyRecord), + )?.therapyType; + + if ( + Array.isArray(treatment_type) && + therapyType && + treatment_type.some((treatment) => drugTreatmentTypes.includes(treatment.toLowerCase())) && + drugTreatmentTypes.includes(therapyType.toLowerCase()) + ) { + validateDrugTreatmentFields(therapyRecord, errors); + } + return { errors }; }; @@ -270,3 +293,32 @@ export function getTreatment( return treatment; } + +const validateDrugTreatmentFields = ( + therapyRecord: DeepReadonly, + errors: SubmissionValidationError[], +) => { + const { drug_name, drug_rxnormcui, drug_database, drug_term, drug_id } = therapyRecord; + const isRxNorm = Boolean(drug_name && drug_rxnormcui); + const isDrugDb = Boolean(drug_database && drug_id && drug_term); + + if (!isRxNorm && isDrugDb) { + // No RxNorm, All Drug DB Fields, no further validation + return; + } else if (!isRxNorm && !isDrugDb) { + // Not RxNorm, but Not All Drug Fields -> Error + const drugFields = { drug_database, drug_term, drug_id }; + for (const [fieldName, drugValue] of Object.entries(drugFields)) { + if (!drugValue) { + errors.push( + utils.buildSubmissionError( + therapyRecord, + DataValidationErrors.INVALID_DRUG_INFO, + fieldName as ClinicalFields, + {}, + ), + ); + } + } + } +}; diff --git a/src/submission/validation-clinical/treatment.ts b/src/submission/validation-clinical/treatment.ts index 04a3e428..5994046d 100644 --- a/src/submission/validation-clinical/treatment.ts +++ b/src/submission/validation-clinical/treatment.ts @@ -17,29 +17,29 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import { - SubmissionValidationError, - SubmittedClinicalRecord, - DataValidationErrors, - SubmissionValidationOutput, - DonorVitalStatusValues, -} from '../submission-entities'; +import { DeepReadonly } from 'deep-freeze'; +import _ from 'lodash'; +import { Donor, FollowUp, Treatment } from '../../clinical/clinical-entities'; import { ClinicalEntitySchemaNames, + ClinicalTherapySchemaNames, + ClinicalTherapyType, ClinicalUniqueIdentifier, TreatmentFieldsEnum, - ClinicalTherapyType, - ClinicalTherapySchemaNames, } from '../../common-model/entities'; -import { DeepReadonly } from 'deep-freeze'; -import { getTreatmentDetailException } from '../../exception/treatment-detail-exceptions/service'; -import { Donor, Treatment, FollowUp } from '../../clinical/clinical-entities'; -import * as utils from './utils'; -import _ from 'lodash'; import { - getSingleClinicalObjectFromDonor, getClinicalObjectsFromDonor, + getSingleClinicalObjectFromDonor, } from '../../common-model/functions'; +import { getTreatmentDetailException } from '../../exception/treatment-detail-exceptions/service'; +import { + DataValidationErrors, + DonorVitalStatusValues, + SubmissionValidationError, + SubmissionValidationOutput, + SubmittedClinicalRecord, +} from '../submission-entities'; +import * as utils from './utils'; import { checkClinicalEntityDoesntBelongToOtherDonor, checkRelatedEntityExists } from './utils'; export const validate = async ( diff --git a/test/unit/submission/stubs.ts b/test/unit/submission/stubs.ts index a460a2b9..301eeabf 100644 --- a/test/unit/submission/stubs.ts +++ b/test/unit/submission/stubs.ts @@ -21,11 +21,11 @@ import { Donor } from '../../../src/clinical/clinical-entities'; import { DonorFieldsEnum, FollowupFieldsEnum, - TreatmentFieldsEnum, - TherapyRxNormFields, PrimaryDiagnosisFieldsEnum, SpecimenFieldsEnum, SurgeryFieldsEnum, + TherapyRxNormFieldsEnum, + TreatmentFieldsEnum, } from '../../../src/common-model/entities'; /** @@ -372,8 +372,8 @@ export const stubs = { clinicalInfo: { [TreatmentFieldsEnum.submitter_treatment_id]: 'T_03', [TreatmentFieldsEnum.submitter_donor_id]: 'AB10', - [TherapyRxNormFields.drug_name]: 'd1', - [TherapyRxNormFields.drug_rxnormid]: '1234', + [TherapyRxNormFieldsEnum.drug_name]: 'd1', + [TherapyRxNormFieldsEnum.drug_rxnormid]: '1234', }, }, ], @@ -494,8 +494,8 @@ export const stubs = { [TreatmentFieldsEnum.program_id]: 'TEST-CA', [TreatmentFieldsEnum.submitter_donor_id]: 'ICGC_0002', [TreatmentFieldsEnum.submitter_treatment_id]: 'T_02', - [TherapyRxNormFields.drug_name]: 'Aminobenzoic Acid', - [TherapyRxNormFields.drug_rxnormid]: '74', + [TherapyRxNormFieldsEnum.drug_name]: 'Aminobenzoic Acid', + [TherapyRxNormFieldsEnum.drug_rxnormid]: '74', }, }, ], diff --git a/test/unit/submission/validation.spec.ts b/test/unit/submission/validation.spec.ts index 3d676510..9c6b6684 100644 --- a/test/unit/submission/validation.spec.ts +++ b/test/unit/submission/validation.spec.ts @@ -17,38 +17,40 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import { fail } from 'assert'; import chai from 'chai'; -import sinon from 'sinon'; import _ from 'lodash'; +import sinon from 'sinon'; +import { Donor } from '../../../src/clinical/clinical-entities'; import { donorDao } from '../../../src/clinical/donor-repo'; -import * as dv from '../../../src/submission/validation-clinical/validation'; +import { + ClinicalEntitySchemaNames, + ClinicalUniqueIdentifier, + DonorFieldsEnum, + FollowupFieldsEnum, + PrimaryDiagnosisFieldsEnum, + RadiationFieldsEnum, + SpecimenFieldsEnum, + SurgeryFieldsEnum, + TherapyDrugFieldsEnum, + TherapyRxNormFieldsEnum, + TreatmentFieldsEnum, +} from '../../../src/common-model/entities'; import * as treatmentDetailExceptionsRepo from '../../../src/exception/treatment-detail-exceptions/repo'; +import featureFlags from '../../../src/feature-flags'; import { - SubmissionValidationError, - DataValidationErrors, CreateRegistrationRecord, + DataValidationErrors, SampleRegistrationFieldsEnum, + SubmissionValidationError, } from '../../../src/submission/submission-entities'; -import { Donor } from '../../../src/clinical/clinical-entities'; -import { stubs } from './stubs'; -import { fail } from 'assert'; import { ClinicalSubmissionRecordsOperations, usingInvalidProgramId, } from '../../../src/submission/validation-clinical/utils'; -import { - ClinicalEntitySchemaNames, - SpecimenFieldsEnum, - DonorFieldsEnum, - TreatmentFieldsEnum, - FollowupFieldsEnum, - ClinicalUniqueIdentifier, - PrimaryDiagnosisFieldsEnum, - SurgeryFieldsEnum, - RadiationFieldsEnum, -} from '../../../src/common-model/entities'; +import * as dv from '../../../src/submission/validation-clinical/validation'; import { success } from '../../../src/utils/results'; -import featureFlags from '../../../src/feature-flags'; +import { stubs } from './stubs'; const genderMutatedErr: SubmissionValidationError = { fieldName: 'gender', @@ -3191,7 +3193,6 @@ describe('data-validator', () => { }, }; - console.log(result[ClinicalEntitySchemaNames.RADIATION].dataErrors); chai.expect(result[ClinicalEntitySchemaNames.RADIATION].dataErrors.length).equal(1); chai.expect(result[ClinicalEntitySchemaNames.RADIATION].dataErrors[0]).to.deep.eq(error); }); @@ -3404,6 +3405,227 @@ describe('data-validator', () => { chai.expect(result[ClinicalEntitySchemaNames.SURGERY].dataErrors.length).equal(1); chai.expect(result[ClinicalEntitySchemaNames.SURGERY].dataErrors[0]).to.deep.eq(error); }); + + it('should error when drug id field is missing (chemotherapy)', async () => { + const existingDonorMock: Donor = stubs.validation.existingDonor01(); + const newDonorAB1Records = {}; + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.PRIMARY_DIAGNOSIS, + newDonorAB1Records, + { + [PrimaryDiagnosisFieldsEnum.submitter_donor_id]: 'AB1', + [PrimaryDiagnosisFieldsEnum.program_id]: 'TEST-CA', + [PrimaryDiagnosisFieldsEnum.submitter_primary_diagnosis_id]: 'PD-1', + index: 0, + }, + ); + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.TREATMENT, + newDonorAB1Records, + { + [SampleRegistrationFieldsEnum.submitter_donor_id]: 'AB1', + [TreatmentFieldsEnum.submitter_treatment_id]: 'T_03', + [PrimaryDiagnosisFieldsEnum.submitter_primary_diagnosis_id]: 'PD-1', + [TreatmentFieldsEnum.treatment_type]: ['Chemotherapy'], + index: 0, + }, + ); + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.CHEMOTHERAPY, + newDonorAB1Records, + { + [SampleRegistrationFieldsEnum.submitter_donor_id]: 'AB1', + [TreatmentFieldsEnum.submitter_treatment_id]: 'T_03', + [TherapyDrugFieldsEnum.drug_term]: 'Epinephrine', + [TherapyDrugFieldsEnum.drug_database]: 'KEGG', + index: 0, + }, + ); + + const result = await dv + .validateSubmissionData({ AB1: newDonorAB1Records }, { AB1: existingDonorMock }) + .catch((err: any) => fail(err)); + + const chemoDrugIdErr: SubmissionValidationError = { + fieldName: TherapyDrugFieldsEnum.drug_id, + message: `Please provide the missing drug information.`, + type: DataValidationErrors.INVALID_DRUG_INFO, + index: 0, + info: { + donorSubmitterId: 'AB1', + value: undefined, + }, + }; + + chai.expect(result.chemotherapy.dataErrors.length).to.eq(1); + chai.expect(result.chemotherapy.dataErrors).to.deep.include(chemoDrugIdErr); + }); + + it('should error when drug term field is missing (immunotherapy)', async () => { + const existingDonorMock: Donor = stubs.validation.existingDonor01(); + const newDonorAB1Records = {}; + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.PRIMARY_DIAGNOSIS, + newDonorAB1Records, + { + [PrimaryDiagnosisFieldsEnum.submitter_donor_id]: 'AB1', + [PrimaryDiagnosisFieldsEnum.program_id]: 'TEST-CA', + [PrimaryDiagnosisFieldsEnum.submitter_primary_diagnosis_id]: 'PD-1', + index: 0, + }, + ); + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.TREATMENT, + newDonorAB1Records, + { + [SampleRegistrationFieldsEnum.submitter_donor_id]: 'AB1', + [TreatmentFieldsEnum.submitter_treatment_id]: 'T_03', + [PrimaryDiagnosisFieldsEnum.submitter_primary_diagnosis_id]: 'PD-1', + [TreatmentFieldsEnum.treatment_type]: ['Immunotherapy'], + index: 0, + }, + ); + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.IMMUNOTHERAPY, + newDonorAB1Records, + { + [SampleRegistrationFieldsEnum.submitter_donor_id]: 'AB1', + [TreatmentFieldsEnum.submitter_treatment_id]: 'T_03', + [TherapyDrugFieldsEnum.drug_id]: '2244', + [TherapyDrugFieldsEnum.drug_database]: 'PubChem', + index: 0, + }, + ); + + const result = await dv + .validateSubmissionData({ AB1: newDonorAB1Records }, { AB1: existingDonorMock }) + .catch((err: any) => fail(err)); + + const immunoDrugTermErr: SubmissionValidationError = { + fieldName: TherapyDrugFieldsEnum.drug_term, + message: `Please provide the missing drug information.`, + type: DataValidationErrors.INVALID_DRUG_INFO, + index: 0, + info: { + donorSubmitterId: 'AB1', + value: undefined, + }, + }; + + chai.expect(result.immunotherapy.dataErrors.length).to.eq(1); + chai.expect(result.immunotherapy.dataErrors).to.deep.include(immunoDrugTermErr); + }); + + it('should error when drug db field is missing (hormonal therapy)', async () => { + const existingDonorMock: Donor = stubs.validation.existingDonor01(); + const newDonorAB1Records = {}; + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.PRIMARY_DIAGNOSIS, + newDonorAB1Records, + { + [PrimaryDiagnosisFieldsEnum.submitter_donor_id]: 'AB1', + [PrimaryDiagnosisFieldsEnum.program_id]: 'TEST-CA', + [PrimaryDiagnosisFieldsEnum.submitter_primary_diagnosis_id]: 'PD-1', + index: 0, + }, + ); + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.TREATMENT, + newDonorAB1Records, + { + [SampleRegistrationFieldsEnum.submitter_donor_id]: 'AB1', + [TreatmentFieldsEnum.submitter_treatment_id]: 'T_03', + [PrimaryDiagnosisFieldsEnum.submitter_primary_diagnosis_id]: 'PD-1', + [TreatmentFieldsEnum.treatment_type]: ['Hormonal therapy'], + index: 0, + }, + ); + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.HORMONE_THERAPY, + newDonorAB1Records, + { + [SampleRegistrationFieldsEnum.submitter_donor_id]: 'AB1', + [TreatmentFieldsEnum.submitter_treatment_id]: 'T_03', + [TherapyDrugFieldsEnum.drug_id]: '2244', + [TherapyDrugFieldsEnum.drug_term]: 'Aspirin', + index: 0, + }, + ); + + const result = await dv + .validateSubmissionData({ AB1: newDonorAB1Records }, { AB1: existingDonorMock }) + .catch((err: any) => fail(err)); + + const hormoneDrugDbErr: SubmissionValidationError = { + fieldName: TherapyDrugFieldsEnum.drug_database, + message: `Please provide the missing drug information.`, + type: DataValidationErrors.INVALID_DRUG_INFO, + index: 0, + info: { + donorSubmitterId: 'AB1', + value: undefined, + }, + }; + + chai.expect(result.hormone_therapy.dataErrors.length).to.eq(1); + chai.expect(result.hormone_therapy.dataErrors).to.deep.include(hormoneDrugDbErr); + }); + + it('should pass when all drug info fields are populated', async () => { + const existingDonorMock: Donor = stubs.validation.existingDonor01(); + const newDonorAB1Records = {}; + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.PRIMARY_DIAGNOSIS, + newDonorAB1Records, + { + [PrimaryDiagnosisFieldsEnum.submitter_donor_id]: 'AB1', + [PrimaryDiagnosisFieldsEnum.program_id]: 'TEST-CA', + [PrimaryDiagnosisFieldsEnum.submitter_primary_diagnosis_id]: 'PD-1', + index: 0, + }, + ); + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.TREATMENT, + newDonorAB1Records, + { + [SampleRegistrationFieldsEnum.submitter_donor_id]: 'AB1', + [TreatmentFieldsEnum.submitter_treatment_id]: 'T_04', + [PrimaryDiagnosisFieldsEnum.submitter_primary_diagnosis_id]: 'PD-1', + [TreatmentFieldsEnum.treatment_type]: ['Chemotherapy'], + index: 0, + }, + ); + + ClinicalSubmissionRecordsOperations.addRecord( + ClinicalEntitySchemaNames.CHEMOTHERAPY, + newDonorAB1Records, + { + [SampleRegistrationFieldsEnum.submitter_donor_id]: 'AB1', + [TreatmentFieldsEnum.submitter_treatment_id]: 'T_04', + [TherapyDrugFieldsEnum.drug_id]: '2244', + [TherapyDrugFieldsEnum.drug_term]: 'Aspirin', + [TherapyDrugFieldsEnum.drug_database]: 'PubChem', + index: 0, + }, + ); + + const result = await dv + .validateSubmissionData({ AB1: newDonorAB1Records }, { AB1: existingDonorMock }) + .catch((err: any) => fail(err)); + + chai.expect(result.treatment.dataErrors.length).to.eq(0); + }); }); describe('follow up validation', () => {