From c4728ba4773e0a7152c8ebae0da2593e4018fadb Mon Sep 17 00:00:00 2001 From: aschmidt34 <124093649+aschmidt34@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:24:29 -0600 Subject: [PATCH 1/3] 24.3 fb notification revamps (#685) * - In Blood Draws Today (All, Animal Care, and Vet Staff), I changed 'Assignment Status' column header to 'Unassigned' and 'Completion Status' to 'Incomplete'. I also sorted all results so Incomplete draws show up first. - In BloodDrawReviewTriggerNotification, I made it so warnings only send when the draw has a date of today. - In BloodDrawReviewDailyNotification, I added a check to verify that there are no upcoming blood draws that will overdraw. - TODO: After testing this on test servers, make sure to remove test dates in BloodDrawsTodayAll and BloodDrawReviewDailyNotification. * Migrated automated test changes from 23.11 to 24.3. 23.11_notificationRevamps is now ready to be deleted. * Added blood overdraw trigger notification. * Registered new notification. * Updated code so message sends when testing/running in browser. Updated log for wnprc_triggers.js for when function is run to help debug until issue with webpack generation is fixed. * Updated overdraw notification. Notification is now created everytime blood is updated. Logic is run inside notification to determine if current draw is an overdraw. If it is an overdraw, the message is sent. * Fixed issue where 'Animal Replacement Fee' showed incorrectly in the revamped Death Notification. This occurred when the Type of Death resulted in a fee in the table 'ehr_lookups' > 'death_cause', but the 'prepaid' field was empty in the table 'study' > 'demographics'. This was due to labkeys table lookup resulting in a string 'null' instead of a real null when using the TableSelector. My new notifications use a new query function i wrote, but this Death Notification used the old TableSelector method. * Reordered messages in BloodDrawReviewDailyNotification.java so overdraws are listed first. * -NotificationToolkit.java: Updated getWeightFromAnimalID() and getSexFromAnimalID() so they use new query function getTableMultiRowMultiColumnWithFieldKeys() instead of old function getTableRowAsList(). Also moved DeathNecropsyObject and DeathDemographicObject to DeathNotificationRevamp.java. - DeathNotificationRevamp.java: Moved DeathNecropsyObject and DeathDemographicObject here from NotificationToolkit.java. Also cleaned up these objects and updated these them to use the new query getTableMultiRowMultiColumnWithFieldKeys. * Removed automated tests that don't aren't finished. * Added 2 new functions to NotificationToolkit for creating URL's. This is to avoid hardcoding URL's per new LabKey policy. Implimented these 2 new functions in DeathNotificationRevamp.java for getting the necropsy and animal abstract URL's. * Adding automated tests for notifications. * - WNPRC_EHRTest.java: Added notification setup function, multiple notification check functions, and a toolkit object containing reusable test functions that are commonly used when writing different tests. - BloodOverdrawTriggerNotification.java & BloodDrawReviewTriggerNotification.java: Added a reset function to clear the data after notification is triggered. This is because there are null checks that don't work when artifacts are left over from previous instances. - NotificationToolkit.java: Added null check to checkIfBloodDrawIsOverdraw() because empty data was crashing the function. * - DeathNotificationRevamp.java: Added a null check to make sure class doesn't cause a failure if there's no taskID in the row returned from the query. - WNPRC_EHRTest.java: Added a check for death notification and prenatal death notification. * Added null check before sending blood overdraw trigger manually. The built in labkey notification setup just doesn't send a notification if there's a null body, but for my trigger notifications where I send it manually, I should check for null. * Moved the automated tests to the end of the list and removed the @Test flag. * Disabled site-wide notifications. Also disable individual notifications. * Re-added 'test' tag before function. Originally removed this because Marty said it was not necessary if I called the function in doSetup(), but it's not showing up in the console log anymore. Trying to re-add this. * Added try/catch for insertValueIntoBloodBilledByDataset due to duplicate data being uploaded. Looks like labkey's loadBloodBilledByLookup() already uploads the same values. * Added try/catch for remaining functions that insert into datasets. * Changed a query function in checkIfAnimalIsAlive() so it no longer references my functions that use QviewObject. Need to evenutally phase out all these functions as my new query functions work much better. * Removing migrated alerts from ehrcron to Java based notifications * Added comment to leave commented-out code block alone. This will be used for future notifications, and it's very tough coding this correctly in the right order. Please do not delete. * Removed function call for notification tests in doSetup(). This was recommended by Binal via a LabKey ticket; it caused my test to run twice since the @test annotation already exists. * - TriggerScriptHelper.java: Added trigger call for new AnimalRequestUpdateNotificationRevamp notification. Also moved call for old notification inside if/else statement so 'on/off' status is checked before sending. Will delete the old call after vefifying new version works well. - WNPRC_EHRModule.java: Registered new EmptyNotificationRevamp and AnimalRequestUpdateNotificationRevamp notifications. - AdminAlertsNotificationRevamp.java: Updated this notification so the days of the week are displayed in the correct order. Also fixed an error with the wrong results being queried (needed to update the filter). - AnimalRequestNotificationRevamp.java: Added test data to be set when notification is triggered from 'Run Report in Browser'. Also added a resetClass function. - AnimalRequestUpdateNotificationRevamp.java: Created this new revamped notification. - ColonyAlertsLiteNotificationRevamp.java: Updated this notification to use the new EmptyNotificationRevamp notification. This is sent instead of the original notification if there is no data to be sent. - EmptyNotificationRevamp.java: Created this new revamped notification. This is sent when certain notifications have no data to send. This is to prevent users from receiving empty emails, but allows Daniel and I to see that the notifications are still being sent. - NotificationToolkit.java: Added function to send the new EmptyNotificationRevamp.java notification. * - TriggerScriptHelper.java: Added 'sendManually' function call that I had forgotten. - BloodDrawReviewDailyNotification.java: Added 2 extra checks requested by blood draw team. - BloodDrawsTodayAnimalCare.java: Updated this to use the new dummy notification when there is no data to be sent. * - animal_requests.js: Added fix for fatal issue with qcstatus being different in dataset and form. - ColonyAlertsNotificationRevamp.java: Added extra query requested by Kim. This query checks all animals in the 'Assignments' dataset and returns any where the project has expired or the protocol has deactivated. * - WNPRC_EHRModule.java: Registered new notification. - TreatmentAlertsNotificationRevamp.java: Created new notification. - BloodDrawsTodayAll.java: Added 'incomplete count' to the notification (as requested by blood draw team). - BloodDrawsTodayVetStaff.java: Added functionality so message does not send when there is no data (as requested by blood draw team). * Removed joda time import statement because it was unused. --------- Co-authored-by: F. Daniel Nicolalde --- .../queries/wnprc/animal_requests.js | 20 +- .../org/labkey/wnprc_ehr/WNPRC_EHRModule.java | 3 +- .../notification/BloodDrawsTodayAll.java | 54 ++- .../notification/BloodDrawsTodayVetStaff.java | 10 +- .../ColonyAlertsNotificationRevamp.java | 102 ++++ .../TreatmentAlertsNotificationRevamp.java | 438 ++++++++++++++++++ 6 files changed, 598 insertions(+), 29 deletions(-) create mode 100644 WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/TreatmentAlertsNotificationRevamp.java diff --git a/WNPRC_EHR/resources/queries/wnprc/animal_requests.js b/WNPRC_EHR/resources/queries/wnprc/animal_requests.js index 2c9cfa381..81cac97da 100644 --- a/WNPRC_EHR/resources/queries/wnprc/animal_requests.js +++ b/WNPRC_EHR/resources/queries/wnprc/animal_requests.js @@ -90,6 +90,24 @@ function onAfterInsert(helper,errors,row){ function onAfterUpdate(helper,errors,row,oldRow){ var rowid = row.rowId; var hostName = 'https://' + LABKEY.serverName; - console.log ("animal_requests.js: New request updated, rowid: "+ rowid); + console.log("animal_requests.js: New request updated, rowid: "+ rowid); + + if ("QCStateLabel" in row) { + delete row.QCState; + row["qcstate"] = row["QCStateLabel"]; + delete row.QCStateLabel; + } + if ("QCStateLabel" in oldRow) { + oldRow["qcstate"] = oldRow["QCStateLabel"]; + delete oldRow.QCStateLabel; + } + + if ("_publicData" in row) { + delete row._publicData; + } + if ("_publicData" in oldRow) { + delete oldRow._publicData; + } + WNPRC.Utils.getJavaHelper().sendAnimalRequestNotificationUpdate(rowid, row, oldRow, hostName); } diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/WNPRC_EHRModule.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/WNPRC_EHRModule.java index 7c9832694..abdebd68e 100644 --- a/WNPRC_EHR/src/org/labkey/wnprc_ehr/WNPRC_EHRModule.java +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/WNPRC_EHRModule.java @@ -392,7 +392,8 @@ public void registerNotifications() { new ColonyAlertsLiteNotificationRevamp(this), new BloodOverdrawTriggerNotification(this), new EmptyNotificationRevamp(this), - new AnimalRequestUpdateNotificationRevamp(this) + new AnimalRequestUpdateNotificationRevamp(this), + new TreatmentAlertsNotificationRevamp(this) ); for (Notification notification : notifications) diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayAll.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayAll.java index 8030b9204..e143099c2 100644 --- a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayAll.java +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayAll.java @@ -81,18 +81,17 @@ public String getMessageBodyHTML(Container c, User u) { // Prints all tables. if (myBloodDrawNotificationObject.resultsByArea.isEmpty()) { - messageBody.append("There are no scheduled blood draws for this group today."); + messageBody.append("There are no scheduled blood draws today."); } else { + messageBody.append("

REMAINING INCOMPLETE TOTALS:
\n"); + messageBody.append("Animal Care: " + myBloodDrawNotificationObject.numIncompleteAnimalCare + "
\n"); + messageBody.append("Research Staff: " + myBloodDrawNotificationObject.numIncompleteResearchStaff + "
\n"); + messageBody.append("SPI: " + myBloodDrawNotificationObject.numIncompleteSPI + "
\n"); + messageBody.append("Vet Staff: " + myBloodDrawNotificationObject.numIncompleteVetStaff + "

"); messageBody.append(myBloodDrawNotificationObject.printTablesAsHTML()); } -// // Creates table. -// String[] myTableColumns = new String[]{"Id", "Blood Remaining", "Project Assignment", "Completion Status", "Group", "Other Groups Drawing Blood Today"}; -// NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(myTableColumns, myBloodDrawNotificationObject.myTableData); -// myTable.rowColors = myBloodDrawNotificationObject.myTableRowColors; -// messageBody.append(myTable.createBasicHTMLTable()); - return messageBody.toString(); } @@ -105,8 +104,11 @@ public static class BloodDrawsTodayObject { NotificationToolkit.DateToolkit dateToolkit = new NotificationToolkit.DateToolkit(); ArrayList myTableData = new ArrayList<>(); // List of all blood draws as [[id, blood remaining, project assignment, completion status, assigned to]] - // ArrayList myTableRowColors = new ArrayList<>(); // List of all row colors (same length as myTableData). HashMap>> resultsByArea = new HashMap<>(); // Area(Room(List of draws)) + Integer numIncompleteSPI = 0; + Integer numIncompleteAnimalCare = 0; + Integer numIncompleteVetStaff = 0; + Integer numIncompleteResearchStaff = 0; //Gets all info for the BloodDrawNotificationObject. @@ -145,8 +147,10 @@ public static class BloodDrawsTodayObject { // Updates id. myCurrentRow[0] = result.get("id"); + // Updates blood remaining. myCurrentRow[1] = result.get("BloodRemaining/AvailBlood"); + // Updates project status (this checks if animal is assigned to a project). if (!result.get("qcstate/label").equals("Request: Denied") && !result.get("projectStatus").isEmpty()) { myCurrentRow[2] = "UNASSIGNED"; @@ -154,6 +158,7 @@ public static class BloodDrawsTodayObject { else { myCurrentRow[2] = ""; } + // Updates completion status (this checks if blood draw has been completed). if (!result.get("qcstate/label").equals("Completed")) { myCurrentRow[3] = "INCOMPLETE"; @@ -161,8 +166,10 @@ public static class BloodDrawsTodayObject { else { myCurrentRow[3] = ""; } + // Updates the current group assigned to this animal. myCurrentRow[4] = result.get("billedby/title"); + // Updates the current area. if (!result.get("Id/curLocation/area").isEmpty()) { myCurrentRow[6] = result.get("Id/curLocation/area"); @@ -170,6 +177,7 @@ public static class BloodDrawsTodayObject { else { myCurrentRow[6] = "Unknown Area"; } + // Updates the current room. if (!result.get("Id/curLocation/room").isEmpty()) { myCurrentRow[7] = result.get("Id/curLocation/room"); @@ -191,18 +199,6 @@ else if (availBlood <= bloodThreshold) { myCurrentRow[8] = "orange"; } } -// String currentRowColor = "white"; -// if (!result.get("BloodRemaining/AvailBlood").isEmpty()) { -// Float availBlood = Float.parseFloat(result.get("BloodRemaining/AvailBlood")); -// if (availBlood <= 0) { -// // If blood draw is over limit, color it red. -// currentRowColor = "red"; -// } -// else if (availBlood <= bloodThreshold) { -// // If blood draw is over threshold limit, color it orange. -// currentRowColor = "orange"; -// } -// } // Adds the current row to myTableData (based on group being queried). if (assignmentGroup.equals("animalCare")) { @@ -234,6 +230,24 @@ else if (assignmentGroup.equals("vetStaff")) { myTableData.add(myCurrentRow); } } + + // Updates number of incomplete draws. + if (assignmentGroup.equals("all")) { + if (myCurrentRow[3].equals("INCOMPLETE")) { + if (result.get("billedby/title").equals("SPI")) { + numIncompleteSPI++; + } + else if (result.get("billedby/title").equals("Animal Care")) { + numIncompleteAnimalCare++; + } + else if (result.get("billedby/title").equals("Research Staff")) { + numIncompleteResearchStaff++; + } + else if (result.get("billedby/title").equals("Vet Staff")) { + numIncompleteVetStaff++; + } + } + } } //Goes through each draw to find draws scheduled for more than one group, then updates myTableData with information. diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayVetStaff.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayVetStaff.java index 35de535fd..a4ebcd972 100644 --- a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayVetStaff.java +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayVetStaff.java @@ -69,17 +69,13 @@ public String getMessageBodyHTML(Container c, User u) { // Creates table. if (myBloodDrawNotificationObject.resultsByArea.isEmpty()) { - messageBody.append("There are no scheduled blood draws for this group today."); + notificationToolkit.sendEmptyNotificationRevamp(c, u, "Blood Draws Today (Vet Staff)"); + return null; } else { messageBody.append(myBloodDrawNotificationObject.printTablesAsHTML()); + return messageBody.toString(); } -// String[] myTableColumns = new String[]{"Id", "Blood Remaining", "Project Assignment", "Completion Status", "Group", "Other Groups Drawing Blood Today"}; -// NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(myTableColumns, myBloodDrawNotificationObject.myTableData); -// myTable.rowColors = myBloodDrawNotificationObject.myTableRowColors; -// messageBody.append(myTable.createBasicHTMLTable()); - - return messageBody.toString(); } } diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/ColonyAlertsNotificationRevamp.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/ColonyAlertsNotificationRevamp.java index bda4861a3..872a428e8 100644 --- a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/ColonyAlertsNotificationRevamp.java +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/ColonyAlertsNotificationRevamp.java @@ -27,6 +27,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -41,6 +43,8 @@ import javax.script.ScriptEngine; +import static org.labkey.api.search.SearchService._log; + /** * Created by Alex Schmidt on 12/27/23. @@ -97,6 +101,12 @@ public String getMessageBodyHTML(Container c, User u) { final StringBuilder messageBody = new StringBuilder(); ColonyInformationObject myColonyAlertObject = new ColonyInformationObject(c, u, "colonyAlert"); + // Creates CSS. + messageBody.append(styleToolkit.beginStyle()); + messageBody.append(styleToolkit.setBasicTableStyle()); + messageBody.append(styleToolkit.setHeaderRowBackgroundColor("#d9d9d9")); + messageBody.append(styleToolkit.endStyle()); + //Begins message info. messageBody.append("

This email contains a series of automatic alerts about the colony. It was run on: " + dateToolkit.getCurrentTime() + ".

"); @@ -373,6 +383,19 @@ public String getMessageBodyHTML(Container c, User u) { messageBody.append(notificationToolkit.createHyperlink("

Click here to view them
\n", myColonyAlertObject.totalFinalizedRecordsWithFutureDatesURLView)); messageBody.append("


\n"); } + //38. Find any animals assigned to an inactivated project or deactivated protocol. + if (!myColonyAlertObject.animalsWithInvalidProjectOrProtocol.isEmpty()) { + // Creates HTML table to return. + String[] myTableColumns = new String[]{"Id", "Project", "Project End Date", "Protocol", "Protocol End Date"}; + NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(myTableColumns, myColonyAlertObject.animalsWithInvalidProjectOrProtocol); + + // Displays message. + messageBody.append("WARNING: There are " + myColonyAlertObject.animalsWithInvalidProjectOrProtocol.size() + " living animals with inactivated projects or deactivated protocols.
"); + messageBody.append(myTable.createBasicHTMLTable()); + messageBody.append(notificationToolkit.createHyperlink("

Click here to view all Assignments.
\n", myColonyAlertObject.animalsWithInvalidProjectOrProtocolURLView)); + messageBody.append("


\n"); + + } //Returns string. return messageBody.toString(); @@ -469,6 +492,8 @@ public ColonyInformationObject(Container currentContainer, User currentUser, Str getPrenatalDeathsInLastFiveDays(); //37. Find the total finalized records with future dates. getTotalFinalizedRecordsWithFutureDates(); + //38. Find any animals assigned to an inactivated project or deactivated protocol. + getAnimalsWithInvalidProjectOrProtocol(); } else if (alertType.equals("colonyManagement")) { // 1. Find all living animals without a weight. @@ -1356,6 +1381,83 @@ private void getProtocolsNearingAnimalLimitPercentage() { this.protocolsNearingAnimalLimitPercentage = returnArray; this.protocolsNearingAnimalLimitPercentageURLView = viewQueryURL; } + + // Find any animals assigned to an inactivated project or deactivated protocol. + ArrayList animalsWithInvalidProjectOrProtocol = new ArrayList<>(); // + String animalsWithInvalidProjectOrProtocolURLView; + private void getAnimalsWithInvalidProjectOrProtocol() { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/Dataset/Demographics/calculated_status", "Alive", CompareType.EQUAL); +// myFilter.addCondition("project/protocol", "", CompareType.NONBLANK); + // Gets columns to retrieve. + String[] targetColumns = new String[]{"id", "project", "project/protocol", "project/enddate", "project/protocol/enddate"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "Assignment", myFilter, null, targetColumns); + + // Sets up a Try/Catch block to catch date parsing errors. + try { + // Sets variables. + SimpleDateFormat myFormat = new SimpleDateFormat("yyyy-MM-dd"); + Date formattedCurrentDate = myFormat.parse(dateToolkit.getCurrentTime()); + + for (HashMap result : returnArray) { + // 0: ID + // 1: Project + // 2: Project End Date + // 3: Protocol + // 4: Protocol End Date + String[] myCurrentRow = new String[5]; + myCurrentRow[1] = ""; + myCurrentRow[2] = ""; + myCurrentRow[3] = ""; + myCurrentRow[4] = ""; + + // Retrieves row data. + String currentID = result.get("id"); + String currentProject = result.get("project"); + String currentProtocol = result.get("project/protocol"); + String currentProjectEnd = result.get("project/enddate"); + String currentProtocolEnd = result.get("project/protocol/enddate"); + Boolean projectOrProtocolExpired = false; + + // Adds id. + myCurrentRow[0] = currentID; + // Checks project. + if (currentProject != null && currentProjectEnd != null) { + if (!currentProject.isEmpty() && !currentProjectEnd.isEmpty()) { + Date formattedProjectEnd = myFormat.parse(currentProjectEnd); + if (formattedCurrentDate.compareTo(formattedProjectEnd) > 0) { + myCurrentRow[1] = currentProject; + myCurrentRow[2] = currentProjectEnd; + projectOrProtocolExpired = true; + } + } + } + // Checks protocol. + if (currentProtocol != null && currentProtocolEnd != null) { + if (!currentProtocol.isEmpty() && !currentProtocolEnd.isEmpty()) { + Date formattedProtocolEnd = myFormat.parse(currentProtocolEnd); + if (formattedCurrentDate.compareTo(formattedProtocolEnd) > 0) { + myCurrentRow[3] = currentProtocol; + myCurrentRow[4] = currentProtocolEnd; + projectOrProtocolExpired = true; + } + } + } + + // Adds row to return list if there is an expired project or protocol. + if (projectOrProtocolExpired) { + animalsWithInvalidProjectOrProtocol.add(myCurrentRow); + } + } + } + catch (ParseException e) { + _log.error("There was a parsing exception for: ColonyAlertsNotificationRevamp->getAnimalsWithInvalidProjectOrProtocol", e); + } + + // Creates url link to the assignments table. + this.animalsWithInvalidProjectOrProtocolURLView = notificationToolkit.createQueryURL(c, "execute", "study", "Assignment", null); + } } } diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/TreatmentAlertsNotificationRevamp.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/TreatmentAlertsNotificationRevamp.java new file mode 100644 index 000000000..13412570a --- /dev/null +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/TreatmentAlertsNotificationRevamp.java @@ -0,0 +1,438 @@ +package org.labkey.wnprc_ehr.notification; + +import org.labkey.api.data.CompareType; +import org.labkey.api.data.Container; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.Sort; +import org.labkey.api.module.Module; +import org.labkey.api.security.User; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +public class TreatmentAlertsNotificationRevamp extends AbstractEHRNotification { + //Class Variables + NotificationToolkit notificationToolkit = new NotificationToolkit(); + NotificationToolkit.DateToolkit dateToolkit = new NotificationToolkit.DateToolkit(); + NotificationToolkit.StyleToolkit styleToolkit = new NotificationToolkit.StyleToolkit(); + + + + + + // Constructors + /** + * This constructor is used to register the notification in WNPRC_EHRModule.java. + * @param owner + */ + public TreatmentAlertsNotificationRevamp(Module owner) {super(owner);} + + + + + + // Notification Details + @Override + public String getName() { + return "Treatment Alerts Notification Revamp"; + } + @Override + public String getDescription() { + return "This email contains any scheduled treatments not marked as completed."; + } + @Override + public String getEmailSubject(Container c) { + return "Treatment Alerts: " + dateToolkit.getCurrentTime(); + } + @Override + public String getScheduleDescription() { + return "Daily at 6:00AM, 10:00AM, 1:00PM, 3:00PM, 5:00PM, 7:00PM"; + } + @Override + public String getCronString() { + return notificationToolkit.createCronString("0", "6,10,13,15,17,19", "*"); + } + @Override + public String getCategory() { + return "Revamped Notifications"; + } + + + + + + //Message Creation + @Override + public String getMessageBodyHTML(Container c, User u) { + + // Creates variables & gets data. + final StringBuilder messageBody = new StringBuilder(); + TreatmentAlertsObject myTreatmentAlertsObject = new TreatmentAlertsObject(c, u); + + // Creates CSS. + messageBody.append(styleToolkit.beginStyle()); + messageBody.append(styleToolkit.setBasicTableStyle()); + messageBody.append(styleToolkit.setHeaderRowBackgroundColor("#d9d9d9")); + messageBody.append(styleToolkit.endStyle()); + + // Begins message info. + messageBody.append("This email contains any scheduled treatments not marked as completed. It was run on: " + dateToolkit.getCurrentTime() + ".

"); + + // Creates message. + // 1. Shows all rooms lacking observations today. + if (!myTreatmentAlertsObject.roomsLackingObservationsToday.isEmpty()) { + messageBody.append("WARNING: The following rooms do not have any obs for today as of: " + dateToolkit.getCurrentTime() + "."); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view them.

\n", myTreatmentAlertsObject.roomsLackingObservationsTodayUrlView)); + for (HashMap result : myTreatmentAlertsObject.roomsLackingObservationsToday) { + messageBody.append(result.get("room") + "
"); + } + messageBody.append("


\n"); + } + // 2. Shows all treatments where the animal is not assigned to that project. + if (!myTreatmentAlertsObject.treatmentsWithAnimalNotAssignedToProject.isEmpty()) { + messageBody.append("WARNING: There are " + myTreatmentAlertsObject.treatmentsWithAnimalNotAssignedToProject.size() + " scheduled treatments where the animal is not assigned to the project.

"); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view them.
\n", myTreatmentAlertsObject.treatmentsWithAnimalNotAssignedToProjectUrlView)); + messageBody.append("
\n"); + } + // 3. Shows treatments for each time of day. + String[] timesOfDay = {"AM", "Noon", "PM", "Any Time", "Night"}; + String[] treatmentColumns = new String[]{"ID", "Treatment", "Route", "Concentration", "Amount To Give", "Volume", "Instructions", "Ordered By"}; + + for (String timeOfDay : timesOfDay) { + // Verifies there are treatments scheduled. + Integer totalTreatments = myTreatmentAlertsObject.incompleteTreatmentsForEachTimeOfDay.get(timeOfDay).size() + myTreatmentAlertsObject.completedTreatmentCountsForEachTimeOfDay.get(timeOfDay); + if (totalTreatments > 0) { + messageBody.append("There are " + totalTreatments + " scheduled " + timeOfDay + " treatments. " + myTreatmentAlertsObject.completedTreatmentCountsForEachTimeOfDay.get(timeOfDay) + " have been completed. "); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view them.

\n", myTreatmentAlertsObject.treatmentsForEachTimeOfDayUrlView.get(timeOfDay))); + + // Creates the current timeOfDay results sorted by (area --> room --> result). + HashMap>>> resultsByArea = new HashMap<>(); + + for (HashMap result : myTreatmentAlertsObject.incompleteTreatmentsForEachTimeOfDay.get(timeOfDay)) { + String currentArea = result.get("CurrentArea"); + String currentRoom = result.get("CurrentRoom"); + // Adds result if area does not yet exist. + if (!resultsByArea.containsKey(currentArea)) { + // Creates new treatments list. + ArrayList> roomTreatments = new ArrayList<>(); + roomTreatments.add(result); + // Adds new treatments list to new room. + HashMap>> newRoom = new HashMap<>(); + newRoom.put(currentRoom, roomTreatments); + // Adds new room to new area. + resultsByArea.put(currentArea, newRoom); + } + // Adds result if area exists but room does not yet exist. + else if (!resultsByArea.get(currentArea).containsKey(currentRoom)) { + // Creates new treatments list. + ArrayList> roomTreatments = new ArrayList<>(); + roomTreatments.add(result); + // Adds new room to new area. + resultsByArea.get(currentArea).put(currentRoom, roomTreatments); + } + // Adds result if area and room both exist. + else { + resultsByArea.get(currentArea).get(currentRoom).add(result); + } + } + + // Iterates through each area (sorted alphabetically). + for (String currentArea : notificationToolkit.sortSetWithNulls(resultsByArea.keySet())) { + messageBody.append("" + currentArea + ":
\n"); + // Iterates through each room (sorted alphabetically) + for (String currentRoom : notificationToolkit.sortSetWithNulls(resultsByArea.get(currentArea).keySet())) { + messageBody.append(currentRoom + ": " + resultsByArea.get(currentArea).get(currentRoom).size() + "
\n"); + // Reformats the treatment hashmap into a String[] List (to be compatible with the table creation function). + ArrayList formattedResults = new ArrayList<>(); + for (HashMap currentTreatment : resultsByArea.get(currentArea).get(currentRoom)) { + String[] newTableRow = new String[]{ + currentTreatment.get("Id"), + currentTreatment.get("meaning"), + currentTreatment.get("route"), + currentTreatment.get("conc2"), + currentTreatment.get("amount2"), + currentTreatment.get("volume2"), + currentTreatment.get("remark"), + currentTreatment.get("performedby") + }; + formattedResults.add(newTableRow); + } + // Displays table with results. + NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(treatmentColumns, formattedResults); + messageBody.append(myTable.createBasicHTMLTable()); + } + } + + } + else { + messageBody.append("There are no scheduled " + timeOfDay + " treatments as of " + dateToolkit.getCurrentTime() + ". Treatments could be added after this email was sent, so please check online closer to the time."); + } + messageBody.append("
\n"); + } + // 4. Shows any treatments from today that differ from the order. + if (!myTreatmentAlertsObject.differentOrderTreatments.isEmpty()) { + // Reformats the String List into a String[] List (to be compatible with the table creation function). + ArrayList formattedResults = new ArrayList<>(); + for (String result : myTreatmentAlertsObject.differentOrderTreatments) { + formattedResults.add(new String[]{result}); + } + // Creates the necessary table. + String[] diffTableColumns = new String[]{"DIFFERING TREATMENTS"}; + NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(diffTableColumns, formattedResults); + messageBody.append(myTable.createBasicHTMLTable()); + messageBody.append("
\n"); + } + // 5. Shows any treatments where the animal is not alive. + if (!myTreatmentAlertsObject.treatmentsWhereAnimalIsNotAlive.isEmpty()) { + messageBody.append("WARNING: There are " + myTreatmentAlertsObject.treatmentsWhereAnimalIsNotAlive.size() + " active treatments for animals not currently at WNPRC."); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view and update them.
\n", myTreatmentAlertsObject.treatmentsWhereAnimalIsNotAliveURLView)); + messageBody.append("
\n"); + } + // 6. Find any problems where the animal is not alive. + if (!myTreatmentAlertsObject.problemsWhereAnimalIsNotAlive.isEmpty()) { + messageBody.append("WARNING: There are " + myTreatmentAlertsObject.problemsWhereAnimalIsNotAlive.size() + " unresolved problems for animals not currently at WNPRC."); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view and update them.
\n", myTreatmentAlertsObject.problemsWhereAnimalIsNotAliveUrlView)); + messageBody.append("
\n"); + } + // 7. Checks for missing In Rooms after 2:30pm, as specified in the SOP. + if (!myTreatmentAlertsObject.missingInRoomsAfterTwoThirty.isEmpty()) { + messageBody.append("WARNING: There are " + myTreatmentAlertsObject.missingInRoomsAfterTwoThirty.size() + " " + notificationToolkit.createHyperlink("animals without In Rooms", myTreatmentAlertsObject.missingInRoomsAfterTwoThirtyUrlView) + "."); + } + + + return messageBody.toString(); + } + + // Gets all info for Treatment Alerts. + public static class TreatmentAlertsObject { + //Set up. + NotificationToolkit notificationToolkit = new NotificationToolkit(); + NotificationToolkit.DateToolkit dateToolkit = new NotificationToolkit.DateToolkit(); + Date todayDate = dateToolkit.getDateToday(); + + TreatmentAlertsObject(Container c, User u) { + getRoomsLackingObservationsToday(c, u); + getTreatmentsWithAnimalNotAssignedToProject(c, u); + getIncompleteTreatmentsForEachTimeOfDay(c, u); + getDifferentOrderTreatments(c, u); + getTreatmentsWhereAnimalIsNotAlive(c, u); + getProblemsWhereAnimalIsNotAlive(c, u); + getMissingInRoomsAfterTwoThirty(c, u); + } + + // Gets any rooms lacking observations for today. + ArrayList> roomsLackingObservationsToday; + String roomsLackingObservationsTodayUrlView; + private void getRoomsLackingObservationsToday(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("hasObs", "N", CompareType.EQUAL); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"room"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "ehr", "RoomsWithoutObsToday", myFilter, null, targetColumns); + + // Assigns data. + this.roomsLackingObservationsToday = returnArray; + this.roomsLackingObservationsTodayUrlView = notificationToolkit.createQueryURL(c, "execute", "ehr", "RoomsWithoutObsToday", myFilter); + } + + // Gets all treatments (today) where the animal is not assigned to that project. + ArrayList> treatmentsWithAnimalNotAssignedToProject; + String treatmentsWithAnimalNotAssignedToProjectUrlView; + private void getTreatmentsWithAnimalNotAssignedToProject(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/DataSet/Demographics/calculated_status", "Alive", CompareType.EQUAL); + myFilter.addCondition("projectStatus", "", CompareType.NONBLANK); + myFilter.addCondition("date", todayDate, CompareType.DATE_EQUAL); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"Id"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "treatmentSchedule", myFilter, null, targetColumns); + + // Assigns data. + this.treatmentsWithAnimalNotAssignedToProject = returnArray; + this.treatmentsWithAnimalNotAssignedToProjectUrlView = notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", myFilter); + } + + // Gets all treatments (today) for each time of the day (AM, Noon, PM, Any Time, Night). + HashMap>> incompleteTreatmentsForEachTimeOfDay = new HashMap<>() {{ + put("AM", new ArrayList()); + put("Noon", new ArrayList()); + put("PM", new ArrayList()); + put("Any Time", new ArrayList()); + put("Night", new ArrayList()); + }}; + HashMap completedTreatmentCountsForEachTimeOfDay = new HashMap<>() {{ + put("AM", 0); + put("Noon", 0); + put("PM", 0); + put("Any Time", 0); + put("Night", 0); + }}; + HashMap treatmentsForEachTimeOfDayUrlView = new HashMap<>(); + private void getIncompleteTreatmentsForEachTimeOfDay(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/DataSet/Demographics/calculated_status", "Alive", CompareType.EQUAL); + myFilter.addCondition("date", todayDate, CompareType.DATE_EQUAL); + // Creates sort. + Sort mySort = new Sort("CurrentArea,CurrentRoom"); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"timeofday", "Id", "CurrentArea", "CurrentRoom", "CurrentCage", "projectStatus", "treatmentStatus", "treatmentStatus/Label", "meaning", "code", "volume2", "conc2", "route", "amount2", "remark", "performedby"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "treatmentSchedule", myFilter, mySort, targetColumns); + + if (!returnArray.isEmpty()) { + // Sorts data for each time of day. + for (HashMap currentTreatment : returnArray) { + String treatmentTime = currentTreatment.get("timeofday"); + // Verifies time of day is one of the 5 supported times. + if (treatmentTime != null) { + if (treatmentTime.equals("AM") || treatmentTime.equals("Noon") || treatmentTime.equals("PM") || treatmentTime.equals("Any Time") || treatmentTime.equals("Night")) { + if (currentTreatment.get("treatmentStatus/Label").equals("Completed")) { + Integer currentCount = completedTreatmentCountsForEachTimeOfDay.get(treatmentTime); + completedTreatmentCountsForEachTimeOfDay.put(treatmentTime, currentCount + 1); + } + else { + incompleteTreatmentsForEachTimeOfDay.get(treatmentTime).add(currentTreatment); + } + } + } + } + // Updates official class variable for each time of day's URL view. + SimpleFilter amFilter = new SimpleFilter(myFilter); + SimpleFilter noonFilter = new SimpleFilter(myFilter); + SimpleFilter pmFilter = new SimpleFilter(myFilter); + SimpleFilter anytimeFilter = new SimpleFilter(myFilter); + SimpleFilter nightFilter = new SimpleFilter(myFilter); + amFilter.addCondition("timeofday", "AM", CompareType.EQUAL); + noonFilter.addCondition("timeofday", "Noon", CompareType.EQUAL); + pmFilter.addCondition("timeofday", "PM", CompareType.EQUAL); + anytimeFilter.addCondition("timeofday", "Any Time", CompareType.EQUAL); + nightFilter.addCondition("timeofday", "Night", CompareType.EQUAL); + this.treatmentsForEachTimeOfDayUrlView.put("AM", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", amFilter)); + this.treatmentsForEachTimeOfDayUrlView.put("Noon", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", noonFilter)); + this.treatmentsForEachTimeOfDayUrlView.put("PM", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", pmFilter)); + this.treatmentsForEachTimeOfDayUrlView.put("Any Time", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", anytimeFilter)); + this.treatmentsForEachTimeOfDayUrlView.put("Night", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", nightFilter)); + } + } + + // Gets all treatments (today) that differ from the order. + List differentOrderTreatments; + String differentOrderTreatmentsUrlView; + private void getDifferentOrderTreatments(Container c, User u) { + // Creates variable. + List resultsAsTableEntries = new ArrayList<>(); + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("date", todayDate, CompareType.DATE_EQUAL); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"CurrentArea", "CurrentRoom", "id", "date", "meaning", "performedby", "drug_performedby", "route", "drug_route", "concentration", "drug_concentration", "conc_units", "drug_conc_units", "dosage", "drug_dosage", "dosage_units", "drug_dosage_units", "amount", "drug_amount", "amount_units", "drug_amount_units", "volume", "drug_volume", "vol_units", "drug_vol_units"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "TreatmentsThatDiffer", myFilter, null, targetColumns); + + for (HashMap currentTreatment : returnArray) { + // Creates variable. + StringBuilder currentTableEntry = new StringBuilder(); + + // Displays the basic info for the current treatment. + currentTableEntry.append("Id: " + currentTreatment.get("id") + "
\n"); + currentTableEntry.append("Date: " + currentTreatment.get("date") + "
\n"); + currentTableEntry.append("Treatment: " + currentTreatment.get("meaning") + "
\n"); + currentTableEntry.append("Ordered By: " + currentTreatment.get("performedby") + "
\n"); + currentTableEntry.append("Performed By: " + currentTreatment.get("drug_performedby") + "
\n"); + + // Displays which order/entry field in the treatment does not match. + // Compares the route. + if (!currentTreatment.get("route").equals(currentTreatment.get("drug_route"))) { + currentTableEntry.append("Route Ordered: " + currentTreatment.get("route") + "
\n"); + currentTableEntry.append("Route Entered: " + currentTreatment.get("drug_route") + "
\n"); + } + // Compares the concentration & concentration units. + if (!currentTreatment.get("concentration").equals(currentTreatment.get("drug_concentration")) || !currentTreatment.get("conc_units").equals(currentTreatment.get("drug_conc_units"))) { + currentTableEntry.append("Concentration Ordered: " + currentTreatment.get("concentration") + " " + currentTreatment.get("conc_units") + "
\n"); + currentTableEntry.append("Concentration Entered: " + currentTreatment.get("drug_concentration") + " " + currentTreatment.get("drug_conc_units") + "
\n"); + } + // Compares the dosage & dosage units. + if (!currentTreatment.get("dosage").equals(currentTreatment.get("drug_dosage")) || !currentTreatment.get("dosage_units").equals(currentTreatment.get("drug_dosage_units"))) { + currentTableEntry.append("Dosage Ordered: " + currentTreatment.get("dosage") + " " + currentTreatment.get("dosage_units") + "
\n"); + currentTableEntry.append("Dosage Entered: " + currentTreatment.get("drug_dosage") + " " + currentTreatment.get("drug_dosage_units") + "
\n"); + } + // Compares the amount & amount units. + if (!currentTreatment.get("amount").equals(currentTreatment.get("drug_amount")) || !currentTreatment.get("amount_units").equals(currentTreatment.get("drug_amount_units"))) { + currentTableEntry.append("Amount Ordered: " + currentTreatment.get("amount") + " " + currentTreatment.get("amount_units") + "
\n"); + currentTableEntry.append("Amount Entered: " + currentTreatment.get("drug_amount") + " " + currentTreatment.get("drug_amount_units") + "
\n"); + } + // Compares the volume & volume units. + if (!currentTreatment.get("volume").equals(currentTreatment.get("drug_volume")) || !currentTreatment.get("vol_units").equals(currentTreatment.get("drug_vol_units"))) { + currentTableEntry.append("Volume Ordered: " + currentTreatment.get("volume") + " " + currentTreatment.get("vol_units") + "
\n"); + currentTableEntry.append("Volume Entered: " + currentTreatment.get("drug_volume") + " " + currentTreatment.get("drug_vol_units") + "
\n"); + } + + // Adds treatment to table row list. + resultsAsTableEntries.add(currentTableEntry.toString()); + } + + // Assigns data. + this.differentOrderTreatments = resultsAsTableEntries; + this.differentOrderTreatmentsUrlView = notificationToolkit.createQueryURL(c, "execute", "study", "TreatmentsThatDiffer", myFilter); + } + + // Gets all treatments (today & future) where the animal is not alive. + ArrayList> treatmentsWhereAnimalIsNotAlive; + String treatmentsWhereAnimalIsNotAliveURLView; + private void getTreatmentsWhereAnimalIsNotAlive(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/DataSet/Demographics/calculated_status", "Alive", CompareType.NEQ_OR_NULL); + myFilter.addCondition("enddate", "", CompareType.ISBLANK); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"Id"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "Treatment Orders", myFilter, null, targetColumns); + + // Assigns data. + this.treatmentsWhereAnimalIsNotAlive = returnArray; + this.treatmentsWhereAnimalIsNotAliveURLView = notificationToolkit.createQueryURL(c, "execute", "study", "Treatment Orders", myFilter); + } + + // Gets any problems (today & future) where the animal is not alive. + ArrayList> problemsWhereAnimalIsNotAlive; + String problemsWhereAnimalIsNotAliveUrlView; + private void getProblemsWhereAnimalIsNotAlive(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/DataSet/Demographics/calculated_status", "Alive", CompareType.NEQ_OR_NULL); + myFilter.addCondition("enddate", "", CompareType.ISBLANK); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"Id"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "Problem List", myFilter, null, targetColumns); + + // Assigns data. + this.problemsWhereAnimalIsNotAlive = returnArray; + this.problemsWhereAnimalIsNotAliveUrlView = notificationToolkit.createQueryURL(c, "execute", "study", "Problem List", myFilter); + } + + // Checks for missing-in-rooms after 2:30pm, as specified in the SOP. + ArrayList> missingInRoomsAfterTwoThirty; + String missingInRoomsAfterTwoThirtyUrlView; + private void getMissingInRoomsAfterTwoThirty(Container c, User u) { + // Creates columns to retrieve. + String[] targetColumns = new String[]{"Id"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "inRoomNotSubmitted", null, null, targetColumns); + + // Assigns data. + this.missingInRoomsAfterTwoThirty = returnArray; + this.missingInRoomsAfterTwoThirtyUrlView = notificationToolkit.createQueryURL(c, "execute", "study", "inRoomNotSubmitted", null); + } + + + + + + + } + +} From fb3615bab349b6b58bd5f04ae780ceacb6501e96 Mon Sep 17 00:00:00 2001 From: Chad Sebranek Date: Wed, 4 Dec 2024 16:02:18 -0600 Subject: [PATCH 2/3] Updates to current blood draw queries and adding visual plot (#703) * Add blood graph along with queries to support it, as well as new column for blood avail including thirty days in the future * exclude today for next 30 days calculation of blood, fix blood summary calc, adjust labels * use next 30 days since it does not include today * use ehr version * remove wnprc blood graph to use ehr instead * Consolidate blood draw reports into currentBloodDraws.sql, adjust references * ignore project type * group up the blood draws if they are done at the same time * Revert back to original AvailBlood calculation, add new calc for future draws in the new current blood report, fix ehr blood store so all records in transaction are used * update notification to revert to old blood avail name, use species as lookups for blood queries * restore demographicsBloodSummary query file * update test comments * restore old blood draw query --------- Co-authored-by: Marty Pradere --- .../resources/queries/study/Current Blood.sql | 113 ------------- .../queries/study/Current Blood/.qview.xml | 42 ----- .../queries/study/bloodDrawChanges.sql | 71 ++++++++ .../queries/study/bloodSummary.query.xml | 72 ++++----- .../resources/queries/study/bloodSummary.sql | 149 ++++++++--------- ....query.xml => currentBloodDraws.query.xml} | 153 ++++++++++-------- .../queries/study/currentBloodDraws.sql | 104 ++++++++++++ .../study/currentBloodDraws/.qview.xml | 43 +++++ .../demographics/Blood Draw Info.qview.xml | 2 +- .../study/demographicsBloodSummary.query.xml | 115 ++++++------- .../study/demographicsBloodSummary.sql | 146 +++++++---------- WNPRC_EHR/resources/web/ehr/ext3/ehrStore.js | 2 +- .../resources/web/wnprc_ehr/wnprcReports.js | 47 ++++++ 13 files changed, 567 insertions(+), 492 deletions(-) delete mode 100644 WNPRC_EHR/resources/queries/study/Current Blood.sql delete mode 100644 WNPRC_EHR/resources/queries/study/Current Blood/.qview.xml create mode 100644 WNPRC_EHR/resources/queries/study/bloodDrawChanges.sql rename WNPRC_EHR/resources/queries/study/{Current Blood.query.xml => currentBloodDraws.query.xml} (53%) create mode 100644 WNPRC_EHR/resources/queries/study/currentBloodDraws.sql create mode 100644 WNPRC_EHR/resources/queries/study/currentBloodDraws/.qview.xml diff --git a/WNPRC_EHR/resources/queries/study/Current Blood.sql b/WNPRC_EHR/resources/queries/study/Current Blood.sql deleted file mode 100644 index 6187c80ed..000000000 --- a/WNPRC_EHR/resources/queries/study/Current Blood.sql +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2010-2013 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -SELECT - bq.*, - cast(CASE - WHEN bq.species = 'Marmoset' - THEN round(bq.weight*0.15*60, 1) - ELSE - round(bq.weight*0.2*60, 1) - END as numeric) AS MaxBlood, - cast(CASE - WHEN bq.species = 'Marmoset' - THEN round((bq.weight*0.15*60) - bq.BloodLast30, 1) - ELSE - round((bq.weight*0.2*60) - bq.BloodLast30, 1) - END AS numeric) AS AvailBlood -FROM -( - SELECT - b.*, - (select species from study.demographics d where d.id = b.id) as species, - ( - CONVERT ( - (SELECT AVG(w.weight) AS _expr - FROM study.weight w - WHERE w.id=b.id AND w.date=b.lastWeighDate - AND w.qcstate.publicdata = true - ), double ) - ) AS weight - FROM - ( - SELECT bi.* - ,timestampadd('SQL_TSI_DAY', -29, bi.date) as minDate - ,timestampadd('SQL_TSI_DAY', 29, bi.date) as maxDate - ,( CONVERT( - (SELECT MAX(w.date) as _expr - FROM study.weight w - WHERE w.id = bi.id - --AND w.date <= bi.date - AND CAST(CAST(w.date AS DATE) AS TIMESTAMP) <= bi.date - AND w.qcstate.publicdata = true - ), timestamp ) - ) AS lastWeighDate - , ( COALESCE ( - (SELECT SUM(coalesce(draws.quantity, 0)) AS _expr - FROM study."Blood Draws" draws - WHERE draws.id=bi.id - AND draws.date >= TIMESTAMPADD('SQL_TSI_DAY', -29, bi.date) - AND cast(draws.date as date) <= bi.date - AND (draws.qcstate.metadata.DraftData = true OR draws.qcstate.publicdata = true) - --when counting backwards, dont include this date - --AND (draws.date != bi.date and draws.qcstate.label != bi.status) - ), 0 ) - ) AS BloodLast30 - , ( COALESCE ( - (SELECT SUM(coalesce(draws.quantity, 0)) AS _expr - FROM study."Blood Draws" draws - WHERE draws.id=bi.id - AND draws.date <= TIMESTAMPADD('SQL_TSI_DAY', 29, bi.date) - AND cast(draws.date as date) >= bi.date - --AND draws.date BETWEEN bi.date AND TIMESTAMPADD('SQL_TSI_DAY', 29, bi.date) - AND (draws.qcstate.metadata.DraftData = true OR draws.qcstate.publicdata = true) - --when counting forwards, dont include this date - --AND (draws.date != bi.date and draws.qcstate.label != bi.status) - ), 0 ) - ) AS BloodNext30 - from ( - SELECT - b.id, - --b.id.dataset.demographics.species as species, - cast(b.date as date) as date, - --b.lsid, - --b.qcstate, - b.qcstate.label as status, - SUM(coalesce(b.quantity, 0)) as quantity - FROM study.blood b - WHERE cast(b.date as date) >= TIMESTAMPADD('SQL_TSI_DAY', -29, now()) - AND (b.qcstate.metadata.DraftData = true OR b.qcstate.publicdata = true) - group by b.id, cast(b.date as date), b.qcstate.label - - UNION ALL - SELECT - b.id, - --b.id.dataset.demographics.species as species, - TIMESTAMPADD('SQL_TSI_DAY', 30, cast(cast(b.date as date) as timestamp)) as date, - --null as lsid, - --null as qcstate, - null as status, - 0 as quantity - FROM study.blood b - WHERE cast(b.date as date) >= TIMESTAMPADD('SQL_TSI_DAY', -29, now()) - AND (b.qcstate.metadata.DraftData = true OR b.qcstate.publicdata = true) - GROUP BY b.id, cast(b.date as date) - - --add one row per animal, showing todays date - UNION ALL - SELECT - b.id, - --b.species, - curdate() as date, - --null as lsid, - --null as qcstate, - null as status, - 0 as quantity - FROM study.demographics b - --WHERE b.id.status.status = 'Alive' - ) bi - ) b - ) bq - diff --git a/WNPRC_EHR/resources/queries/study/Current Blood/.qview.xml b/WNPRC_EHR/resources/queries/study/Current Blood/.qview.xml deleted file mode 100644 index 33013bb26..000000000 --- a/WNPRC_EHR/resources/queries/study/Current Blood/.qview.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/WNPRC_EHR/resources/queries/study/bloodDrawChanges.sql b/WNPRC_EHR/resources/queries/study/bloodDrawChanges.sql new file mode 100644 index 000000000..f91415d21 --- /dev/null +++ b/WNPRC_EHR/resources/queries/study/bloodDrawChanges.sql @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--this query is designed to return any dates when allowable blood draw volume changes +--this includes dates of blood draws, plus the date those draws drop off +PARAMETERS(DATE_INTERVAL INTEGER DEFAULT 30) + +SELECT + b2.id, + b2.status, + b2.dateOnly, + b2.quantity, + DATE_INTERVAL as blood_draw_interval, + TIMESTAMPADD('SQL_TSI_DAY', (-1 * DATE_INTERVAL), b2.dateOnly) as minDate, + TIMESTAMPADD('SQL_TSI_DAY', DATE_INTERVAL, b2.dateOnly) as maxDate, + +FROM ( + SELECT + b.id, + b.dateOnly, + b.status, + sum(b.quantity) as quantity + + FROM ( + --find all blood draws within the interval, looking backwards + SELECT + b.id, + b.qcstate as status, + b.dateOnly, + b.quantity, + FROM study.blood b + WHERE b.dateOnly > timestampadd('SQL_TSI_DAY', -1 * DATE_INTERVAL, curdate()) + + UNION ALL + + --join 1 row for the current date + SELECT + d1.id, + null as status, + curdate() as dateOnly, + 0 as quantity, + FROM study.demographics d1 + WHERE d1.calculated_status = 'Alive' + + UNION ALL + + --add one row for each date when the draw drops off the record + SELECT + b.id, + null as status, + timestampadd('SQL_TSI_DAY', DATE_INTERVAL, b.dateOnly), + 0 as quantity, + FROM study.blood b + WHERE timestampadd('SQL_TSI_DAY', DATE_INTERVAL, b.dateOnly) >= timestampadd('SQL_TSI_DAY', -1 * DATE_INTERVAL, curdate()) + + ) b + + GROUP BY b.id, b.dateOnly, b.status +) b2 \ No newline at end of file diff --git a/WNPRC_EHR/resources/queries/study/bloodSummary.query.xml b/WNPRC_EHR/resources/queries/study/bloodSummary.query.xml index b8a667e34..c5163f487 100644 --- a/WNPRC_EHR/resources/queries/study/bloodSummary.query.xml +++ b/WNPRC_EHR/resources/queries/study/bloodSummary.query.xml @@ -1,36 +1,36 @@ - - - - - - - true - - - - Drawn in Previous 30 Days (inclusive) - /query/executeQuery.view?schemaName=study& - query.queryName=Blood Draws& - query.viewName=Blood Summary& - query.Id~eq=${Id}& - query.Date~lte=${Date}& - query.Date~gte=${minDate}& - query.sort=-Date& - - - - Scheduled in Next 30 Days (inclusive) - /query/executeQuery.view?schemaName=study& - query.queryName=Blood Draws& - query.viewName=Blood Summary& - query.Id~eq=${Id}& - query.Date~gt=${Date}& - query.sort=-Date& - - - - BloodLast30 -
-
-
-
+ + + + + + + true + + + + Drawn in Previous 30 Days (including today) + /query/executeQuery.view?schemaName=study& + query.queryName=Blood Draws& + query.viewName=Blood Summary& + query.Id~eq=${Id}& + query.Date~lte=${Date}& + query.Date~gte=${minDate}& + query.sort=-Date& + + + + Scheduled in Next 30 Days (excluding today) + /query/executeQuery.view?schemaName=study& + query.queryName=Blood Draws& + query.viewName=Blood Summary& + query.Id~eq=${Id}& + query.Date~gt=${Date}& + query.sort=-Date& + + + + BloodLast30 +
+
+
+
diff --git a/WNPRC_EHR/resources/queries/study/bloodSummary.sql b/WNPRC_EHR/resources/queries/study/bloodSummary.sql index 29754e3f8..1e486d444 100644 --- a/WNPRC_EHR/resources/queries/study/bloodSummary.sql +++ b/WNPRC_EHR/resources/queries/study/bloodSummary.sql @@ -1,80 +1,69 @@ -/* - * Copyright (c) 2010-2013 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -SELECT - bq.lsid, - bq.id, - bq.date, - bq.minDate, --- bq.maxDate, - bq.weight, - bq.lastWeighDate, - cast(bq.BloodLast30 as numeric) as BloodLast30, --- bq.BloodNext30, --- round(bq.weight*0.2*60, 1) AS MaxBlood, --- round((bq.weight*0.2*60) - bq.BloodLast30, 1) AS AvailBlood - cast(CASE - WHEN bq.species = 'Marmoset' - THEN round(bq.weight*0.15*60, 1) - ELSE - round(bq.weight*0.2*60, 1) - END as numeric) AS MaxBlood, - cast(CASE - WHEN bq.species = 'Marmoset' - THEN round((bq.weight*0.15*60) - bq.BloodLast30, 1) - ELSE - round((bq.weight*0.2*60) - bq.BloodLast30, 1) - END AS numeric) AS AvailBlood -FROM -( - SELECT - b.*, - (select species from study.demographics d where d.id = b.id) as species, - ( - CONVERT ( - (SELECT AVG(w.weight) AS _expr - FROM study.weight w - WHERE w.id=b.id AND w.date=b.lastWeighDate - AND w.qcstate.publicdata = true - ), double ) - ) AS weight - FROM - ( - SELECT bi.* - ,timestampadd('SQL_TSI_DAY', -29, bi.date) as minDate --- ,timestampadd('SQL_TSI_DAY', 29, bi.date) as maxDate - , ( CONVERT( - (SELECT MAX(w.date) as _expr - FROM study.weight w - WHERE w.id = bi.id - --NOTE: we are doing this comparison such that it considers date only, not datetime - --AND w.date <= bi.date - AND CAST(CAST(w.date AS DATE) AS TIMESTAMP) <= bi.date - AND w.qcstate.publicdata = true - ), timestamp ) - ) AS lastWeighDate - , ( COALESCE ( - (SELECT SUM(draws.quantity) AS _expr - FROM study."Blood Draws" draws - WHERE draws.id=bi.id - --AND draws.date BETWEEN TIMESTAMPADD('SQL_TSI_DAY', -30, bi.date) AND bi.date - AND (cast(draws.date as date) >= cast(TIMESTAMPADD('SQL_TSI_DAY', -29, bi.date) as date) AND cast(draws.date as date) <= cast(bi.date as date)) - AND (draws.qcstate.metadata.DraftData = true OR draws.qcstate.publicdata = true) - --AND draws.qcstate.publicdata = true - ), 0 ) - ) AS BloodLast30 --- , ( COALESCE ( --- (SELECT SUM(draws.quantity) AS _expr --- FROM study."Blood Draws" draws --- WHERE draws.id=bi.id --- AND draws.date BETWEEN TIMESTAMPADD('SQL_TSI_DAY', 30, bi.date) AND bi.date --- AND (draws.qcstate.metadata.DraftData = true OR draws.qcstate.publicdata = true) --- ), 0 ) --- ) AS BloodNext30 - FROM study.blood bi - --WHERE (bi.qcstate.metadata.DraftData = true OR bi.qcstate.publicdata = true) - ) b - ) bq - +/* + * Copyright (c) 2010-2013 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +SELECT + bq.lsid, + bq.id, + bq.date, + bq.minDate, +-- bq.maxDate, + bq.weight, + bq.lastWeighDate, + cast(bq.BloodLast30 as numeric) as BloodLast30, + bq.BloodNext30, +-- round(bq.weight*0.2*60, 1) AS MaxBlood, +-- round((bq.weight*0.2*60) - bq.BloodLast30, 1) AS AvailBlood + cast(round((bq.weight*(species.max_draw_pct)*(species.blood_per_kg)), 1) as numeric) AS MaxBlood, + cast(round((bq.weight*(species.max_draw_pct)*(species.blood_per_kg)) - bq.BloodLast30, 1) AS numeric) AS AvailBlood +FROM +( + SELECT + b.*, + d.species as species, + ( + CONVERT ( + (SELECT AVG(w.weight) AS _expr + FROM study.weight w + WHERE w.id=b.id AND w.date=b.lastWeighDate + ), double ) + ) AS weight + FROM + ( + SELECT bi.* + ,timestampadd('SQL_TSI_DAY', -30, bi.date) as minDate +-- ,timestampadd('SQL_TSI_DAY', 29, bi.date) as maxDate + , ( CONVERT( + (SELECT MAX(w.date) as _expr + FROM study.weight w + WHERE w.id = bi.id + --NOTE: we are doing this comparison such that it considers date only, not datetime + --AND w.date <= bi.date + AND CAST(CAST(w.date AS DATE) AS TIMESTAMP) <= bi.date + ), timestamp ) + ) AS lastWeighDate + , ( COALESCE ( + (SELECT SUM(draws.quantity) AS _expr + FROM study."Blood Draws" draws + WHERE draws.id=bi.id + AND (cast(draws.date as date) >= cast(TIMESTAMPADD('SQL_TSI_DAY', -30, bi.date) as date) AND cast(draws.date as date) <= cast(bi.date as date)) + --AND draws.qcstate.publicdata = true + ), 0 ) + ) AS BloodLast30, + COALESCE (( + SELECT + SUM(draws.quantity) AS _expr + FROM study."Blood Draws" draws + WHERE draws.id=bi.id AND + (cast(draws.date as date) <= cast(TIMESTAMPADD('SQL_TSI_DAY', 30, bi.date) as date) AND cast(draws.date as date) > cast(bi.date as date) ) + + ), 0) AS BloodNext30 + FROM study.blood bi + + --WHERE (bi.qcstate.metadata.DraftData = true OR bi.qcstate.publicdata = true) + ) b + + JOIN study.demographics d ON b.id = d.id + ) bq + diff --git a/WNPRC_EHR/resources/queries/study/Current Blood.query.xml b/WNPRC_EHR/resources/queries/study/currentBloodDraws.query.xml similarity index 53% rename from WNPRC_EHR/resources/queries/study/Current Blood.query.xml rename to WNPRC_EHR/resources/queries/study/currentBloodDraws.query.xml index a37953f2a..cf0b8081c 100644 --- a/WNPRC_EHR/resources/queries/study/Current Blood.query.xml +++ b/WNPRC_EHR/resources/queries/study/currentBloodDraws.query.xml @@ -1,70 +1,83 @@ - - - - - - - true - - - - - study - Animal - Id - - - - Previous 30 Days (inclusive) - /query/executeQuery.view?schemaName=study& - query.queryName=Blood Draws& - query.viewName=Blood Summary& - query.Id~eq=${Id}& - query.Date~datelte=${Date}& - query.Date~dategte=${minDate}& - query.sort=-Date& - - - - Next 30 Days (inclusive) - /query/executeQuery.view?schemaName=study& - query.queryName=Blood Draws& - query.viewName=Blood Summary& - query.Id~eq=${Id}& - query.Date~dategte=${Date}& - query.sort=-Date& - - - - Latest Weight (kg) - /query/executeQuery.view?schemaName=study& - query.queryName=Weight& - query.Id~eq=${Id}& - query.wDate~gt=${wDate}& - query.sort=-Date& - - - - yyyy-MM-dd - - - yyyy-MM-dd - - - yyyy-MM-dd - - - yyyy-MM-dd HH:mm - - - 20% Volume By Weight - - - Volume Remaining After Draw (mL) - - - BloodLast30 -
-
-
-
+ + + + + + + true + + + + + study + Animal + Id + + + + Status + + core + qcstate + rowid + + + + Previous 30 Days (including today) + /query/executeQuery.view?schemaName=study& + query.queryName=Blood Draws& + query.viewName=Blood Summary& + query.Id~eq=${Id}& + query.Date~datelte=${Date}& + query.Date~dategte=${minDate}& + query.sort=-Date& + + This shows the amount of blood draws taken in the past, including today. + + + Next 30 Days (excluding today) + /query/executeQuery.view?schemaName=study& + query.queryName=Blood Draws& + query.viewName=Blood Summary& + query.Id~eq=${Id}& + query.Date~dategte=${Date}& + query.sort=-Date& + + This shows the amount of blood draws scheduled in the next 30 days after the given date. + + + Latest Weight (kg) + /query/executeQuery.view?schemaName=study& + query.queryName=Weight& + query.Id~eq=${Id}& + query.wDate~gt=${wDate}& + query.sort=-Date& + + + + yyyy-MM-dd + + + yyyy-MM-dd + + + yyyy-MM-dd + + + 20% Volume By Weight + This shows the maximum blood an animal can get drawn based on their weight. + + + Volume Available Today (mL) + This shows the amount of blood remaining today. It includes blood draws that happened 30 days in the past up to today. + + + Volume Remaining After All Draws (mL) + /WNPRC/EHR/ehr-animalHistory.view#subjects:${Id}&inputType:singleSubject&showReport:1&activeReport:BloodSummary + This column shows the amount of blood remaining for the animal while considering future draws. It includes approved blood draws 30 days into the future and past blood draws 30 days in the past. + + +
+
+
+
diff --git a/WNPRC_EHR/resources/queries/study/currentBloodDraws.sql b/WNPRC_EHR/resources/queries/study/currentBloodDraws.sql new file mode 100644 index 000000000..f90605794 --- /dev/null +++ b/WNPRC_EHR/resources/queries/study/currentBloodDraws.sql @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +SELECT + t.id, + t.date, + t.status, + cast(t.quantity as double) as quantity, + t.species, + t.max_draw_pct, + t.blood_draw_interval, + t.blood_per_kg, + t.mostRecentWeight, + t.mostRecentWeightDate, + t.death, + cast(round(t.allowableBlood,1) as numeric) as maxAllowableBlood, + cast(t.bloodPrevious as double) as bloodPrevious, + cast((t.allowableBlood - t.bloodPrevious) as double) as allowablePrevious, + + cast(t.bloodFuture as double) as bloodFuture, + cast((t.allowableBlood - t.bloodFuture) as double) as allowableFuture, + + ROUND(CAST((t.allowableBlood - t.bloodPrevious) AS double),1) as allowableBlood, + ROUND(CAST((t.allowableBlood - t.bloodPrevious - t.bloodFuture) AS double),1) as allowableBloodIncludingFutureDraws, + t.minDate, + t.maxDate + +FROM ( + + SELECT + bd.id, + bd.status, + bd.dateOnly as date, + bd.quantity, + d.species, + d.death, + ( + CONVERT ( + (SELECT AVG(w.weight) AS _expr + FROM study.weight w + WHERE w.id=bd.id AND w.date=d.id.mostRecentWeight.MostRecentWeightDate + AND w.qcstate.publicdata = true + ), double ) + ) * d.species.max_draw_pct * d.species.blood_per_kg AS allowableBlood, + + (CONVERT ( + (SELECT AVG(w.weight) AS _expr + FROM study.weight w + WHERE w.id=bd.id AND w.date=d.id.mostRecentWeight.MostRecentWeightDate + AND w.qcstate.publicdata = true + ), double ) ) as weight, + d.id.mostRecentWeight.MostRecentWeight, + d.id.mostRecentWeight.MostRecentWeightDate, + d.species.blood_per_kg, + d.species.max_draw_pct, + bd.blood_draw_interval, + bd.minDate, + bd.maxDate, + COALESCE( + (SELECT SUM(coalesce(draws.quantity, 0)) AS _expr + FROM study."Blood Draws" draws + WHERE draws.id = bd.id + AND draws.dateOnly > bd.minDate + AND draws.dateOnly <= bd.dateOnly + --NOTE: this has been changed to include pending/non-approved draws + AND draws.countsAgainstVolume = true + ), 0) AS BloodPrevious, + + COALESCE( + (SELECT SUM(coalesce(draws.quantity, 0)) AS _expr + FROM study."Blood Draws" draws + WHERE draws.id = bd.id + AND draws.dateOnly < bd.maxDate + AND draws.dateOnly > bd.dateOnly + --NOTE: this has been changed to include pending/non-approved draws + AND draws.countsAgainstVolume = true + ), 0) AS BloodFuture, + +COALESCE( + (SELECT SUM(coalesce(draws.quantity, 0)) AS _expr + FROM study."Blood Draws" draws + WHERE draws.id = bd.id + AND draws.dateOnly = curdate() + --NOTE: this has been changed to include pending/non-approved draws + AND draws.countsAgainstVolume = true + ), 0) AS BloodToday + + FROM study.bloodDrawChanges bd + JOIN study.demographics d ON (d.id = bd.id) + + ) t \ No newline at end of file diff --git a/WNPRC_EHR/resources/queries/study/currentBloodDraws/.qview.xml b/WNPRC_EHR/resources/queries/study/currentBloodDraws/.qview.xml new file mode 100644 index 000000000..22719345e --- /dev/null +++ b/WNPRC_EHR/resources/queries/study/currentBloodDraws/.qview.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WNPRC_EHR/resources/queries/study/demographics/Blood Draw Info.qview.xml b/WNPRC_EHR/resources/queries/study/demographics/Blood Draw Info.qview.xml index 7315e444b..6b6dd23f8 100644 --- a/WNPRC_EHR/resources/queries/study/demographics/Blood Draw Info.qview.xml +++ b/WNPRC_EHR/resources/queries/study/demographics/Blood Draw Info.qview.xml @@ -62,7 +62,7 @@ - + diff --git a/WNPRC_EHR/resources/queries/study/demographicsBloodSummary.query.xml b/WNPRC_EHR/resources/queries/study/demographicsBloodSummary.query.xml index e680dda90..d90021b23 100644 --- a/WNPRC_EHR/resources/queries/study/demographicsBloodSummary.query.xml +++ b/WNPRC_EHR/resources/queries/study/demographicsBloodSummary.query.xml @@ -1,63 +1,52 @@ - - - - - - - true - true - - - true - - - /query/executeQuery.view?schemaName=study& - query.queryName=Blood Draws& - query.viewName=Blood Summary& - query.Id~eq=${Id}& - query.Date~lte=${Date}& - query.sort=-Date& - - Blood Drawn In Previous 30 Days - - - Scheduled in Next 30 Days - /query/executeQuery.view?schemaName=study& - query.queryName=Blood Draws& - query.viewName=Blood Summary& - query.Id~eq=${Id}& - query.Date~gt=${Date}& - query.sort=-Date& - - - - Max Blood Per 30 Days (mL) - - - Available Blood (mL) - - - - - - FF0000 - - - - - Current Blood (mL) - - - - - - FBEC5D - - - - - AvailBlood -
-
-
-
+ + + + + + + true + true + + + true + + + /query/executeQuery.view?schemaName=study& + query.queryName=Blood Draws& + query.viewName=Blood Summary& + query.Id~eq=${Id}& + query.Date~lte=${Date}& + query.sort=-Date& + + Blood Drawn In Previous 30 Days + + + Scheduled in Next 30 Days + /query/executeQuery.view?schemaName=study& + query.queryName=Blood Draws& + query.viewName=Blood Summary& + query.Id~eq=${Id}& + query.Date~gt=${Date}& + query.sort=-Date& + + + + Max Blood Per 30 Days (mL) + + + Available Blood (mL) + + + + + + FF0000 + + + + + AvailBlood +
+
+
+
\ No newline at end of file diff --git a/WNPRC_EHR/resources/queries/study/demographicsBloodSummary.sql b/WNPRC_EHR/resources/queries/study/demographicsBloodSummary.sql index dcf9fd08c..fad214145 100644 --- a/WNPRC_EHR/resources/queries/study/demographicsBloodSummary.sql +++ b/WNPRC_EHR/resources/queries/study/demographicsBloodSummary.sql @@ -1,87 +1,61 @@ - - -/* - * Copyright (c) 2010-2014 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -SELECT - b.lsid, - b.id, - --b.date, - b.wdate as MostRecentWeightDate, - b.weight as MostRecentWeight, - convert(BloodLast30, float) as BloodLast30, - convert(BloodNext30, float) as BloodNext30, - convert(CASE - WHEN (b.species = 'Marmoset') - THEN (b.weight*0.15*60) - ELSE - (b.weight*0.2*60) - END, float) AS MaxBlood, - - convert(case - when (b.species = 'Marmoset') - THEN ((b.weight*0.15*60) - b.BloodLast30) - else - ((b.weight*0.2*60) - b.BloodLast30) - end, float) as CurrentBlood, - - TRUNCATE(ROUND(CAST(case - when (b.BloodLast30 > b.BloodNext30 and b.species = 'Marmoset') - THEN ((b.weight*0.15*60) - b.BloodLast30) - when (b.BloodLast30 > b.BloodNext30 and b.species != 'Marmoset') - THEN ((b.weight*0.2*60) - b.BloodLast30 -b.BloodNext30) - when (b.BloodLast30 <= b.BloodNext30 and b.species = 'Marmoset') - THEN ((b.weight*0.15*60) - b.BloodNext30) - else - ((b.weight*0.2*60) - b.BloodNext30 - b.BloodLast30) - end AS NUMERIC),2),2) as AvailBlood -from ( -SELECT - d.lsid, - d.id, - d.species, --- d.weight, --- d.wdate, - lastWeight.date as wdate, - ( - SELECT AVG(w.weight) AS _expr - FROM study.weight w - WHERE w.id=d.id AND w.date=lastWeight.date - AND w.qcstate.publicdata = true - ) AS weight, - COALESCE (( - SELECT - SUM(bd.quantity) AS _expr - FROM study."Blood Draws" bd - WHERE bd.id=d.id AND - bd.qcstate.publicdata = true AND - --bd.date BETWEEN TIMESTAMPADD('SQL_TSI_DAY', -30, now()) AND now() - (cast(bd.date as date) >= cast(TIMESTAMPADD('SQL_TSI_DAY', -30, now()) as date) AND cast(bd.date as date) <= cast(curdate() as date)) - - ), 0) AS BloodLast30, - COALESCE (( - SELECT - SUM(bd.quantity) AS _expr - FROM study."Blood Draws" bd - WHERE bd.id=d.id AND - (bd.qcstate.publicdata = true OR bd.qcstate.metadata.DraftData = true) AND - --bd.date BETWEEN now() AND TIMESTAMPADD('SQL_TSI_DAY', 30, now()) - (cast(bd.date as date) >= cast(curdate() as date) AND cast(bd.date as date) <= cast(TIMESTAMPADD('SQL_TSI_DAY', 30, now()) as date)) - - ), 0) AS BloodNext30 - -FROM - study.demographics d - LEFT OUTER JOIN - (SELECT w.id, MAX(date) as date FROM study.weight w - WHERE w.qcstate.publicdata = true - GROUP BY w.id) lastWeight ON d.id = lastWeight.id - --- WHERE b.date >= TIMESTAMPADD('SQL_TSI_DAY', -30, now()) -WHERE - -d.calculated_status = 'Alive' - + + +/* + * Copyright (c) 2010-2014 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +SELECT + b.lsid, + b.id, + --b.date, + b.wdate as MostRecentWeightDate, + b.weight as MostRecentWeight, + convert(BloodLast30, float) as BloodLast30, + convert(BloodNext30, float) as BloodNext30, + convert((b.weight*(b.species.max_draw_pct)*(b.species.blood_per_kg)), float) AS MaxBlood, + TRUNCATE(ROUND(CAST(((b.weight*(b.species.max_draw_pct)*(b.species.blood_per_kg)) - b.BloodLast30) AS NUMERIC),2),2) as AvailBlood +from ( +SELECT + d.lsid, + d.id, + d.species, +-- d.weight, +-- d.wdate, + lastWeight.date as wdate, + ( + SELECT AVG(w.weight) AS _expr + FROM study.weight w + WHERE w.id=d.id AND w.date=lastWeight.date + ) AS weight, + COALESCE (( + SELECT + SUM(bd.quantity) AS _expr + FROM study."Blood Draws" bd + WHERE bd.id=d.id AND + --bd.date BETWEEN TIMESTAMPADD('SQL_TSI_DAY', -30, now()) AND now() + (cast(bd.date as date) >= cast(TIMESTAMPADD('SQL_TSI_DAY', -30, now()) as date) AND cast(bd.date as date) <= cast(curdate() as date)) + + ), 0) AS BloodLast30, + COALESCE (( + SELECT + SUM(bd.quantity) AS _expr + FROM study."Blood Draws" bd + WHERE bd.id=d.id AND + --bd.date BETWEEN now() AND TIMESTAMPADD('SQL_TSI_DAY', 30, now()) + (cast(bd.date as date) > cast(curdate() as date) AND cast(bd.date as date) <= cast(TIMESTAMPADD('SQL_TSI_DAY', 30, now()) as date)) + + ), 0) AS BloodNext30 + +FROM + study.demographics d + LEFT OUTER JOIN + (SELECT w.id, MAX(date) as date FROM study.weight w + GROUP BY w.id) lastWeight ON d.id = lastWeight.id + +-- WHERE b.date >= TIMESTAMPADD('SQL_TSI_DAY', -30, now()) +WHERE + +d.calculated_status = 'Alive' + ) b \ No newline at end of file diff --git a/WNPRC_EHR/resources/web/ehr/ext3/ehrStore.js b/WNPRC_EHR/resources/web/ehr/ext3/ehrStore.js index 4ce13bc69..708226c66 100644 --- a/WNPRC_EHR/resources/web/ehr/ext3/ehrStore.js +++ b/WNPRC_EHR/resources/web/ehr/ext3/ehrStore.js @@ -667,7 +667,7 @@ EHR.ext.AdvancedStore = Ext.extend(LABKEY.ext.Store, { // top-level request, rather than only per-blood. in this instance only Blood Draws.js // is using the data, but weights are a good example of something that might be // globally useful. - if (this.queryName == 'Blood Draws'){ + if (this.queryName == 'blood'){ var bloodDrawMap = {}; var allRecords = this.getAllRecords(); for (var idx = 0; idx < allRecords.length; ++idx){ diff --git a/WNPRC_EHR/resources/web/wnprc_ehr/wnprcReports.js b/WNPRC_EHR/resources/web/wnprc_ehr/wnprcReports.js index b9e718175..2bebd2370 100644 --- a/WNPRC_EHR/resources/web/wnprc_ehr/wnprcReports.js +++ b/WNPRC_EHR/resources/web/wnprc_ehr/wnprcReports.js @@ -661,6 +661,26 @@ EHR.reports.diagnostics = function(panel, tab){ }); }; +EHR.reports.currentBloodDraws = function(panel, tab) { + var filterArray = panel.getFilterArray(tab); + var title = panel.getTitleSuffix(); + + var config = panel.getQWPConfig({ + schemaName: 'study', + queryName: 'currentBloodDraws', + title: "Current Blood " + title, + parameters: {'interval': '30'}, + filters: filterArray.nonRemovable, + removeableFilters: filterArray.removable + }); + + tab.add({ + xtype: 'ldk-querycmp', + style: 'margin-bottom:20px;', + queryConfig: config + }); +} + EHR.reports.bloodChemistry = function(panel, tab){ var filterArray = panel.getFilterArray(tab); var title = panel.getTitleSuffix(); @@ -1247,6 +1267,33 @@ EHR.reports['abstract'] = function(panel, tab){ EHR.reports.weightGraph(panel, tab); }; +EHR.reports.BloodSummary = function(panel, tab){ + tab.add({ + html: 'This report summarizes the blood available for the animals below. ' + + '

If there have been recent blood draws for the animal, a graph will show the available blood over time. On the graph, dots indicate dates when either blood was drawn or a previous blood draw fell off. The horizontal lines indicate the maximum allowable blood that can be drawn on that date.', + border: false, + style: 'padding-bottom: 20px;' + }); + + var subjects = tab.filters.subjects || []; + + if (subjects.length){ + tab.add({ + xtype: 'ehr-bloodsummarypanel', + subjects: subjects + }); + } + else + { + panel.resolveSubjectsFromHousing(tab, function(subjects, tab){ + tab.add({ + xtype: 'ehr-bloodsummarypanel', + subjects: subjects + }); + }, this); + } +}; + (function() { var abstractReport = EHR.reports['abstract']; From d8e2390cb42024be33ef01c620f0f199856b736d Mon Sep 17 00:00:00 2001 From: Chad Sebranek Date: Thu, 12 Dec 2024 15:00:13 -0600 Subject: [PATCH 3/3] Changes to automated blood charges (#710) * group up the blood draws if they are done at the same time * remove count from comment * comment sql --- .../wnprc_billing/bloodDrawsAllTubesSPI.sql | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/wnprc_billing/resources/queries/wnprc_billing/bloodDrawsAllTubesSPI.sql b/wnprc_billing/resources/queries/wnprc_billing/bloodDrawsAllTubesSPI.sql index fb5d7bec8..c46de5b39 100644 --- a/wnprc_billing/resources/queries/wnprc_billing/bloodDrawsAllTubesSPI.sql +++ b/wnprc_billing/resources/queries/wnprc_billing/bloodDrawsAllTubesSPI.sql @@ -2,15 +2,18 @@ SELECT Id, date, project, - coalesce(account, project.account.alias) AS debitedAccount, + coalesce(account, project.account.alias) as debitedAccount, coalesce(a.tier_rate.tierRate, project.account.tier_rate.tierRate) as otherRate, - objectid AS sourceRecord, - ('Blood Draws ' || Id) AS comment, - CAST(1 as DOUBLE) AS quantity, - taskId, - performedby + group_concat(objectid,';') as objectids, + ('Blood Draws ' || Id ) as comment, + CAST(1 AS DOUBLE) AS quantity, + group_concat(taskid, ';') as taskids, + group_concat(performedby, ';') as performedby FROM studyLinked.BloodSchedule bloodSch LEFT JOIN ehr_billing.aliases a ON bloodSch.account = a.alias WHERE billedBy.value = 'c' AND - qcstate.publicdata = true \ No newline at end of file + qcstate.publicdata = true + +--group blood draws drawn on the same date with same project +GROUP BY id, date, project, coalesce(account, project.account.alias), coalesce(a.tier_rate.tierRate, project.account.tier_rate.tierRate);