diff --git a/Dockerfile b/Dockerfile index 0ea2066..7662dd3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,22 @@ FROM gradle:6.9.0-jdk11 # Set working directory so that all subsequent command runs in this folder WORKDIR /test-ehr + +ENV GRADLE_USER_HOME=/tmp/gradle-user-home + +USER root +RUN mkdir -p /test-ehr/.gradle && chown -R gradle:gradle /test-ehr +USER gradle + # Copy app files to container COPY --chown=gradle:gradle . . RUN gradle build + +USER root +RUN rm -rf /test-ehr/.gradle /tmp/gradle-user-home \ + && mkdir -p /test-ehr/.gradle /tmp/gradle-user-home \ + && chmod 0777 /test-ehr/.gradle /tmp/gradle-user-home + # Expose port to access the app EXPOSE 8080 @@ -13,4 +26,4 @@ EXPOSE 8080 HEALTHCHECK --interval=45s --start-period=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8080/test-ehr/r4/metadata || exit 1 # Command to run our app -CMD ./dockerRunnerProd.sh \ No newline at end of file +CMD ./dockerRunnerProd.sh diff --git a/Dockerfile.dev b/Dockerfile.dev index 46c3ad1..99c3816 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -3,9 +3,22 @@ FROM gradle:6.9.0-jdk11 # Set working directory so that all subsequent command runs in this folder WORKDIR /test-ehr + +ENV GRADLE_USER_HOME=/tmp/gradle-user-home + +USER root +RUN mkdir -p /test-ehr/.gradle && chown -R gradle:gradle /test-ehr +USER gradle + # Copy app files to container COPY --chown=gradle:gradle . . RUN gradle build + +USER root +RUN rm -rf /test-ehr/.gradle /tmp/gradle-user-home \ + && mkdir -p /test-ehr/.gradle /tmp/gradle-user-home \ + && chmod 0777 /test-ehr/.gradle /tmp/gradle-user-home + # Expose port to access the app EXPOSE 8080 EXPOSE 8081 @@ -13,4 +26,4 @@ EXPOSE 8081 HEALTHCHECK --interval=45s --start-period=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8080/test-ehr/r4/metadata || exit 1 # Command to run our app -CMD ./dockerRunnerDev.sh \ No newline at end of file +CMD ./dockerRunnerDev.sh diff --git a/Dockerfile.keycloak b/Dockerfile.keycloak index 47e0a35..ebeb158 100644 --- a/Dockerfile.keycloak +++ b/Dockerfile.keycloak @@ -1,3 +1,3 @@ -FROM keycloak/keycloak:22.0.1 +FROM quay.io/keycloak/keycloak:22.0.1 HEALTHCHECK --interval=30s --start-period=15s --timeout=10m --retries=10 CMD bash -c 'echo -n > /dev/tcp/127.0.0.1/8080' COPY ./src/main/resources/ClientFhirServerRealm.json /opt/keycloak/data/import/ClientFhirServerRealm.json \ No newline at end of file diff --git a/dockerRunnerDev.sh b/dockerRunnerDev.sh index fd6e664..db83867 100755 --- a/dockerRunnerDev.sh +++ b/dockerRunnerDev.sh @@ -4,7 +4,11 @@ trap "kill $LOAD_DATA_PID $CONTINUOUS_BUILD_PID $SERVER_PID; gradle --stop; exit" INT # Set environment variables -mkdir logs +BUILD_PROJECT_CACHE=/tmp/test-ehr-gradle-project-cache/build +RUN_PROJECT_CACHE=/tmp/test-ehr-gradle-project-cache/run +LOAD_DATA_PROJECT_CACHE=/tmp/test-ehr-gradle-project-cache/load-data + +mkdir -p logs # Reset log file content for new application boot echo "*** Logs for 'gradle installBootDist --continuous' ***" > ./logs/builder.log echo "*** Logs for 'gradle bootRun' ***" > ./logs/runner.log @@ -18,7 +22,7 @@ echo "Starting continuous data loader..." sleep 1 done echo "loading data into test-ehr..." -gradle loadData +gradle --project-cache-dir "$LOAD_DATA_PROJECT_CACHE" loadData # Continuous Load Data command whenever fhirResourcesToLoad directory changes resources_modify_time=$(stat -c %Y fhirResourcesToLoad) @@ -29,7 +33,7 @@ do if [[ "$resources_modify_time" != "$new_resources_modify_time" ]] then echo "loading data into test-ehr..." - gradle loadData + gradle --project-cache-dir "$LOAD_DATA_PROJECT_CACHE" loadData fi resources_modify_time=$new_resources_modify_time @@ -38,14 +42,14 @@ done ) & LOAD_DATA_PID=$! # Start the continious build listener process echo "starting continuous build listener..." -( gradle build --continuous | tee ./logs/builder.log ) & CONTINUOUS_BUILD_PID=$! +( gradle --project-cache-dir "$BUILD_PROJECT_CACHE" build --continuous | tee ./logs/builder.log ) & CONTINUOUS_BUILD_PID=$! # Start server process once initial build finishes ( while ! grep -m1 'BUILD SUCCESSFUL' < ./logs/builder.log; do sleep 1 done echo "starting test-ehr server in debug mode..." -gradle bootRun -Pdebug 2>&1 | tee ./logs/runner.log ) & SERVER_PID=$! +gradle --project-cache-dir "$RUN_PROJECT_CACHE" bootRun -Pdebug 2>&1 | tee ./logs/runner.log ) & SERVER_PID=$! @@ -53,4 +57,3 @@ gradle bootRun -Pdebug 2>&1 | tee ./logs/runner.log ) & SERVER_PID=$! wait $CONTINUOUS_BUILD_PID $SERVER_PID $LOAD_DATA_PID EXIT_CODE=$? echo "application exited with exit code $EXIT_CODE..." - diff --git a/dockerRunnerProd.sh b/dockerRunnerProd.sh index 61cc14f..9493fa5 100755 --- a/dockerRunnerProd.sh +++ b/dockerRunnerProd.sh @@ -4,7 +4,10 @@ trap "kill $LOAD_DATA_PID $SERVER_PID; gradle --stop; exit" INT # Set environment variables -mkdir logs +RUN_PROJECT_CACHE=/tmp/test-ehr-gradle-project-cache/run +LOAD_DATA_PROJECT_CACHE=/tmp/test-ehr-gradle-project-cache/load-data + +mkdir -p logs # Reset log file content for new application boot echo "*** Logs for 'gradle bootRun' ***" > ./logs/runner.log @@ -16,14 +19,13 @@ echo "Starting application in production mode..." sleep 1 done echo "loading data into test-ehr..." -gradle loadData ) & LOAD_DATA_PID=$! +gradle --project-cache-dir "$LOAD_DATA_PROJECT_CACHE" loadData ) & LOAD_DATA_PID=$! # Start server process echo "starting test-ehr server..." -( gradle bootRun 2>&1 | tee ./logs/runner.log ) & SERVER_PID=$! +( gradle --project-cache-dir "$RUN_PROJECT_CACHE" bootRun 2>&1 | tee ./logs/runner.log ) & SERVER_PID=$! # Handle application background process exiting wait $SERVER_PID $LOAD_DATA_PID EXIT_CODE=$? echo "application exited with exit code $EXIT_CODE..." - diff --git a/fhirResourcesToLoad/0. pharmacy.json b/fhirResourcesToLoad/0. pharmacy.json index fd573b6..b1d4eb1 100644 --- a/fhirResourcesToLoad/0. pharmacy.json +++ b/fhirResourcesToLoad/0. pharmacy.json @@ -2,7 +2,7 @@ "resourceType": "HealthcareService", "id": "pharm0111", "active": true, - "name": "RiteAid Pharmacy", + "name": "PIMS Pharmacy A", "category": [ { "coding": [ @@ -28,7 +28,7 @@ "location": [ { "reference": "Location/pharm-location-001", - "display": "123 Main Street, Detroit, MI 48224" + "display": "123 Main Street, Boston, MA 02134" } ] -} \ No newline at end of file +} diff --git a/fhirResourcesToLoad/7. pharmacy-location.json b/fhirResourcesToLoad/7. pharmacy-location.json index 84ff05a..9ae6419 100644 --- a/fhirResourcesToLoad/7. pharmacy-location.json +++ b/fhirResourcesToLoad/7. pharmacy-location.json @@ -2,8 +2,8 @@ "resourceType": "Location", "id": "pharm-location-001", "status": "active", - "name": "Test Pharmacy - Main Location", - "description": "Main location for Test Pharmacy providing community pharmacy services", + "name": "PIMS Pharmacy A - Main Location", + "description": "Main location for PIMS Pharmacy A providing community pharmacy services", "mode": "instance", "address": { "use": "work", @@ -11,9 +11,9 @@ "line": [ "123 Main Street" ], - "city": "Detroit", - "state": "MI", - "postalCode": "48224", + "city": "Boston", + "state": "MA", + "postalCode": "02134", "country": "US" }, "telecom": [ @@ -28,4 +28,4 @@ "use": "work" } ] -} \ No newline at end of file +} diff --git a/fhirResourcesToLoad/8.pharmacy-2.json b/fhirResourcesToLoad/8.pharmacy-2.json index 16b3536..f120f45 100644 --- a/fhirResourcesToLoad/8.pharmacy-2.json +++ b/fhirResourcesToLoad/8.pharmacy-2.json @@ -2,7 +2,7 @@ "resourceType": "HealthcareService", "id": "pharm4321", "active": true, - "name": "Test Pharmacy #2", + "name": "PIMS Pharmacy B", "category": [ { "coding": [ @@ -27,8 +27,8 @@ ], "location": [ { - "reference": "Location/pharm-location-001", - "display": "561 Street Road, Anytown, CA 12345" + "reference": "Location/pharm-location-002", + "display": "456 Central Street, Cambridge, MA 02139" } ] -} \ No newline at end of file +} diff --git a/fhirResourcesToLoad/9. pharmacy-location-2.json b/fhirResourcesToLoad/9. pharmacy-location-2.json index b7cc8cb..f9419b0 100644 --- a/fhirResourcesToLoad/9. pharmacy-location-2.json +++ b/fhirResourcesToLoad/9. pharmacy-location-2.json @@ -1,19 +1,19 @@ { "resourceType": "Location", - "id": "pharm-location-001", + "id": "pharm-location-002", "status": "active", - "name": "Test Pharmacy - Secondary Location", - "description": "Secondary location for Test Pharmacy providing community pharmacy services", + "name": "PIMS Pharmacy B", + "description": "Second PIMS pharmacy location for product availability testing", "mode": "instance", "address": { "use": "work", "type": "physical", "line": [ - "561 Street Road" + "456 Central Street" ], - "city": "Anytown", - "state": "CA", - "postalCode": "12345", + "city": "Cambridge", + "state": "MA", + "postalCode": "02139", "country": "US" }, "telecom": [ @@ -28,4 +28,4 @@ "use": "work" } ] -} \ No newline at end of file +} diff --git a/fhirResourcesToLoad/medication_pexidartinib_generic.json b/fhirResourcesToLoad/medication_pexidartinib_generic.json new file mode 100644 index 0000000..0e59f3e --- /dev/null +++ b/fhirResourcesToLoad/medication_pexidartinib_generic.json @@ -0,0 +1,53 @@ +{ + "resourceType": "Medication", + "id": "med-pexidartinib-generic", + "code": { + "coding": [ + { + "system": "http://www.nlm.nih.gov/research/umls/rxnorm", + "code": "2183126", + "display": "Pexidartinib Hydrochloride 200 MG Oral Capsule" + }, + { + "system": "http://hl7.org/fhir/sid/ndc", + "code": "99999-407-20", + "display": "Pexidartinib Hydrochloride" + } + ], + "text": "Pexidartinib Hydrochloride 200 MG Oral Capsule" + }, + "form": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "385055001", + "display": "Capsule dose form" + } + ] + }, + "ingredient": [ + { + "itemCodeableConcept": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "774167006", + "display": "Pexidartinib" + } + ] + }, + "strength": { + "numerator": { + "value": 200, + "system": "http://unitsofmeasure.org", + "code": "mg" + }, + "denominator": { + "value": 1, + "system": "http://terminology.hl7.org/CodeSystem/v3-orderableDrugForm", + "code": "CAP" + } + } + } + ] +} diff --git a/fhirResourcesToLoad/rems_smith_01_patient.json b/fhirResourcesToLoad/rems_smith_01_patient.json index bf48c5a..0f786ae 100644 --- a/fhirResourcesToLoad/rems_smith_01_patient.json +++ b/fhirResourcesToLoad/rems_smith_01_patient.json @@ -7,9 +7,9 @@ { "use": "home", "type": "both", - "state": "Wonderland", - "city": "Wonderland", - "postalCode": "12345", + "state": "MA", + "city": "Somerville", + "postalCode": "02143", "line": ["47 Main St"] } ], diff --git a/fhirResourcesToLoad/rems_smith_medicationrequest_pexidartinib_generic.json b/fhirResourcesToLoad/rems_smith_medicationrequest_pexidartinib_generic.json new file mode 100644 index 0000000..1814c7e --- /dev/null +++ b/fhirResourcesToLoad/rems_smith_medicationrequest_pexidartinib_generic.json @@ -0,0 +1,105 @@ +{ + "resourceType": "MedicationRequest", + "id": "pat036-mr-pexidartinib-generic", + "medicationReference": { + "reference": "Medication/med-pexidartinib-generic", + "display": "Pexidartinib Hydrochloride 200 MG Oral Capsule" + }, + "contained": [ + { + "resourceType": "Medication", + "id": "med-pexidartinib-generic", + "code": { + "coding": [ + { + "system": "http://www.nlm.nih.gov/research/umls/rxnorm", + "code": "2183126", + "display": "Pexidartinib Hydrochloride 200 MG Oral Capsule" + }, + { + "system": "http://hl7.org/fhir/sid/ndc", + "code": "99999-407-20", + "display": "Pexidartinib Hydrochloride" + } + ], + "text": "Pexidartinib Hydrochloride 200 MG Oral Capsule" + } + } + ], + "status": "active", + "intent": "order", + "subject": { + "reference": "Patient/pat036", + "display": "Alice Smith" + }, + "authoredOn": "2020-07-11", + "requester": { + "reference": "Practitioner/pra1234", + "display": "Jane Doe" + }, + "reasonCode": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "52042003", + "display": "Systemic lupus erythematosus glomerulonephritis syndrome, World Health Organization class V (disorder)" + } + ] + } + ], + "insurance": [ + { + "reference": "Coverage/cov036" + } + ], + "dosageInstruction": [ + { + "sequence": 1, + "text": "200mg twice daily", + "timing": { + "repeat": { + "frequency": 2, + "period": 1, + "periodUnit": "d" + } + }, + "route": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "26643006", + "display": "Oral route (qualifier value)" + } + ] + }, + "doseAndRate": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/dose-rate-type", + "code": "ordered", + "display": "Ordered" + } + ] + }, + "doseQuantity": { + "value": 200, + "unit": "mg", + "system": "http://unitsofmeasure.org", + "code": "mg" + } + } + ] + } + ], + "dispenseRequest": { + "quantity": { + "value": 90, + "system": "http://terminology.hl7.org/CodeSystem/v3-orderableDrugForm", + "code": "CAP" + }, + "numberOfRepeatsAllowed": 3 + } +} diff --git a/fhirResourcesToLoad/rems_snow_medicationrequest_pexidartinib_generic.json b/fhirResourcesToLoad/rems_snow_medicationrequest_pexidartinib_generic.json new file mode 100644 index 0000000..abafe05 --- /dev/null +++ b/fhirResourcesToLoad/rems_snow_medicationrequest_pexidartinib_generic.json @@ -0,0 +1,95 @@ +{ + "resourceType": "MedicationRequest", + "id": "pat017-mr-pexidartinib-generic", + "medicationCodeableConcept": { + "coding": [ + { + "system": "http://www.nlm.nih.gov/research/umls/rxnorm", + "code": "2183126", + "display": "Pexidartinib Hydrochloride 200 MG Oral Capsule" + }, + { + "system": "http://hl7.org/fhir/sid/ndc", + "code": "99999-407-20", + "display": "Pexidartinib Hydrochloride" + } + ], + "text": "Pexidartinib Hydrochloride 200 MG Oral Capsule" + }, + "status": "active", + "intent": "order", + "subject": { + "reference": "Patient/pat017", + "display": "John Snow" + }, + "authoredOn": "2020-07-11", + "requester": { + "reference": "Practitioner/pra1234", + "display": "Jane Doe" + }, + "reasonCode": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "52042003", + "display": "Systemic lupus erythematosus glomerulonephritis syndrome, World Health Organization class V (disorder)" + } + ] + } + ], + "insurance": [ + { + "reference": "Coverage/cov017" + } + ], + "dosageInstruction": [ + { + "sequence": 1, + "text": "200mg twice daily", + "timing": { + "repeat": { + "frequency": 2, + "period": 1, + "periodUnit": "d" + } + }, + "route": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "26643006", + "display": "Oral route (qualifier value)" + } + ] + }, + "doseAndRate": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/dose-rate-type", + "code": "ordered", + "display": "Ordered" + } + ] + }, + "doseQuantity": { + "value": 200, + "unit": "mg", + "system": "http://unitsofmeasure.org", + "code": "mg" + } + } + ] + } + ], + "dispenseRequest": { + "quantity": { + "value": 90, + "system": "http://terminology.hl7.org/CodeSystem/v3-orderableDrugForm", + "code": "CAP" + }, + "numberOfRepeatsAllowed": 3 + } +} diff --git a/src/main/java/org/hl7/codex/rems/script/FillStatus.java b/src/main/java/org/hl7/codex/rems/script/FillStatus.java index ba905f2..db95599 100644 --- a/src/main/java/org/hl7/codex/rems/script/FillStatus.java +++ b/src/main/java/org/hl7/codex/rems/script/FillStatus.java @@ -61,5 +61,18 @@ public DispensedStatusEnum getStatus() { return DispensedStatusEnum.UNKNOWN; } } - + + public DispensedStatus getDispensedStatus() { + if (getDispensed() != null) { + return getDispensed(); + } else if (getPartiallyDispensed() != null) { + return getPartiallyDispensed(); + } else if (getNotDispensed() != null) { + return getNotDispensed(); + } else if (getTransferred() != null) { + return getTransferred(); + } else { + return null; + } + } } diff --git a/src/main/java/org/hl7/codex/rems/script/NcpdpScriptController.java b/src/main/java/org/hl7/codex/rems/script/NcpdpScriptController.java index c58b3b1..cd29b25 100644 --- a/src/main/java/org/hl7/codex/rems/script/NcpdpScriptController.java +++ b/src/main/java/org/hl7/codex/rems/script/NcpdpScriptController.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.hl7.fhir.r4.model.MedicationRequest; +import org.hl7.fhir.r4.model.Annotation; import org.hl7.fhir.r4.model.MedicationDispense; import org.hl7.fhir.r4.model.Reference; @@ -80,6 +81,9 @@ private void handleRxFillMessage(RxFill rxFill, Header header) { logger.info(" PrescriberOrderNumber: " + header.getPrescriberOrderNumber()); logger.info(" Dispensed Status: " + dispensedStatus); + String note = rxFill.getFillStatus().getDispensedStatus().Note; + logger.info(" Dispensed Note: " + note); + IFhirResourceDao medicationRequestDao = jpaRestfulServer.getDao(MedicationRequest.class); IFhirResourceDao medicationDispenseDao = @@ -114,6 +118,7 @@ private void handleRxFillMessage(RxFill rxFill, Header header) { medicationDispense.setMedication(medicationRequest.getMedication()); medicationDispense.setSubject(medicationRequest.getSubject()); medicationDispense.addAuthorizingPrescription(new Reference(requestId)); + medicationDispense.addNote(new Annotation().setText(note)); // store the MedicationDispense RequestDetails dispenseDetails = new SystemRequestDetails(); @@ -130,7 +135,7 @@ private MedicationDispense.MedicationDispenseStatus convertRxFillDispensedStatus case PARTIALLY_DISPENSED: return MedicationDispense.MedicationDispenseStatus.INPROGRESS; case NOT_DISPENSED: - return MedicationDispense.MedicationDispenseStatus.PREPARATION; + return MedicationDispense.MedicationDispenseStatus.ONHOLD; case TRANSFERRED: case UNKNOWN: default: