From e80f26f028c8d9f3f46dc5ef6222dc7f3e44c6d5 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 09:40:03 +0100 Subject: [PATCH 01/22] use smoking_status constant Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index d2c37c08..9c0d9c1b 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -1868,7 +1868,7 @@ def calculate_kpi_35_smoking_status_screened( valid_smoking_visits = Visit.objects.filter( patient=OuterRef("pk"), visit_date__range=self.AUDIT_DATE_RANGE, - smoking_status__in=[1, 2], + smoking_status__in=[SMOKING_STATUS[0][0],SMOKING_STATUS[1][0]], ) eligible_pts_annotated_smoke_screen_visits = eligible_patients.annotate( smoke_valid_visits=Exists( From 85114981eb68a2d25463d0a832a7d98e19b3a8a7 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 09:49:28 +0100 Subject: [PATCH 02/22] adds data type for calc return obj Signed-off-by: anchit-chandran --- project/constants/types/kpi_types.py | 17 ++++++++++++++++- project/npda/general_functions/kpis.py | 4 ++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/project/constants/types/kpi_types.py b/project/constants/types/kpi_types.py index 2b14203d..40b586bc 100644 --- a/project/constants/types/kpi_types.py +++ b/project/constants/types/kpi_types.py @@ -1,5 +1,7 @@ # Object types from dataclasses import dataclass +from datetime import date, datetime +from typing import Dict, Union @dataclass @@ -7,4 +9,17 @@ class KPIResult: total_eligible: int total_ineligible: int total_passed: int - total_failed: int \ No newline at end of file + total_failed: int + + +@dataclass +class KPICalculationsObject: + pz_code: str + calculation_datetime: datetime + audit_start_date: date + audit_end_date: date + total_patients_count: int + calculated_kpi_values: Dict[ + str, + Union[KPIResult, str], + ] # looks like { 'kpi_name' : KPIResult OR "Not implemented"} diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 9c0d9c1b..9623c45a 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -28,7 +28,7 @@ from project.constants.retinal_screening_results import \ RETINAL_SCREENING_RESULTS from project.constants.smoking_status import SMOKING_STATUS -from project.constants.types.kpi_types import KPIResult +from project.constants.types.kpi_types import KPICalculationsObject, KPIResult from project.constants.yes_no_unknown import YES_NO_UNKNOWN from project.npda.general_functions import get_audit_period_for_date from project.npda.models import Patient @@ -185,7 +185,7 @@ def _run_kpi_calculation_method( return kpi_result - def calculate_kpis_for_patients(self) -> dict: + def calculate_kpis_for_patients(self) -> KPICalculationsObject: """Calculate KPIs 1 - 49 for given self.pz_code and cohort range (self.audit_start_date and self.audit_end_date). From 4e22df3d64632d6155814759028a5464e1ae130c Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 09:57:57 +0100 Subject: [PATCH 03/22] refactor kpis 1,2,6 to use reused attrs Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 43 +++++++++++++------ .../tests/kpi_calculations/test_kpis_1_12.py | 3 -- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 9623c45a..76d031a0 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -257,13 +257,9 @@ def calculate_kpi_1_total_eligible(self) -> KPIResult: ) ) - eligible_patients = ( - self.total_kpi_1_eligible_pts_base_query_set.distinct() - ) - self.kpi_1_total_eligible = eligible_patients.count() - # Count eligible patients and set as attribute # to be used in subsequent KPI calculations + self.kpi_1_total_eligible = self.total_kpi_1_eligible_pts_base_query_set.count() total_eligible = self.kpi_1_total_eligible # Calculate ineligible patients @@ -293,14 +289,11 @@ def calculate_kpi_2_total_new_diagnoses(self) -> KPIResult: * Date of diagnosis within the audit period" """ - # If we have not already calculated KPI 1, do so now to set - # self.total_kpi_1_eligible_pts_base_query_set - if not hasattr(self, "total_kpi_1_eligible_pts_base_query_set"): - self.calculate_kpi_1_total_eligible() + base_eligible_patients, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() # This is same as KPI1 but with an additional filter for diagnosis date self.total_kpi_2_eligible_pts_base_query_set = ( - self.total_kpi_1_eligible_pts_base_query_set.filter( + base_eligible_patients.filter( Q(diagnosis_date__range=(self.AUDIT_DATE_RANGE)) ).distinct() ) @@ -712,14 +705,16 @@ def calculate_kpi_9_total_service_transitions(self) -> dict: Number of eligible patients (measure 1) with * a leaving date in the audit period """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set.filter( + base_eligible_patients, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + + eligible_patients = base_eligible_patients.filter( # a leaving date in the audit period Q( paediatric_diabetes_units__date_leaving_service__range=( self.AUDIT_DATE_RANGE ) ) - ).distinct() + ) # Count eligible patients total_eligible = eligible_patients.count() @@ -2566,7 +2561,7 @@ def _debug_helper_print_postcode_and_attrs(self, queryset, *attrs): def _get_total_kpi_1_eligible_pts_base_query_set_and_total_count( self, - ) -> Tuple[QuerySet, int]: + ) -> Tuple[QuerySet[Patient], int]: """Enables reuse of the base query set for KPI 1 If running calculation methods in order, this attribute will be set in calculate_kpi_1_total_eligible(). @@ -2586,6 +2581,28 @@ def _get_total_kpi_1_eligible_pts_base_query_set_and_total_count( self.kpi_1_total_eligible, ) + def _get_total_kpi_2_eligible_pts_base_query_set_and_total_count( + self, + ) -> Tuple[QuerySet[Patient], int]: + """Enables reuse of the base query set for KPI 2 + + If running calculation methods in order, this attribute will be set in calculate_kpi_2_total_new_diagnoses(). + + If running another kpi calculation standalone, need to run that method first to have the attribute set. + + Returns: + QuerySet: Base query set of eligible patients for KPI 2 + int: base query set count of total eligible patients for KPI 2 + """ + + if not hasattr(self, "total_kpi_2_eligible_pts_base_query_set"): + self.calculate_kpi_2_total_new_diagnoses() + + return ( + self.total_kpi_2_eligible_pts_base_query_set, + self.kpi_2_total_eligible, + ) + def _get_total_kpi_5_eligible_pts_base_query_set_and_total_count( self, ) -> Tuple[QuerySet, int]: diff --git a/project/npda/tests/kpi_calculations/test_kpis_1_12.py b/project/npda/tests/kpi_calculations/test_kpis_1_12.py index 64c43aa3..c5c12917 100644 --- a/project/npda/tests/kpi_calculations/test_kpis_1_12.py +++ b/project/npda/tests/kpi_calculations/test_kpis_1_12.py @@ -118,9 +118,6 @@ def test_kpi_calculation_2(AUDIT_START_DATE): total_failed=N_PATIENTS_FAIL * 3, ) - # First set kpi1 result of total eligible - calc_kpis.calculate_kpi_1_total_eligible() - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_2_total_new_diagnoses(), From b1ee67a2f9302d2d424d319ad2914b4b28b67601 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 09:58:48 +0100 Subject: [PATCH 04/22] refactor kpi10 to reuse kpi1 Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 76d031a0..dcf95aef 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -750,8 +750,9 @@ def calculate_kpi_10_total_coeliacs(self) -> dict: ) # Filter the Patient queryset based on the subquery + base_query_set, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() eligible_patients = ( - self.total_kpi_1_eligible_pts_base_query_set.filter( + base_query_set.filter( Q( id__in=Subquery( Patient.objects.filter( From 1a51635c180165d76e16b90aa49800c4a91166ee Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 09:59:19 +0100 Subject: [PATCH 05/22] refactor kpi11 to use getter kpi1 Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index dcf95aef..2a13f548 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -799,8 +799,9 @@ def calculate_kpi_11_total_thyroids(self) -> dict: ) # Filter the Patient queryset based on the subquery + base_query_set, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() eligible_patients = ( - self.total_kpi_1_eligible_pts_base_query_set.filter( + base_query_set.filter( Q( id__in=Subquery( Patient.objects.filter( From 99c9b435cabd0e79c827a7ff3a8c8634aa47bf8a Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 09:59:39 +0100 Subject: [PATCH 06/22] refactor kpi 12 to use kpi1 getter Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 2a13f548..42934838 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -848,8 +848,9 @@ def calculate_kpi_12_total_ketone_test_equipment(self) -> dict: ) # Filter the Patient queryset based on the subquery + base_query_set, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() eligible_patients = ( - self.total_kpi_1_eligible_pts_base_query_set.filter( + base_query_set.filter( Q( id__in=Subquery( Patient.objects.filter( From 969239b5580010235e4599702b30a23f4d64f894 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 10:00:07 +0100 Subject: [PATCH 07/22] refactor kpi 13 to use kpi1 getter Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 42934838..29ef908a 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -888,8 +888,8 @@ def calculate_kpi_13_one_to_three_injections_per_day(self) -> dict: Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 1 From 83f23028b18032f373c14df672170563d79cec3c Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 10:00:31 +0100 Subject: [PATCH 08/22] refactor kpi14 to use kpi1 getter Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 29ef908a..8a99b317 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -926,8 +926,8 @@ def calculate_kpi_14_four_or_more_injections_per_day(self) -> dict: Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 2 From ddc1694ef29c27a3a890d0bccbc024731a6c4e96 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 10:01:20 +0100 Subject: [PATCH 09/22] refactor kpi 15 to use kpi1 getter Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 8a99b317..d193a1ef 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -964,8 +964,8 @@ def calculate_kpi_15_insulin_pump(self) -> dict: Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 3 From c5c5a9899d62b3945193b38c5317003577f8868a Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 10:01:54 +0100 Subject: [PATCH 10/22] refactor kpi16 to reuse kpi 1 getter Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index d193a1ef..43c15292 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -1003,8 +1003,8 @@ def calculate_kpi_16_one_to_three_injections_plus_other_medication( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 4 From 4f9204abae970bcd358471108e719b199369b77e Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 10:02:50 +0100 Subject: [PATCH 11/22] refactor kpis17 - 22 to use kpi1 getter Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 43c15292..c22a5a15 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -1042,8 +1042,7 @@ def calculate_kpi_17_four_or_more_injections_plus_other_medication( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 5 @@ -1081,8 +1080,7 @@ def calculate_kpi_18_insulin_pump_plus_other_medication( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 6 @@ -1120,8 +1118,7 @@ def calculate_kpi_19_dietary_management_alone( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 7 @@ -1159,8 +1156,7 @@ def calculate_kpi_20_dietary_management_plus_other_medication( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 8 @@ -1198,8 +1194,7 @@ def calculate_kpi_21_flash_glucose_monitor( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where blood glucose monitoring (item 22) is either 2 = Flash glucose monitor or 3 = Modified flash glucose monitor (e.g. with MiaoMiao, Blucon etc.) @@ -1239,8 +1234,7 @@ def calculate_kpi_22_real_time_cgm_with_alarms( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set - total_eligible = self.kpi_1_total_eligible + eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where blood glucose monitoring (item 22) is 4 = Real time continuous glucose monitor with alarms From 0d31072c2db05e0b0298fe363999f834e038cac6 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 10:03:44 +0100 Subject: [PATCH 12/22] refactor kpi23 to use kpi2 getter Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index c22a5a15..ef53c176 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -1272,13 +1272,8 @@ def calculate_kpi_23_type1_real_time_cgm_with_alarms( Denominator: Number of eligible patients whose most recent entry (based on visit date) for blood glucose monitoring (item 22) is 4 = Real time continuous glucose monitor with alarms """ - # If running this method standalone, need to set self.kpi_2_total_eligible first - # by running its calculation method - if not hasattr(self, "kpi_2_total_eligible"): - self.calculate_kpi_2_total_new_diagnoses() + eligible_patients, total_eligible = self._get_total_kpi_2_eligible_pts_base_query_set_and_total_count() - eligible_patients = self.total_kpi_2_eligible_pts_base_query_set - total_eligible = self.kpi_2_total_eligible total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where blood glucose monitoring (item 22) is 4 = Real time continuous glucose monitor with alarms From fe48b3b3d955546a0b70796297b0fe6ae9d9d0a3 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 10:06:14 +0100 Subject: [PATCH 13/22] rm TODO comment as done Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index ef53c176..a26f06ac 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -1,9 +1,4 @@ -"""Views for KPIs - -TODO: - - refactor all calculate_kpi methods which require kpi_1 base query set to use - _get_total_kpi_1_eligible_pts_base_query_set_and_total_count - - additionally, do same for any other reused attrs +"""Views for KPIs calculations """ # Python imports From 6d5341758260ecd12dd5dd9d2e582c180f6e4291 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Mon, 30 Sep 2024 10:09:23 +0100 Subject: [PATCH 14/22] updates test to remove setting a denominator kpi calc as no longer needed Signed-off-by: anchit-chandran --- .../tests/kpi_calculations/test_kpis_13_20.py | 4 --- .../tests/kpi_calculations/test_kpis_1_12.py | 33 ++----------------- .../tests/kpi_calculations/test_kpis_21_23.py | 8 ----- 3 files changed, 3 insertions(+), 42 deletions(-) diff --git a/project/npda/tests/kpi_calculations/test_kpis_13_20.py b/project/npda/tests/kpi_calculations/test_kpis_13_20.py index 4886096b..423592ae 100644 --- a/project/npda/tests/kpi_calculations/test_kpis_13_20.py +++ b/project/npda/tests/kpi_calculations/test_kpis_13_20.py @@ -81,10 +81,6 @@ def test_kpi_calculations_13_to_20( pz_code="PZ130", calculation_date=AUDIT_START_DATE ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() - # Dynamically get the kpi calc method based on treatment type # `treatment` is an int between 1-8 # these kpi calulations start at 13 diff --git a/project/npda/tests/kpi_calculations/test_kpis_1_12.py b/project/npda/tests/kpi_calculations/test_kpis_1_12.py index c5c12917..d357eaa9 100644 --- a/project/npda/tests/kpi_calculations/test_kpis_1_12.py +++ b/project/npda/tests/kpi_calculations/test_kpis_1_12.py @@ -178,10 +178,6 @@ def test_kpi_calculation_3(AUDIT_START_DATE): total_failed=N_PATIENTS_FAIL * 3, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_3_total_t1dm(), @@ -250,10 +246,6 @@ def test_kpi_calculation_4(AUDIT_START_DATE): total_failed=N_PATIENTS_FAIL * 4, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_4_total_t1dm_gte_12yo(), @@ -358,9 +350,6 @@ def test_kpi_calculation_5(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, @@ -533,9 +522,7 @@ def test_kpi_calculation_7(AUDIT_START_DATE): calc_kpis = CalculateKPIS( pz_code="PZ130", calculation_date=AUDIT_START_DATE ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() + EXPECTED_TOTAL_ELIGIBLE = len(observation_field_names) EXPECTED_TOTAL_INELIGIBLE = 2 @@ -611,9 +598,6 @@ def test_kpi_calculation_8(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, @@ -689,9 +673,6 @@ def test_kpi_calculation_9(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, @@ -758,9 +739,7 @@ def test_kpi_calculation_10(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() + assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, @@ -836,9 +815,7 @@ def test_kpi_calculation_11(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() + assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, @@ -905,10 +882,6 @@ def test_kpi_calculation_12(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_12_total_ketone_test_equipment(), diff --git a/project/npda/tests/kpi_calculations/test_kpis_21_23.py b/project/npda/tests/kpi_calculations/test_kpis_21_23.py index d0841501..555b6138 100644 --- a/project/npda/tests/kpi_calculations/test_kpis_21_23.py +++ b/project/npda/tests/kpi_calculations/test_kpis_21_23.py @@ -78,10 +78,6 @@ def test_kpi_calculation_21(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_FAILED, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_21_flash_glucose_monitor(), @@ -151,10 +147,6 @@ def test_kpi_calculation_22(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_FAILED, ) - # First set self.total_kpi_1_eligible_pts_base_query_set result - # of total eligible - calc_kpis.calculate_kpi_1_total_eligible() - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_22_real_time_cgm_with_alarms(), From da546d86e5713997cb9b15d4f156034fdb648f64 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Tue, 1 Oct 2024 09:27:45 +0100 Subject: [PATCH 15/22] rm distinct as kpi1 base query already handles Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index a26f06ac..aff4f56a 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -290,7 +290,7 @@ def calculate_kpi_2_total_new_diagnoses(self) -> KPIResult: self.total_kpi_2_eligible_pts_base_query_set = ( base_eligible_patients.filter( Q(diagnosis_date__range=(self.AUDIT_DATE_RANGE)) - ).distinct() + ) ) # Count eligible patients @@ -330,7 +330,7 @@ def calculate_kpi_3_total_t1dm(self) -> KPIResult: eligible_patients = self.total_kpi_1_eligible_pts_base_query_set.filter( # is type 1 diabetes Q(diabetes_type=DIABETES_TYPES[0][0]) - ).distinct() + ) # Count eligible patients total_eligible = eligible_patients.count() From 5cb539b869b3941eb82c18fe4cefaa23459c4dc0 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Tue, 1 Oct 2024 09:28:44 +0100 Subject: [PATCH 16/22] refactor kpi3 to reuse base kpi1 queryset Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 119 ++++++++++++++++--------- 1 file changed, 76 insertions(+), 43 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index aff4f56a..7d6633cc 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -32,6 +32,7 @@ # Logging logger = logging.getLogger(__name__) + class CalculateKPIS: def __init__(self, pz_code: str, calculation_date: date = None): @@ -254,7 +255,9 @@ def calculate_kpi_1_total_eligible(self) -> KPIResult: # Count eligible patients and set as attribute # to be used in subsequent KPI calculations - self.kpi_1_total_eligible = self.total_kpi_1_eligible_pts_base_query_set.count() + self.kpi_1_total_eligible = ( + self.total_kpi_1_eligible_pts_base_query_set.count() + ) total_eligible = self.kpi_1_total_eligible # Calculate ineligible patients @@ -284,7 +287,9 @@ def calculate_kpi_2_total_new_diagnoses(self) -> KPIResult: * Date of diagnosis within the audit period" """ - base_eligible_patients, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + base_eligible_patients, _ = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) # This is same as KPI1 but with an additional filter for diagnosis date self.total_kpi_2_eligible_pts_base_query_set = ( @@ -327,7 +332,11 @@ def calculate_kpi_3_total_t1dm(self) -> KPIResult: (1, Type 1 Insulin-Dependent Diabetes Mellitus) """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set.filter( + base_eligible_patients, _ = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) + + eligible_patients = base_eligible_patients.filter( # is type 1 diabetes Q(diabetes_type=DIABETES_TYPES[0][0]) ) @@ -700,7 +709,9 @@ def calculate_kpi_9_total_service_transitions(self) -> dict: Number of eligible patients (measure 1) with * a leaving date in the audit period """ - base_eligible_patients, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + base_eligible_patients, _ = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) eligible_patients = base_eligible_patients.filter( # a leaving date in the audit period @@ -745,15 +756,15 @@ def calculate_kpi_10_total_coeliacs(self) -> dict: ) # Filter the Patient queryset based on the subquery - base_query_set, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() - eligible_patients = ( - base_query_set.filter( - Q( - id__in=Subquery( - Patient.objects.filter( - visit__in=latest_visit_subquery - ).values("id") - ) + base_query_set, _ = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) + eligible_patients = base_query_set.filter( + Q( + id__in=Subquery( + Patient.objects.filter( + visit__in=latest_visit_subquery + ).values("id") ) ) ) @@ -794,15 +805,15 @@ def calculate_kpi_11_total_thyroids(self) -> dict: ) # Filter the Patient queryset based on the subquery - base_query_set, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() - eligible_patients = ( - base_query_set.filter( - Q( - id__in=Subquery( - Patient.objects.filter( - visit__in=latest_visit_subquery - ).values("id") - ) + base_query_set, _ = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) + eligible_patients = base_query_set.filter( + Q( + id__in=Subquery( + Patient.objects.filter( + visit__in=latest_visit_subquery + ).values("id") ) ) ) @@ -843,15 +854,15 @@ def calculate_kpi_12_total_ketone_test_equipment(self) -> dict: ) # Filter the Patient queryset based on the subquery - base_query_set, _ = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() - eligible_patients = ( - base_query_set.filter( - Q( - id__in=Subquery( - Patient.objects.filter( - visit__in=latest_visit_subquery - ).values("id") - ) + base_query_set, _ = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) + eligible_patients = base_query_set.filter( + Q( + id__in=Subquery( + Patient.objects.filter( + visit__in=latest_visit_subquery + ).values("id") ) ) ) @@ -883,7 +894,9 @@ def calculate_kpi_13_one_to_three_injections_per_day(self) -> dict: Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible @@ -921,7 +934,9 @@ def calculate_kpi_14_four_or_more_injections_per_day(self) -> dict: Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible @@ -959,7 +974,9 @@ def calculate_kpi_15_insulin_pump(self) -> dict: Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible @@ -998,7 +1015,9 @@ def calculate_kpi_16_one_to_three_injections_plus_other_medication( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible @@ -1037,7 +1056,9 @@ def calculate_kpi_17_four_or_more_injections_plus_other_medication( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 5 @@ -1075,7 +1096,9 @@ def calculate_kpi_18_insulin_pump_plus_other_medication( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 6 @@ -1113,7 +1136,9 @@ def calculate_kpi_19_dietary_management_alone( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 7 @@ -1151,7 +1176,9 @@ def calculate_kpi_20_dietary_management_plus_other_medication( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where treatment_regimen = 8 @@ -1189,7 +1216,9 @@ def calculate_kpi_21_flash_glucose_monitor( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where blood glucose monitoring (item 22) is either 2 = Flash glucose monitor or 3 = Modified flash glucose monitor (e.g. with MiaoMiao, Blucon etc.) @@ -1229,7 +1258,9 @@ def calculate_kpi_22_real_time_cgm_with_alarms( Denominator: Total number of eligible patients (measure 1) """ - eligible_patients, total_eligible = self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible # Define the subquery to find the latest visit where blood glucose monitoring (item 22) is 4 = Real time continuous glucose monitor with alarms @@ -1267,7 +1298,9 @@ def calculate_kpi_23_type1_real_time_cgm_with_alarms( Denominator: Number of eligible patients whose most recent entry (based on visit date) for blood glucose monitoring (item 22) is 4 = Real time continuous glucose monitor with alarms """ - eligible_patients, total_eligible = self._get_total_kpi_2_eligible_pts_base_query_set_and_total_count() + eligible_patients, total_eligible = ( + self._get_total_kpi_2_eligible_pts_base_query_set_and_total_count() + ) total_ineligible = self.total_patients_count - total_eligible @@ -1850,7 +1883,7 @@ def calculate_kpi_35_smoking_status_screened( valid_smoking_visits = Visit.objects.filter( patient=OuterRef("pk"), visit_date__range=self.AUDIT_DATE_RANGE, - smoking_status__in=[SMOKING_STATUS[0][0],SMOKING_STATUS[1][0]], + smoking_status__in=[SMOKING_STATUS[0][0], SMOKING_STATUS[1][0]], ) eligible_pts_annotated_smoke_screen_visits = eligible_patients.annotate( smoke_valid_visits=Exists( From 81cea0161aede67a15c4ea718b5a10eec7362c33 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Tue, 1 Oct 2024 09:30:06 +0100 Subject: [PATCH 17/22] refactor kpi4 to reuse base kpi1 queryset Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 7d6633cc..93d32913 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -372,7 +372,11 @@ def calculate_kpi_4_total_t1dm_gte_12yo(self) -> KPIResult: * Diagnosis of Type 1 diabetes" """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set.filter( + base_eligible_patients, _ = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) + + eligible_patients = base_eligible_patients.filter( # Diagnosis of Type 1 diabetes Q(diabetes_type=DIABETES_TYPES[0][0]) # Age 12 and above years at the start of the audit period @@ -380,7 +384,7 @@ def calculate_kpi_4_total_t1dm_gte_12yo(self) -> KPIResult: date_of_birth__lte=self.audit_start_date - relativedelta(years=12) ) - ).distinct() + ) # Count eligible patients total_eligible = eligible_patients.count() From 9589890a6e6ed91ab78528b169e2932e489c160c Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Tue, 1 Oct 2024 09:32:55 +0100 Subject: [PATCH 18/22] rm more unneeded .distincts Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 93d32913..1751b5d0 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -438,7 +438,7 @@ def calculate_kpi_5_total_t1dm_complete_year(self) -> KPIResult: ) # EXCLUDE Date of death within the audit period" | Q(death_date__range=(self.AUDIT_DATE_RANGE)) - ).distinct() + ) # Count eligible patients total_eligible = eligible_patients.count() @@ -558,7 +558,7 @@ def calculate_kpi_6_total_t1dm_complete_year_gte_12yo(self) -> dict: ) ) ) - ).distinct() + ) # Count eligible patients total_eligible = eligible_patients.count() @@ -593,8 +593,9 @@ def calculate_kpi_7_total_new_diagnoses_t1dm(self) -> dict: * Date of diagnosis within the audit period """ - # total_kpi_1_eligible_pts_base_query_set is slightly different (additionally specifies - # visit date). So we need to make a new query set + # total_kpi_1_eligible_pts_base_query_set is slightly different + # (additionally specifies visit date). So we need to make a new + # query set eligible_patients = self.patients.filter( # Valid attributes Q(nhs_number__isnull=False) @@ -653,7 +654,7 @@ def calculate_kpi_7_total_new_diagnoses_t1dm(self) -> dict: ) ) ) - ).distinct() + ) # Count eligible patients total_eligible = eligible_patients.count() @@ -683,10 +684,14 @@ def calculate_kpi_8_total_deaths(self) -> dict: Number of eligible patients (measure 1) with: * a death date in the audit period """ - eligible_patients = self.total_kpi_1_eligible_pts_base_query_set.filter( + base_eligible_patients, _ = ( + self._get_total_kpi_1_eligible_pts_base_query_set_and_total_count() + ) + + eligible_patients = base_eligible_patients.filter( # Date of death within the audit period" Q(death_date__range=(self.AUDIT_DATE_RANGE)) - ).distinct() + ) # Count eligible patients total_eligible = eligible_patients.count() From 424475f4305453dd347cda6bf27e407e8664dcec Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Tue, 1 Oct 2024 10:03:25 +0100 Subject: [PATCH 19/22] handles kpi6 calc for when only subsequent Visits have valid values Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 63 ++++++++++--------- .../tests/kpi_calculations/test_kpis_1_12.py | 46 ++++++++++---- 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index 1751b5d0..a2fc4b25 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -501,7 +501,7 @@ def calculate_kpi_6_total_t1dm_complete_year_gte_12yo(self) -> dict: | Q(death_date__range=(self.AUDIT_DATE_RANGE)) ) - eligible_patients = eligible_patients_exclusions.filter( + base_eligible_patients = eligible_patients_exclusions.filter( # Valid attributes Q(nhs_number__isnull=False) & Q(date_of_birth__isnull=False) @@ -512,58 +512,66 @@ def calculate_kpi_6_total_t1dm_complete_year_gte_12yo(self) -> dict: ) # Diagnosis of Type 1 diabetes & Q(diabetes_type=DIABETES_TYPES[0][0]) - & ( - # an observation within the audit period - # this requires checking for a date in any of the Visit model's - # observation fields (found simply by searching for date fields - # with the word 'observation' in the field verbose_name) + ) + + # Find patients with at least one observation within the audit period + # this requires checking for a date in any of the Visits for a given + # patient + valid_visit_subquery = Visit.objects.filter( + Q( Q( - visit__height_weight_observation_date__range=( - self.AUDIT_DATE_RANGE - ) - ) - | Q(visit__hba1c_date__range=(self.AUDIT_DATE_RANGE)) - | Q( - visit__blood_pressure_observation_date__range=( + height_weight_observation_date__range=( self.AUDIT_DATE_RANGE ) ) + | Q(hba1c_date__range=(self.AUDIT_DATE_RANGE)) | Q( - visit__foot_examination_observation_date__range=( + blood_pressure_observation_date__range=( self.AUDIT_DATE_RANGE ) ) | Q( - visit__retinal_screening_observation_date__range=( + foot_examination_observation_date__range=( self.AUDIT_DATE_RANGE ) ) | Q( - visit__albumin_creatinine_ratio_date__range=( + retinal_screening_observation_date__range=( self.AUDIT_DATE_RANGE ) ) | Q( - visit__total_cholesterol_date__range=( + albumin_creatinine_ratio_date__range=( self.AUDIT_DATE_RANGE ) ) + | Q(total_cholesterol_date__range=(self.AUDIT_DATE_RANGE)) + | Q(thyroid_function_date__range=(self.AUDIT_DATE_RANGE)) + | Q(coeliac_screen_date__range=(self.AUDIT_DATE_RANGE)) | Q( - visit__thyroid_function_date__range=(self.AUDIT_DATE_RANGE) - ) - | Q(visit__coeliac_screen_date__range=(self.AUDIT_DATE_RANGE)) - | Q( - visit__psychological_screening_assessment_date__range=( + psychological_screening_assessment_date__range=( self.AUDIT_DATE_RANGE ) ) - ) + ), + patient=OuterRef("pk"), + visit_date__range=self.AUDIT_DATE_RANGE, + ) + + # Check any observation across all visits + eligible_pts_annotated_kpi_6_visits = base_eligible_patients.annotate( + valid_kpi_6_visits=Exists(valid_visit_subquery) + ) + + eligible_patients = eligible_pts_annotated_kpi_6_visits.filter( + valid_kpi_6_visits__gte=1 ) # Count eligible patients total_eligible = eligible_patients.count() - # In case we need to use this as a base query set for subsequent KPIs + # We reuse this as a base query set for subsequent KPIs so set + # as an attribute self.total_kpi_6_eligible_pts_base_query_set = eligible_patients self.kpi_6_total_eligible = total_eligible @@ -2573,15 +2581,14 @@ def calculate_kpi_49_albuminuria_present( total_failed=total_failed, ) - def _debug_helper_print_postcode_and_attrs(self, queryset, *attrs): + def _debug_helper_print_postcode_and_attrs(self, patient_queryset, *attrs): """Helper function to be used with tests which prints out the postcode - (`can add name to postcode as non-validated string field`) and specified attributes for each patient in the queryset """ - logger.debug(f"===QuerySet:{str(queryset)}===") + logger.debug(f"===QuerySet:{str(patient_queryset)}===") logger.debug(f"==={self.AUDIT_DATE_RANGE=}===\n") - for item in queryset.values("postcode", *attrs): + for item in patient_queryset.values("postcode", *attrs): logger.debug(f'Patient Name: {item["postcode"]}') del item["postcode"] logger.debug(pformat(item) + "\n") diff --git a/project/npda/tests/kpi_calculations/test_kpis_1_12.py b/project/npda/tests/kpi_calculations/test_kpis_1_12.py index d357eaa9..a9b0f233 100644 --- a/project/npda/tests/kpi_calculations/test_kpis_1_12.py +++ b/project/npda/tests/kpi_calculations/test_kpis_1_12.py @@ -9,6 +9,7 @@ from project.npda.general_functions.kpis import CalculateKPIS, KPIResult from project.npda.models import Patient from project.npda.tests.factories.patient_factory import PatientFactory +from project.npda.tests.factories.visit_factory import VisitFactory from project.npda.tests.kpi_calculations.test_kpi_calculations import \ assert_kpi_result_equal @@ -350,7 +351,6 @@ def test_kpi_calculation_5(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_5_total_t1dm_complete_year(), @@ -392,7 +392,7 @@ def test_kpi_calculation_6(AUDIT_START_DATE): for field_name in observation_field_names: eligible_patient_pt_obs = PatientFactory( # string field without validation, just using for debugging - # postcode=f"eligible_patient_{field_name}", + postcode=f"eligible_patient_{field_name}", # KPI1 eligible # Age 12 and above at the start of the audit period date_of_birth=AUDIT_START_DATE - relativedelta(years=12), @@ -401,10 +401,41 @@ def test_kpi_calculation_6(AUDIT_START_DATE): # an observation within the audit period **{ f"visit__{field_name}": AUDIT_START_DATE - + relativedelta(days=2) + + relativedelta(days=2), + f"visit__visit_date": AUDIT_START_DATE + relativedelta(days=2), }, ) + # Additionally create a patient where first visit observations are None + # but the second visit has an observation + eligible_patient_second_visit_observation = PatientFactory( + postcode="eligible_patient_second_visit_observation", + # KPI1 eligible + # Age 12 and above at the start of the audit period + date_of_birth=AUDIT_START_DATE - relativedelta(years=12), + # Diagnosis of Type 1 diabetes + diabetes_type=DIABETES_TYPES[0][0], + # observations all None + visit__visit_date=AUDIT_START_DATE + relativedelta(days=2), + visit__height_weight_observation_date=None, + visit__hba1c_date=None, + visit__blood_pressure_observation_date=None, + visit__albumin_creatinine_ratio_date=None, + visit__total_cholesterol_date=None, + visit__thyroid_function_date=None, + visit__coeliac_screen_date=None, + visit__psychological_screening_assessment_date=None, + ) + # 2nd visit has observations + VisitFactory( + patient=eligible_patient_second_visit_observation, + visit_date=AUDIT_START_DATE + relativedelta(months=2), + height_weight_observation_date=AUDIT_START_DATE + + relativedelta(months=2), + psychological_screening_assessment_date=AUDIT_START_DATE + + relativedelta(months=2), + ) + # Create Patients and Visits that should FAIL KPI3 ineligible_patient_diag_within_audit_period = PatientFactory( postcode="ineligible_patient_diag_within_audit_period", @@ -437,7 +468,7 @@ def test_kpi_calculation_6(AUDIT_START_DATE): pz_code="PZ130", calculation_date=AUDIT_START_DATE ) - EXPECTED_TOTAL_ELIGIBLE = len(observation_field_names) + EXPECTED_TOTAL_ELIGIBLE = len(observation_field_names) + 1 EXPECTED_TOTAL_INELIGIBLE = 3 EXPECTED_KPIRESULT = KPIResult( @@ -523,7 +554,6 @@ def test_kpi_calculation_7(AUDIT_START_DATE): pz_code="PZ130", calculation_date=AUDIT_START_DATE ) - EXPECTED_TOTAL_ELIGIBLE = len(observation_field_names) EXPECTED_TOTAL_INELIGIBLE = 2 @@ -598,7 +628,6 @@ def test_kpi_calculation_8(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_8_total_deaths(), @@ -673,7 +702,6 @@ def test_kpi_calculation_9(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_9_total_service_transitions(), @@ -739,8 +767,6 @@ def test_kpi_calculation_10(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_10_total_coeliacs(), @@ -815,8 +841,6 @@ def test_kpi_calculation_11(AUDIT_START_DATE): total_failed=EXPECTED_TOTAL_INELIGIBLE, ) - - assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_11_total_thyroids(), From 6b0306193bc1535999c7e4b477364f977e785898 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Tue, 1 Oct 2024 10:06:48 +0100 Subject: [PATCH 20/22] fix broken tests Signed-off-by: anchit-chandran --- .../npda/tests/kpi_calculations/test_kpis_25_32.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/project/npda/tests/kpi_calculations/test_kpis_25_32.py b/project/npda/tests/kpi_calculations/test_kpis_25_32.py index 285d55f4..179bd2cf 100644 --- a/project/npda/tests/kpi_calculations/test_kpis_25_32.py +++ b/project/npda/tests/kpi_calculations/test_kpis_25_32.py @@ -17,9 +17,12 @@ def test_kpi_calculation_25(AUDIT_START_DATE): """Tests that KPI25 is calculated correctly. - Numerator: Number of eligible patients with at least one valid entry for HbA1c value (item 17) with an observation date (item 19) within the audit period + Numerator: Number of eligible patients with at least one valid entry for + HbA1c value (item 17) with an observation date (item 19) within the audit + period - Denominator: Number of patients with Type 1 diabetes with a complete year of care in the audit period (measure 5) + Denominator: Number of patients with Type 1 diabetes with a complete year + of care in the audit period (measure 5) """ # Ensure starting with clean pts in test db @@ -415,6 +418,7 @@ def test_kpi_calculation_28(AUDIT_START_DATE): # Diagnosis of Type 1 diabetes "diabetes_type": DIABETES_TYPES[0][0], # KPI 6 specific = an observation within the audit period + "visit__visit_date": AUDIT_START_DATE + relativedelta(days=2), "visit__height_weight_observation_date": AUDIT_START_DATE + relativedelta(days=2), # Also has same exclusions as KPI 5 @@ -538,6 +542,7 @@ def test_kpi_calculation_29(AUDIT_START_DATE): # Diagnosis of Type 1 diabetes "diabetes_type": DIABETES_TYPES[0][0], # KPI 6 specific = an observation within the audit period + "visit__visit_date": AUDIT_START_DATE + relativedelta(days=2), "visit__height_weight_observation_date": AUDIT_START_DATE + relativedelta(days=2), # Also has same exclusions as KPI 5 @@ -661,6 +666,7 @@ def test_kpi_calculation_30(AUDIT_START_DATE): # Diagnosis of Type 1 diabetes "diabetes_type": DIABETES_TYPES[0][0], # KPI 6 specific = an observation within the audit period + "visit__visit_date": AUDIT_START_DATE + relativedelta(days=2), "visit__height_weight_observation_date": AUDIT_START_DATE + relativedelta(days=2), # Also has same exclusions as KPI 5 @@ -793,6 +799,7 @@ def test_kpi_calculation_31(AUDIT_START_DATE): # Diagnosis of Type 1 diabetes "diabetes_type": DIABETES_TYPES[0][0], # KPI 6 specific = an observation within the audit period + "visit__visit_date": AUDIT_START_DATE + relativedelta(days=2), "visit__height_weight_observation_date": AUDIT_START_DATE + relativedelta(days=2), # Also has same exclusions as KPI 5 From bfb1fb67dabf0b149b50ea314a3d0ae489357ffb Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Sat, 5 Oct 2024 11:40:21 +0100 Subject: [PATCH 21/22] fix seeding for test kpi calc 35 Signed-off-by: anchit-chandran --- project/npda/general_functions/kpis.py | 2 ++ .../tests/kpi_calculations/test_kpis_33_40.py | 34 +++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/project/npda/general_functions/kpis.py b/project/npda/general_functions/kpis.py index a2fc4b25..04ee41e0 100644 --- a/project/npda/general_functions/kpis.py +++ b/project/npda/general_functions/kpis.py @@ -501,6 +501,7 @@ def calculate_kpi_6_total_t1dm_complete_year_gte_12yo(self) -> dict: | Q(death_date__range=(self.AUDIT_DATE_RANGE)) ) + base_eligible_patients = eligible_patients_exclusions.filter( # Valid attributes Q(nhs_number__isnull=False) @@ -1919,6 +1920,7 @@ def calculate_kpi_35_smoking_status_screened( ) ) + total_passed = total_passed_query_set.count() total_failed = total_eligible - total_passed diff --git a/project/npda/tests/kpi_calculations/test_kpis_33_40.py b/project/npda/tests/kpi_calculations/test_kpis_33_40.py index 9d7cb8d1..1a9140b6 100644 --- a/project/npda/tests/kpi_calculations/test_kpis_33_40.py +++ b/project/npda/tests/kpi_calculations/test_kpis_33_40.py @@ -1,4 +1,5 @@ """Tests for the 7 Key Processes KPIs.""" + from typing import List import pytest @@ -327,7 +328,7 @@ def test_kpi_calculation_35(AUDIT_START_DATE): # Passing patients passing_patient_1 = PatientFactory( postcode="passing_patient_1", - # KPI5 eligible + # KPI6 eligible **eligible_criteria, # KPI 35 specific visit__visit_date=AUDIT_START_DATE + relativedelta(days=5), @@ -336,7 +337,7 @@ def test_kpi_calculation_35(AUDIT_START_DATE): # second visit has a valid smoking status passing_patient_2 = PatientFactory( postcode="passing_patient_2", - # KPI5 eligible + # KPI6 eligible **eligible_criteria, # KPI 35 specific visit__visit_date=None, @@ -345,6 +346,9 @@ def test_kpi_calculation_35(AUDIT_START_DATE): # create 2nd visit VisitFactory( patient=passing_patient_2, + # KPI 6 specific = an observation within the audit period + height_weight_observation_date=AUDIT_START_DATE + + relativedelta(days=5), visit_date=AUDIT_START_DATE + relativedelta(days=5), smoking_status=SMOKING_STATUS[1][0], ) @@ -356,6 +360,7 @@ def test_kpi_calculation_35(AUDIT_START_DATE): # KPI5 eligible **eligible_criteria, # KPI 35 specific + visit__visit_date=AUDIT_START_DATE + relativedelta(days=5), visit__smoking_status=SMOKING_STATUS[2][0], ) # No smoke screening @@ -364,6 +369,7 @@ def test_kpi_calculation_35(AUDIT_START_DATE): # KPI5 eligible **eligible_criteria, # KPI 35 specific + visit__visit_date=AUDIT_START_DATE + relativedelta(days=5), visit__smoking_status=None, ) @@ -568,7 +574,7 @@ def test_kpi_calculation_37(AUDIT_START_DATE): # KPI5 eligible **eligible_criteria, # KPI 37 specific - visit__dietician_additional_appointment_offered=1 + visit__dietician_additional_appointment_offered=1, ) # only second visit has a valid dietician appt offered passing_patient_2 = PatientFactory( @@ -657,6 +663,7 @@ def test_kpi_calculation_37(AUDIT_START_DATE): actual=calc_kpis.calculate_kpi_37_additional_dietetic_appointment_offered(), ) + @pytest.mark.django_db def test_kpi_calculation_38(AUDIT_START_DATE): """Tests that KPI38 is calculated correctly. @@ -690,7 +697,8 @@ def test_kpi_calculation_38(AUDIT_START_DATE): # KPI5 eligible **eligible_criteria, # KPI 38 specific - visit__dietician_additional_appointment_date=AUDIT_START_DATE+relativedelta(days=30) + visit__dietician_additional_appointment_date=AUDIT_START_DATE + + relativedelta(days=30), ) # only second visit has a valid additional dietician appt passing_patient_2 = PatientFactory( @@ -704,7 +712,8 @@ def test_kpi_calculation_38(AUDIT_START_DATE): VisitFactory( patient=passing_patient_2, visit_date=AUDIT_START_DATE + relativedelta(days=5), - dietician_additional_appointment_date=AUDIT_START_DATE+relativedelta(days=4) + dietician_additional_appointment_date=AUDIT_START_DATE + + relativedelta(days=4), ) # Failing patients @@ -779,6 +788,7 @@ def test_kpi_calculation_38(AUDIT_START_DATE): actual=calc_kpis.calculate_kpi_38_patients_attending_additional_dietetic_appointment(), ) + @pytest.mark.django_db def test_kpi_calculation_39(AUDIT_START_DATE): """Tests that KPI39 is calculated correctly. @@ -812,7 +822,8 @@ def test_kpi_calculation_39(AUDIT_START_DATE): # KPI5 eligible **eligible_criteria, # KPI 39 specific - visit__flu_immunisation_recommended_date=AUDIT_START_DATE+relativedelta(days=30) + visit__flu_immunisation_recommended_date=AUDIT_START_DATE + + relativedelta(days=30), ) # only second visit has a valid influenza immunisation recommended passing_patient_2 = PatientFactory( @@ -826,7 +837,8 @@ def test_kpi_calculation_39(AUDIT_START_DATE): VisitFactory( patient=passing_patient_2, visit_date=AUDIT_START_DATE + relativedelta(days=5), - flu_immunisation_recommended_date=AUDIT_START_DATE+relativedelta(days=4) + flu_immunisation_recommended_date=AUDIT_START_DATE + + relativedelta(days=4), ) # Failing patients @@ -901,6 +913,7 @@ def test_kpi_calculation_39(AUDIT_START_DATE): actual=calc_kpis.calculate_kpi_39_influenza_immunisation_recommended(), ) + @pytest.mark.django_db def test_kpi_calculation_40(AUDIT_START_DATE): """Tests that KPI40 is calculated correctly. @@ -926,7 +939,8 @@ def test_kpi_calculation_40(AUDIT_START_DATE): # KPI1 eligible **eligible_criteria, # KPI 40 specific - visit__sick_day_rules_training_date=AUDIT_START_DATE+relativedelta(days=30) + visit__sick_day_rules_training_date=AUDIT_START_DATE + + relativedelta(days=30), ) # only second visit has a valid sick day rule passing_patient_2 = PatientFactory( @@ -940,7 +954,7 @@ def test_kpi_calculation_40(AUDIT_START_DATE): VisitFactory( patient=passing_patient_2, visit_date=AUDIT_START_DATE + relativedelta(days=5), - sick_day_rules_training_date=AUDIT_START_DATE+relativedelta(days=4) + sick_day_rules_training_date=AUDIT_START_DATE + relativedelta(days=4), ) # Failing patients @@ -983,4 +997,4 @@ def test_kpi_calculation_40(AUDIT_START_DATE): assert_kpi_result_equal( expected=EXPECTED_KPIRESULT, actual=calc_kpis.calculate_kpi_40_sick_day_rules_advice(), - ) \ No newline at end of file + ) From 0b001aa3710382cad2240f22f6a6440323b31888 Mon Sep 17 00:00:00 2001 From: anchit-chandran Date: Sat, 5 Oct 2024 11:45:49 +0100 Subject: [PATCH 22/22] adds extra vase test kpi calc 33 to ensure it handles first Visit having None value Signed-off-by: anchit-chandran --- .../tests/kpi_calculations/test_kpis_33_40.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/project/npda/tests/kpi_calculations/test_kpis_33_40.py b/project/npda/tests/kpi_calculations/test_kpis_33_40.py index 1a9140b6..6d8ddbfa 100644 --- a/project/npda/tests/kpi_calculations/test_kpis_33_40.py +++ b/project/npda/tests/kpi_calculations/test_kpis_33_40.py @@ -69,6 +69,21 @@ def test_kpi_calculation_33(AUDIT_START_DATE): hba1c=43, hba1c_date=AUDIT_START_DATE + relativedelta(days=6), ) + # 1 of the Visits has no HbA1c + passing_patient_3 = PatientFactory( + postcode="passing_patient_3", + # KPI5 eligible + **eligible_criteria, + visit__hba1c=None, + visit__hba1c_date=None, + ) + for i in range(4): + VisitFactory( + patient=passing_patient_3, + visit_date=AUDIT_START_DATE, + hba1c=46, + hba1c_date=AUDIT_START_DATE + relativedelta(days=i), + ) # Failing patients # < 4 hba1c @@ -141,9 +156,9 @@ def test_kpi_calculation_33(AUDIT_START_DATE): calculation_date=AUDIT_START_DATE, ) - EXPECTED_TOTAL_ELIGIBLE = 4 + EXPECTED_TOTAL_ELIGIBLE = 5 EXPECTED_TOTAL_INELIGIBLE = 5 - EXPECTED_TOTAL_PASSED = 2 + EXPECTED_TOTAL_PASSED = 3 EXPECTED_TOTAL_FAILED = 2 EXPECTED_KPIRESULT = KPIResult(