diff --git a/onprc_ehr/resources/queries/study/Demographics_NotInMMA.query.xml b/onprc_ehr/resources/queries/study/Demographics_NotInMMA.query.xml new file mode 100644 index 000000000..48f834dca --- /dev/null +++ b/onprc_ehr/resources/queries/study/Demographics_NotInMMA.query.xml @@ -0,0 +1,9 @@ + + + + + Demographics (Excluding animals in Weight MMA regimen) +
+
+
+
diff --git a/onprc_ehr/resources/queries/study/Demographics_NotInMMA.sql b/onprc_ehr/resources/queries/study/Demographics_NotInMMA.sql new file mode 100644 index 000000000..f197e4ee1 --- /dev/null +++ b/onprc_ehr/resources/queries/study/Demographics_NotInMMA.sql @@ -0,0 +1,54 @@ +/* + Created by Kollil in Dec 2025 + Tkt # 13461 + Added two filters to the Demographics dataset: + 1. Filter out any animal with the following SNOMED Codes: + Begin active weight management regimen (P-YY961) + However, we would need to include animals that have this additional SNOMED Code if it's entered AFTER the one above + Release from active weight management regimen (P-YY960) + 2. Remove Shelters, Corral and Hospital locations from the lists + */ + +SELECT + d.Id.curlocation.area AS Area, + d.Id.curlocation.room AS Room, + d.Id.curlocation.cage AS Cage, + d.Id, + d.Id.utilization.use AS ProjectsAndGroups, + d.species, + d.geographic_origin, + d.gender AS Sex, + d.calculated_status, + d.birth, + d.Id.Age.YearAndDays, + d.Id.MostRecentWeight.MostRecentWeight, + d.Id.MostRecentWeight.MostRecentWeightDate, + d.Id.viral_status.viralStatus, + d.history +FROM Demographics d +WHERE d.Id.curlocation.area NOT IN ('Shelters', 'Corral', 'Hospital')-- Exclude animals from these locations + AND NOT (-- Exclude females under 5yrs, males under 7yrs + (d.gender.code = 'f' AND d.Id.age.ageInYears < 5) + OR (d.gender.code = 'm' AND d.Id.age.ageInYears < 7) + ) + AND NOT EXISTS ( + -- -- Find animals whose latest 'Weight MMA BEGIN' has no later 'Weight MMA RELEASE' + SELECT 1 + FROM study.WeightManagementMMAData b + WHERE b.Id = d.Id + AND b.code = 'P-YY961' + AND b.date = ( + SELECT MAX(b2.date) + FROM study.WeightManagementMMAData b2 + WHERE b2.Id = d.Id + AND b2.code = 'P-YY961' + ) + AND NOT EXISTS ( + SELECT 1 + FROM study.WeightManagementMMAData r + WHERE r.Id = d.Id + AND r.code = 'P-YY960' + AND r.date > b.date + ) +) + diff --git a/onprc_ehr/resources/queries/study/weightConsecutiveDrops_NotInMMA.query.xml b/onprc_ehr/resources/queries/study/weightConsecutiveDrops_NotInMMA.query.xml new file mode 100644 index 000000000..c781c7ded --- /dev/null +++ b/onprc_ehr/resources/queries/study/weightConsecutiveDrops_NotInMMA.query.xml @@ -0,0 +1,50 @@ + + + + + Weight Change, Relative to Previous Weight (Excluding the animals enrolled in MMA) + This query shows the percent change of each weight, relative to the weight immediately prior to it + + + true + true + + + /query/executeQuery.view?schemaName=study& + query.queryName=weight& + query.id~eq=${id}& + query.sort=-date + + + + /query/executeQuery.view?schemaName=study& + query.queryName=weight& + query.date~eq=${PrevDate}& + query.sort=-date + + + + /query/executeQuery.view?schemaName=study& + query.queryName=weight& + query.lsid~eq=${lsid}& + + Percent Change + + + Interval (Days) + + + Current Weight (kg) + + + Weight Date + + + + + + PctChange +
+
+
+
diff --git a/onprc_ehr/resources/queries/study/weightConsecutiveDrops_NotInMMA.sql b/onprc_ehr/resources/queries/study/weightConsecutiveDrops_NotInMMA.sql new file mode 100644 index 000000000..13fbd4571 --- /dev/null +++ b/onprc_ehr/resources/queries/study/weightConsecutiveDrops_NotInMMA.sql @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2013-2019 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +--this query contains a handful of calculated fields for the weights table +--it is designed to be joined into weights using lsid + +SELECT + w.lsid, + w.Id, + w.date, + w.weight AS curWeight, + pd1.PrevDate as prevDate1, + pw1.weight AS prevWeight1, + Round(((w.weight - pw1.weight) * 100 / w.weight), 1) AS pctChange1, + timestampdiff('SQL_TSI_DAY', pw1.date, w.date) AS interval1, + pd2.PrevDate as PrevDate2, + pw2.weight AS PrevWeight2, + Round(((pw1.weight - pw2.weight) * 100 / pw1.weight), 1) AS PctChange2, + timestampdiff('SQL_TSI_DAY', pw2.date, pw1.date) AS Interval2 +FROM study.weight w + --Find the next most recent weight date before this one + JOIN + (SELECT T2.Id, T2.date, max(T1.date) as PrevDate + FROM study.weight T1 + JOIN study.weight T2 ON (T1.Id = T2.Id AND T1.date < T2.date) + WHERE t1.qcstate.publicdata = true AND t2.qcstate.publicdata = true + GROUP BY T2.Id, T2.date) pd1 + ON (w.Id = pd1.Id AND w.date = pd1.Date) + + --and the weight associated with that date + JOIN study.weight pw1 + ON (w.Id = pw1.Id AND pw1.date = pd1.prevdate AND pw1.qcstate.publicdata = true) + + --then find the next most recent date + LEFT JOIN + (SELECT T2.Id, T2.date, max(T1.date) as PrevDate + FROM study.weight T1 + JOIN study.weight T2 ON (T1.Id = T2.Id AND T1.date < T2.date) + WHERE t1.qcstate.publicdata = true AND t2.qcstate.publicdata = true + GROUP BY T2.Id, T2.date + ) pd2 + ON (pd1.Id = pd2.Id AND pd1.PrevDate = pd2.date) + + --and the weight associated with that date + JOIN study.weight pw2 + ON (w.Id = pw2.Id AND pw2.date = pd2.prevdate AND pw2.qcstate.publicdata = true) +WHERE + w.qcstate.publicdata = true + AND pd1.date is not null + AND pd2.date is not null + --only include drops + AND w.weight < pw1.weight + AND pw1.weight < pw2.weight + AND ((w.weight - pw2.weight) * 100 / w.weight) < -3.0 + + AND w.Id.curlocation.area NOT IN ('Shelters', 'Corral', 'Hospital')-- Exclude animals from these locations + AND NOT (-- Exclude females under 5yrs, males under 7yrs + (w.Id.demographics.gender.code = 'f' AND w.Id.age.ageInYears < 5) + OR (w.Id.demographics.gender.code = 'm' AND w.Id.age.ageInYears < 7) + ) + AND w.Id NOT IN ( + SELECT 1 -- Find animals whose latest 'Weight MMA BEGIN' has no later 'Weight MMA RELEASE' + FROM study.WeightManagementMMAData b + WHERE b.Id = w.Id + AND b.code = 'P-YY961' + AND b.date = ( + SELECT MAX(b2.date) + FROM study.WeightManagementMMAData b2 + WHERE b2.Id = w.Id + AND b2.code = 'P-YY961' + ) + AND NOT EXISTS ( + SELECT 1 + FROM study.WeightManagementMMAData r + WHERE r.Id = w.Id + AND r.code = 'P-YY960' + AND r.date > b.date + ) + ) diff --git a/onprc_ehr/resources/queries/study/weightRelChange_NotInMMA.query.xml b/onprc_ehr/resources/queries/study/weightRelChange_NotInMMA.query.xml new file mode 100644 index 000000000..5b6170eca --- /dev/null +++ b/onprc_ehr/resources/queries/study/weightRelChange_NotInMMA.query.xml @@ -0,0 +1,68 @@ + + + + + Weight Change, Relative to Current Weight (Excluding the animals enrolled in MMA) + This query shows the percent change of each weight, relative to the current weight + + + true + true + + + true + + study + animal + id + + + + true + Date of Last Weight + + + Old Weight (kg) + + + % Change Relative To Current + + + + + + true + FF0000 + + + + + + true + 458B00 + + + + + Days Since Weight + + + Months Since Weight + + + Latest Weight (kg) + /query/executeQuery.view?schemaName=study& + query.queryName=weight& + query.date~eq=${LatestWeightDate}& + query.sort=-date + + + + Latest Weight Date + + + PctChange +
+
+
+
diff --git a/onprc_ehr/resources/queries/study/weightRelChange_NotInMMA.sql b/onprc_ehr/resources/queries/study/weightRelChange_NotInMMA.sql new file mode 100644 index 000000000..884348e4c --- /dev/null +++ b/onprc_ehr/resources/queries/study/weightRelChange_NotInMMA.sql @@ -0,0 +1,62 @@ +/* + Created by Kollil in Dec 2025 + Tkt # 13461 + Added two filters to the Demographics dataset: + 1. Filter out any animal with the following SNOMED Codes: + Begin active weight management regimen (P-YY961) + However, we would need to include animals that have this additional SNOMED Code if it's entered AFTER the one above + Release from active weight management regimen (P-YY960) + 2. Remove Shelters, Corral and Hospital locations from the lists + */ + +SELECT + w.lsid, + w.Id, + w.date, + w.Id.MostRecentWeight.MostRecentWeightDate as LatestWeightDate, + w.Id.MostRecentWeight.MostRecentWeight AS LatestWeight, + + timestampdiff('SQL_TSI_DAY', w.date, w.Id.MostRecentWeight.MostRecentWeightDate) AS IntervalInDays, + age_in_months(w.date, w.Id.MostRecentWeight.MostRecentWeightDate) AS IntervalInMonths, + + w.weight, + CASE WHEN w.date >= timestampadd('SQL_TSI_DAY', -730, w.Id.MostRecentWeight.MostRecentWeightDate) THEN + Round(((w.Id.MostRecentWeight.MostRecentWeight - w.weight) * 100 / w.weight), 1) + ELSE + null + END AS PctChange, + + CASE WHEN w.date >= timestampadd('SQL_TSI_DAY', -730, w.Id.MostRecentWeight.MostRecentWeightDate) THEN + Abs(Round(((w.Id.MostRecentWeight.MostRecentWeight - w.weight) * 100 / w.weight), 1)) + else + null + END AS AbsPctChange, + w.qcstate +FROM study.weight w +WHERE w.Id.curlocation.area NOT IN ('Shelters', 'Corral', 'Hospital')-- Exclude animals from these locations + AND NOT (-- Exclude females under 5yrs, males under 7yrs + (w.Id.demographics.gender.code = 'f' AND w.Id.age.ageInYears < 5) + OR (w.Id.demographics.gender.code = 'm' AND w.Id.age.ageInYears < 7) + ) + AND w.qcstate.publicdata = true + AND NOT EXISTS ( + -- -- Find animals whose latest 'Weight MMA BEGIN' has no later 'Weight MMA RELEASE' + SELECT 1 + FROM study.WeightManagementMMAData b + WHERE b.Id = w.Id + AND b.code = 'P-YY961' + AND b.date = ( + SELECT MAX(b2.date) + FROM study.WeightManagementMMAData b2 + WHERE b2.Id = w.Id + AND b2.code = 'P-YY961' + ) + AND NOT EXISTS ( + SELECT 1 + FROM study.WeightManagementMMAData r + WHERE r.Id = w.Id + AND r.code = 'P-YY960' + AND r.date > b.date + ) + ) + diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/notification/WeightAlertsNotification.java b/onprc_ehr/src/org/labkey/onprc_ehr/notification/WeightAlertsNotification.java index b661a3a64..b16ec0b25 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/notification/WeightAlertsNotification.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/notification/WeightAlertsNotification.java @@ -20,6 +20,7 @@ import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.CompareType; import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerFilter; import org.labkey.api.data.Results; import org.labkey.api.data.ResultsImpl; import org.labkey.api.data.Selector; @@ -50,6 +51,10 @@ * User: bbimber * Date: 7/23/12 * Time: 7:41 PM + * + * Modified by Kollil as per ticket # 13461 + * Date: Jan 2026 + * */ public class WeightAlertsNotification extends AbstractEHRNotification { @@ -79,13 +84,13 @@ public String getEmailSubject(Container c) @Override public String getCronString() { - return "0 15 9 ? * MON"; - } + return "0 0 12 ? * THU"; + } //Made changes to the alert by Kollil, Refer to tkt # 13461 @Override public String getScheduleDescription() { - return "every Monday, at 9:15 AM"; + return "every Thursday, at 12pm"; } @Override @@ -97,7 +102,11 @@ public String getMessageBodyHTML(Container c, User u) Date now = new Date(); msg.append("This email contains alerts of significant weight changes. It was run on: " + getDateFormat(c).format(now) + " at " + _timeFormat.format(now) + ".

"); - getLivingWithoutWeight(c, u, msg); + /* + Changed by Kollil: 1/2026, tkt #13461 + 1. Delete the "animals do not have a weight" since that's captured on the colony management emails already. + */ + //getLivingWithoutWeight(c, u, msg); generateCombinedWeightTable(c, u, msg); @@ -110,68 +119,36 @@ private void generateCombinedWeightTable(final Container c, User u, final String //first weight drops Set dropDistinctIds = new HashSet<>(); - processWeights(c, u, sb, 0, 30, CompareType.LTE, -10, dropDistinctIds); - consecutiveWeightDrops(c, u, sb, dropDistinctIds); + processWeights(c, u, sb, 0, 100, CompareType.LTE, -10, dropDistinctIds); + /* + Changed by Kollil: 1/2026 + Disabling this section as per ticket #13461 + */ +// consecutiveWeightDrops(c, u, sb, dropDistinctIds); if (!dropDistinctIds.isEmpty()) { - String url = getExecuteQueryUrl(c, "study", "Demographics", "By Location") + "&query.calculated_status~eq=Alive&query.Id~in=" + (StringUtils.join(new ArrayList(dropDistinctIds), ";")); + String url = getExecuteQueryUrl(c, "study", "Demographics_NotInMMA", null ) + "&query.calculated_status~eq=Alive&query.Id~in=" + (StringUtils.join(new ArrayList(dropDistinctIds), ";")); sb.insert(0, "WARNING: There are " + dropDistinctIds.size() + " animals that experienced either a large weight loss, or 3 consecutive weight drops. Click here to view this list, or view the data below.


"); } //also weight gains Set gainDistinctIds = new HashSet<>(); - processWeights(c, u, sb, 0, 30, CompareType.GTE, 10, gainDistinctIds); + processWeights(c, u, sb, 0, 100, CompareType.GTE, 10, gainDistinctIds); if (!gainDistinctIds.isEmpty()) { - String url = getExecuteQueryUrl(c, "study", "Demographics", "By Location") + "&query.calculated_status~eq=Alive&query.Id~in=" + (StringUtils.join(new ArrayList(gainDistinctIds), ";")); + String url = getExecuteQueryUrl(c, "study", "Demographics_NotInMMA", null) + "&query.calculated_status~eq=Alive&query.Id~in=" + (StringUtils.join(new ArrayList(gainDistinctIds), ";")); sb.insert(0, "WARNING: There are " + gainDistinctIds.size() + " animals that experienced large weight gain (>10%). Click here to view this list, or view the data below.


"); } msg.append(sb); } - private void getLivingWithoutWeight(final Container c, User u, final StringBuilder msg) - { - SimpleFilter filter = new SimpleFilter(FieldKey.fromString("calculated_status"), "Alive"); - filter.addCondition(FieldKey.fromString("Id/MostRecentWeight/MostRecentWeightDate"), null, CompareType.ISBLANK); - Sort sort = new Sort(getStudy(c).getSubjectColumnName()); - - TableInfo ti = getStudySchema(c, u).getTable("Demographics"); - List colKeys = new ArrayList<>(); - colKeys.add(FieldKey.fromString(getStudy(c).getSubjectColumnName())); - colKeys.add(FieldKey.fromString("Id/age/AgeFriendly")); - final Map columns = QueryService.get().getColumns(ti, colKeys); - - TableSelector ts = new TableSelector(ti, columns.values(), filter, sort); - if (ts.exists()) - { - msg.append("WARNING: The animals listed below do not have a weight.\n"); - msg.append(" Click here to view these animals

\n"); - - ts.forEach(new TableSelector.ForEachBlock<>() - { - @Override - public void exec(ResultSet rs) throws SQLException - { - Results results = new ResultsImpl(rs, columns); - msg.append(rs.getString(getStudy(c).getSubjectColumnName())); - String age = results.getString(FieldKey.fromString("Id/age/AgeFriendly")); - if (age != null) - msg.append(" (Age: " + age + ")"); - - msg.append("
\n"); - } - }); - - msg.append("
\n"); - } - } - private void processWeights(Container c, User u, final StringBuilder msg, int min, int max, CompareType ct, double pct, @Nullable Set distinctIds) { - TableInfo ti = getStudySchema(c, u).getTable("weightRelChange"); + TableInfo ti = QueryService.get().getUserSchema(u, c, "study").getTable("weightRelChange_NotInMMA", ContainerFilter.Type.AllFolders.create(c, u)); +// TableInfo ti = getStudySchema(c, u).getTable("weightRelChange"); assert ti != null; final FieldKey areaKey = FieldKey.fromString("Id/curLocation/Area"); @@ -180,7 +157,7 @@ private void processWeights(Container c, User u, final StringBuilder msg, int mi final FieldKey ageKey = FieldKey.fromString("Id/age/AgeFriendly"); final FieldKey problemKey = FieldKey.fromString("Id/openProblems/problems"); final FieldKey investKey = FieldKey.fromString("Id/activeAssignments/investigators"); - final FieldKey vetsKey = FieldKey.fromString("Id/activeAssignments/vets"); + final FieldKey vetsKey = FieldKey.fromString("Id/assignedVet/AssignedVet"); final FieldKey peKey = FieldKey.fromString("Id/physicalExamHistory/daysSinceExam"); List colKeys = new ArrayList<>(); @@ -206,7 +183,7 @@ private void processWeights(Container c, User u, final StringBuilder msg, int mi filter.addCondition(FieldKey.fromString("IntervalInDays"), max, CompareType.LTE); Calendar date = Calendar.getInstance(); - date.add(Calendar.DATE, -30); + date.add(Calendar.DATE, -100); /// change to last 100 days filter.addCondition(FieldKey.fromString("LatestWeightDate"), getDateFormat(c).format(date.getTime()), CompareType.DATE_GTE); TableSelector ts = new TableSelector(ti, columns.values(), filter, null); @@ -262,14 +239,13 @@ public void exec(ResultSet object) throws SQLException if (!summary.isEmpty()) { - msg.append("

Click here to view these " + distinctAnimals.size() + " animals

\n"); + msg.append("

Click here to view these " + distinctAnimals.size() + " animals

\n"); msg.append(""); for (String area : summary.keySet()) { Map>> areaValue = summary.get(area); - for (String room : areaValue.keySet()) - { + for (String room : areaValue.keySet()) { List> roomValue = areaValue.get(room); for (Map map : roomValue) { @@ -305,8 +281,7 @@ public void exec(ResultSet object) throws SQLException msg.append("
AreaRoomCageIdInvestigatorsResponsible VetOpen ProblemsDays Since Last PEWeight DatesDays BetweenWeight (kg)Percent Change

\n"); msg.append("


"); } - else - { + else { msg.append("There are no changes during this period.
"); } @@ -314,6 +289,7 @@ public void exec(ResultSet object) throws SQLException distinctIds.addAll(distinctAnimals); } + protected void consecutiveWeightDrops(final Container c, User u, final StringBuilder msg, @Nullable Set distinctIds) { SimpleFilter filter = new SimpleFilter(FieldKey.fromString("Id/dataset/demographics/calculated_status"), "Alive"); @@ -326,7 +302,7 @@ protected void consecutiveWeightDrops(final Container c, User u, final StringBui sort.appendSortColumn(new Sort.SortField(FieldKey.fromString("Id/curLocation/room"), Sort.SortDirection.ASC)); sort.appendSortColumn(new Sort.SortField(FieldKey.fromString("Id/curLocation/cage"), Sort.SortDirection.ASC)); - TableInfo ti = getStudySchema(c, u).getTable("weightConsecutiveDrops"); + TableInfo ti = getStudySchema(c, u).getTable("weightConsecutiveDrops_NotInMMA"); assert ti != null; List colKeys = new ArrayList<>(); @@ -404,7 +380,7 @@ public void exec(ResultSet rs) throws SQLException tableMsg.append("\n"); - msg.append("

Click here to view these " + animalIds.size() + " animals

\n"); + msg.append("

Click here to view these " + animalIds.size() + " animals

\n"); msg.append(tableMsg); msg.append("
\n"); @@ -413,6 +389,43 @@ public void exec(ResultSet rs) throws SQLException } } + // private void getLivingWithoutWeight(final Container c, User u, final StringBuilder msg) +// { +// SimpleFilter filter = new SimpleFilter(FieldKey.fromString("calculated_status"), "Alive"); +// filter.addCondition(FieldKey.fromString("Id/MostRecentWeight/MostRecentWeightDate"), null, CompareType.ISBLANK); +// Sort sort = new Sort(getStudy(c).getSubjectColumnName()); +// +// TableInfo ti = getStudySchema(c, u).getTable("Demographics"); +// List colKeys = new ArrayList<>(); +// colKeys.add(FieldKey.fromString(getStudy(c).getSubjectColumnName())); +// colKeys.add(FieldKey.fromString("Id/age/AgeFriendly")); +// final Map columns = QueryService.get().getColumns(ti, colKeys); +// +// TableSelector ts = new TableSelector(ti, columns.values(), filter, sort); +// if (ts.exists()) +// { +// msg.append("WARNING: The animals listed below do not have a weight.\n"); +// msg.append(" Click here to view these animals

\n"); +// +// ts.forEach(new TableSelector.ForEachBlock<>() +// { +// @Override +// public void exec(ResultSet rs) throws SQLException +// { +// Results results = new ResultsImpl(rs, columns); +// msg.append(rs.getString(getStudy(c).getSubjectColumnName())); +// String age = results.getString(FieldKey.fromString("Id/age/AgeFriendly")); +// if (age != null) +// msg.append(" (Age: " + age + ")"); +// +// msg.append("
\n"); +// } +// }); +// +// msg.append("
\n"); +// } +// } + private String getValue(Results rs, String prop) throws SQLException { String val = rs.getString(FieldKey.fromString(prop));