Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

💊 Feat/1194 Drug Info Validation #1195

Merged
merged 18 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 26 additions & 19 deletions src/common-model/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -101,10 +101,11 @@ export type ClinicalFields =
| PrimaryDiagnosisFieldsEnum
| FamilyHistoryFieldsEnum
| TreatmentFieldsEnum
| TherapyRxNormFields
| TherapyRxNormFieldsEnum
| TherapyDrugFieldsEnum
| RadiationFieldsEnum
| SurgeryFieldsEnum
| CommonTherapyFields
| CommonTherapyFieldsEnum
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standardize variable names

| ExposureFieldsEnum
| ComorbidityFieldsEnum
| BiomarkerFieldsEnum;
Expand Down Expand Up @@ -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',
Expand All @@ -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',
}

Expand Down Expand Up @@ -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,
Expand Down
45 changes: 23 additions & 22 deletions src/submission/submission-entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Copy link
Contributor Author

@demariadaniel demariadaniel Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several alphabetized imports auto-updated

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';

/**
Expand Down Expand Up @@ -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<{
Expand Down Expand Up @@ -406,19 +407,19 @@ export const ClinicalEntityToEnumFieldsMap: Record<ClinicalEntitySchemaNames, st
[ClinicalEntitySchemaNames.BIOMARKER]: Object.values(BiomarkerFieldsEnum),
[ClinicalEntitySchemaNames.FOLLOW_UP]: Object.values(FollowupFieldsEnum),
[ClinicalEntitySchemaNames.TREATMENT]: Object.values(TreatmentFieldsEnum),
[ClinicalEntitySchemaNames.CHEMOTHERAPY]: (Object.values(TherapyRxNormFields) as string[]).concat(
Object.values(CommonTherapyFields),
),
[ClinicalEntitySchemaNames.CHEMOTHERAPY]: (Object.values(
TherapyRxNormFieldsEnum,
) as string[]).concat(Object.values(CommonTherapyFieldsEnum)),
[ClinicalEntitySchemaNames.RADIATION]: (Object.values(RadiationFieldsEnum) as string[]).concat(
Object.values(CommonTherapyFields),
Object.values(CommonTherapyFieldsEnum),
),
[ClinicalEntitySchemaNames.HORMONE_THERAPY]: (Object.values(
TherapyRxNormFields,
) as string[]).concat(Object.values(CommonTherapyFields)),
[ClinicalEntitySchemaNames.IMMUNOTHERAPY]: (Object.values(TherapyRxNormFields) as string[])
.concat(Object.values(CommonTherapyFields))
.concat(Object.values(ImmunotherapyFields) as string[]),
[ClinicalEntitySchemaNames.SURGERY]: Object.values(CommonTherapyFields) as string[],
TherapyRxNormFieldsEnum,
) as string[]).concat(Object.values(CommonTherapyFieldsEnum)),
[ClinicalEntitySchemaNames.IMMUNOTHERAPY]: (Object.values(TherapyRxNormFieldsEnum) as string[])
.concat(Object.values(CommonTherapyFieldsEnum))
.concat(Object.values(ImmunotherapyFieldsEnum) as string[]),
[ClinicalEntitySchemaNames.SURGERY]: Object.values(CommonTherapyFieldsEnum) as string[],
};

export const TreatmentTypeValuesMappedByTherapy: Record<ClinicalTherapyType, string> = {
Expand Down
3 changes: 2 additions & 1 deletion src/submission/submission-error-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } = {
/* ***************** *
Expand Down Expand Up @@ -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<SubmissionBatchErrorTypes, (errorData: any) => string> = {
Expand Down
22 changes: 11 additions & 11 deletions src/submission/submission-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -79,12 +79,12 @@ import {
RegistrationStat,
RegistrationStats,
RevalidateClinicalSubmissionCommand,
SUBMISSION_STATE,
SampleRegistrationFieldsEnum,
SubmissionBatchError,
SubmissionBatchErrorTypes,
SubmissionValidationError,
SubmissionValidationUpdate,
SUBMISSION_STATE,
SubmittedRegistrationRecord,
ValidateSubmissionResult,
} from './submission-entities';
Expand Down Expand Up @@ -971,9 +971,9 @@ export namespace operations {
}
const recordWithRxNormPopulated = _.cloneDeep(r) as Record<string, any>;
// 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,
Expand All @@ -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),
Expand All @@ -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) {
Expand All @@ -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, {
Expand All @@ -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`,
Expand Down
44 changes: 22 additions & 22 deletions src/submission/validation-clinical/surgery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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],
},
),
);
Expand Down Expand Up @@ -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],
},
);
Expand All @@ -160,12 +160,12 @@ function findSubmittedSurgeryByDonorAndTreatment(
existenDonor: DeepReadonly<Donor>,
therapyRecord: DeepReadonly<SubmittedClinicalRecord>,
) {
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<Therapy>;
}
Expand All @@ -174,12 +174,12 @@ function findSurgeryInCurrentSubmission(
mergedDonor: Donor,
therapyRecord: DeepReadonly<SubmittedClinicalRecord>,
) {
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<Therapy>[];

Expand Down
Loading