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

Implement core metadata schema in the eCR viewer #2756

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
8 changes: 2 additions & 6 deletions .github/workflows/container-ecr-viewer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,9 @@ jobs:
working-directory: ./containers/${{env.CONTAINER}} # Navigate to your Node.js app directory
run: npm install

- name: Copy seed data
working-directory: ./containers/${{env.CONTAINER}}
run: cp ./cypress/assets/data.sql ./seed-scripts/sql

- name: Start ${{env.CONTAINER}}
working-directory: ./containers/${{env.CONTAINER}}
run: docker compose --env-file .env.test up -d
working-directory: ./containers/${{env.CONTAINER}}/cypress
run: docker compose --env-file ../.env.test up -d

- name: Wait for server to be ready
run: |
Expand Down
1 change: 1 addition & 0 deletions containers/ecr-viewer/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export default defineConfig({
env: {
BASE_PATH: `${isDev ? "/" : "/ecr-viewer"}`,
},
defaultCommandTimeout: 120000,
},
});
23 changes: 17 additions & 6 deletions containers/ecr-viewer/cypress/assets/data.sql

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions containers/ecr-viewer/cypress/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
services:
# PostgreSQL database
db:
image: "postgres:alpine"
ports:
- "5432:5432"
volumes:
- ../sql/core.sql:/docker-entrypoint-initdb.d/core.sql
- ../seed-scripts/sql/01-init.sql:/docker-entrypoint-initdb.d/01-init.sql
- ./assets/data.sql:/docker-entrypoint-initdb.d/data.sql
- ../seed-scripts/sql/.pgpass/:/usr/local/lib/.pgpass
environment:
- POSTGRES_USER=postgres
- PGUSER=postgres
- POSTGRES_PASSWORD=pw
- POSTGRES_DB=ecr_viewer_db
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 2s
timeout: 5s
retries: 20

# Next.js app
ecr-viewer:
build:
context: ../../..
dockerfile: ./containers/ecr-viewer/Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URL=${DATABASE_URL:-postgres://postgres:pw@db:5432/ecr_viewer_db}
- APP_ENV=${APP_ENV:-prod}
4 changes: 2 additions & 2 deletions containers/ecr-viewer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ services:
ports:
- "5432:5432"
volumes:
- ./sql/:/docker-entrypoint-initdb.d/
- ./seed-scripts/sql/:/docker-entrypoint-initdb.d/
- ./sql/core.sql:/docker-entrypoint-initdb.d/core.sql
- ./seed-scripts/sql/01-init.sql:/docker-entrypoint-initdb.d/01-init.sql
- ./seed-scripts/sql/.pgpass/:/usr/local/lib/.pgpass
environment:
- POSTGRES_USER=postgres
Expand Down
2 changes: 1 addition & 1 deletion containers/ecr-viewer/next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
2 changes: 1 addition & 1 deletion containers/ecr-viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"update-cypress-data": "docker compose -f ./seed-scripts/docker-compose-create-sql.yml up --build --abort-on-container-exit",
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"cypress:run-local": "cp ./cypress/assets/data.sql ./seed-scripts/sql && docker compose --env-file .env.test up db -d && concurrently --kill-others 'npm run dev' 'npx wait-on http://localhost:3000 && NODE_ENV=dev cypress run ; docker compose down && rm ./seed-scripts/sql/data.sql'",
"cypress:run-local": "docker compose -f cypress/docker-compose.yml --env-file .env.test up db -d && concurrently --kill-others 'npm run dev' 'npx wait-on http://localhost:3000 && NODE_ENV=dev cypress run ; docker compose down'",
"cypress:run-prod": "NODE_ENV=production cypress run"
},
"dependencies": {
Expand Down
21 changes: 16 additions & 5 deletions containers/ecr-viewer/seed-scripts/create-e2e-sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,22 @@ def save_sql_insert_metadata(metadata):
rule_summary = parsed_values["rule_summary"]
report_date = parsed_values["report_date"]
data_source = "DB"
query = f"""INSERT INTO fhir_metadata (
ecr_id,patient_name_last,patient_name_first,patient_birth_date,data_source,reportable_condition,rule_summary,report_date
) VALUES (
'{ecr_id}','{last_name}','{first_name}','{birth_date}','{data_source}','{reportable_condition}','{rule_summary}',{'NULL' if report_date is None else f"'{report_date}'"}
) ON CONFLICT (ecr_id) DO NOTHING;\n"""
query = f"""WITH inserted_data AS (
INSERT INTO ecr_data (
eICR_ID, patient_name_last, patient_name_first, patient_birth_date, data_source, report_date
) VALUES (
'{ecr_id}', '{last_name}', '{first_name}', '{birth_date}', '{data_source}', {'NULL' if report_date is None else f"'{report_date}'"}
)
ON CONFLICT (eICR_ID) DO NOTHING
RETURNING eICR_ID
), inserted_condition AS (
INSERT INTO ecr_rr_conditions (uuid, eICR_ID, condition)
VALUES (uuid_generate_v4(), (SELECT eICR_ID FROM inserted_data), '{reportable_condition}')
RETURNING uuid
)
INSERT INTO ecr_rr_rule_summaries (uuid, ecr_rr_conditions_id, rule_summary)
VALUES (uuid_generate_v4(), (SELECT uuid FROM inserted_condition), '{rule_summary}')
RETURNING (SELECT eICR_ID FROM inserted_data);"""
output_file.write(query)


Expand Down
4 changes: 2 additions & 2 deletions containers/ecr-viewer/seed-scripts/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ services:
ports:
- "5432:5432"
volumes:
- ../sql/:/docker-entrypoint-initdb.d/
- ./sql/:/docker-entrypoint-initdb.d/
- ./sql/01-init.sql:/docker-entrypoint-initdb.d/01-init.sql
- ../sql/core.sql:/docker-entrypoint-initdb.d/core.sql
- ./sql/.pgpass/:/usr/local/lib/.pgpass
- db:/var/lib/postgresql/data
environment:
Expand Down
16 changes: 1 addition & 15 deletions containers/ecr-viewer/seed-scripts/sql/01-init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,4 @@ CREATE TABLE IF NOT EXISTS fhir (
data JSONB NOT NULL,
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (ecr_id)
);

CREATE TABLE IF NOT EXISTS fhir_metadata(
ecr_id VARCHAR(200) NOT NULL,
data_source VARCHAR(2) NOT NULL, -- S3 or DB
data_link VARCHAR(500), -- Link to the data
patient_name_first VARCHAR(100) NOT NULL,
patient_name_last VARCHAR(100) NOT NULL,
patient_birth_date DATE NOT NULL,
reportable_condition VARCHAR(10000),
rule_summary VARCHAR(10000),
report_date DATE,
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (ecr_id)
)
);
5 changes: 4 additions & 1 deletion containers/ecr-viewer/sql/core.sql
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE ecr_data (
eICR_ID VARCHAR(200) PRIMARY KEY,
data_source VARCHAR(2), -- S3 or DB
data_link VARCHAR(500), -- Link to the data
fhir_reference_link VARCHAR(500), -- Link to the ecr fhir bundle
gordonfarrell marked this conversation as resolved.
Show resolved Hide resolved
patient_name_first VARCHAR(100),
patient_name_last VARCHAR(100),
patient_birth_date DATE,
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
gordonfarrell marked this conversation as resolved.
Show resolved Hide resolved
report_date DATE
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export const saveFhirData = async (
* @param metadata - The FHIR bundle metadata to be saved.
* @param ecrId - The unique identifier for the Electronic Case Reporting (ECR) associated with the FHIR bundle.
* @returns A promise that resolves when the FHIR bundle metadata is successfully saved to postgres.
* @throws {Error} Throws an error if the FHIR bundle metadata cannot be saved to postgress.
* @throws {Error} Throws an error if the FHIR bundle metadata cannot be saved to postgres.
*/
export const saveToMetadataPostgres = async (
metadata: BundleMetadata,
Expand All @@ -203,25 +203,36 @@ export const saveToMetadataPostgres = async (
const database = db(db_url);

const { ParameterizedQuery: PQ } = pgPromise;
const addMetadata = new PQ({
text: "INSERT INTO fhir_metadata (ecr_id,patient_name_last,patient_name_first,patient_birth_date,data_source,reportable_condition,rule_summary,report_date) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING ecr_id",
const saveToEcrData = new PQ({
text: "INSERT INTO ecr_data (eICR_ID,patient_name_last,patient_name_first,patient_birth_date,data_source,report_date) VALUES ($1, $2, $3, $4, $5, $6) RETURNING eICR_ID",
values: [
ecrId,
metadata.first_name,
metadata.last_name,
metadata.first_name,
metadata.birth_date,
"DB",
metadata.reportable_condition,
metadata.rule_summary,
metadata.report_date,
],
});

const saveRRConditions = new PQ({
text: "INSERT INTO ecr_rr_conditions (uuid, eICR_ID, condition) VALUES (uuid_generate_v4(), $1, $2) RETURNING uuid",
values: [ecrId, metadata.reportable_condition],
});

try {
const saveECR = await database.one(addMetadata);
const saveECR = await database.one(saveToEcrData);
const savedRRCondition = await database.one(saveRRConditions);

const saveRRSummary = new PQ({
text: "INSERT INTO ecr_rr_rule_summaries (uuid, ecr_rr_conditions_id,rule_summary) VALUES (uuid_generate_v4(), $1, $2)",
values: [savedRRCondition.uuid, metadata.rule_summary],
});

await database.none(saveRRSummary);

return NextResponse.json(
{ message: "Success. Saved metadata to database: " + saveECR.ecr_id },
{ message: "Success. Saved metadata to database: " + saveECR.eICR_ID },
{ status: 200 },
);
} catch (error: any) {
Expand Down
14 changes: 7 additions & 7 deletions containers/ecr-viewer/src/app/api/services/listEcrDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type EcrMetadataModel = {
patient_name_first: string;
patient_name_last: string;
patient_birth_date: Date;
reportable_condition: string;
condition: string;
rule_summary: string;
report_date: Date;
date_created: Date;
Expand All @@ -38,13 +38,13 @@ export async function listEcrData(
startIndex: number,
itemsPerPage: number,
): Promise<EcrDisplay[]> {
const fhirMetadataQuery =
"SELECT ecr_id, patient_name_first, patient_name_last, patient_birth_date, report_date, reportable_condition, rule_summary, date_created FROM fhir_metadata order by date_created DESC OFFSET " +
const ecrDataQuery =
"SELECT ed.eICR_ID, ed.patient_name_first, ed.patient_name_last, ed.patient_birth_date, ed.date_created, ed.report_date, erc.condition, ers.rule_summary, ed.report_date FROM ecr_data ed LEFT JOIN ecr_rr_conditions erc ON ed.eICR_ID = erc.eICR_ID LEFT JOIN ecr_rr_rule_summaries ers ON erc.uuid = ers.ecr_rr_conditions_id order by ed.report_date DESC OFFSET " +
startIndex +
" ROWS FETCH NEXT " +
itemsPerPage +
" ROWS ONLY";
let list = await database.manyOrNone<EcrMetadataModel>(fhirMetadataQuery);
let list = await database.manyOrNone<EcrMetadataModel>(ecrDataQuery);
return processMetadata(list);
}

Expand All @@ -64,7 +64,7 @@ export const processMetadata = (
patient_date_of_birth: object.patient_birth_date
? formatDate(new Date(object.patient_birth_date!).toISOString())
: "",
reportable_condition: object.reportable_condition || "",
reportable_condition: object.condition || "",
rule_summary: object.rule_summary || "",
date_created: object.date_created
? convertUTCToLocalString(
Expand All @@ -81,10 +81,10 @@ export const processMetadata = (
};

/**
* Retrieves the total number of eCRs stored in the fhir table.
* Retrieves the total number of eCRs stored in the ecr_data table.
* @returns A promise resolving to the total number of eCRs.
*/
export const getTotalEcrCount = async (): Promise<number> => {
let number = await database.one("SELECT count(*) FROM fhir_metadata");
let number = await database.one("SELECT count(*) FROM ecr_data");
return number.count;
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe("listEcrDataService", () => {
patient_name_last: "Person",
patient_birth_date: new Date(),
report_date: new Date(),
reportable_condition: "Long",
condition: "Long",
rule_summary: "Longer",
data_source: "DB",
data: "",
Expand All @@ -38,7 +38,7 @@ describe("listEcrDataService", () => {
patient_name_last: "Test",
patient_birth_date: new Date(),
report_date: new Date(),
reportable_condition: "Stuff",
condition: "Stuff",
rule_summary: "Other stuff",
data_source: "DB",
data: "",
Expand Down Expand Up @@ -103,7 +103,7 @@ describe("listEcrDataService", () => {
database.manyOrNone = jest.fn(() => Promise.resolve([]));
const actual = await listEcrData(startIndex, itemsPerPage);
expect(database.manyOrNone).toHaveBeenCalledExactlyOnceWith(
"SELECT ecr_id, patient_name_first, patient_name_last, patient_birth_date, report_date, reportable_condition, rule_summary, date_created FROM fhir_metadata order by date_created DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY",
"SELECT ed.eICR_ID, ed.patient_name_first, ed.patient_name_last, ed.patient_birth_date, ed.date_created, ed.report_date, erc.condition, ers.rule_summary, ed.report_date FROM ecr_data ed LEFT JOIN ecr_rr_conditions erc ON ed.eICR_ID = erc.eICR_ID LEFT JOIN ecr_rr_rule_summaries ers ON erc.uuid = ers.ecr_rr_conditions_id order by ed.report_date DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY",
);
expect(actual).toBeEmpty();
});
Expand All @@ -118,7 +118,7 @@ describe("listEcrDataService", () => {
patient_name_first: "Billy",
patient_name_last: "Bob",
report_date: new Date("06/21/2024 8:00 AM EDT"),
reportable_condition: "stuff",
condition: "stuff",
rule_summary: "yup",
data: "",
data_link: "",
Expand All @@ -132,7 +132,7 @@ describe("listEcrDataService", () => {
const actual: EcrDisplay[] = await listEcrData(startIndex, itemsPerPage);

expect(database.manyOrNone).toHaveBeenCalledExactlyOnceWith(
"SELECT ecr_id, patient_name_first, patient_name_last, patient_birth_date, report_date, reportable_condition, rule_summary, date_created FROM fhir_metadata order by date_created DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY",
"SELECT ed.eICR_ID, ed.patient_name_first, ed.patient_name_last, ed.patient_birth_date, ed.date_created, ed.report_date, erc.condition, ers.rule_summary, ed.report_date FROM ecr_data ed LEFT JOIN ecr_rr_conditions erc ON ed.eICR_ID = erc.eICR_ID LEFT JOIN ecr_rr_rule_summaries ers ON erc.uuid = ers.ecr_rr_conditions_id order by ed.report_date DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY",
);
expect(actual).toEqual([
{
Expand All @@ -158,7 +158,7 @@ describe("listEcrDataService", () => {
patient_name_last: "lnam",
patient_birth_date: new Date("1990-01-01T05:00:00.000Z"),
report_date: new Date("2024-06-20T04:00:00.000Z"),
reportable_condition: "sick",
condition: "sick",
rule_summary: "stuff",
data: "",
data_link: "",
Expand All @@ -171,7 +171,7 @@ describe("listEcrDataService", () => {
let itemsPerPage = 25;
const actual: EcrDisplay[] = await listEcrData(startIndex, itemsPerPage);
expect(database.manyOrNone).toHaveBeenCalledExactlyOnceWith(
"SELECT ecr_id, patient_name_first, patient_name_last, patient_birth_date, report_date, reportable_condition, rule_summary, date_created FROM fhir_metadata order by date_created DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY",
"SELECT ed.eICR_ID, ed.patient_name_first, ed.patient_name_last, ed.patient_birth_date, ed.date_created, ed.report_date, erc.condition, ers.rule_summary, ed.report_date FROM ecr_data ed LEFT JOIN ecr_rr_conditions erc ON ed.eICR_ID = erc.eICR_ID LEFT JOIN ecr_rr_rule_summaries ers ON erc.uuid = ers.ecr_rr_conditions_id order by ed.report_date DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY",
);
expect(actual).toEqual([
{
Expand Down
Loading