From 18dcbdf3419b0f02cd0254eb3a2db946c408ad59 Mon Sep 17 00:00:00 2001 From: Daniel Lorigan Date: Thu, 20 Jun 2024 17:51:47 -0700 Subject: [PATCH 01/11] Add carin sandbox to sof urls --- src/lib/config.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/config.ts b/src/lib/config.ts index 28440ab6..1ec581fc 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -40,6 +40,13 @@ export const SOF_HOSTS = [ url: "https://launch.smarthealthit.org/v/r4/sim/WzMsIiIsIiIsIkFVVE8iLDAsMCwwLCIiLCIiLCIiLCIiLCIiLCIiLCIiLDAsMF0/fhir", clientId: "", note: "Credentials provided" + }, + { + id: "c4bb", + name: "CARIN Blue Button", + url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", + clientId: "0oaf5w78xfWspNqeA1d7", + note: "Credentials provided" } ]; export const SOF_REDIRECT_URI = '/create'; From 2a5fe802314cdfeca49875189f771be87c88b5be Mon Sep 17 00:00:00 2001 From: daniellrgn Date: Tue, 25 Jun 2024 16:49:28 -0700 Subject: [PATCH 02/11] Add carin SOF config (no apparent standalone support) --- src/lib/config.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lib/config.ts b/src/lib/config.ts index 1ec581fc..2c75ae93 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -41,13 +41,18 @@ export const SOF_HOSTS = [ clientId: "", note: "Credentials provided" }, - { - id: "c4bb", - name: "CARIN Blue Button", - url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", - clientId: "0oaf5w78xfWspNqeA1d7", - note: "Credentials provided" - } + // { + // id: "c4bb", + // name: "CARIN Blue Button", + // // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", + // clientId: "0oaf5w78xfWspNqeA1d7", + // note: "Credentials provided" + // } ]; export const SOF_REDIRECT_URI = '/create'; export const SOF_RESOURCES = [ From 1818dc8c728de155a5f1946c71dba51e7b797bd6 Mon Sep 17 00:00:00 2001 From: Daniel Lorigan Date: Thu, 20 Jun 2024 17:51:47 -0700 Subject: [PATCH 03/11] Add carin sandbox to sof urls --- src/lib/config.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/config.ts b/src/lib/config.ts index 28440ab6..1ec581fc 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -40,6 +40,13 @@ export const SOF_HOSTS = [ url: "https://launch.smarthealthit.org/v/r4/sim/WzMsIiIsIiIsIkFVVE8iLDAsMCwwLCIiLCIiLCIiLCIiLCIiLCIiLCIiLDAsMF0/fhir", clientId: "", note: "Credentials provided" + }, + { + id: "c4bb", + name: "CARIN Blue Button", + url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", + clientId: "0oaf5w78xfWspNqeA1d7", + note: "Credentials provided" } ]; export const SOF_REDIRECT_URI = '/create'; From 3d46b085658abedbbc0f8f3c0eede8d12de492e6 Mon Sep 17 00:00:00 2001 From: daniellrgn Date: Tue, 25 Jun 2024 16:49:28 -0700 Subject: [PATCH 04/11] Add carin SOF config (no apparent standalone support) --- src/lib/config.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lib/config.ts b/src/lib/config.ts index 1ec581fc..2c75ae93 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -41,13 +41,18 @@ export const SOF_HOSTS = [ clientId: "", note: "Credentials provided" }, - { - id: "c4bb", - name: "CARIN Blue Button", - url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", - clientId: "0oaf5w78xfWspNqeA1d7", - note: "Credentials provided" - } + // { + // id: "c4bb", + // name: "CARIN Blue Button", + // // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", + // clientId: "0oaf5w78xfWspNqeA1d7", + // note: "Credentials provided" + // } ]; export const SOF_REDIRECT_URI = '/create'; export const SOF_RESOURCES = [ From 657e2a8c7ef416f417230721f1107316e85c7d51 Mon Sep 17 00:00:00 2001 From: daniellrgn Date: Sun, 22 Sep 2024 08:31:01 -0700 Subject: [PATCH 05/11] Add Aetna and reduce scopes for insurance connection --- src/lib/FetchSoF.svelte | 4 ++-- src/lib/config.ts | 42 ++++++++++++++++++++++++----------------- src/lib/sofClient.js | 7 ++++--- src/lib/types.ts | 1 + 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/lib/FetchSoF.svelte b/src/lib/FetchSoF.svelte index e4f186ca..8a55520c 100644 --- a/src/lib/FetchSoF.svelte +++ b/src/lib/FetchSoF.svelte @@ -10,7 +10,7 @@ import { SOF_HOSTS } from './config'; import type { ResourceRetrieveEvent, SOFAuthEvent, SOFHost } from './types'; - import { authorize, getResourcesWithReferences } from './sofClient.js'; + import { authorize, getResourcesWithReferences, activePatient } from './sofClient.js'; import { createEventDispatcher, onMount } from 'svelte'; import { getContext } from 'svelte'; import type { Writable } from 'svelte/store'; @@ -38,7 +38,7 @@ try { if (sofHost) { try { - authorize(sofHost.url, sofHost.clientId); + authorize(sofHost.url, sofHost.clientId, sofHost.clientSecret ?? undefined); authDispatch('sof-auth-init'); } catch (e) { authDispatch('sof-auth-fail') diff --git a/src/lib/config.ts b/src/lib/config.ts index 2c75ae93..bee5c6f4 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -41,18 +41,26 @@ export const SOF_HOSTS = [ clientId: "", note: "Credentials provided" }, - // { - // id: "c4bb", - // name: "CARIN Blue Button", - // // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", - // url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", - // clientId: "0oaf5w78xfWspNqeA1d7", - // note: "Credentials provided" - // } + { + id: "aetna", + name: "AETNA Insurance Sandbox", + url: "https://vteapif1.aetna.com/fhirdemo/v1/patientaccess", + clientId: "09cbb76344009c25a2ec587b39ebc303", + clientSecret: "4fe39eccbfc586647407ea19f408521f", + note: "VTETestUser01 / FHIRdemo2020" + }, + { + id: "c4bb", + name: "CARIN Blue Button", + // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", + url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", + clientId: "0oaf5w78xfWspNqeA1d7", + note: "Credentials provided" + }, ]; export const SOF_REDIRECT_URI = '/create'; export const SOF_RESOURCES = [ @@ -81,19 +89,19 @@ export const SOF_PATIENT_RESOURCES = [ 'AllergyIntolerance', // 'MedicationStatement', // Not in EPIC USCDI R4 'MedicationRequest', - // 'Medication', // can't search by patient; "Only an _ID search is allowed." + // 'Medication', // Pulled in via references - can't search by patient; "Only an _ID search is allowed." 'Condition', 'Encounter', - // 'Observation', // "Must have either code or category." - // 'Organization', // can't search by patient; "Only an _ID search is allowed." + // 'Observation', // Handle specially for IPS codes - "Must have either code or category." + // 'Organization', // Pulled in via references - can't search by patient; "Only an _ID search is allowed." 'Immunization', // 'Device', // 'DeviceUseStatement', // Not in EPIC USCDI R4 'DiagnosticReport', // TODO change to subject // 'ImagingStudy', // Not in EPIC USCDI R4 // 'Media', // Not in EPIC USCDI R4 - // 'Practitioner', // can't search by patient; "Either name, family, or identifier is a required parameter." - // 'PractitionerRole', // can't search by patient; "An identifier, practitioner, organization, location, or specialty parameter is required." + // 'Practitioner', // Pulled in via references - can't search by patient; "Either name, family, or identifier is a required parameter." + // 'PractitionerRole', // Pulled in via references - can't search by patient; "An identifier, practitioner, organization, location, or specialty parameter is required." 'Procedure', // TODO change to subject // 'Specimen', // Not in EPIC USCDI R4 ]; diff --git a/src/lib/sofClient.js b/src/lib/sofClient.js index 00313aec..e9d6fe5d 100644 --- a/src/lib/sofClient.js +++ b/src/lib/sofClient.js @@ -7,7 +7,8 @@ const patientResourceScope = SOF_PATIENT_RESOURCES.map(resourceType => `patient/ const resourceScope = patientResourceScope.join(" "); const config = { clientId: '(ehr client id, populated later)', // clientId() is ignored at smit - scope: `openid fhirUser launch/patient ${resourceScope}`, + // scope: `openid fhirUser launch/patient ${resourceScope}`, + scope: `launch/patient patient/*.read`, iss: '(authorization url, populated later)', redirect_uri: SOF_REDIRECT_URI }; @@ -70,9 +71,8 @@ async function requestResources(client, resourceType) { async function activePatient() { if (client === undefined) { client = await FHIR.oauth2.ready(); - return client.getPatientId(); } - return null + return client.getPatientId() ?? undefined; } async function getResources() { @@ -85,6 +85,7 @@ async function getResources() { // Establish resource display methods let resources; if (client.state.clientId === "XfubBaEQzzHCOvgeB9Q7qZbg4QcK3Jro_65w5VWFRP8") { + // Minimum required requests for eClinicalWorks HIMSS 2024 demo resources = (await Promise.allSettled(['Patient', 'Immunization'].map((resourceType) => { return requestResources(client, resourceType); }))).filter(x => x.status == "fulfilled").map(x => x.value); diff --git a/src/lib/types.ts b/src/lib/types.ts index 30ce8187..057a4684 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -32,6 +32,7 @@ export interface SOFHost { name:string; url:string; clientId:string; + clientSecret?: string; note:string | undefined; } From 5f6bd403ef57dcb264cb1bf557727c608c158162 Mon Sep 17 00:00:00 2001 From: daniellrgn Date: Sun, 22 Sep 2024 10:59:03 -0700 Subject: [PATCH 06/11] Handle errors and update insurance clientIDs --- default.env | 7 +++++++ src/env.d.ts | 4 ++++ src/lib/FetchSoF.svelte | 10 ++++++---- src/lib/config.ts | 13 ++++++------- src/lib/sofClient.js | 9 ++++++--- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/default.env b/default.env index 48353bcf..9d8c5256 100644 --- a/default.env +++ b/default.env @@ -34,9 +34,16 @@ COMPOSE_FILE=docker-compose.yaml:docker-compose.static-ingress.yaml # SMART on FHIR client id configurations # Ensure that your development client is registered with the proper redirect uris +#EHRs VITE_EPIC_CLIENT_ID= VITE_CERNER_CLIENT_ID= +#Insurance +VITE_AETNA_CLIENT_ID= +VITE_CAREFIRST_CLIENT_ID= +VITE_HUMANA_CLIENT_ID= +VITE_ACENTRA_CLIENT_ID= + # HIMSS 2024 only: #VITE_EPIC_HIMSS_CLIENT_ID= #VITE_ECW_HIMSS_CLIENT_ID= diff --git a/src/env.d.ts b/src/env.d.ts index 6475dcb1..8e4d6cbb 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -5,6 +5,10 @@ interface ImportMetaEnv { readonly VITE_ECW_HIMSS_CLIENT_ID: string readonly VITE_EPIC_CLIENT_ID: string readonly VITE_CERNER_CLIENT_ID: string + readonly VITE_AETNA_CLIENT_ID: string + readonly VITE_CAREFIRST_CLIENT_ID: string + readonly VITE_HUMANA_CLIENT_ID: string + readonly VITE_ACENTRA_CLIENT_ID: string readonly VITE_API_BASE: string readonly VITE_VIEWER_BASE: string readonly VITE_INTERMEDIATE_FHIR_SERVER_BASE: string diff --git a/src/lib/FetchSoF.svelte b/src/lib/FetchSoF.svelte index f3243bc8..d5507c55 100644 --- a/src/lib/FetchSoF.svelte +++ b/src/lib/FetchSoF.svelte @@ -36,10 +36,10 @@ try { if (sofHost) { try { - authorize(sofHost.url, sofHost.clientId, sofHost.clientSecret ?? undefined); - authDispatch('sof-auth-init'); + authorize(sofHost.url, sofHost.clientId);// , sofHost.clientSecret); + authDispatch('sof-auth-init', { data: true }); } catch (e) { - authDispatch('sof-auth-fail') + authDispatch('sof-auth-fail', { data: false }); } } } catch (e) { @@ -82,7 +82,9 @@ console.log(resources) processing = false; return resourceDispatch('update-resources', result); - } catch (e) { + } catch (e: any) { + console.log(e.message); + fetchError = e.message; processing = false; endSession(); } diff --git a/src/lib/config.ts b/src/lib/config.ts index bee5c6f4..d29943a9 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -45,20 +45,19 @@ export const SOF_HOSTS = [ id: "aetna", name: "AETNA Insurance Sandbox", url: "https://vteapif1.aetna.com/fhirdemo/v1/patientaccess", - clientId: "09cbb76344009c25a2ec587b39ebc303", - clientSecret: "4fe39eccbfc586647407ea19f408521f", + clientId: import.meta.env.VITE_AETNA_CLIENT_ID, note: "VTETestUser01 / FHIRdemo2020" }, { - id: "c4bb", - name: "CARIN Blue Button", - // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", + id: "carefirst", + name: "CareFirst", + url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", - url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", - clientId: "0oaf5w78xfWspNqeA1d7", + // url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", + clientId: import.meta.env.VITE_CAREFIRST_CLIENT_ID, note: "Credentials provided" }, ]; diff --git a/src/lib/sofClient.js b/src/lib/sofClient.js index e9d6fe5d..80c866c6 100644 --- a/src/lib/sofClient.js +++ b/src/lib/sofClient.js @@ -76,11 +76,15 @@ async function activePatient() { } async function getResources() { - client = await FHIR.oauth2.ready(); + try { + client = await FHIR.oauth2.ready(); + } catch (e) { + throw Error('SMART authorization failed. The service you selected may be unavailable.') + } let pid = client.getPatientId(); if (!pid) { console.error("No patient ID found"); - return undefined; + throw Error('The service you selected did not return an ID for the authorized patient. Please try a different service.') } // Establish resource display methods let resources; @@ -94,7 +98,6 @@ async function getResources() { return requestResources(client, resourceType); }))).filter(x => x.status == "fulfilled").map(x => x.value); } - return resources; } From b79f459a2a03351813a64cbb8912a60e3f142e7b Mon Sep 17 00:00:00 2001 From: Daniel Lorigan Date: Thu, 20 Jun 2024 17:51:47 -0700 Subject: [PATCH 07/11] Add carin sandbox to sof urls --- src/lib/config.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/config.ts b/src/lib/config.ts index ed1998ae..b15194a0 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -42,6 +42,13 @@ export const SOF_HOSTS = [ url: "https://launch.smarthealthit.org/v/r4/sim/WzMsIiIsIiIsIkFVVE8iLDAsMCwwLCIiLCIiLCIiLCIiLCIiLCIiLCIiLDAsMF0/fhir", clientId: "", note: "Credentials provided" + }, + { + id: "c4bb", + name: "CARIN Blue Button", + url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", + clientId: "0oaf5w78xfWspNqeA1d7", + note: "Credentials provided" } ]; From 44dc9269d76212c365631c48962436384e17a5db Mon Sep 17 00:00:00 2001 From: daniellrgn Date: Tue, 25 Jun 2024 16:49:28 -0700 Subject: [PATCH 08/11] Add carin SOF config (no apparent standalone support) --- src/lib/config.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lib/config.ts b/src/lib/config.ts index b15194a0..fae2b8c3 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -43,13 +43,18 @@ export const SOF_HOSTS = [ clientId: "", note: "Credentials provided" }, - { - id: "c4bb", - name: "CARIN Blue Button", - url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", - clientId: "0oaf5w78xfWspNqeA1d7", - note: "Credentials provided" - } + // { + // id: "c4bb", + // name: "CARIN Blue Button", + // // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", + // // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", + // clientId: "0oaf5w78xfWspNqeA1d7", + // note: "Credentials provided" + // } ]; export const BEARER_AUTHORIZATION = { From 55ff17c474b03aae684d5b4bbbf5a70e91a65e1c Mon Sep 17 00:00:00 2001 From: daniellrgn Date: Sun, 22 Sep 2024 08:31:01 -0700 Subject: [PATCH 09/11] Add Aetna and reduce scopes for insurance connection --- src/lib/components/app/FetchSoF.svelte | 2 +- src/lib/config.ts | 42 ++++++++----- src/lib/types.ts | 87 ++++++++++++++++++++++++++ src/lib/utils/sofClient.js | 7 ++- 4 files changed, 117 insertions(+), 21 deletions(-) create mode 100644 src/lib/types.ts diff --git a/src/lib/components/app/FetchSoF.svelte b/src/lib/components/app/FetchSoF.svelte index e72851ba..df20d52a 100644 --- a/src/lib/components/app/FetchSoF.svelte +++ b/src/lib/components/app/FetchSoF.svelte @@ -40,7 +40,7 @@ try { if (sofHost) { try { - authorize(sofHost.url, sofHost.clientId); + authorize(sofHost.url, sofHost.clientId, sofHost.clientSecret ?? undefined); authDispatch('sof-auth-init'); } catch (e) { authDispatch('sof-auth-fail') diff --git a/src/lib/config.ts b/src/lib/config.ts index fae2b8c3..8abcd81a 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -43,18 +43,26 @@ export const SOF_HOSTS = [ clientId: "", note: "Credentials provided" }, - // { - // id: "c4bb", - // name: "CARIN Blue Button", - // // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", - // url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", - // clientId: "0oaf5w78xfWspNqeA1d7", - // note: "Credentials provided" - // } + { + id: "aetna", + name: "AETNA Insurance Sandbox", + url: "https://vteapif1.aetna.com/fhirdemo/v1/patientaccess", + clientId: "09cbb76344009c25a2ec587b39ebc303", + clientSecret: "4fe39eccbfc586647407ea19f408521f", + note: "VTETestUser01 / FHIRdemo2020" + }, + { + id: "c4bb", + name: "CARIN Blue Button", + // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", + url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", + clientId: "0oaf5w78xfWspNqeA1d7", + note: "Credentials provided" + }, ]; export const BEARER_AUTHORIZATION = { @@ -87,19 +95,19 @@ export const SOF_PATIENT_RESOURCES = [ 'AllergyIntolerance', // 'MedicationStatement', // Not in EPIC USCDI R4 'MedicationRequest', - // 'Medication', // can't search by patient; "Only an _ID search is allowed." + // 'Medication', // Pulled in via references - can't search by patient; "Only an _ID search is allowed." 'Condition', 'Encounter', - // 'Observation', // "Must have either code or category." - // 'Organization', // can't search by patient; "Only an _ID search is allowed." + // 'Observation', // Handle specially for IPS codes - "Must have either code or category." + // 'Organization', // Pulled in via references - can't search by patient; "Only an _ID search is allowed." 'Immunization', // 'Device', // 'DeviceUseStatement', // Not in EPIC USCDI R4 'DiagnosticReport', // TODO change to subject // 'ImagingStudy', // Not in EPIC USCDI R4 // 'Media', // Not in EPIC USCDI R4 - // 'Practitioner', // can't search by patient; "Either name, family, or identifier is a required parameter." - // 'PractitionerRole', // can't search by patient; "An identifier, practitioner, organization, location, or specialty parameter is required." + // 'Practitioner', // Pulled in via references - can't search by patient; "Either name, family, or identifier is a required parameter." + // 'PractitionerRole', // Pulled in via references - can't search by patient; "An identifier, practitioner, organization, location, or specialty parameter is required." 'Procedure', // TODO change to subject // 'Specimen', // Not in EPIC USCDI R4 ]; diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 00000000..057a4684 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,87 @@ +export type Bundle = any; +export interface SHLSubmitEvent { + shcs: SHCFile[]; + label?: string; + // content: Bundle; + passcode?: string; + exp?: number; + patientName?: string; +} + +export interface ResourceRetrieveEvent { + resources: Array | undefined; + source?: string | undefined; +} +export interface SHCRetrieveEvent { + shc: SHCFile | undefined; + source?: string | undefined; +} +export interface IPSRetrieveEvent { + ips: Bundle | undefined; + source?: string | undefined; +} +export interface SOFAuthEvent { + data: any | undefined; +} +export interface SHCFile { + verifiableCredential: string[]; +} + +export interface SOFHost { + id:string; + name:string; + url:string; + clientId:string; + clientSecret?: string; + note:string | undefined; +} + +export class ResourceHelper { + tempId: string; + original_resource: any; + simple_resource: any; + resource: any; + include: boolean = true; + + // Constructor + constructor(resource:any) { + this.original_resource = resource; + this.simple_resource = this.simplify(resource); + this.resource = resource; + this.tempId = this.hash(this.simple_resource); + } + + hash(value:any) { + return JSON.stringify(value); + // return crypto.createHash('sha1').update(value).digest('hex'); + } + + simplify(resource:any) { + let simpleResource = JSON.parse(JSON.stringify(resource)); + delete simpleResource.id; + delete simpleResource.meta; + delete simpleResource.text; + // delete simpleResource.patient; + // delete simpleResource.subject; + // delete simpleResource.encounter; + // delete simpleResource.requester; + return this.removeEntries(simpleResource, "reference"); + } + + removeEntries(obj:any, key:string) { + if (typeof obj === "object") { + for (let k in obj) { + if (k === key) { + delete obj[k]; + } else { + obj[k] = this.removeEntries(obj[k], key); + } + } + } else if (obj instanceof Array) { + for (let i=0; i < obj.length; i++) { + obj[i] = this.removeEntries(obj[i], key); + } + } + return obj; + } +} diff --git a/src/lib/utils/sofClient.js b/src/lib/utils/sofClient.js index 4e9ddf48..991207e6 100644 --- a/src/lib/utils/sofClient.js +++ b/src/lib/utils/sofClient.js @@ -7,7 +7,8 @@ const patientResourceScope = SOF_PATIENT_RESOURCES.map(resourceType => `patient/ const resourceScope = patientResourceScope.join(" "); const config = { clientId: '(ehr client id, populated later)', // clientId() is ignored at smit - scope: `openid fhirUser launch/patient ${resourceScope}`, + // scope: `openid fhirUser launch/patient ${resourceScope}`, + scope: `launch/patient patient/*.read`, iss: '(authorization url, populated later)', redirect_uri: SOF_REDIRECT_URI }; @@ -70,9 +71,8 @@ async function requestResources(client, resourceType) { async function activePatient() { if (client === undefined) { client = await FHIR.oauth2.ready(); - return client.getPatientId(); } - return null + return client.getPatientId() ?? undefined; } async function getResources() { @@ -85,6 +85,7 @@ async function getResources() { // Establish resource display methods let resources; if (client.state.clientId === "XfubBaEQzzHCOvgeB9Q7qZbg4QcK3Jro_65w5VWFRP8") { + // Minimum required requests for eClinicalWorks HIMSS 2024 demo resources = (await Promise.allSettled(['Patient', 'Immunization'].map((resourceType) => { return requestResources(client, resourceType); }))).filter(x => x.status == "fulfilled").map(x => x.value); From c3ff28dc2935d1e2163846a11b48183117c606cf Mon Sep 17 00:00:00 2001 From: daniellrgn Date: Sun, 22 Sep 2024 10:59:03 -0700 Subject: [PATCH 10/11] Handle errors and update insurance clientIDs --- default.env | 7 +++++++ src/env.d.ts | 4 ++++ src/lib/components/app/FetchSoF.svelte | 10 ++++++---- src/lib/config.ts | 13 ++++++------- src/lib/utils/sofClient.js | 9 ++++++--- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/default.env b/default.env index e08637f0..ec94a2d6 100644 --- a/default.env +++ b/default.env @@ -43,9 +43,16 @@ VITE_VERSION_STRING= # SMART on FHIR client id configurations: # Ensure that your development client is registered with the proper redirect uris +#EHRs VITE_EPIC_CLIENT_ID= VITE_CERNER_CLIENT_ID= +#Insurance +VITE_AETNA_CLIENT_ID= +VITE_CAREFIRST_CLIENT_ID= +VITE_HUMANA_CLIENT_ID= +VITE_ACENTRA_CLIENT_ID= + # SOF for HIMSS 2024 only: #VITE_EPIC_HIMSS_CLIENT_ID= #VITE_ECW_HIMSS_CLIENT_ID= diff --git a/src/env.d.ts b/src/env.d.ts index 49907d62..287fd7b7 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -5,6 +5,10 @@ interface ImportMetaEnv { readonly VITE_ECW_HIMSS_CLIENT_ID: string readonly VITE_EPIC_CLIENT_ID: string readonly VITE_CERNER_CLIENT_ID: string + readonly VITE_AETNA_CLIENT_ID: string + readonly VITE_CAREFIRST_CLIENT_ID: string + readonly VITE_HUMANA_CLIENT_ID: string + readonly VITE_ACENTRA_CLIENT_ID: string readonly VITE_MEDITECH_BEARER_TOKEN: string readonly VITE_API_BASE: string readonly VITE_VIEWER_BASE: string diff --git a/src/lib/components/app/FetchSoF.svelte b/src/lib/components/app/FetchSoF.svelte index df20d52a..8fdc0938 100644 --- a/src/lib/components/app/FetchSoF.svelte +++ b/src/lib/components/app/FetchSoF.svelte @@ -40,10 +40,10 @@ try { if (sofHost) { try { - authorize(sofHost.url, sofHost.clientId, sofHost.clientSecret ?? undefined); - authDispatch('sof-auth-init'); + authorize(sofHost.url, sofHost.clientId);// , sofHost.clientSecret); + authDispatch('sof-auth-init', { data: true }); } catch (e) { - authDispatch('sof-auth-fail') + authDispatch('sof-auth-fail', { data: false }); } } } catch (e) { @@ -86,7 +86,9 @@ console.log(resources) processing = false; return resourceDispatch('update-resources', result); - } catch (e) { + } catch (e: any) { + console.log(e.message); + fetchError = e.message; processing = false; endSession(); } diff --git a/src/lib/config.ts b/src/lib/config.ts index 8abcd81a..1cf7b3cd 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -47,20 +47,19 @@ export const SOF_HOSTS = [ id: "aetna", name: "AETNA Insurance Sandbox", url: "https://vteapif1.aetna.com/fhirdemo/v1/patientaccess", - clientId: "09cbb76344009c25a2ec587b39ebc303", - clientSecret: "4fe39eccbfc586647407ea19f408521f", + clientId: import.meta.env.VITE_AETNA_CLIENT_ID, note: "VTETestUser01 / FHIRdemo2020" }, { - id: "c4bb", - name: "CARIN Blue Button", - // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", + id: "carefirst", + name: "CareFirst", + url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", - url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", - clientId: "0oaf5w78xfWspNqeA1d7", + // url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", + clientId: import.meta.env.VITE_CAREFIRST_CLIENT_ID, note: "Credentials provided" }, ]; diff --git a/src/lib/utils/sofClient.js b/src/lib/utils/sofClient.js index 991207e6..5a4223df 100644 --- a/src/lib/utils/sofClient.js +++ b/src/lib/utils/sofClient.js @@ -76,11 +76,15 @@ async function activePatient() { } async function getResources() { - client = await FHIR.oauth2.ready(); + try { + client = await FHIR.oauth2.ready(); + } catch (e) { + throw Error('SMART authorization failed. The service you selected may be unavailable.') + } let pid = client.getPatientId(); if (!pid) { console.error("No patient ID found"); - return undefined; + throw Error('The service you selected did not return an ID for the authorized patient. Please try a different service.') } // Establish resource display methods let resources; @@ -94,7 +98,6 @@ async function getResources() { return requestResources(client, resourceType); }))).filter(x => x.status == "fulfilled").map(x => x.value); } - return resources; } From 5801be04665005c4229642982a394efcdec5595c Mon Sep 17 00:00:00 2001 From: Daniel Lorigan Date: Tue, 14 Jan 2025 20:18:33 -0800 Subject: [PATCH 11/11] WIP carin api, fetcher and param matcher --- src/lib/components/app/FetchCARINBB.svelte | 222 +++++++++++++++++++++ src/params/carin.ts | 5 + src/routes/api/proxy/+server.ts | 48 +++++ 3 files changed, 275 insertions(+) create mode 100644 src/lib/components/app/FetchCARINBB.svelte create mode 100644 src/params/carin.ts create mode 100644 src/routes/api/proxy/+server.ts diff --git a/src/lib/components/app/FetchCARINBB.svelte b/src/lib/components/app/FetchCARINBB.svelte new file mode 100644 index 00000000..c5e42ea6 --- /dev/null +++ b/src/lib/components/app/FetchCARINBB.svelte @@ -0,0 +1,222 @@ + +
prepareIps()}> + + + {#each SOF_HOSTS as host} + + + {#if host.note} +

{@html host.note}

+ {/if} +
+ {/each} +
+ + + + + + + + + + + + {#if processing || loadingSample} + + + + {/if} + +
+{fetchError} diff --git a/src/params/carin.ts b/src/params/carin.ts new file mode 100644 index 00000000..1b353236 --- /dev/null +++ b/src/params/carin.ts @@ -0,0 +1,5 @@ +import type { ParamMatcher } from '@sveltejs/kit'; + +export const match = ((param: string): param is ('acentra' | 'aetna' | 'carefirst' | 'cpcds' | 'humana') => { + return param === 'acentra' || param === 'aetna' || param === 'carefirst' || param === 'cpcds' || param === 'humana'; +}) satisfies ParamMatcher; \ No newline at end of file diff --git a/src/routes/api/proxy/+server.ts b/src/routes/api/proxy/+server.ts new file mode 100644 index 00000000..a570d50e --- /dev/null +++ b/src/routes/api/proxy/+server.ts @@ -0,0 +1,48 @@ +import { json } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; // Access private environment variables + +const CPCDS_URL = 'https://cpcds-server.lantanagroup.com/fhir'; +const SMART_LAUNCH_URL = `${CPCDS_URL}/.well-known/smart-configuration`; + +export const POST = async ({ request, url }: { request: Request; url: URL }) => { + try { + // Get the base URL path to the current route + const proxyPath = url.pathname; + + // Construct the forward URL by removing the base path (dynamic) + const forwardPath = proxyPath.replace(url.origin, ''); // Strip the origin + const forwardQuery = url.search; // Includes the "?" and any query parameters + + // Construct the full target API URL + const targetUrl = `https://api.example.com${forwardPath}${forwardQuery}`; + + // Read the incoming request body + const body = await request.text(); // Use `text()` for flexibility (JSON, form data, etc.) + + // Set up headers and include the client secret + const clientSecret = env.CLIENT_SECRET; + + const headers = new Headers(request.headers); + headers.set('Authorization', `Basic ${btoa(`client_id:${clientSecret}`)}`); + headers.set('Content-Type', request.headers.get('Content-Type') || 'application/json'); + + // Forward the request to the target API + const apiResponse = await fetch(targetUrl, { + method: request.method, + headers, + body: ['POST', 'PUT', 'PATCH'].includes(request.method) ? body : undefined, // Include body if applicable + }); + + // Return the API response to the client + const responseText = await apiResponse.text(); + return new Response(responseText, { + status: apiResponse.status, + headers: apiResponse.headers, // Pass response headers + }); + } catch (error) { + console.error('Proxy error:', error); + + // Handle and return errors + return json({ error: 'An error occurred while forwarding the request' }, { status: 500 }); + } +}; \ No newline at end of file