From b64a02d81cf7d61a866dac061591fd4bb1292bc9 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Sun, 9 Aug 2026 06:52:16 -0600 Subject: [PATCH 1/3] Drop legacy nbri_ehr tables and tighten birth and conception entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conception is the only table left in the nbri_ehr schema — the rest were carried over from the legacy system and nothing reads them, so their queries, lookups and referencing columns go too. On the entry side, birth condition and conception term date are gone, conception date is now required, and the fields the Start with Conception window populates are read-only. --- nbri_ehr/resources/data/birth_condition.tsv | 3 - nbri_ehr/resources/data/editable_lookups.tsv | 2 - nbri_ehr/resources/data/id_history_type.tsv | 6 - nbri_ehr/resources/data/lookup_sets.tsv | 2 - nbri_ehr/resources/data/lookupsManifest.tsv | 3 - .../resources/data/lookupsManifestTest.tsv | 2 - .../queries/nbri_ehr/Account.query.xml | 48 -- .../queries/nbri_ehr/AnimalDelivery.query.xml | 86 --- .../nbri_ehr/AnimalDeliveryEsig.query.xml | 36 -- .../queries/nbri_ehr/AnimalReqOrder.query.xml | 135 ----- .../nbri_ehr/AnimalReqOrderEsig.query.xml | 36 -- .../queries/nbri_ehr/AnimalShipment.query.xml | 42 -- .../queries/nbri_ehr/AnimalVendor.query.xml | 29 - .../queries/nbri_ehr/AnimalVendor/.qview.xml | 20 - .../queries/nbri_ehr/CageCard.query.xml | 56 -- .../nbri_ehr/CageCardHistory.query.xml | 65 -- .../queries/nbri_ehr/Conception.query.xml | 4 +- .../queries/nbri_ehr/ConceptionsByDam.sql | 1 - .../queries/nbri_ehr/DeletedRecord.query.xml | 24 - .../queries/nbri_ehr/Department.query.xml | 25 - .../queries/nbri_ehr/IdHistory.query.xml | 29 - .../resources/queries/nbri_ehr/Lot.query.xml | 20 - .../queries/nbri_ehr/ProtocolEsig.query.xml | 31 - .../nbri_ehr/ProtocolProcedures.query.xml | 21 - .../queries/nbri_ehr/ProtocolStress.query.xml | 21 - .../queries/nbri_ehr/QuestionResponse.js | 35 -- .../nbri_ehr/QuestionResponse.query.xml | 38 -- .../queries/nbri_ehr/locationTypes/.qview.xml | 5 - .../queries/nbri_ehr/locations.query.xml | 19 - .../queries/nbri_ehr/locations/.qview.xml | 6 - .../nbri_ehr/locationsMapping.query.xml | 28 - .../nbri_ehr/locationsMapping/.qview.xml | 7 - .../queries/nbri_ehr/staff.query.xml | 128 ---- .../queries/nbri_ehr/stress.query.xml | 20 - nbri_ehr/resources/queries/study/aliases.sql | 4 - .../resources/queries/study/birth.query.xml | 9 - .../queries/study/demographics.query.xml | 8 - .../study/datasets/datasets_metadata.xml | 6 - .../postgresql/nbri_ehr-26.001-26.002.sql | 40 ++ nbri_ehr/resources/schemas/nbri_ehr.xml | 572 +----------------- .../web/nbri_ehr/model/sources/Birth.js | 37 +- .../web/nbri_ehr/model/sources/Conception.js | 9 +- .../org/labkey/nbri_ehr/NBRI_EHRModule.java | 2 +- .../section/NBRIBirthFormSection.java | 1 - .../tests.nbri_ehr/NBRI_EHRTest.java | 3 +- 45 files changed, 76 insertions(+), 1648 deletions(-) delete mode 100644 nbri_ehr/resources/data/birth_condition.tsv delete mode 100644 nbri_ehr/resources/data/id_history_type.tsv delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/Account.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/AnimalDelivery.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/AnimalDeliveryEsig.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrder.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrderEsig.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/AnimalShipment.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/AnimalVendor.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/AnimalVendor/.qview.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/CageCard.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/CageCardHistory.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/DeletedRecord.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/Department.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/IdHistory.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/Lot.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/ProtocolEsig.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/ProtocolProcedures.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/ProtocolStress.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/QuestionResponse.js delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/QuestionResponse.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/locationTypes/.qview.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/locations.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/locations/.qview.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/locationsMapping.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/locationsMapping/.qview.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/staff.query.xml delete mode 100644 nbri_ehr/resources/queries/nbri_ehr/stress.query.xml create mode 100644 nbri_ehr/resources/schemas/dbscripts/postgresql/nbri_ehr-26.001-26.002.sql diff --git a/nbri_ehr/resources/data/birth_condition.tsv b/nbri_ehr/resources/data/birth_condition.tsv deleted file mode 100644 index bdb4e26..0000000 --- a/nbri_ehr/resources/data/birth_condition.tsv +++ /dev/null @@ -1,3 +0,0 @@ -value title sort_order -L Live 1 -D Dead 2 diff --git a/nbri_ehr/resources/data/editable_lookups.tsv b/nbri_ehr/resources/data/editable_lookups.tsv index d0ed33a..700bffe 100644 --- a/nbri_ehr/resources/data/editable_lookups.tsv +++ b/nbri_ehr/resources/data/editable_lookups.tsv @@ -18,7 +18,6 @@ ehr_lookups bcs_score Clinical Body Condition Score Clinical observation fixed v ehr_lookups behavior_abnormality Clinical Behavior Abnormality Clinical observation fixed values. ehr_lookups behavior_mgmt_codes Behavior Behavior Management Codes Behavior observation fixed values. ehr_lookups behavior_types Behavior Behavior Types Behavior observation fixed values. -ehr_lookups birth_condition Colony Management Birth Condition Birth condition values. ehr_lookups blood_draw_reason Clinical Blood Draw Reason Used in blood draw datasets. ehr_lookups blood_draw_tube_type Clinical Blood Draw Tube Type Used in blood draw datasets. ehr_lookups blood_sample_type Clinical Blood Sample Types Used in blood draw datasets. @@ -68,7 +67,6 @@ ehr_lookups geographic_origins Colony Management Geographic Origins Used in demo ehr_lookups hernia_types Clinical Hernia Types Clinical observation fixed values. ehr_lookups housing_reason Housing Housing Move Reason Used in housing dataset for location transfers. ehr_lookups hyd_score Clinical Hydration Score Clinical observation fixed values. -ehr_lookups id_history_type Colony Management Id History Type ehr_lookups incision_score Clinical Incision Status Clinical observation fixed values. ehr_lookups indoor_outdoor Colony Management Indoor/Outdoor Cage details ehr_lookups keyword Clinical Keyword Clinical observation fixed values. diff --git a/nbri_ehr/resources/data/id_history_type.tsv b/nbri_ehr/resources/data/id_history_type.tsv deleted file mode 100644 index ba59b93..0000000 --- a/nbri_ehr/resources/data/id_history_type.tsv +++ /dev/null @@ -1,6 +0,0 @@ -value title -1 Old Tattoo Number -2 ISIS Stud Book Number -3 CITES Number -4 Name -5 Transponder Number \ No newline at end of file diff --git a/nbri_ehr/resources/data/lookup_sets.tsv b/nbri_ehr/resources/data/lookup_sets.tsv index c465f4a..e71f832 100644 --- a/nbri_ehr/resources/data/lookup_sets.tsv +++ b/nbri_ehr/resources/data/lookup_sets.tsv @@ -16,7 +16,6 @@ bcs_score BCS Store value title behavior_abnormality Behavior Abnormality value behavior_mgmt_codes Behavior Management Codes value behavior_types Behavior Types value -birth_condition Birth Condition value title blood_draw_reason Blood Draw Reason value blood_sample_type Blood Sample Types value breeding_type Breeding Type value title @@ -55,7 +54,6 @@ genitourinary_obs Genitourinary Observations value hernia_types Hernia Types value housing_reason Housing Reason value hyd_score Hyd Score value -id_history_type Id History Type value title incision_score Incision Status value indoor_outdoor Indoor/Outdoor value keyword Keyword value title diff --git a/nbri_ehr/resources/data/lookupsManifest.tsv b/nbri_ehr/resources/data/lookupsManifest.tsv index 9f32d12..ccc18c5 100644 --- a/nbri_ehr/resources/data/lookupsManifest.tsv +++ b/nbri_ehr/resources/data/lookupsManifest.tsv @@ -18,7 +18,6 @@ bcs_score behavior_abnormality behavior_mgmt_codes behavior_types -birth_condition blood_draw_reason blood_draw_tube_type blood_sample_type @@ -56,7 +55,6 @@ fecal_score fecal_smear_score feed_assess_types flag_categories -flag_values gastro_types gender_codes general_obs @@ -66,7 +64,6 @@ geographic_origins hernia_types housing_reason hyd_score -id_history_type incision_score indoor_outdoor lameness diff --git a/nbri_ehr/resources/data/lookupsManifestTest.tsv b/nbri_ehr/resources/data/lookupsManifestTest.tsv index d64c8ba..64b5720 100644 --- a/nbri_ehr/resources/data/lookupsManifestTest.tsv +++ b/nbri_ehr/resources/data/lookupsManifestTest.tsv @@ -18,7 +18,6 @@ bcs_score behavior_abnormality behavior_mgmt_codes behavior_types -birth_condition blood_draw_reason blood_draw_tube_type blood_sample_type @@ -66,7 +65,6 @@ geographic_origins hernia_types housing_reason hyd_score -id_history_type incision_score indoor_outdoor lameness diff --git a/nbri_ehr/resources/queries/nbri_ehr/Account.query.xml b/nbri_ehr/resources/queries/nbri_ehr/Account.query.xml deleted file mode 100644 index 7f498db..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/Account.query.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - Account - - - - Department - - nbri_ehr - Department - DepartmentId - Name - - - - Cost Type - - ehr_lookups - cost_type - value - title - - - - Expense Class - - ehr_lookups - expense_class - value - description - - - - Project - - ehr - project - project - displayName - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/AnimalDelivery.query.xml b/nbri_ehr/resources/queries/nbri_ehr/AnimalDelivery.query.xml deleted file mode 100644 index e19c0a8..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/AnimalDelivery.query.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - Animal Delivery - - - - Animal Delivery Id - - - Animal Shipment - - nbri_ehr - AnimalShipment - AnimalShipmentId - AnimalShipmentId - - - - Ship To - - nbri_ehr - ShipTo - ShipToId - Name - - - - Animal Req Order - - nbri_ehr - AnimalReqOrder - AnimalReqOrderId - AnimalReqOrder - - - - Delivery State - - ehr_lookups - delivery_state - value - title - - - - Project - - ehr - project - project - displayName - - - - Bill To Account - - nbri_ehr - Account - AccountId - AccountNumber - - - - Bill To Staff - http://www.labkey.org/types#userId - - - Per Diem Account - - nbri_ehr - Account - AccountId - AccountNumber - - - - Per Diem Staff - http://www.labkey.org/types#userId - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/AnimalDeliveryEsig.query.xml b/nbri_ehr/resources/queries/nbri_ehr/AnimalDeliveryEsig.query.xml deleted file mode 100644 index 0c74ac4..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/AnimalDeliveryEsig.query.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Animal Delivery ESignature - - - Animal Delivery - - nbri_ehr - AnimalDelivery - AnimalDeliveryId - AnimalDeliveryId - - - - Esig Event - - ehr_lookups - esig_events - value - title - - - - User Profile - http://www.labkey.org/types#userId - - - Esig Date - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrder.query.xml b/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrder.query.xml deleted file mode 100644 index e282d4d..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrder.query.xml +++ /dev/null @@ -1,135 +0,0 @@ - - - - - Animal Req Order - - - - Animal Vendor - - nbri_ehr - AnimalVendor - AnimalVendorId - VendorName - - - - Requisitioner Staff - http://www.labkey.org/types#userId - - - Req Order Type - - ehr_lookups - req_order_type - value - title - - - - Req Order State - - ehr_lookups - req_order_state - value - title - - - - Bill To Account - - nbri_ehr - Account - AccountId - AccountNumber - - - - Bill To Staff - http://www.labkey.org/types#userId - - - Per Diem Account - - nbri_ehr - Account - AccountId - AccountNumber - - - - Per Diem Staff - http://www.labkey.org/types#userId - - - Submitted By Staff - http://www.labkey.org/types#userId - - - Approved By Staff - http://www.labkey.org/types#userId - - - Project - - ehr - project - project - displayName - - - - Site Cage - /nbri_ehr/cageDetails.view?room=${SiteCage/room}&cage=${SiteCage}& - - ehr_lookups - cage - location - cage - - - - Site Room - /nbri_ehr/cageDetails.view?room=${SiteRoom}& - - ehr_lookups - rooms - room - room - - - - Site Floor - - ehr_lookups - floors - floor - name - - - - Site Building - - ehr_lookups - buildings - name - - - - Site Area - - ehr_lookups - areas - area - - - - Created By Staff - http://www.labkey.org/types#userId - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrderEsig.query.xml b/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrderEsig.query.xml deleted file mode 100644 index 2d8bfb2..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrderEsig.query.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Animal Req Order ESignature - - - Animal Req Order - - nbri_ehr - AnimalReqOrder - AnimalReqOrderId - AnimalReqOrderId - - - - Esig Event - - ehr_lookups - esig_events - value - title - - - - User Profile - http://www.labkey.org/types#userId - - - Esig Date - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/AnimalShipment.query.xml b/nbri_ehr/resources/queries/nbri_ehr/AnimalShipment.query.xml deleted file mode 100644 index 60aa8de..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/AnimalShipment.query.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - Animal Shipment - - - - Received By Staff - http://www.labkey.org/types#userId - - - Animal Delivery - - nbri_ehr - AnimalDelivery - AnimalDeliveryId - AnimalDeliveryId - - - - Lot Id - - nbri_ehr - lot - lotId - - - - Cost Center - - ehr_lookups - cost_center - value - title - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/AnimalVendor.query.xml b/nbri_ehr/resources/queries/nbri_ehr/AnimalVendor.query.xml deleted file mode 100644 index 426952f..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/AnimalVendor.query.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - Animal Vendor - - - Vendor Approval Code - - ehr_lookups - vendor_approval_code - value - title - - - - Vendor Production Location - - ehr_lookups - vendor_production_location - value - title - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/AnimalVendor/.qview.xml b/nbri_ehr/resources/queries/nbri_ehr/AnimalVendor/.qview.xml deleted file mode 100644 index 9691552..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/AnimalVendor/.qview.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/CageCard.query.xml b/nbri_ehr/resources/queries/nbri_ehr/CageCard.query.xml deleted file mode 100644 index e70dfc5..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/CageCard.query.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - Cage Card - - - - Account Staff - http://www.labkey.org/types#userId - - - Account Staff - http://www.labkey.org/types#userId - - - Animal Delivery - - nbri_ehr - AnimalDelivery - AnimalDeliveryId - AnimalDeliveryId - - - - Cost Center - - ehr_lookups - cost_center - value - title - - - - Card Format - - ehr_lookups - card_format - value - title - - - - Census Activity Status - - ehr_lookups - census_activity_status - value - title - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/CageCardHistory.query.xml b/nbri_ehr/resources/queries/nbri_ehr/CageCardHistory.query.xml deleted file mode 100644 index 088f8b4..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/CageCardHistory.query.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - Cage Card History - - - - Account Staff - http://www.labkey.org/types#userId - - - Account Staff - http://www.labkey.org/types#userId - - - Animal Delivery - - nbri_ehr - AnimalDelivery - AnimalDeliveryId - AnimalDeliveryId - - - - Cost Center - - ehr_lookups - cost_center - value - title - - - - Card Format - - ehr_lookups - card_format - value - title - - - - Census Activity Status - - ehr_lookups - census_activity_status - value - title - - - - Account - - nbri_ehr - Account - AccountId - AccountNumber - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/Conception.query.xml b/nbri_ehr/resources/queries/nbri_ehr/Conception.query.xml index 7fc6298..32e2b50 100644 --- a/nbri_ehr/resources/queries/nbri_ehr/Conception.query.xml +++ b/nbri_ehr/resources/queries/nbri_ehr/Conception.query.xml @@ -13,9 +13,7 @@ Conception Date - - - Conception Term Date + true Estimated diff --git a/nbri_ehr/resources/queries/nbri_ehr/ConceptionsByDam.sql b/nbri_ehr/resources/queries/nbri_ehr/ConceptionsByDam.sql index ae7697f..68b07b4 100644 --- a/nbri_ehr/resources/queries/nbri_ehr/ConceptionsByDam.sql +++ b/nbri_ehr/resources/queries/nbri_ehr/ConceptionsByDam.sql @@ -7,7 +7,6 @@ SELECT c.Dam AS Id, c.ConceptId, c.ConceptDate, - c.ConceptTermDate, c.Estimated, c.Sire, CASE diff --git a/nbri_ehr/resources/queries/nbri_ehr/DeletedRecord.query.xml b/nbri_ehr/resources/queries/nbri_ehr/DeletedRecord.query.xml deleted file mode 100644 index 6b894a7..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/DeletedRecord.query.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Deleted Record - - - Esig Event - - ehr_lookups - esig_events - value - title - - - - ESignature User - http://www.labkey.org/types#userId - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/Department.query.xml b/nbri_ehr/resources/queries/nbri_ehr/Department.query.xml deleted file mode 100644 index 91062cf..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/Department.query.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Department - - - - Parent Department - - nbri_ehr - Department - DepartmentId - Name - - - - Staff - http://www.labkey.org/types#userId - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/IdHistory.query.xml b/nbri_ehr/resources/queries/nbri_ehr/IdHistory.query.xml deleted file mode 100644 index 686bf4c..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/IdHistory.query.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - Id History - - - Id - - study - Animal - Id - Id - - - - Type - - ehr_lookups - id_history_type - Value - Title - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/Lot.query.xml b/nbri_ehr/resources/queries/nbri_ehr/Lot.query.xml deleted file mode 100644 index a5de660..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/Lot.query.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Animal Lot - - - Animal Shipment - - nbri_ehr - AnimalShipment - AnimalShipmentId - AnimalShipmentId - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/ProtocolEsig.query.xml b/nbri_ehr/resources/queries/nbri_ehr/ProtocolEsig.query.xml deleted file mode 100644 index 366d740..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/ProtocolEsig.query.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - Protocol ESignature - - - - Esig Id - - - Esig Event - - ehr_lookups - esig_events - value - title - - - - User Profile - http://www.labkey.org/types#userId - - - Esig Date - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/ProtocolProcedures.query.xml b/nbri_ehr/resources/queries/nbri_ehr/ProtocolProcedures.query.xml deleted file mode 100644 index 27ee2e0..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/ProtocolProcedures.query.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Protocol Procedures - - - - Procedures - - ehr_lookups - procedures - name - name - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/ProtocolStress.query.xml b/nbri_ehr/resources/queries/nbri_ehr/ProtocolStress.query.xml deleted file mode 100644 index 2b1ca94..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/ProtocolStress.query.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Protocol Stress - - - - Stress - - nbri_ehr - Stress - StressId - Name - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/QuestionResponse.js b/nbri_ehr/resources/queries/nbri_ehr/QuestionResponse.js deleted file mode 100644 index 537630a..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/QuestionResponse.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2026 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -require("ehr/triggers").initScript(this); - -EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_INSERT, 'nbri_ehr', 'QuestionResponse', function (helper, scriptErrors, row, oldRow) { - - if (row.Response) { - plainTextParts = row.Response.split("\\loch\\af0") - let plainText = ""; - for(let i = 1; i < plainTextParts.length; i++) - { - let chunk = LABKEY.Utils.encodeHtml(plainTextParts[i].split("}")[0]); - chunk = chunk.replace("\\hich\\af0 \\'85", "…"); // ellipsis - chunk = chunk.replace("\\hich\\af0 \\'92", "'"); - chunk = chunk.replace("\\hich\\af0 \\'93", "\""); - chunk = chunk.replace("\\hich\\af0 \\'94", "\""); - chunk = chunk.replace("\\hich\\af0 \\'99", "™"); // trademark - chunk = chunk.replace("\\hich\\af0 \\'ae", "®"); // registered trademark - chunk = chunk.replace("\\hich\\af0 \\'b0", "°"); // Degree sign - chunk = chunk.replace("\\hich\\af0 \\'b5", "µ"); // Micro sign (Greek mu) - chunk = chunk.replace("\\hich\\af0 \\'b7", "·"); // Middle dot - chunk = chunk.replace("\\hich\\af0 \\'ef", "ï"); // i-diaeresis - // TODO: As a general catch all, could convert the hex value in the match to the HTML ascii entity - // Currently this covers all the cases though in the source data - - plainText += chunk.trim(); - plainText += " "; - } - row.Response = plainText.trim(); - } - -}); \ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/QuestionResponse.query.xml b/nbri_ehr/resources/queries/nbri_ehr/QuestionResponse.query.xml deleted file mode 100644 index 218470d..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/QuestionResponse.query.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - Question Response - - - Protocol - - ehr - protocol - objectid - protocol - - - - Question - - nbri_ehr - Question - QuestionId - Text - - - - Questionnaire - - ehr_lookups - questionnaire - value - title - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/locationTypes/.qview.xml b/nbri_ehr/resources/queries/nbri_ehr/locationTypes/.qview.xml deleted file mode 100644 index b1d4016..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/locationTypes/.qview.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/locations.query.xml b/nbri_ehr/resources/queries/nbri_ehr/locations.query.xml deleted file mode 100644 index 3f4fe51..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/locations.query.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - Location Type - - nbri_ehr - locationTypes - locationTypeId - name - - - -
-
-
-
diff --git a/nbri_ehr/resources/queries/nbri_ehr/locations/.qview.xml b/nbri_ehr/resources/queries/nbri_ehr/locations/.qview.xml deleted file mode 100644 index 08af13d..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/locations/.qview.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/locationsMapping.query.xml b/nbri_ehr/resources/queries/nbri_ehr/locationsMapping.query.xml deleted file mode 100644 index d2357c7..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/locationsMapping.query.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - Location - - nbri_ehr - locations - locationId - name - - - - Parent Location - - nbri_ehr - locations - locationId - name - - - -
-
-
-
diff --git a/nbri_ehr/resources/queries/nbri_ehr/locationsMapping/.qview.xml b/nbri_ehr/resources/queries/nbri_ehr/locationsMapping/.qview.xml deleted file mode 100644 index 6496f3b..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/locationsMapping/.qview.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/staff.query.xml b/nbri_ehr/resources/queries/nbri_ehr/staff.query.xml deleted file mode 100644 index c39c37c..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/staff.query.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - Staff - - - First Name - - - Last Name - - - Middle Name - - - Display Name - - - Hire Date - - - Last Employ Date - - - Office Phone - - - Office Phone Ext - - - Office Fax - - - Home Phone - - - Beeper Phone - - - Cell Phone - - - Emergency Contact - - - Emergency Phone - - - Home Address 1 - - - Home Address2 - - - Home City - - - Home State - - - Home Zip - - - Home Zip Ext - - - Home Country - - - Office Address 1 - - - Office Address 2 - - - Office City - - - Office State - - - Office Zip - - - Office Zip Ext - - - Office Country - - - Birth Date - - - High School Name - - - College Grad Date - - - College Degree - - - College Major - - - College Name - - - Supervisor - - nbri_ehr - staff - staffId - displayName - - - - Position Name - - - Position Description - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/nbri_ehr/stress.query.xml b/nbri_ehr/resources/queries/nbri_ehr/stress.query.xml deleted file mode 100644 index 590673d..0000000 --- a/nbri_ehr/resources/queries/nbri_ehr/stress.query.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Stress Levels - - - Regulatory Stress Level - - ehr_lookups - regulatory_stress_levels - value - title - - - -
-
-
-
\ No newline at end of file diff --git a/nbri_ehr/resources/queries/study/aliases.sql b/nbri_ehr/resources/queries/study/aliases.sql index 4ed9962..e6c16d0 100644 --- a/nbri_ehr/resources/queries/study/aliases.sql +++ b/nbri_ehr/resources/queries/study/aliases.sql @@ -9,10 +9,6 @@ SELECT Id, Id as alias FROM study.Animal where Dataset.Demographics.calculated_status != 'Alive - In Progress' UNION -SELECT Id, - Name as alias -FROM nbri_ehr.IdHistory -UNION SELECT Id, Alias as alias FROM study.alias where Id.demographics.calculated_status != 'Alive - In Progress' \ No newline at end of file diff --git a/nbri_ehr/resources/queries/study/birth.query.xml b/nbri_ehr/resources/queries/study/birth.query.xml index 18a5a92..034b7f0 100644 --- a/nbri_ehr/resources/queries/study/birth.query.xml +++ b/nbri_ehr/resources/queries/study/birth.query.xml @@ -42,15 +42,6 @@ title
- - Birth Condition - - ehr_lookups - birth_condition - value - title - - Project diff --git a/nbri_ehr/resources/queries/study/demographics.query.xml b/nbri_ehr/resources/queries/study/demographics.query.xml index f6fafa6..01fdfc9 100644 --- a/nbri_ehr/resources/queries/study/demographics.query.xml +++ b/nbri_ehr/resources/queries/study/demographics.query.xml @@ -82,14 +82,6 @@ Animal Status - - Lot - - nbri_ehr - Lot - LotId - - Origin diff --git a/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml b/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml index 2ae212d..4acb1e6 100644 --- a/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml +++ b/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml @@ -227,9 +227,6 @@ varchar - - varchar - varchar @@ -472,9 +469,6 @@ varchar - - integer - varchar diff --git a/nbri_ehr/resources/schemas/dbscripts/postgresql/nbri_ehr-26.001-26.002.sql b/nbri_ehr/resources/schemas/dbscripts/postgresql/nbri_ehr-26.001-26.002.sql new file mode 100644 index 0000000..afac7bc --- /dev/null +++ b/nbri_ehr/resources/schemas/dbscripts/postgresql/nbri_ehr-26.001-26.002.sql @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2026 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +ALTER TABLE nbri_ehr.Conception DROP COLUMN ConceptTermDate; + +-- Drop the tables carried over from the legacy system that nothing in the module reads or writes. Conception is the +-- only table left in the schema afterward. Each DROP also removes that table's primary key and its +-- IX_..._Container index, so no separate DROP INDEX is needed. + +DROP TABLE IF EXISTS nbri_ehr.CageCardHistory; +DROP TABLE IF EXISTS nbri_ehr.CageCard; +DROP TABLE IF EXISTS nbri_ehr.AnimalDeliveryEsig; +DROP TABLE IF EXISTS nbri_ehr.AnimalReqOrderEsig; +DROP TABLE IF EXISTS nbri_ehr.AnimalDelivery; +DROP TABLE IF EXISTS nbri_ehr.AnimalReqOrder; +DROP TABLE IF EXISTS nbri_ehr.Lot; +DROP TABLE IF EXISTS nbri_ehr.AnimalShipment; +DROP TABLE IF EXISTS nbri_ehr.AnimalVendor; +DROP TABLE IF EXISTS nbri_ehr.ShipTo; + +DROP TABLE IF EXISTS nbri_ehr.Account; +DROP TABLE IF EXISTS nbri_ehr.Department; + +DROP TABLE IF EXISTS nbri_ehr.ProtocolStress; +DROP TABLE IF EXISTS nbri_ehr.Stress; +DROP TABLE IF EXISTS nbri_ehr.ProtocolProcedures; +DROP TABLE IF EXISTS nbri_ehr.ProtocolEsig; +DROP TABLE IF EXISTS nbri_ehr.ProtocolUsage; + +DROP TABLE IF EXISTS nbri_ehr.LocationsMapping; +DROP TABLE IF EXISTS nbri_ehr.Locations; +DROP TABLE IF EXISTS nbri_ehr.LocationTypes; + +DROP TABLE IF EXISTS nbri_ehr.QuestionResponse; +DROP TABLE IF EXISTS nbri_ehr.Question; +DROP TABLE IF EXISTS nbri_ehr.DeletedRecord; +DROP TABLE IF EXISTS nbri_ehr.Staff; +DROP TABLE IF EXISTS nbri_ehr.IdHistory; diff --git a/nbri_ehr/resources/schemas/nbri_ehr.xml b/nbri_ehr/resources/schemas/nbri_ehr.xml index 2bc125d..9c11a59 100644 --- a/nbri_ehr/resources/schemas/nbri_ehr.xml +++ b/nbri_ehr/resources/schemas/nbri_ehr.xml @@ -2,573 +2,6 @@ - - Location Types - DETAILED - - - - - - - - - - -
- - - Locations - DETAILED - - - - - - - - - - - -
- - - Locations Mapping - DETAILED - - - - - - - - - - - -
- - - Staff - DETAILED - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - Lot - DETAILED - - - - - - - - - - - -
- - - Animal Shipment - DETAILED - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - Animal Delivery - DETAILED - - - - - - - - - - - - - - - - - - - - - - - - -
- - - Animal Delivery ESignature - DETAILED - - - - - - - - - - - - -
- - - Animal Req Order - DETAILED - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - Animal Req Order ESignature - DETAILED - - - - - - - - - - - - -
- - - Animal Vendor - DETAILED - - - - - - - - - - - - - - - - - - - - - - -
- - - Ship To - DETAILED - - - - - - - - - - - - - - - - -
- - - Protocol ESignature - DETAILED - - - - - - - - - - - - -
- - - Protocol Usage - DETAILED - - - - - - - - - - - - - - - - - - - - - - - - -
- - - Account - DETAILED - - - - - - - - - - - - - - - - -
- - - Department - DETAILED - - - - - - - - - - - - -
- - - Id History - DETAILED - - - - - - - - - - -
- - - Deleted Record - DETAILED - - - - - - - - - - - - - - - -
- - - Question - DETAILED - - - - - - - - - - - -
- - - Question Response - DETAILED - - - - - - - - - - - - -
- - - Protocol Stress - DETAILED - - - - - - - - - - - - -
- - - Stress - DETAILED - - - - - - - - - - - - - -
- - - Protocol Procedures - DETAILED - - - - - - - - - - - -
- - - Cage Card - DETAILED - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - Cage Card History - DETAILED - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ConceptionDETAILED @@ -578,9 +11,6 @@ Date - - Date - @@ -595,4 +25,4 @@
-
\ No newline at end of file + diff --git a/nbri_ehr/resources/web/nbri_ehr/model/sources/Birth.js b/nbri_ehr/resources/web/nbri_ehr/model/sources/Birth.js index f266f1e..0b76f92 100644 --- a/nbri_ehr/resources/web/nbri_ehr/model/sources/Birth.js +++ b/nbri_ehr/resources/web/nbri_ehr/model/sources/Birth.js @@ -32,12 +32,35 @@ EHR.model.DataModelManager.registerMetadata('Birth', { allowBlank: false, nullable: false }, + // conception Id, species, dam and sire all come from the conception picked in the Start with Conception + // window, so they are shown but not entered by hand. That window writes to the store directly, which is + // unaffected by these read-only editor settings. 'Id/demographics/species': { allowBlank: false, nullable: false, columnConfig: { fixed: true, - width: 250 + width: 250, + editable: false + }, + formEditorConfig: { + readOnly: true + } + }, + 'Id/demographics/dam': { + columnConfig: { + editable: false + }, + formEditorConfig: { + readOnly: true + } + }, + 'Id/demographics/sire': { + columnConfig: { + editable: false + }, + formEditorConfig: { + readOnly: true } }, 'cage': { @@ -52,11 +75,6 @@ EHR.model.DataModelManager.registerMetadata('Birth', { width: 200 }, }, - cond: { - columnConfig: { - width: 200 - }, - }, // project and protocol are entered through the Project Assignment and Protocol Assignment sections project: { allowBlank: true, @@ -75,11 +93,16 @@ EHR.model.DataModelManager.registerMetadata('Birth', { allowBlank: false, nullable: false }, + // see the note above on the fields the Start with Conception window populates conceptId: { allowBlank: false, nullable: false, columnConfig: { - width: 150 + width: 150, + editable: false + }, + formEditorConfig: { + readOnly: true } }, breedingType: { diff --git a/nbri_ehr/resources/web/nbri_ehr/model/sources/Conception.js b/nbri_ehr/resources/web/nbri_ehr/model/sources/Conception.js index 949d8cb..076ce02 100644 --- a/nbri_ehr/resources/web/nbri_ehr/model/sources/Conception.js +++ b/nbri_ehr/resources/web/nbri_ehr/model/sources/Conception.js @@ -24,13 +24,8 @@ EHR.model.DataModelManager.registerMetadata('Conception', { ConceptDate: { xtype: 'datefield', extFormat: LABKEY.extDefaultDateFormat, - columnConfig: { - width: 200 - }, - }, - ConceptTermDate: { - xtype: 'datefield', - extFormat: LABKEY.extDefaultDateFormat, + allowBlank: false, + nullable: false, columnConfig: { width: 200 }, diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java index aa76225..c3ee1d2 100644 --- a/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java +++ b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java @@ -80,7 +80,7 @@ public String getName() @Override public @Nullable Double getSchemaVersion() { - return 26.001; + return 26.002; } @Override diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java index ddecd53..10c8ab7 100644 --- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java +++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java @@ -38,7 +38,6 @@ public class NBRIBirthFormSection extends NewAnimalFormSection FieldKey.fromString("Id/demographics/sire"), FieldKey.fromString("cage"), FieldKey.fromString("type"), - FieldKey.fromString("cond"), FieldKey.fromString("breedingType"), FieldKey.fromString("remark"), FieldKey.fromString("performedby") diff --git a/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java b/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java index afaab10..b11d6ab 100644 --- a/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java +++ b/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java @@ -863,7 +863,6 @@ public void testConceptionForm() _helper.addRecordToGrid(conceptions); conceptions.setGridCell(1, "ConceptId", conceptId); conceptions.setGridCellJS(1, "ConceptDate", now.minusDays(30).format(_dateFormat)); - conceptions.setGridCellJS(1, "ConceptTermDate", now.plusDays(135).format(_dateFormat)); conceptions.setGridCellJS(1, "Estimated", true); conceptions.setGridCell(1, "Dam", damId); conceptions.setGridCell(1, "Sire", sireId); @@ -1963,7 +1962,7 @@ private void createBreedingPair(String damId, String sireId, String species) thr private void verifyBirthColumnOrder(Ext4GridRef births) { List expectedOrder = List.of("Id", "date", "conceptId", "Id/demographics/species", "Id/demographics/gender", - "Id/demographics/dam", "Id/demographics/sire", "cage", "type", "cond", "breedingType", "remark", "performedby"); + "Id/demographics/dam", "Id/demographics/sire", "cage", "type", "breedingType", "remark", "performedby"); int previousIdx = 0; String previousCol = null; From 8f8c7a97a102bc6c69efdf47c594c7c840a1cba7 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Mon, 10 Aug 2026 09:41:44 -0600 Subject: [PATCH 2/3] Derive demographics birth/death from the birth and death records The birth, arrival and death forms each wrote demographics.birth and demographics.death independently, and the death form also hand-set calculated_status. One trigger helper now derives both dates from the saved event records, and status is left to the shared recalc that owns the death/departure precedence. Adds a drift query for reconciling values the old paths left stale. --- nbri_ehr/resources/queries/study/arrival.js | 30 ++++-- nbri_ehr/resources/queries/study/birth.js | 25 ++++- nbri_ehr/resources/queries/study/deaths.js | 44 +++++---- .../study/diagnosticsBirthDeathDrift.sql | 69 +++++++++++++ .../nbri_ehr/query/NBRI_EHRTriggerHelper.java | 97 +++++++++++++++++++ 5 files changed, 236 insertions(+), 29 deletions(-) create mode 100644 nbri_ehr/resources/queries/study/diagnosticsBirthDeathDrift.sql diff --git a/nbri_ehr/resources/queries/study/arrival.js b/nbri_ehr/resources/queries/study/arrival.js index 2010ffd..80761d7 100644 --- a/nbri_ehr/resources/queries/study/arrival.js +++ b/nbri_ehr/resources/queries/study/arrival.js @@ -6,6 +6,13 @@ require("ehr/triggers").initScript(this); var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id); +var idsToSync = []; + +EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'Arrival', function(event, helper){ + + // the script scope can outlive a single save, so never inherit ids from a prior one + idsToSync = []; +}); EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_UPSERT, 'study', 'Arrival', function(helper, scriptErrors, row, oldRow) { @@ -132,12 +139,6 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even hasUpdates = true; } - if (row.birth && row.birth !== data.birth) - { - obj.birth = row.birth; - hasUpdates = true; - } - if (row.sire && row.sire !== data.sire) { obj.sire = row.sire; @@ -171,5 +172,22 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even helper.cacheDemographics(row.Id, row); } } + + if (row.Id && idsToSync.indexOf(row.Id) === -1) { + idsToSync.push(row.Id); + } + } +}); + +EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.COMPLETE, 'study', 'Arrival', function(event, errors, helper){ + + // Single writer for the denormalized demographics birth date. saveBirthRecord() above wrote the birth record; + // this reads it back so demographics and the event record cannot disagree. + if (!helper.isETL() && idsToSync.length) { + var demographicsUpdates = triggerHelper.computeDemographicsSync(idsToSync); + if (demographicsUpdates.size() > 0) { + helper.getJavaHelper().updateDemographicsRecord(demographicsUpdates); + } + idsToSync = []; } }); \ No newline at end of file diff --git a/nbri_ehr/resources/queries/study/birth.js b/nbri_ehr/resources/queries/study/birth.js index cd0cce1..52aa379 100644 --- a/nbri_ehr/resources/queries/study/birth.js +++ b/nbri_ehr/resources/queries/study/birth.js @@ -7,6 +7,7 @@ require("ehr/triggers").initScript(this); EHR.Server.Utils = require("ehr/utils").EHR.Server.Utils; var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id); +var idsToSync = []; function onInit(event, helper){ helper.setScriptOptions({ @@ -21,9 +22,24 @@ function onInit(event, helper){ skipAssignmentCheck: true, }); + // the script scope can outlive a single save, so never inherit ids from a prior one + idsToSync = []; + helper.decodeExtraContextProperty('birthsInTransaction'); } +EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.COMPLETE, 'study', 'birth', function(event, errors, helper){ + + // Single writer for the denormalized demographics birth date, derived from the saved birth records. + if (!helper.isETL() && idsToSync.length) { + var demographicsUpdates = triggerHelper.computeDemographicsSync(idsToSync); + if (demographicsUpdates.size() > 0) { + helper.getJavaHelper().updateDemographicsRecord(demographicsUpdates); + } + idsToSync = []; + } +}); + EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_UPSERT, 'study', 'birth', function(helper, scriptErrors, row, oldRow) { if (!oldRow && row.Id && triggerHelper.birthExists(row.Id)) { @@ -147,11 +163,6 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even hasUpdates = true; } - if (obj.birth && obj.birth !== data.birth) { - record.birth = obj.birth; - hasUpdates = true; - } - if (obj.sire && obj.sire !== data.sire) { record.sire = obj.sire; hasUpdates = true; @@ -180,6 +191,10 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even helper.cacheDemographics(row.Id, row); } } + + if (row.Id && idsToSync.indexOf(row.Id) === -1) { + idsToSync.push(row.Id); + } } } }); \ No newline at end of file diff --git a/nbri_ehr/resources/queries/study/deaths.js b/nbri_ehr/resources/queries/study/deaths.js index abe7bc4..c7dae52 100644 --- a/nbri_ehr/resources/queries/study/deaths.js +++ b/nbri_ehr/resources/queries/study/deaths.js @@ -8,9 +8,13 @@ require("ehr/triggers").initScript(this); var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id); var idMap = {}; var deathIdMap = {}; +var idsToSync = []; function onInit(event, helper){ + // the script scope can outlive a single save, so never inherit ids from a prior one + idsToSync = []; + helper.decodeExtraContextProperty('deathsInTransaction'); // Cache valid Ids for check on each row @@ -59,7 +63,6 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even demographicsUpdates.push({ Id: row.Id, death: null, - calculated_status: 'Alive', QCState: helper.getJavaHelper().getQCStateForLabel('Completed').getRowId(), }); @@ -69,8 +72,6 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even function onUpsert(helper, scriptErrors, row, oldRow) { - var demographicsUpdates = []; - if (!helper.isETL()) { //skip other checks so that the admins can update a death record @@ -130,21 +131,6 @@ function onUpsert(helper, scriptErrors, row, oldRow) { EHR.Server.Utils.addError(scriptErrors, 'Id', errorMsg, 'ERROR'); } else { - if (!helper.isValidateOnly() && row.Id && row.date && rowQCState === 'COMPLETED') { - - // update demographics - demographicsUpdates.push({ - Id: row.Id, - death: row.date, - calculated_status: 'Dead', - QCState: helper.getJavaHelper().getQCStateForLabel(row.QCStateLabel).getRowId() - }); - - console.log('updating demographics death date for animal: ' + row.Id); - helper.getJavaHelper().updateDemographicsRecord(demographicsUpdates); - console.log('updated demographics death date for animal: ' + row.Id); - } - if (!helper.isValidateOnly() && row.date && row.QCStateLabel && EHR.Server.Security.getQCStateByLabel(row.QCStateLabel).PublicData) { var qcstate = helper.getJavaHelper().getQCStateForLabel(row.QCStateLabel).getRowId(); @@ -177,9 +163,31 @@ function onUpsert(helper, scriptErrors, row, oldRow) { EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.AFTER_INSERT, 'study', 'deaths', function(helper, scriptErrors, row, oldRow) { helper.registerDeath(row.Id, row.date); triggerHelper.reportDataChange("study", "deaths", [row.Id]); + + if (row.Id && idsToSync.indexOf(row.Id) === -1) { + idsToSync.push(row.Id); + } +}); + +EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.AFTER_UPDATE, 'study', 'deaths', function(helper, scriptErrors, row, oldRow) { + if (row.Id && idsToSync.indexOf(row.Id) === -1) { + idsToSync.push(row.Id); + } }); EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.COMPLETE, 'study', 'Deaths', function(event, errors, helper){ + + // Single writer for the denormalized demographics death date. Runs once per save, after the death rows are saved, + // and derives the value from the stored record rather than from the incoming row. calculated_status is left to the + // shared status recalc, which owns the death/departure/re-arrival precedence. + if (!helper.isETL() && idsToSync.length) { + var demographicsUpdates = triggerHelper.computeDemographicsSync(idsToSync); + if (demographicsUpdates.size() > 0) { + helper.getJavaHelper().updateDemographicsRecord(demographicsUpdates); + } + idsToSync = []; + } + var rows = helper.getRows() || []; for (var i = 0; i < rows.length; i++) { var row = rows[i].row; diff --git a/nbri_ehr/resources/queries/study/diagnosticsBirthDeathDrift.sql b/nbri_ehr/resources/queries/study/diagnosticsBirthDeathDrift.sql new file mode 100644 index 0000000..6ee5fd9 --- /dev/null +++ b/nbri_ehr/resources/queries/study/diagnosticsBirthDeathDrift.sql @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2026 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +/* + * Reports animals whose demographics birth/death values disagree with the birth and deaths event records that are + * supposed to feed them. + * + * demographics.birth and demographics.death are denormalized copies, written by trigger scripts rather than derived, + * so anything that bypasses those triggers -- ETL loads, admin edits, a partially failed save -- leaves them stale. + * The framework reads the demographics copy (not the event record) for age, lastDayAtCenter and status, so drift here + * is silently wrong data everywhere those appear. + * + * Only public (Completed) event records count as backing, and draft demographics records are excluded, so rows still + * in data entry are not reported. Each animal yields at most one row; the three drift columns are independent and can + * be filtered separately in the grid. + * + * Note: on a container populated by ETL or legacy import, "no completed birth record" can be the common case rather + * than the exception. Filter birthDrift to triage. + */ + +SELECT * FROM ( + SELECT + ids.Id, + dem.calculated_status, + + dem.birth AS demographicsBirth, + b.date AS birthRecordDate, + CASE + WHEN dem.Id IS NULL THEN 'Event record exists with no demographics record' + WHEN dem.birth IS NULL AND b.Id IS NOT NULL THEN 'Birth record exists but demographics birth is empty' + WHEN dem.birth IS NOT NULL AND b.Id IS NULL THEN 'Demographics birth is set with no completed birth record' + WHEN CAST(dem.birth AS DATE) <> CAST(b.date AS DATE) THEN 'Birth dates disagree' + END AS birthDrift, + + dem.death AS demographicsDeath, + d.date AS deathRecordDate, + CASE + WHEN dem.Id IS NULL THEN 'Event record exists with no demographics record' + WHEN dem.death IS NULL AND d.Id IS NOT NULL THEN 'Death record exists but demographics death is empty' + WHEN dem.death IS NOT NULL AND d.Id IS NULL THEN 'Demographics death is set with no completed death record' + WHEN CAST(dem.death AS DATE) <> CAST(d.date AS DATE) THEN 'Death dates disagree' + END AS deathDrift, + + CASE + WHEN dem.Id IS NULL THEN NULL + WHEN d.Id IS NOT NULL AND (dem.calculated_status IS NULL OR dem.calculated_status <> 'Dead') + THEN 'Completed death record but status is not Dead' + WHEN d.Id IS NULL AND dem.calculated_status = 'Dead' + THEN 'Status is Dead with no completed death record' + END AS statusDrift, + + dem.QCState.PublicData AS demographicsIsPublic + + FROM ( + SELECT Id FROM study.demographics + UNION + SELECT Id FROM study.birth + UNION + SELECT Id FROM study.deaths + ) ids + LEFT JOIN study.demographics dem ON ids.Id = dem.Id + LEFT JOIN (SELECT Id, date FROM study.birth WHERE QCState.PublicData = true) b ON ids.Id = b.Id + LEFT JOIN (SELECT Id, date FROM study.deaths WHERE QCState.PublicData = true) d ON ids.Id = d.Id +) t +WHERE (t.demographicsIsPublic = true OR t.demographicsIsPublic IS NULL) + AND (t.birthDrift IS NOT NULL OR t.deathDrift IS NOT NULL OR t.statusDrift IS NOT NULL) diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/query/NBRI_EHRTriggerHelper.java b/nbri_ehr/src/org/labkey/nbri_ehr/query/NBRI_EHRTriggerHelper.java index 662ab54..893c53a 100644 --- a/nbri_ehr/src/org/labkey/nbri_ehr/query/NBRI_EHRTriggerHelper.java +++ b/nbri_ehr/src/org/labkey/nbri_ehr/query/NBRI_EHRTriggerHelper.java @@ -323,6 +323,103 @@ public boolean deathExists(String id) return false; } + /** + * Derives the denormalized birth/death values on study.demographics from the birth and deaths event records, which + * are authoritative, and returns only the animals whose stored values disagree. The result is intended to be handed + * straight to the shared trigger helper's updateDemographicsRecord(), so that lsid resolution and the demographics + * cache recache stay in the single place that already handles them. + *

+ * Only public (Completed) event records count, so a record still in data entry never overwrites a saved value. + *

+ * calculated_status is deliberately absent from the result. It belongs to the shared status recalc, which owns the + * death/departure/re-arrival precedence. + *

+ * Every lookup is set-based - one query per event dataset for the whole id list, not one per animal - because a + * bulk save can pass hundreds of ids and per-animal SQL in a trigger exhausts the script's wall-clock budget. + * + * @param ids animals touched by the current save + * @return rows ready for updateDemographicsRecord(); empty when nothing has drifted + */ + public List> computeDemographicsSync(List ids) + { + if (ids == null || ids.isEmpty()) + return Collections.emptyList(); + + Set idSet = new HashSet<>(ids); + + Map births = getPublicEventDates("birth", idSet); + Map deaths = getPublicEventDates("deaths", idSet); + + List> updates = new ArrayList<>(); + + TableInfo demographics = getTableInfo("study", "demographics"); + SimpleFilter filter = new SimpleFilter(FieldKey.fromString("Id"), idSet, CompareType.IN); + TableSelector ts = new TableSelector(demographics, PageFlowUtil.set("Id", "birth", "death"), filter, null); + + for (Map current : ts.getMapCollection()) + { + String id = (String)current.get("Id"); + Map update = new CaseInsensitiveHashMap<>(); + + // A public event record always wins - the same rule createDemographicsRecord() already applies to death on + // insert, extended to updates and to birth. The absence of an event record is NOT evidence the stored value + // is wrong: animals loaded by ETL, or acquired before these datasets were in use, legitimately carry a date + // with no event row, so a missing record leaves the value alone. Clearing a value is only ever driven by an + // explicit delete of the event record. + Date birth = births.get(id); + if (birth != null && differsByDay(birth, (Date)current.get("birth"))) + update.put("birth", birth); + + Date death = deaths.get(id); + if (death != null && differsByDay(death, (Date)current.get("death"))) + update.put("death", death); + + if (!update.isEmpty()) + { + update.put("Id", id); + updates.add(update); + } + } + + if (!updates.isEmpty()) + _log.info("Demographics birth/death out of sync with event records for {} animal(s); updating", updates.size()); + + return updates; + } + + /** Most recent public event date per animal for a demographic event dataset, in a single query. */ + private Map getPublicEventDates(String queryName, Set ids) + { + SimpleFilter filter = new SimpleFilter(FieldKey.fromString("Id"), ids, CompareType.IN); + filter.addCondition(FieldKey.fromString("qcstate/publicdata"), true); + + Map ret = new HashMap<>(); + new TableSelector(getTableInfo("study", queryName), PageFlowUtil.set("Id", "date"), filter, null) + .forEachMap(row -> { + String id = (String)row.get("Id"); + Date date = ConvertHelper.convert(row.get("date"), Date.class); + // birth and deaths are demographic datasets (one row per animal), but tolerate duplicates from a + // legacy load by keeping the latest rather than picking arbitrarily. + if (date != null && (ret.get(id) == null || date.after(ret.get(id)))) + ret.put(id, date); + }); + + return ret; + } + + /** + * Compares to day precision. Event dates are entered with the time stripped, but values that arrived by ETL or + * predate that behavior can carry a time component; treating those as drift would rewrite the whole colony on the + * first save. + */ + private boolean differsByDay(Date a, Date b) + { + if (a == null || b == null) + return a != b; + + return !DateUtils.isSameDay(a, b); + } + public boolean upsertWeightRecord(Map row) throws QueryUpdateServiceException, DuplicateKeyException, SQLException, BatchValidationException, InvalidKeyException { return upsertWeightRecord(row, true); From 66014ba7779a5d6f5e0365784951b444bdee8cb6 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Mon, 10 Aug 2026 13:10:53 -0600 Subject: [PATCH 3/3] Cover the demographics birth/death sync and align the sample data Adds a test for deleting a death record, which has to hand the status back to the shared recalc rather than assume the animal is alive, and asserts the birth and death dates reach demographics on the arrival, birth and death forms. The birth and deaths sample data disagreed with demographics on nine rows and now matches, leaving only the animals that legitimately have no birth record. --- .../study/study/datasets/datasetBirth.tsv | 14 +-- .../study/study/datasets/datasetDeaths.tsv | 4 +- .../tests.nbri_ehr/NBRI_EHRTest.java | 103 +++++++++++++++++- 3 files changed, 111 insertions(+), 10 deletions(-) diff --git a/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetBirth.tsv b/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetBirth.tsv index cecdcb7..6ca8a47 100644 --- a/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetBirth.tsv +++ b/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetBirth.tsv @@ -1,9 +1,9 @@ objectid Id date QCStateLabel performedby -1 44444 -2895d Completed 1004 -2 44445 -2854d Completed 1004 -3 44446 -2703d Completed 1004 +1 44444 -1381d Completed 1004 +2 44445 -1414d Completed 1004 +3 44446 -1406d Completed 1004 4 44447 -2600d Completed 1004 -5 TEST6390238 -5601d Completed 1004 -6 TEST5904521 -5602d Completed 1004 -7 TEST3804589 -2603d Completed 1004 -8 TEST2312318 -3000d Completed 1004 +5 TEST6390238 -3923d Completed 1004 +6 TEST5904521 -5431d Completed 1004 +7 TEST3804589 -5806d Completed 1004 +8 TEST2312318 -8069d Completed 1004 diff --git a/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetDeaths.tsv b/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetDeaths.tsv index 0c46745..0a481b8 100644 --- a/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetDeaths.tsv +++ b/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetDeaths.tsv @@ -1,3 +1,3 @@ objectid Id date QCStateLabel cause manner remark performedby -1 TEST1993532 -2212d Completed 1 S sed nibh viverra 1004 -2 44445 -723d Completed 2 X labor omnia vincit 1004 +1 TEST1993532 -2259d Completed 1 S sed nibh viverra 1004 +2 44445 -726d Completed 2 X labor omnia vincit 1004 diff --git a/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java b/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java index b11d6ab..bcd4b33 100644 --- a/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java +++ b/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java @@ -71,6 +71,7 @@ import java.io.File; import java.io.IOException; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; @@ -632,7 +633,7 @@ public void testWeightValidation() } @Test - public void testArrivalForm() + public void testArrivalForm() throws IOException, CommandException { String arrivedAnimal = "30905"; LocalDateTime now = LocalDateTime.now(); @@ -692,6 +693,13 @@ public void testArrivalForm() verifyRowCreated("study", "protocolAssignment", arrivedAnimal, 1); verifyRowCreated("study", "demographics", arrivedAnimal, 1); verifyRowCreated("study", "housing", arrivedAnimal, 1); + + log("Verifying the birth date reached demographics and agrees with the birth record"); + String arrivalBirthDay = now.minusDays(7).format(_dateFormat); + assertEquals("Birth record does not carry the birth date entered on the arrival form", + arrivalBirthDay, getDatasetDay("birth", arrivedAnimal, "date")); + assertEquals("Demographics birth date does not match the birth record", + arrivalBirthDay, getDatasetDay("demographics", arrivedAnimal, "birth")); } @Test @@ -794,6 +802,13 @@ public void testBirthForm() throws Exception verifyRowCreated("study", "housing", bornAnimal, 1); verifyRowCreated("study", "demographics", bornAnimal, 1); + log("Verifying the birth date reached demographics and agrees with the birth record"); + String bornBirthDay = now.minusDays(1).format(_dateFormat); + assertEquals("Birth record does not carry the date entered on the birth form", + bornBirthDay, getDatasetDay("birth", bornAnimal, "date")); + assertEquals("Demographics birth date does not match the birth record", + bornBirthDay, getDatasetDay("demographics", bornAnimal, "birth")); + log("Verifying conception outcome and offspring in ConceptionsByDam"); goToSchemaBrowser(); DataRegionTable report = viewQueryData("nbri_ehr", "ConceptionsByDam"); @@ -1453,6 +1468,14 @@ public void testDeathNecropsyForm() throws IOException, CommandException goToEHRFolder(); verifyRowCreated("study", "weight", aliveAnimalId, 1); + log("Verify the death date reached demographics and agrees with the death record"); + String finalizedDeathDay = getDatasetDay("deaths", aliveAnimalId, "date"); + Assert.assertNotNull("Death record has no date", finalizedDeathDay); + assertEquals("Demographics death date does not match the death record", + finalizedDeathDay, getDatasetDay("demographics", aliveAnimalId, "death")); + // the waitForText below is a weak check: the necropsy diagnosis on this page is also the text "Dead" + assertEquals("Animal should be Dead once the death is finalized", "Dead", getCalculatedStatus(aliveAnimalId)); + log("Verify animal is marked as dead"); AnimalHistoryPage historyPage = AnimalHistoryPage.beginAt(this); historyPage.searchSingleAnimal(aliveAnimalId); @@ -1478,6 +1501,48 @@ public void testDeathNecropsyForm() throws IOException, CommandException } + /** + * Deleting a death record has to hand the status back to the shared recalc rather than assume the animal is alive: + * an animal that also has a departure is Shipped, not Alive. Also covers demographics.death being cleared. + */ + @Test + public void testDeathDeleteRestoresDepartedStatus() throws Exception + { + String animalId = "DD9001"; + LocalDateTime now = LocalDateTime.now(); + + log("Creating an animal via a birth record"); + getApiHelper().doSaveRows(DATA_ADMIN.getEmail(), getApiHelper().prepareInsertCommand("study", "birth", "lsid", + new String[]{"Id", "Date", "gender", "QCStateLabel", "performedby"}, + new Object[][]{{animalId, now.minusDays(30), getMale(), "Completed", 1004}} + ), getExtraContext()); + + // the death has to be recorded before the departure: the deaths trigger rejects an animal that has shipped + log("Recording the death"); + InsertRowsCommand deaths = new InsertRowsCommand("study", "deaths"); + deaths.addRow(Map.of("Id", animalId, "date", now.minusDays(10), "reason", "4", "QCStateLabel", "Completed", "performedby", 1004)); + deaths.execute(getApiHelper().getConnection(), getContainerPath()); + + assertEquals("Demographics death date does not match the death record", + now.minusDays(10).format(_dateFormat), getDatasetDay("demographics", animalId, "death")); + assertEquals("Animal should be Dead while the death record exists", "Dead", getCalculatedStatus(animalId)); + + log("Departing the animal, so the deleted death has a departure to fall back to"); + InsertRowsCommand departure = new InsertRowsCommand("study", "departure"); + departure.addRow(Map.of("Id", animalId, "date", now.minusDays(5), "destination", "ORPRC", "QCStateLabel", "Completed", "performedby", 1004)); + departure.execute(getApiHelper().getConnection(), getContainerPath()); + + assertEquals("A death outranks a departure", "Dead", getCalculatedStatus(animalId)); + + log("Deleting the death record"); + getApiHelper().deleteAllRecords("study", "deaths", new Filter("Id", animalId)); + + Assert.assertNull("Demographics death date should be cleared when the death record is deleted", + getDatasetDay("demographics", animalId, "death")); + assertEquals("Deleting the death should fall back to the departure, not to Alive", + "Shipped", getCalculatedStatus(animalId)); + } + @Test public void testClinicalCasesWorkflow() { @@ -1980,6 +2045,42 @@ private void waitForFormError(String message) waitFor(() -> isTextPresent(message), "Form did not report: " + message, WAIT_FOR_JAVASCRIPT); } + /** + * Reads a date field for one animal through the API rather than off a grid, so assertions compare stored values + * instead of formatted display text, and normalizes to the day: event dates are entered with the time stripped, + * but values reaching demographics by other paths can carry a time component. + * + * @return the date as yyyy-MM-dd, or null when the field is empty + */ + private String getDatasetDay(String queryName, String animalId, String column) throws IOException, CommandException + { + Object value = getSingleRowForAnimal(queryName, animalId, List.of("Id", column)).get(column); + if (value == null) + return null; + + if (value instanceof Date) + return _dateFormat.format(((Date)value).toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); + + String text = String.valueOf(value); + return text.length() >= 10 ? text.substring(0, 10) : text; + } + + private String getCalculatedStatus(String animalId) throws IOException, CommandException + { + return (String)getSingleRowForAnimal("demographics", animalId, List.of("Id", "calculated_status")).get("calculated_status"); + } + + private Map getSingleRowForAnimal(String queryName, String animalId, List columns) throws IOException, CommandException + { + SelectRowsCommand select = new SelectRowsCommand("study", queryName); + select.setColumns(columns); + select.addFilter(new Filter("Id", animalId)); + SelectRowsResponse response = select.execute(getApiHelper().getConnection(), getContainerPath()); + + Assert.assertEquals("Expected exactly one study." + queryName + " row for " + animalId, 1, response.getRows().size()); + return response.getRows().get(0); + } + private void verifyRowCreated(String schema, String query, String animalId, int rowCount) { goToSchemaBrowser();