Skip to content

Commit

Permalink
Merge branch 'main' into rob/2783-fix-errors-generated-by-eslintrcjson
Browse files Browse the repository at this point in the history
  • Loading branch information
robertandremitchell authored Oct 22, 2024
2 parents 23ca516 + 836844a commit 286d667
Show file tree
Hide file tree
Showing 13 changed files with 273 additions and 223 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ALTER TABLE valuesets
ADD COLUMN dibbsConceptType text GENERATED ALWAYS AS (
ADD COLUMN dibbs_concept_type text GENERATED ALWAYS AS (
CASE
WHEN type IN ('lotc', 'lrtc', 'ostc') THEN 'labs'
WHEN type = 'mrtc' THEN 'medications'
Expand Down
6 changes: 3 additions & 3 deletions query-connector/src/app/api/query/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { handleRequestError } from "./error-handling-service";
import {
getSavedQueryByName,
mapQueryRowsToValueSetItems,
mapQueryRowsToConceptValueSets,
} from "@/app/database-service";

/**
Expand Down Expand Up @@ -92,7 +92,7 @@ export async function POST(request: NextRequest) {
// Lookup default parameters for particular use-case search
const queryName = UseCaseToQueryName[use_case as USE_CASES];
const queryResults = await getSavedQueryByName(queryName);
const vsItems = await mapQueryRowsToValueSetItems(queryResults);
const valueSets = await mapQueryRowsToConceptValueSets(queryResults);

// Add params & patient identifiers to UseCaseRequest
const UseCaseRequest: UseCaseQueryRequest = {
Expand All @@ -111,7 +111,7 @@ export async function POST(request: NextRequest) {

const UseCaseQueryResponse: QueryResponse = await UseCaseQuery(
UseCaseRequest,
vsItems,
valueSets,
);

// Bundle data
Expand Down
56 changes: 13 additions & 43 deletions query-connector/src/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,38 +302,6 @@ export const metadata = {
description: "Try out TEFCA with queries for public health use cases.",
};

// TODO: Remove ValueSetItem, ValueSet, and valueSetTypeToClincalServiceTypeMap once
// ticket #2789 is resolved

/*Type to specify the expected components for each item in a value set that will be
displayed in the CustomizeQuery component*/
export interface ValueSetItem {
code: string;
display: string;
system: string;
include: boolean;
author: string;
clinicalServiceType: string;
valueSetName: string;
}

/*Type to specify the expected expected types of valueset items that will be displayed
as separate tabs in the CusomizeQuery component*/
export interface ValueSet {
labs: ValueSetItem[];
medications: ValueSetItem[];
conditions: ValueSetItem[];
}

export type ValueSetType = keyof ValueSet;

export const valueSetTypeToClincalServiceTypeMap = {
labs: ["ostc", "lotc", "lrtc"],
medications: ["mrtc"],
conditions: ["dxtc", "sdtc"],
};
/// TODO: Remove the above once ticket #2789 is resolved

/*
* The expected type of a ValueSet concept.
*/
Expand All @@ -346,17 +314,17 @@ export interface Concept {
/*
* The expected type of a ValueSet.
*/
// export interface ValueSet {
// valueset_id: string;
// valueset_version: string;
// valueset_name: string;
// author: string;
// system: string;
// ersdConceptType?: string;
// dibbsConceptType: string;
// includeValueSet: boolean;
// concepts: Concept[];
// }
export interface ValueSet {
valueSetId: string;
valueSetVersion: string;
valueSetName: string;
author: string;
system: string;
ersdConceptType?: string;
dibbsConceptType: string;
includeValueSet: boolean;
concepts: Concept[];
}

/*
* The expected type of ValueSets grouped by dibbsConceptType for the purpose of display.
Expand All @@ -366,6 +334,8 @@ export interface ValueSetDisplay {
medications: ValueSet[];
conditions: ValueSet[];
}
export type DibbsValueSetType = keyof ValueSetDisplay;


// Define the type guard for FHIR resources
// Define the FHIR Resource types
Expand Down
75 changes: 39 additions & 36 deletions query-connector/src/app/database-service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use server";
import { Pool, PoolConfig, QueryResultRow } from "pg";
import { Bundle, OperationOutcome } from "fhir/r4";
import { ValueSetItem, valueSetTypeToClincalServiceTypeMap } from "./constants";
import { encode } from "base-64";
import { ValueSet } from "./constants";

const getQuerybyNameSQL = `
select q.query_name, q.id, qtv.valueset_id, vs.name as valueset_name, vs.author as author, vs.type, qic.concept_id, qic.include, c.code, c.code_system, c.display
select q.query_name, q.id, qtv.valueset_id, vs.name as valueset_name, vs.version, vs.author as author, vs.type, vs.dibbs_concept_type as dibbs_concept_type, qic.concept_id, qic.include, c.code, c.code_system, c.display
from query q
left join query_to_valueset qtv on q.id = qtv.query_id
left join valuesets vs on qtv.valueset_id = vs.id
Expand Down Expand Up @@ -48,45 +48,48 @@ export const getSavedQueryByName = async (name: string) => {
};

/**
* Helper function to filter the valueset-mapped rows of results returned from
* the DB for particular types of related clinical services.
* @param vsItems A list of value sets mapped from DB rows.
* @param type One of "labs", "medications", or "conditions".
* @returns A list of rows containing only the predicate service type.
* Maps the results returned from the DIBBs value set and coding system database
* into a collection of value sets, each containing one or more Concepts build out
* of the coding information in the DB.
* @param rows The Rows returned from the DB Query.
* @returns A list of ValueSets, which hold the Concepts pulled from the DB.
*/
export const filterValueSets = async (
vsItems: ValueSetItem[],
type: "labs" | "medications" | "conditions",
export const mapQueryRowsToConceptValueSets = async (
rows: QueryResultRow[],
) => {
// Assign clinical code type based on desired filter
// Mapping is established in TCR, so follow that convention
let valuesetFilters = valueSetTypeToClincalServiceTypeMap[type];
const results = vsItems.filter((vs) =>
valuesetFilters.includes(vs.clinicalServiceType),
);
return results;
};
// Create groupings of rows (each of which is a single Concept) by their ValueSet ID
const vsIdGroupedRows = rows.reduce((conceptsByVSId, r) => {
if (!(r["valueset_id"] in conceptsByVSId)) {
conceptsByVSId[r["valueset_id"]] = [];
}
conceptsByVSId[r["valueset_id"]].push(r);
return conceptsByVSId;
}, {});

/**
* Helper function that transforms and groups a set of database rows into a list of
* ValueSet items grouped by author and code_system for display on the CustomizeQuery page.
* @param rows The rows returned from the DB.
* @returns A list of ValueSetItems grouped by author and system.
*/
export const mapQueryRowsToValueSetItems = async (rows: QueryResultRow[]) => {
const vsItems = rows.map((r) => {
const vsTranslation: ValueSetItem = {
code: r["code"],
display: r["display"],
system: r["code_system"],
include: r["include"],
author: r["author"],
valueSetName: r["valueset_name"],
clinicalServiceType: r["type"],
// Each "prop" of the struct is now a ValueSet ID
// Iterate over them to create formal Concept Groups attached to a formal VS
const valueSets = Object.keys(vsIdGroupedRows).map((vsID) => {
const conceptGroup: QueryResultRow[] = vsIdGroupedRows[vsID];
const valueSet: ValueSet = {
valueSetId: conceptGroup[0]["valueset_id"],
valueSetVersion: conceptGroup[0]["version"],
valueSetName: conceptGroup[0]["valueset_name"],
author: conceptGroup[0]["author"],
system: conceptGroup[0]["code_system"],
ersdConceptType: conceptGroup[0]["type"],
dibbsConceptType: conceptGroup[0]["dibbs_concept_type"],
includeValueSet: conceptGroup.find((c) => c["include"]) ? true : false,
concepts: conceptGroup.map((c) => {
return {
code: c["code"],
display: c["display"],
include: c["include"],
};
}),
};
return vsTranslation;
return valueSet;
});
return vsItems;
return valueSets;
};

/*
Expand Down
31 changes: 20 additions & 11 deletions query-connector/src/app/format-service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ContactPoint,
Identifier,
} from "fhir/r4";
import { ValueSetItem } from "./constants";
import { ValueSet } from "./constants";
import { QueryStruct } from "./query-service";

/**
Expand Down Expand Up @@ -261,29 +261,38 @@ export async function GetPhoneQueryFormats(phone: string) {
}

/**
* Formats a statefully updated list of value set items into a JSON structure
* Formats a statefully updated list of value sets into a JSON structure
* used for executing custom queries.
* @param useCase The base use case being queried for.
* @param vsItems The list of value set items the user wants included.
* @param valueSets The list of value sets the user wants included.
* @returns A structured specification of a query that can be executed.
*/
export const formatValueSetItemsAsQuerySpec = async (
export const formatValueSetsAsQuerySpec = async (
useCase: string,
vsItems: ValueSetItem[],
valueSets: ValueSet[],
) => {
let secondEncounter: boolean = false;
if (["cancer", "chlamydia", "gonorrhea", "syphilis"].includes(useCase)) {
secondEncounter = true;
}
const labCodes: string[] = vsItems
const labCodes: string[] = valueSets
.filter((vs) => vs.system === "http://loinc.org")
.map((vs) => vs.code);
const snomedCodes: string[] = vsItems
.reduce((acc, vs) => {
vs.concepts.forEach((concept) => acc.push(concept.code));
return acc;
}, [] as string[]);
const snomedCodes: string[] = valueSets
.filter((vs) => vs.system === "http://snomed.info/sct")
.map((vs) => vs.code);
const rxnormCodes: string[] = vsItems
.reduce((acc, vs) => {
vs.concepts.forEach((concept) => acc.push(concept.code));
return acc;
}, [] as string[]);
const rxnormCodes: string[] = valueSets
.filter((vs) => vs.system === "http://www.nlm.nih.gov/research/umls/rxnorm")
.map((vs) => vs.code);
.reduce((acc, vs) => {
vs.concepts.forEach((concept) => acc.push(concept.code));
return acc;
}, [] as string[]);

const spec: QueryStruct = {
labCodes: labCodes,
Expand Down
14 changes: 6 additions & 8 deletions query-connector/src/app/query-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import FHIRClient from "./fhir-servers";
import {
USE_CASES,
FHIR_SERVERS,
ValueSetItem,
ValueSet,
isFhirResource,
FhirResource,
} from "./constants";

import { CustomQuery } from "./CustomQuery";
import { GetPhoneQueryFormats } from "./format-service";
import { formatValueSetItemsAsQuerySpec } from "./format-service";
import { formatValueSetsAsQuerySpec } from "./format-service";

/**
* The query response when the request source is from the Viewer UI.
Expand Down Expand Up @@ -146,7 +147,7 @@ async function patientQuery(
*/
export async function UseCaseQuery(
request: UseCaseQueryRequest,
queryValueSets: ValueSetItem[],
queryValueSets: ValueSet[],
queryResponse: QueryResponse = {},
): Promise<QueryResponse> {
const fhirClient = new FHIRClient(request.fhir_server);
Expand Down Expand Up @@ -187,15 +188,12 @@ export async function UseCaseQuery(
*/
async function generalizedQuery(
useCase: USE_CASES,
queryValueSets: ValueSetItem[],
queryValueSets: ValueSet[],
patientId: string,
fhirClient: FHIRClient,
queryResponse: QueryResponse,
): Promise<QueryResponse> {
const querySpec = await formatValueSetItemsAsQuerySpec(
useCase,
queryValueSets,
);
const querySpec = await formatValueSetsAsQuerySpec(useCase, queryValueSets);
const builtQuery = new CustomQuery(querySpec, patientId);
let response: fetch.Response | fetch.Response[];

Expand Down
7 changes: 4 additions & 3 deletions query-connector/src/app/query/SelectQuery.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";
import React, { useEffect, useState } from "react";
import { FHIR_SERVERS, USE_CASES, ValueSetItem } from "../constants";
import { FHIR_SERVERS, USE_CASES, ValueSet } from "../constants";
import CustomizeQuery from "./components/CustomizeQuery";
import SelectSavedQuery from "./components/selectQuery/SelectSavedQuery";

Expand Down Expand Up @@ -57,8 +57,8 @@ const SelectQuery: React.FC<SelectQueryProps> = ({
setFhirServer,
setShowCustomizeQuery,
}) => {
const [queryValueSets, setQueryValueSets] = useState<ValueSetItem[]>(
[] as ValueSetItem[],
const [queryValueSets, setQueryValueSets] = useState<ValueSet[]>(
[] as ValueSet[],
);
const [loadingQueryValueSets, setLoadingQueryValueSets] =
useState<boolean>(false);
Expand Down Expand Up @@ -97,6 +97,7 @@ const SelectQuery: React.FC<SelectQueryProps> = ({
}

const displayLoading = loadingResultResponse || loadingQueryValueSets;

return (
<div>
{displayLoading && <LoadingView loading={loadingResultResponse} />}
Expand Down
Loading

0 comments on commit 286d667

Please sign in to comment.