Skip to content

Commit

Permalink
Merge pull request #303 from rcpch/kpis-in-the-uis
Browse files Browse the repository at this point in the history
Kpis-in-the-uis
  • Loading branch information
eatyourpeas authored Oct 6, 2024
2 parents c899c8a + b66bb5d commit 723af57
Show file tree
Hide file tree
Showing 15 changed files with 584 additions and 620 deletions.
1 change: 1 addition & 0 deletions project/npda/kpi_class/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .kpis import *
512 changes: 226 additions & 286 deletions project/npda/general_functions/kpis.py → project/npda/kpi_class/kpis.py

Large diffs are not rendered by default.

145 changes: 145 additions & 0 deletions project/npda/templates/partials/kpi_patient.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<div class="w-100">
<strong>
Key Performance Indicators for {{patient.nhs_number}} ({{ kpi_results.pz_code }})
</strong>
<table class="table-auto w-full text-sm text-left text-gray-500 mb-5 font-montserrat">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 bg-rcpch_dark_blue text-white">
<tr>
<th class="px-6 py-3">KPI</th>
<th class="px-6 py-3">Status</th>
</tr>
</thead>
<tbody>
{% for kpi_key, kpi_value in kpi_results.calculated_kpi_values.items %}
{% if not kpi_key|slice:":6" == "kpi_32" %}
<tr class="bg-white border-b">
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">{{ kpi_value.kpi_label }}</td>
<td class="px-6 py-4">
{% if kpi_value.total_failed == 1 %}
<span class="text-red-500 tooltip" data-tip="{{patient.nhs_number}} has failed this measure"></span>
{% elif kpi_value.total_passed == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has passed this measure" data-inverted="" data-position="top left">
<svg width="12px" height="12px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 12L10.5 15L16.5 9M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#e00087" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% elif kpi_value.total_ineligible == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} is not eligible for this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?>
<svg height="12px" viewBox="0 0 1792 1792" width="12px" xmlns="http://www.w3.org/2000/svg" fill='#11a7f2'>
<path d="M1152 896q0 106-75 181t-181 75-181-75-75-181 75-181 181-75 181 75 75 181zm-256-544q-148 0-273 73t-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273-73-273-198-198-273-73zm768 544q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z" stroke="#11a7f2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% else %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has not done this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?><svg height="12px" viewBox="0 0 48 48" width="12px" xmlns="http://www.w3.org/2000/svg" fill="#11a7f2"><path d="M0 0h48v48h-48z" fill="none"/><path d="M12 20c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm24 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-12 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/></svg>
</span>
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
<!-- Sub-rows for KPI 32 -->
<tr class="bg-white border-b">
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">KPI 32</td>
<td class="px-6 py-4">
{% if kpi_results.calculated_kpi_values.kpi_32_1_health_check_completion_rate.total_failed == 1 %}
<span class="text-red-500 tooltip" data-tip="{{patient.nhs_number}} has failed this measure"></span>
{% elif kpi_results.calculated_kpi_values.kpi_32_1_health_check_completion_rate.total_passed == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has passed this measure" data-inverted="" data-position="top left">
<svg width="12px" height="12px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg>
<path d="M7.5 12L10.5 15L16.5 9M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#e00087" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% elif kpi_results.calculated_kpi_values.kpi_32_1_health_check_completion_rate.total_ineligible == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} is not eligible for this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?>
<svg height="12px" viewBox="0 0 1792 1792" width="12px" xmlns="http://www.w3.org/2000/svg" fill='#11a7f2'>
<path d="M1152 896q0 106-75 181t-181 75-181-75-75-181 75-181 181-75 181 75 75 181zm-256-544q-148 0-273 73t-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273-73-273-198-198-273-73zm768 544q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z" stroke="#11a7f2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% else %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has not done this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?><svg height="12px" viewBox="0 0 48 48" width="12px" xmlns="http://www.w3.org/2000/svg" fill="#11a7f2"><path d="M0 0h48v48h-48z" fill="none"/><path d="M12 20c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm24 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-12 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/></svg>
</span>
{% endif %}
</td>
</tr>
<tr class="bg-gray-50 border-b">
<td class="px-6 py-4 pl-12 font-medium text-gray-900 whitespace-nowrap">Care Process Completion Rate</td>
<td class="px-6 py-4">
{% if kpi_results.calculated_kpi_values.kpi_32_1_health_check_completion_rate.total_failed == 1 %}
<span class="text-red-500 tooltip" data-tip="{{patient.nhs_number}} has failed this measure"></span>
{% elif kpi_results.calculated_kpi_values.kpi_32_1_health_check_completion_rate.total_passed == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has passed this measure" data-inverted="" data-position="top left">
<svg width="12px" height="12px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 12L10.5 15L16.5 9M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#e00087" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% elif kpi_results.calculated_kpi_values.kpi_32_1_health_check_completion_rate.total_ineligible == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} is not eligible for this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?>
<svg height="12px" viewBox="0 0 1792 1792" width="12px" xmlns="http://www.w3.org/2000/svg" fill='#11a7f2'>
<path d="M1152 896q0 106-75 181t-181 75-181-75-75-181 75-181 181-75 181 75 75 181zm-256-544q-148 0-273 73t-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273-73-273-198-198-273-73zm768 544q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z" stroke="#11a7f2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% else %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has not done this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?><svg height="12px" viewBox="0 0 48 48" width="12px" xmlns="http://www.w3.org/2000/svg" fill="#11a7f2"><path d="M0 0h48v48h-48z" fill="none"/><path d="M12 20c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm24 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-12 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/></svg>
</span>
{% endif %}
</td>
</tr>
<tr class="bg-gray-50 border-b">
<td class="px-6 py-4 pl-12 font-medium text-gray-900 whitespace-nowrap">Health Check &lt; 12yo</td>
<td class="px-6 py-4">
{% if kpi_results.calculated_kpi_values.kpi_32_2_health_check_lt_12yo.total_failed == 1 %}
<span class="text-red-500 tooltip" data-tip="{{patient.nhs_number}} has failed this measure"></span>
{% elif kpi_results.calculated_kpi_values.kpi_32_2_health_check_lt_12yo.total_passed == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has passed this measure" data-inverted="" data-position="top left">
<svg width="12px" height="px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 12L10.5 15L16.5 9M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#e00087" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% elif kpi_results.calculated_kpi_values.kpi_32_2_health_check_lt_12yo.total_ineligible == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} is not eligible for this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?>
<svg height="12px" viewBox="0 0 1792 1792" width="12px" xmlns="http://www.w3.org/2000/svg" fill='#11a7f2'></svg>
<path d="M1152 896q0 106-75 181t-181 75-181-75-75-181 75-181 181-75 181 75 75 181zm-256-544q-148 0-273 73t-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273-73-273-198-198-273-73zm768 544q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z" stroke="#11a7f2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% else %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has not done this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?><svg height="12px" viewBox="0 0 48 48" width="12px" xmlns="http://www.w3.org/2000/svg" fill="#11a7f2"><path d="M0 0h48v48h-48z" fill="none"/><path d="M12 20c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm24 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-12 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/></svg>
</span>
{% endif %}
</td>
</tr>
<tr class="bg-gray-50 border-b">
<td class="px-6 py-4 pl-12 font-medium text-gray-900 whitespace-nowrap">Health Check &gt;= 12yo</td>
<td class="px-6 py-4">
{% if kpi_results.calculated_kpi_values.kpi_32_3_health_check_gte_12yo.total_failed == 1 %}
<span class="text-red-500 tooltip" data-tip="{{patient.nhs_number}} has failed this measure"></span>
{% elif kpi_results.calculated_kpi_values.kpi_32_3_health_check_gte_12yo.total_passed == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has passed this measure" data-inverted="" data-position="top left">
<svg width="12px" height="px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 12L10.5 15L16.5 9M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#e00087" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% elif kpi_results.calculated_kpi_values.kpi_32_3_health_check_gte_12yo.total_ineligible == 1 %}
<span class='tooltip' data-tip="{{patient.nhs_number}} is not eligible for this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?>
<svg height="12px" viewBox="0 0 1792 1792" width="12px" xmlns="http://www.w3.org/2000/svg" fill='#11a7f2'>
<path d="M1152 896q0 106-75 181t-181 75-181-75-75-181 75-181 181-75 181 75 75 181zm-256-544q-148 0-273 73t-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273-73-273-198-198-273-73zm768 544q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z" stroke="#11a7f2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
{% else %}
<span class='tooltip' data-tip="{{patient.nhs_number}} has not done this measure" data-inverted="" data-position="top left">
<?xml version="1.0" ?><svg height="12px" viewBox="0 0 48 48" width="12px" xmlns="http://www.w3.org/2000/svg" fill="#11a7f2"><path d="M0 0h48v48h-48z" fill="none"/><path d="M12 20c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm24 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-12 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/></svg>
</span>
{% endif %}
</td>
</tr>
</tbody>
</table>
</div>
4 changes: 2 additions & 2 deletions project/npda/templates/visits.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ <h5>This child has had no visits yet!</h5>
</div>
</div>

<div class="row mt-20">
<strong>Patient KPIs will go here...</strong>
<div class="row mt-1">
{% include 'partials/kpi_patient.html' with kpi_results=kpi_results patient=patient %}
</div>
</div>
</div>
Expand Down
19 changes: 8 additions & 11 deletions project/npda/tests/kpi_calculations/test_kpi_calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import pytest

from project.npda.general_functions.kpis import CalculateKPIS, KPIResult
from project.npda.kpi_class.kpis import CalculateKPIS, KPIResult
from project.npda.models.patient import Patient

# Logging
Expand All @@ -29,9 +29,7 @@ def assert_kpi_result_equal(expected: KPIResult, actual: KPIResult) -> None:
f"expected must be of type KPIResult (current: {type(expected)}"
)
if isinstance(actual, KPIResult) is False:
raise TypeError(
f"actual must be of type KPIResult (current: {type(actual)}"
)
raise TypeError(f"actual must be of type KPIResult (current: {type(actual)}")

mismatches = []

Expand Down Expand Up @@ -90,13 +88,12 @@ def test_kpi_calculations_dont_break_when_no_patients(AUDIT_START_DATE):
pz_code="PZ130", calculation_date=AUDIT_START_DATE
).calculate_kpis_for_patients()

for kpi, results in kpi_calculations_object[
"calculated_kpi_values"
].items():
for kpi, results in kpi_calculations_object["calculated_kpi_values"].items():
# remove the kpi_label key from the results
results.pop("kpi_label", None)

values = list(results.values())

assert all(
[
isinstance(value, int) or isinstance(value, float)
for value in values
]
[isinstance(value, int) or isinstance(value, float) for value in values]
), f"KPI {kpi} has non-integer values: {results}"
12 changes: 6 additions & 6 deletions project/npda/tests/kpi_calculations/test_kpis_13_20.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"""Tests for the Treatment Regimen KPIS."""

import pytest
from dateutil.relativedelta import relativedelta

from project.constants.diabetes_treatment import TREATMENT_TYPES
from project.npda.general_functions.kpis import CalculateKPIS, KPIResult
from project.npda.kpi_class.kpis import CalculateKPIS, KPIResult
from project.npda.models import Patient
from project.npda.tests.factories.patient_factory import PatientFactory
from project.npda.tests.kpi_calculations.test_kpi_calculations import \
assert_kpi_result_equal
from project.npda.tests.kpi_calculations.test_kpi_calculations import (
assert_kpi_result_equal,
)

# Set up test params for kpis 13-20, as they all have the same denominator
# and the only thing being changed is value for visit__treatment
Expand Down Expand Up @@ -77,9 +79,7 @@ def test_kpi_calculations_13_to_20(
)

# The default pz_code is "PZ130" for PaediatricsDiabetesUnitFactory
calc_kpis = CalculateKPIS(
pz_code="PZ130", calculation_date=AUDIT_START_DATE
)
calc_kpis = CalculateKPIS(pz_code="PZ130", calculation_date=AUDIT_START_DATE)

# Dynamically get the kpi calc method based on treatment type
# `treatment` is an int between 1-8
Expand Down
Loading

0 comments on commit 723af57

Please sign in to comment.