Skip to content
This repository has been archived by the owner on Dec 5, 2024. It is now read-only.

Commit

Permalink
Update to latest service library and update types
Browse files Browse the repository at this point in the history
  • Loading branch information
dmpotter44 committed Dec 19, 2022
1 parent 4c3f379 commit 9d362d0
Show file tree
Hide file tree
Showing 9 changed files with 2,504 additions and 2,525 deletions.
3,756 changes: 1,877 additions & 1,879 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@
"license": "Apache-2.0",
"dependencies": {
"body-parser": "^1.19.0",
"clinical-trial-matching-service": "^0.0.3",
"clinical-trial-matching-service": "^0.0.7",
"dotenv-flow": "^3.2.0",
"express": "^4.17.1",
"fhirclient": "^2.3.11",
"fhirpath": "^2.7.4",
"node-fetch": "^2.6.1"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@types/dotenv-flow": "^3.0.0",
"@types/express": "^4.17.11",
"@types/fhir": "^0.0.35",
"@types/jasmine": "^3.6.9",
"@types/node": "^14.14.41",
"@types/node": "^18.11.17",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
Expand Down
1,120 changes: 561 additions & 559 deletions spec/mcode.spec.ts

Large diffs are not rendered by default.

56 changes: 32 additions & 24 deletions spec/trialscope.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { ClinicalTrialsGovService, ResearchStudy } from 'clinical-trial-matching-service';
import { Bundle, BundleEntry, FhirResource } from 'fhir/r4';
// For spying purposes:
import nock from 'nock';

import {
isTrialScopeResponse,
isTrialScopeErrorResponse,
Expand All @@ -6,9 +11,6 @@ import {
TrialScopeServerError,
TrialScopeTrial
} from '../src/trialscope';
import { fhir, ClinicalTrialsGovService, ResearchStudy } from 'clinical-trial-matching-service';
// For spying purposes:
import nock from 'nock';

describe('isTrialScopeResponse', () => {
it('returns false with a non-object', () => {
Expand Down Expand Up @@ -45,12 +47,12 @@ describe('isTrialScopeErrorResponse', () => {
describe('TrialScopeQuery', () => {
it('ignores bad entries', () => {
// This involves a bit of lying to TypeScript
const patientBundle: fhir.Bundle = {
const patientBundle: Bundle = {
resourceType: 'Bundle',
type: 'collection',
entry: []
};
patientBundle.entry.push(({ invalid: true } as unknown) as fhir.BundleEntry);
patientBundle.entry?.push({ invalid: true } as unknown as BundleEntry);
new TrialScopeQuery(patientBundle);
// Success is the object being created at all
});
Expand Down Expand Up @@ -126,14 +128,15 @@ describe('TrialScopeQuery', () => {
// TypeScript assumes extra fields are an error, although in this case the
// FHIR types supplies are somewhat intentionally sparse as they're not a
// full definition
const condition: fhir.Resource = {
const condition: FhirResource = {
resourceType: 'Condition',
meta: {
profile: [
'http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-primary-cancer-condition',
'http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition'
]
},
subject: {},
clinicalStatus: {
coding: [
{
Expand Down Expand Up @@ -174,7 +177,7 @@ describe('TrialScopeQuery', () => {
}
}
]
} as fhir.Resource;
};
const query = new TrialScopeQuery({
resourceType: 'Bundle',
type: 'collection',
Expand Down Expand Up @@ -203,15 +206,19 @@ describe('TrialScopeQuery', () => {
// currently the case
let m = /mcode:\s*{(.*?)}/.exec(graphQL);
expect(m).toBeTruthy();
const mcodeFilters = m[1];
expect(mcodeFilters).toMatch(/primaryCancer:\s*BREAST_CANCER/);
if (m) {
const mcodeFilters = m[1];
expect(mcodeFilters).toMatch(/primaryCancer:\s*BREAST_CANCER/);
}
m = /baseFilters:\s*{(.*?)}/.exec(graphQL);
expect(m).toBeTruthy();
const baseFilters = m[1];
expect(baseFilters).toMatch(/zipCode:\s*"01234"/);
expect(baseFilters).toMatch(/travelRadius:\s*15\b/);
expect(baseFilters).toMatch(/phase:\s*PHASE_1\b/);
expect(baseFilters).toMatch(/recruitmentStatus:\s*RECRUITING\b/);
if (m) {
const baseFilters = m[1];
expect(baseFilters).toMatch(/zipCode:\s*"01234"/);
expect(baseFilters).toMatch(/travelRadius:\s*15\b/);
expect(baseFilters).toMatch(/phase:\s*PHASE_1\b/);
expect(baseFilters).toMatch(/recruitmentStatus:\s*RECRUITING\b/);
}
});

it("excludes parameters that weren't included", () => {
Expand All @@ -234,11 +241,13 @@ describe('TrialScopeQuery', () => {
const graphQL = query.toQuery();
const m = /baseFilters:\s*{(.*?)}/.exec(graphQL);
expect(m).toBeTruthy();
const baseFilters = m[1];
expect(baseFilters).toMatch(/zipCode:\s*"98765"/);
expect(baseFilters).not.toMatch('travelRadius');
expect(baseFilters).not.toMatch('phase');
expect(baseFilters).not.toMatch('recruitmentStatus');
if (m) {
const baseFilters = m[1];
expect(baseFilters).toMatch(/zipCode:\s*"98765"/);
expect(baseFilters).not.toMatch('travelRadius');
expect(baseFilters).not.toMatch('phase');
expect(baseFilters).not.toMatch('recruitmentStatus');
}
});
});

Expand All @@ -256,8 +265,6 @@ describe('TrialScopeQueryRunner', () => {
});
afterEach(() => {
expect(scope.isDone()).toBeTrue();
interceptor = null;
scope = null;
});

it('handles an empty response', () => {
Expand Down Expand Up @@ -308,7 +315,7 @@ describe('TrialScopeQueryRunner', () => {
});

describe('runQuery', () => {
let patientBundle: fhir.Bundle;
let patientBundle: Bundle;
beforeEach(() => {
// This is basically the minimum bundle required to run a query
patientBundle = {
Expand Down Expand Up @@ -460,8 +467,9 @@ describe('TrialScopeQueryRunner', () => {
expected.enrollment = [reference];
// For the sake of this test, kill the createResourceId functions
// (it shouldn't be enumerable anyway)
expected.createReferenceId = null;
(actual.entry[i].resource as ResearchStudy).createReferenceId = null;
// Note this involves lying to the TypeScript compiler
(expected as unknown as { createReferenceId: null }).createReferenceId = null;
(actual.entry[i].resource as unknown as { createReferenceId: null }).createReferenceId = null;
expect(actual.entry[i].resource).toEqual(expected);
}
expect(actual.entry[0].search.score).toEqual(1);
Expand Down
43 changes: 8 additions & 35 deletions src/mcode.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,16 @@
import { fhirclient } from 'fhirclient/lib/types';
import { Bundle, Coding, Quantity, Ratio, Resource } from 'fhir/r4';
import * as fhirpath from 'fhirpath';

import { CodeProfile, ProfileSystemCodes } from './profileSystemLogic';

import profile_system_codes_json from '../data/profile-system-codes-json.json';
import { fhir } from 'clinical-trial-matching-service';

const profile_system_codes = profile_system_codes_json as ProfileSystemCodes;

export type FHIRPath = string;
// fhirpath now has official TypeScript types. Unfortunately, the result they use is "any"
export type PathLookupResult = Record<string, unknown> | string | number;

export interface Coding {
system?: string;
code?: string;
display?: string;
}

export interface Quantity {
value?: number | string;
comparator?: string;
unit?: string;
system?: string;
code?: string;
}

export interface Ratio {
numerator?: Quantity;
denominator?: Quantity;
}

export interface PrimaryCancerCondition {
clinicalStatus?: Coding[];
coding?: Coding[];
Expand Down Expand Up @@ -89,7 +69,7 @@ export class ExtractedMCODE {
ecogPerformaceStatus: number;
karnofskyPerformanceStatus: number;

constructor(patientBundle: fhir.Bundle) {
constructor(patientBundle: Bundle | null) {
if (patientBundle != null) {
for (const entry of patientBundle.entry) {
if (!('resource' in entry)) {
Expand Down Expand Up @@ -319,12 +299,8 @@ export class ExtractedMCODE {
}
}

lookup(
resource: fhirclient.FHIR.Resource,
path: FHIRPath,
environment?: { [key: string]: string }
): PathLookupResult[] {
return fhirpath.evaluate(resource, path, environment);
lookup(resource: Resource, path: FHIRPath, environment?: { [key: string]: string }): PathLookupResult[] {
return fhirpath.evaluate(resource, path, environment) as PathLookupResult[];
}
resourceProfile(profiles: PathLookupResult[], key: string): boolean {
for (const profile of profiles) {
Expand Down Expand Up @@ -1299,13 +1275,10 @@ export class ExtractedMCODE {
const system = this.normalizeCodeSystem(coding.system);
for (const sheetName of sheetNames) {
let codeProfile: CodeProfile = undefined;
try {
codeProfile = profile_system_codes[sheetName] as CodeProfile; // Pull the codes for the profile
} finally {
if (codeProfile == undefined) {
console.error('Code Profile ' + sheetName + ' is undefined.');
continue;
}
codeProfile = profile_system_codes[sheetName]; // Pull the codes for the profile
if (codeProfile == undefined) {
console.error('Code Profile ' + sheetName + ' is undefined.');
continue;
}

let codeSet: { code: string }[] = codeProfile[system] as { code: string }[]; // Pull the system codes from the codes
Expand Down
6 changes: 3 additions & 3 deletions src/profileSystemLogic.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Coding } from './mcode';
import { Coding } from 'fhir/r4';

export interface ProfileType {
types: string[];
}
export interface ProfileSystemCodes {
[key: string] : CodeProfile;
[key: string]: CodeProfile;
'Cancer-Skin'?: CodeProfile;
'Treatment-Pertuzumab'?: CodeProfile;
'Treatment-SRS-Brain'?: CodeProfile;
Expand Down Expand Up @@ -45,7 +45,7 @@ export interface ProfileSystemCodes {
'Biomarker-ER'?: CodeProfile;
}
export interface CodeProfile {
[key: string] : { code: string }[];
[key: string]: { code: string }[];
SNOMED?: { code: string }[];
RxNorm?: { code: string }[];
ICD10?: { code: string }[];
Expand Down
10 changes: 6 additions & 4 deletions src/research-study-mapping.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ResearchStudy, fhir, convertStringsToCodeableConcept } from 'clinical-trial-matching-service';
import { ResearchStudy, convertStringsToCodeableConcept } from 'clinical-trial-matching-service';
import { TrialScopeTrial } from './trialscope';

type ResearchStudyStatus = ResearchStudy['status'];

// Mappings between trialscope value sets and FHIR value sets
const phaseCodeMap = new Map<string, string>([
['Early Phase 1', 'early-phase-1'],
Expand All @@ -13,7 +15,7 @@ const phaseCodeMap = new Map<string, string>([
['Phase 4', 'phase-4']
]);

const statusMap = new Map<string, fhir.ResearchStudyStatus>([
const statusMap = new Map<string, ResearchStudyStatus>([
['Active, not recruiting', 'closed-to-accrual'],
['Approved for marketing', 'approved'],
['Available', 'active'],
Expand All @@ -22,7 +24,7 @@ const statusMap = new Map<string, fhir.ResearchStudyStatus>([
['Recruiting', 'active']
]);

function convertStatus(tsStatus: string): fhir.ResearchStudyStatus {
function convertStatus(tsStatus: string): ResearchStudyStatus {
return statusMap.get(tsStatus);
}

Expand Down Expand Up @@ -55,7 +57,7 @@ export function convertTrialScopeToResearchStudy(trial: TrialScopeTrial, id: num
};
}
if (trial.studyType) {
result.category = [{ text: "Study Type: " + trial.studyType }];
result.category = [{ text: 'Study Type: ' + trial.studyType }];
}
if (trial.conditions) {
const conditions = convertStringsToCodeableConcept(trial.conditions);
Expand Down
14 changes: 5 additions & 9 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { ClinicalTrialsGovService, ClinicalTrialMatchingService, configFromEnv } from 'clinical-trial-matching-service';
import * as dotenv from 'dotenv-flow';
import express from 'express';
import TrialScopeQueryRunner from './trialscope';
import { Bundle } from 'fhir/r4';

import {
fhir,
ClinicalTrialsGovService,
ClinicalTrialMatchingService,
configFromEnv
} from 'clinical-trial-matching-service';
import * as dotenv from 'dotenv-flow';
import TrialScopeQueryRunner from './trialscope';

export class TrialScopeService extends ClinicalTrialMatchingService {
queryRunner: TrialScopeQueryRunner;
backupService: ClinicalTrialsGovService;

constructor(config: Record<string, string | number>) {
super((patientBundle: fhir.Bundle) => {
super((patientBundle: Bundle) => {
return this.queryRunner.runQuery(patientBundle);
}, config);

Expand Down
18 changes: 9 additions & 9 deletions src/trialscope.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* Module for running queries via TrialScope
*/
import { ClinicalTrialsGovService, ServerError } from 'clinical-trial-matching-service';
import { ClientError, ClinicalTrialsGovService, SearchSet, ServerError } from 'clinical-trial-matching-service';
import { Bundle, ResearchStudy } from 'fhir/r4';
import https from 'https';
import { IncomingMessage } from 'http';
import { convertTrialScopeToResearchStudy } from './research-study-mapping';
import { ClientError, SearchSet, fhir } from 'clinical-trial-matching-service';
import * as mcode from './mcode';
import { convertTrialScopeToResearchStudy } from './research-study-mapping';

/**
* Maps FHIR phases to TrialScope phases.
Expand Down Expand Up @@ -183,7 +183,7 @@ export class TrialScopeQuery {
phase = 'any';
recruitmentStatus: string | null = null;
after?: string = null;
first = 30;
first: number | null = 30;
mcode?: {
[key: string]: string;
primaryCancer: string;
Expand Down Expand Up @@ -226,11 +226,11 @@ export class TrialScopeQuery {
'maximumAge'
];

constructor(patientBundle: fhir.Bundle) {
constructor(patientBundle: Bundle) {
const extractedMCODE = new mcode.ExtractedMCODE(patientBundle);
console.log(extractedMCODE);
let stageValues = extractedMCODE.getStageValues();
let medicationStatementValues = extractedMCODE.getMedicationStatementValues();
const stageValues = extractedMCODE.getStageValues();
const medicationStatementValues = extractedMCODE.getMedicationStatementValues();
this.mcode = {
primaryCancer: extractedMCODE.getPrimaryCancerValue(),
secondaryCancer: extractedMCODE.getSecondaryCancerValue(),
Expand Down Expand Up @@ -332,7 +332,7 @@ export class TrialScopeQuery {
export class TrialScopeQueryRunner {
constructor(public endpoint: string, private token: string, private backupService: ClinicalTrialsGovService) {}

runQuery(patientBundle: fhir.Bundle): Promise<SearchSet> {
runQuery(patientBundle: Bundle): Promise<SearchSet> {
// update for advanced matches query
return new Promise<TrialScopeResponse>((resolve, reject) => {
const query = new TrialScopeQuery(patientBundle);
Expand Down Expand Up @@ -378,7 +378,7 @@ export class TrialScopeQueryRunner {
*/
convertToSearchSet(trialscopeResponse: TrialScopeResponse): Promise<SearchSet> {
const searchSet = new SearchSet();
const studies: fhir.ResearchStudy[] = [];
const studies: ResearchStudy[] = [];
let index = 0;
for (const node of trialscopeResponse.data.advancedMatches.edges) {
const trial: TrialScopeTrial = node.node;
Expand Down

0 comments on commit 9d362d0

Please sign in to comment.