Skip to content

Commit

Permalink
Show statin nudge alert when statins can be prescribed (#5074)
Browse files Browse the repository at this point in the history
Co-authored-by: Siddharth Agarwal <sagarwal@rtsl.org>
Co-authored-by: Sasikanth Miriyampalli <hello@sasikanth.dev>
  • Loading branch information
3 people authored Sep 10, 2024
1 parent d7e6744 commit 73c1ea6
Show file tree
Hide file tree
Showing 15 changed files with 348 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Bump AndroidX ViewModel to v2.8.5
- Bump AndroidX Lifecycle to v2.8.5
- Check statin nudge status in patient summary screen
- Show statin nudge alert when statins can be prescribed

## 2024-08-05-9175

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ data class PatientSummaryModel(
val hasPrescribedDrugsChangedToday: Boolean?,
val scheduledAppointment: ParcelableOptional<Appointment>?,
val hasShownDiagnosisWarningDialog: Boolean,
val canPrescribeStatin: Boolean,
val statin: StatinModel?,
) : Parcelable, PatientSummaryChildModel {

companion object {
Expand All @@ -46,7 +46,7 @@ data class PatientSummaryModel(
hasPrescribedDrugsChangedToday = null,
scheduledAppointment = null,
hasShownDiagnosisWarningDialog = false,
canPrescribeStatin = false,
statin = null,
)
}
}
Expand Down Expand Up @@ -81,6 +81,9 @@ data class PatientSummaryModel(
val hasScheduledAppointment: Boolean
get() = scheduledAppointment != null && scheduledAppointment.isPresent()

val hasStatinInfoLoaded: Boolean
get() = statin != null

override fun readyToRender(): Boolean {
return hasLoadedPatientSummaryProfile && hasLoadedCurrentFacility && hasPatientRegistrationData != null
}
Expand Down Expand Up @@ -125,7 +128,7 @@ data class PatientSummaryModel(
return copy(scheduledAppointment = appointment.toOptional().parcelable())
}

fun updateStatinPrescriptionStatus(canPrescribeStatin: Boolean): PatientSummaryModel {
return copy(canPrescribeStatin = canPrescribeStatin)
fun updateStatinInfo(statin: StatinModel): PatientSummaryModel {
return copy(statin = statin)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ class PatientSummaryScreen :
private val clinicalDecisionSupportAlertView
get() = binding.clinicalDecisionSupportBpHighAlert.rootView

private val statinAlertView
get() = binding.statinAlert.rootView

private val statinAlertDescription
get() = binding.statinAlert.statinAlertSubtitle

@Inject
lateinit var router: Router

Expand Down Expand Up @@ -792,6 +798,42 @@ class PatientSummaryScreen :
clinicalDecisionSupportAlertView.visibility = GONE
}

override fun showStatinAlert(statin: StatinModel) {
statinAlertDescription.text = buildString {
append("${getString(R.string.statin_alert_patient)} ")

if (statin.hasDiabetes) {
append(String.format(getString(R.string.statin_alert_has_diabetes), statin.age.toString()))

if (statin.hasHadHeartAttack.xor(statin.hasHadStroke)) {
append(" ${getString(R.string.statin_alert_and_seperator)} ")
}
}

when {
statin.hasHadHeartAttack && statin.hasHadStroke -> append(getCVDString(statin.hasDiabetes))
statin.hasHadHeartAttack -> append(getString(R.string.statin_alert_heart_attack))
statin.hasHadStroke -> append(getString(R.string.statin_alert_stroke))
}

append(".")
}

statinAlertView.visibility = VISIBLE
}

private fun getCVDString(hasDiabetes: Boolean): String {
return if (hasDiabetes) {
getString(R.string.statin_alert_cvd_with_diabetes)
} else {
getString(R.string.statin_alert_cvd)
}
}

override fun hideStatinAlert() {
statinAlertView.visibility = GONE
}

override fun showReassignPatientWarningSheet(
patientUuid: UUID,
currentFacility: Facility,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ interface PatientSummaryScreenUi {
fun showClinicalDecisionSupportAlert()
fun hideClinicalDecisionSupportAlert()
fun hideClinicalDecisionSupportAlertWithoutAnimation()
fun showStatinAlert(statin: StatinModel)
fun hideStatinAlert()
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,21 +102,27 @@ class PatientSummaryUpdate(
model: PatientSummaryModel
): Next<PatientSummaryModel, PatientSummaryEffect> {
val minAgeForStatin = 40
val hasCVD = with(event.medicalHistory) {
hasHadStroke == Yes || hasHadHeartAttack == Yes
}
val isPatientEligibleForStatin = (event.age >= minAgeForStatin &&
event.medicalHistory.diagnosedWithDiabetes == Yes) ||
hasCVD
val hasHadStroke = event.medicalHistory.hasHadStroke == Yes
val hasHadHeartAttack = event.medicalHistory.hasHadHeartAttack == Yes
val hasDiabetes = event.medicalHistory.diagnosedWithDiabetes == Yes

val hasCVD = hasHadStroke || hasHadHeartAttack
val isPatientEligibleForStatin = (event.age >= minAgeForStatin && hasDiabetes) || hasCVD
val hasStatinsPrescribedAlready = event.prescriptions.any { it.name.contains("statin", ignoreCase = true) }
val canPrescribeStatin = event.isPatientDead.not() &&
event.assignedFacility?.facilityType.equals("UHC", ignoreCase = true) &&
event.hasBPRecordedToday &&
hasStatinsPrescribedAlready.not() &&
isPatientEligibleForStatin

val updatedModel = model.updateStatinPrescriptionStatus(
canPrescribeStatin = canPrescribeStatin
val updatedModel = model.updateStatinInfo(
StatinModel(
canPrescribeStatin = canPrescribeStatin,
age = event.age,
hasDiabetes = hasDiabetes,
hasHadStroke = hasHadStroke,
hasHadHeartAttack = hasHadHeartAttack,
)
)
return next(updatedModel)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PatientSummaryViewRenderer(
private val today = LocalDate.now(userClock)

override fun render(model: PatientSummaryModel) {
modelUpdateCallback?.invoke(model)
modelUpdateCallback.invoke(model)

with(ui) {
if (model.hasLoadedPatientSummaryProfile) {
Expand All @@ -42,10 +42,15 @@ class PatientSummaryViewRenderer(
} else {
ui.hideClinicalDecisionSupportAlertWithoutAnimation()
}

renderStatinAlert(model)
}
}

private fun renderClinicalDecisionBasedOnAppointment(model: PatientSummaryModel) {
if (model.statin?.canPrescribeStatin == true)
return

if (model.hasScheduledAppointment) {
renderClinicalDecisionBasedOnAppointmentOverdue(model)
} else {
Expand Down Expand Up @@ -145,4 +150,15 @@ class PatientSummaryViewRenderer(
ui.hideDiabetesView()
}
}

private fun renderStatinAlert(model: PatientSummaryModel) {
if (model.hasStatinInfoLoaded.not()) return

if (model.statin!!.canPrescribeStatin) {
ui.showStatinAlert(model.statin)
ui.hideClinicalDecisionSupportAlertWithoutAnimation()
} else {
ui.hideStatinAlert()
}
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/org/simple/clinic/summary/StatinModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.simple.clinic.summary

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class StatinModel(
val canPrescribeStatin: Boolean,
val age: Int,
val hasDiabetes: Boolean,
val hasHadStroke: Boolean,
val hasHadHeartAttack: Boolean,
) : Parcelable
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/background_statin_alert.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<solid android:color="@color/simple_yellow_100" />

<stroke
android:width="@dimen/spacing_1"
android:color="@color/simple_yellow_550_alpha_40" />

<corners android:radius="@dimen/spacing_4" />
</shape>
27 changes: 27 additions & 0 deletions app/src/main/res/drawable/ic_statin_alert.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<group>
<clip-path
android:pathData="M20,20m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"/>
<path
android:pathData="M20,20m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"
android:fillColor="#E0B000"/>
<path
android:pathData="M21.154,7.309L21.154,7.309A3.077,3.077 0,0 1,24.231 10.386L24.231,22.693A3.077,3.077 0,0 1,21.154 25.77L21.154,25.77A3.077,3.077 0,0 1,18.077 22.693L18.077,10.386A3.077,3.077 0,0 1,21.154 7.309z"
android:fillColor="#2F363D"
android:fillAlpha="0.32"/>
<path
android:pathData="M21.154,31.538m-3.077,0a3.077,3.077 0,1 1,6.154 0a3.077,3.077 0,1 1,-6.154 0"
android:fillColor="#2F363D"
android:fillAlpha="0.32"/>
<path
android:pathData="M20.192,6.153L20.192,6.153A2.885,2.885 0,0 1,23.077 9.038L23.077,21.73A2.885,2.885 0,0 1,20.192 24.615L20.192,24.615A2.885,2.885 0,0 1,17.308 21.73L17.308,9.038A2.885,2.885 0,0 1,20.192 6.153z"
android:fillColor="#ffffff"/>
<path
android:pathData="M20.192,30.576m-2.885,0a2.885,2.885 0,1 1,5.769 0a2.885,2.885 0,1 1,-5.769 0"
android:fillColor="#ffffff"/>
</group>
</vector>
4 changes: 4 additions & 0 deletions app/src/main/res/layout/screen_patient_summary.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@
android:id="@+id/clinicalDecisionSupportBpHighAlert"
layout="@layout/view_clinical_decision_support_bp_high" />

<include
android:id="@+id/statinAlert"
layout="@layout/view_statin_alert" />

<org.simple.clinic.widgets.PatientStatusView
android:id="@+id/patientDiedStatusView"
android:layout_width="match_parent"
Expand Down
56 changes: 56 additions & 0 deletions app/src/main/res/layout/view_statin_alert.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/spacing_8"
android:layout_marginTop="@dimen/spacing_8"
android:layout_marginBottom="@dimen/spacing_0"
android:background="@drawable/background_statin_alert"
android:elevation="@dimen/spacing_4"
android:paddingHorizontal="@dimen/spacing_8"
android:paddingVertical="@dimen/spacing_12"
android:visibility="gone"
tools:visibility="visible">

<ImageView
android:id="@+id/statinAlertIcon"
android:layout_width="@dimen/spacing_56"
android:layout_height="@dimen/spacing_56"
android:contentDescription="@null"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_statin_alert" />

<com.google.android.material.textview.MaterialTextView
android:id="@+id/statinAlertTitle"
android:layout_width="@dimen/spacing_0"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_8"
android:text="@string/statin_alert_title"
android:textAppearance="?attr/textAppearanceSubtitle1Medium"
android:textColor="@color/simple_brown_600"
app:layout_constraintBottom_toTopOf="@id/statinAlertSubtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/statinAlertIcon"
app:layout_constraintTop_toTopOf="@id/statinAlertIcon"
app:layout_constraintVertical_chainStyle="packed" />

<com.google.android.material.textview.MaterialTextView
android:id="@+id/statinAlertSubtitle"
android:layout_width="@dimen/spacing_0"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_8"
android:layout_marginTop="@dimen/spacing_2"
android:textAppearance="?attr/textAppearanceBody1"
android:textColor="@color/simple_brown_600_alpha_80"
app:layout_constraintBottom_toBottomOf="@id/statinAlertIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/statinAlertIcon"
app:layout_constraintTop_toBottomOf="@id/statinAlertTitle" />

</androidx.constraintlayout.widget.ConstraintLayout>
13 changes: 12 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,7 @@ The Simple app contains private health information of patients (“Data”).\n\n
<string name="reassign_patient_title">Eligible for refills at CC. Transfer patient?</string>
<string name="reassign_patient_description">Assign to preferred community clinic for future visits.</string>
<string name="change_patient_facility">Change</string>

<!-- Diabetes Diagnosis Warning Dialog -->
<string name="diabetes_warning_dialog_title">Add Diabetes diagnosis?</string>
<string name="diabetes_warning_dialog_desc">This patient is on diabetes medication.</string>
Expand All @@ -1082,4 +1082,15 @@ The Simple app contains private health information of patients (“Data”).\n\n
<string name="htn_warning_dialog_desc">This patient is on hypertension medication.</string>
<string name="htn_warning_dialog_positive_button">Yes, has hypertension</string>
<string name="htn_warning_dialog_negative_button">Not now</string>

<!-- Statin Alerts -->
<string name="statin_alert_title">High risk. Add statin medicine</string>
<string name="statin_alert_patient">Patient</string>
<string name="statin_alert_has_diabetes">is %s with diabetes</string>
<string name="statin_alert_and_seperator">and</string>
<string name="statin_alert_cvd_with_diabetes">, has history of heart attack and stroke</string>
<string name="statin_alert_cvd">has history of heart attack and stroke</string>
<string name="statin_alert_heart_attack">has history of heart attack</string>
<string name="statin_alert_stroke">has history of stroke</string>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -2108,7 +2108,15 @@ class PatientSummaryUpdateTest {
),
))
.then(assertThatNext(
hasModel(defaultModel.updateStatinPrescriptionStatus(canPrescribeStatin = true)),
hasModel(defaultModel.updateStatinInfo(
StatinModel(
canPrescribeStatin = true,
age = 50,
hasDiabetes = true,
hasHadStroke = false,
hasHadHeartAttack = false,
)
)),
hasNoEffects()
))
}
Expand Down
Loading

0 comments on commit 73c1ea6

Please sign in to comment.