Skip to content

Commit f567bc5

Browse files
authored
Calculate and load lab-based statin info if lab based statin nudge feature flag is enabled (#5288)
**Story:** https://app.shortcut.com/simpledotorg/story/14632/calculate-lab-based-cvd-risk-score-when-conditions-are-met
1 parent 1123491 commit f567bc5

File tree

12 files changed

+562
-36
lines changed

12 files changed

+562
-36
lines changed

app/src/main/java/org/simple/clinic/cvdrisk/StatinInfo.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ data class StatinInfo(
1212
val isSmoker: Answer = Answer.Unanswered,
1313
val bmiReading: BMIReading? = null,
1414
val hasCVD: Boolean = false,
15+
val hasDiabetes: Boolean = false,
16+
val age: Int = 0,
17+
val cholesterol: Float? = null,
1518
) : Parcelable {
1619
companion object {
1720
fun default(): StatinInfo {

app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistory.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import androidx.room.Query
1212
import androidx.room.RawQuery
1313
import androidx.sqlite.db.SimpleSQLiteQuery
1414
import io.reactivex.Flowable
15-
import io.reactivex.Observable
1615
import kotlinx.parcelize.Parcelize
1716
import org.simple.clinic.medicalhistory.Answer.Unanswered
1817
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.DiagnosedWithDiabetes
@@ -69,6 +68,14 @@ data class MedicalHistory(
6968

7069
val deletedAt: Instant?
7170
) : Parcelable {
71+
72+
companion object {
73+
74+
fun convertCholesterolToMmol(cholesterol: Float): Float {
75+
return cholesterol / 38.67f
76+
}
77+
}
78+
7279
val diagnosisRecorded: Boolean
7380
get() = diagnosedWithHypertension != Unanswered && diagnosedWithDiabetes != Unanswered
7481

app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistoryPayload.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ data class MedicalHistoryPayload(
4646
@Json(name = "smoking")
4747
val isSmoking: Answer,
4848

49-
@Json(name = "cholesterol_value")
49+
@Json(name = "cholesterol")
5050
val cholesterol: Float?,
5151

5252
@Json(name = "created_at")

app/src/main/java/org/simple/clinic/summary/PatientSummaryEffect.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,5 @@ data class ShowHypertensionDiagnosisWarning(val continueToDiabetesDiagnosisWarni
141141
data object ShowSmokingStatusDialog : PatientSummaryViewEffect()
142142

143143
data class OpenBMIEntrySheet(val patientUuid: UUID) : PatientSummaryViewEffect()
144+
145+
data class OpenCholesterolEntrySheet(val patientUuid: UUID) : PatientSummaryViewEffect()

app/src/main/java/org/simple/clinic/summary/PatientSummaryEffectHandler.kt

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ import org.simple.clinic.cvdrisk.CVDRiskRange
1616
import org.simple.clinic.cvdrisk.CVDRiskRepository
1717
import org.simple.clinic.cvdrisk.LabBasedCVDRiskInput
1818
import org.simple.clinic.cvdrisk.NonLabBasedCVDRiskInput
19-
import org.simple.clinic.cvdrisk.StatinInfo
2019
import org.simple.clinic.cvdrisk.calculator.LabBasedCVDRiskCalculator
2120
import org.simple.clinic.cvdrisk.calculator.NonLabBasedCVDRiskCalculator
2221
import org.simple.clinic.drugs.DiagnosisWarningPrescriptions
2322
import org.simple.clinic.drugs.PrescriptionRepository
2423
import org.simple.clinic.facility.Facility
2524
import org.simple.clinic.facility.FacilityRepository
25+
import org.simple.clinic.medicalhistory.MedicalHistory
2626
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion
2727
import org.simple.clinic.medicalhistory.MedicalHistoryRepository
2828
import org.simple.clinic.overdue.AppointmentRepository
@@ -233,7 +233,7 @@ class PatientSummaryEffectHandler @AssistedInject constructor(
233233
systolic = bloodPressure.reading.systolic,
234234
isSmoker = medicalHistory.isSmoking,
235235
diagnosedWithDiabetes = medicalHistory.diagnosedWithDiabetes,
236-
cholesterol = null //Update once the value is available in medical history
236+
cholesterol = medicalHistory.cholesterol?.let { MedicalHistory.convertCholesterolToMmol(it) }
237237
)
238238
)
239239
}
@@ -279,18 +279,20 @@ class PatientSummaryEffectHandler @AssistedInject constructor(
279279
.observeOn(schedulersProvider.io())
280280
.map { effect ->
281281
val patientUuid = effect.patientUuid
282+
val patient = patientRepository.patientImmediate(patientUuid)
282283
val medicalHistory = medicalHistoryRepository.historyForPatientOrDefaultImmediate(
283284
defaultHistoryUuid = uuidGenerator.v4(),
284285
patientUuid = patientUuid
285286
)
286-
val bmiReading = patientAttributeRepository.getPatientAttributeImmediate(patientUuid)
287-
val cvdRisk = cvdRiskRepository.getCVDRiskImmediate(patientUuid)
288-
StatinInfoLoaded(StatinInfo(
289-
canPrescribeStatin = cvdRisk?.riskScore?.canPrescribeStatin ?: false,
290-
cvdRisk = cvdRisk?.riskScore,
291-
isSmoker = medicalHistory.isSmoking,
292-
bmiReading = bmiReading?.bmiReading,
293-
))
287+
val patientAttribute = patientAttributeRepository.getPatientAttributeImmediate(patientUuid)
288+
val riskRange = cvdRiskRepository.getCVDRiskImmediate(patientUuid)?.riskScore
289+
290+
StatinInfoLoaded(
291+
age = patient!!.ageDetails.estimateAge(userClock),
292+
medicalHistory = medicalHistory,
293+
riskRange = riskRange,
294+
bmiReading = patientAttribute?.bmiReading,
295+
)
294296
}
295297
}
296298
}

app/src/main/java/org/simple/clinic/summary/PatientSummaryEvent.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ package org.simple.clinic.summary
22

33
import org.simple.clinic.cvdrisk.CVDRisk
44
import org.simple.clinic.cvdrisk.CVDRiskRange
5-
import org.simple.clinic.cvdrisk.StatinInfo
65
import org.simple.clinic.drugs.DiagnosisWarningPrescriptions
76
import org.simple.clinic.drugs.PrescribedDrug
87
import org.simple.clinic.facility.Facility
98
import org.simple.clinic.medicalhistory.Answer
109
import org.simple.clinic.medicalhistory.MedicalHistory
1110
import org.simple.clinic.overdue.Appointment
11+
import org.simple.clinic.patientattribute.BMIReading
1212
import org.simple.clinic.patientattribute.PatientAttribute
1313
import org.simple.clinic.reassignpatient.ReassignPatientSheetClosedFrom
1414
import org.simple.clinic.reassignpatient.ReassignPatientSheetOpenedFrom
@@ -163,7 +163,10 @@ data class CVDRiskCalculated(
163163
data object CVDRiskUpdated : PatientSummaryEvent()
164164

165165
data class StatinInfoLoaded(
166-
val statinInfo: StatinInfo
166+
val age: Int,
167+
val medicalHistory: MedicalHistory,
168+
val riskRange: CVDRiskRange?,
169+
val bmiReading: BMIReading?,
167170
) : PatientSummaryEvent()
168171

169172
data object AddSmokingClicked : PatientSummaryEvent()
@@ -175,3 +178,7 @@ data class SmokingStatusAnswered(
175178
data object BMIReadingAdded : PatientSummaryEvent()
176179

177180
data object AddBMIClicked : PatientSummaryEvent()
181+
182+
data object AddCholesterolClicked: PatientSummaryEvent()
183+
184+
data object CholesterolAdded : PatientSummaryEvent()

app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import org.simple.clinic.reassignpatient.ReassignPatientSheetOpenedFrom
7171
import org.simple.clinic.remoteconfig.ConfigReader
7272
import org.simple.clinic.scheduleappointment.ScheduleAppointmentSheet
7373
import org.simple.clinic.scheduleappointment.facilityselection.FacilitySelectionScreen
74+
import org.simple.clinic.summary.addcholesterol.CholesterolEntrySheet
7475
import org.simple.clinic.summary.addphone.AddPhoneNumberDialog
7576
import org.simple.clinic.summary.compose.StatinNudge
7677
import org.simple.clinic.summary.linkId.LinkIdWithPatientSheet.LinkIdWithPatientSheetKey
@@ -757,6 +758,10 @@ class PatientSummaryScreen :
757758
.show()
758759
}
759760

761+
override fun openCholesterolEntrySheet(patientUuid: UUID) {
762+
router.pushExpectingResult(ScreenRequest.CholesterolEntrySheet, CholesterolEntrySheet.Key(patientUuid))
763+
}
764+
760765
override fun openBMIEntrySheet(patientUuid: UUID) {
761766
router.pushExpectingResult(ScreenRequest.BMIEntrySheet, BMIEntrySheet.Key(patientUuid))
762767
}
@@ -903,5 +908,8 @@ class PatientSummaryScreen :
903908

904909
@Parcelize
905910
data object BMIEntrySheet : ScreenRequest()
911+
912+
@Parcelize
913+
data object CholesterolEntrySheet : ScreenRequest()
906914
}
907915
}

app/src/main/java/org/simple/clinic/summary/PatientSummaryUiActions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ interface PatientSummaryUiActions {
4242
fun showHypertensionDiagnosisWarning(continueToDiabetesDiagnosisWarning: Boolean)
4343
fun showSmokingStatusDialog()
4444
fun openBMIEntrySheet(patientUuid: UUID)
45+
fun openCholesterolEntrySheet(patientUuid: UUID)
4546
}

app/src/main/java/org/simple/clinic/summary/PatientSummaryUpdate.kt

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.spotify.mobius.Next.next
55
import com.spotify.mobius.Next.noChange
66
import com.spotify.mobius.Update
77
import org.simple.clinic.cvdrisk.CVDRiskLevel
8+
import org.simple.clinic.cvdrisk.CVDRiskRange
89
import org.simple.clinic.cvdrisk.StatinInfo
910
import org.simple.clinic.drugs.DiagnosisWarningPrescriptions
1011
import org.simple.clinic.drugs.PrescribedDrug
@@ -109,12 +110,89 @@ class PatientSummaryUpdate(
109110
is SmokingStatusAnswered -> dispatch(UpdateSmokingStatus(model.patientUuid, event.isSmoker))
110111
is BMIReadingAdded -> dispatch(CalculateNonLabBasedCVDRisk(model.patientSummaryProfile!!.patient))
111112
is AddBMIClicked -> dispatch(OpenBMIEntrySheet(model.patientUuid))
113+
is AddCholesterolClicked -> dispatch(OpenCholesterolEntrySheet(model.patientUuid))
114+
CholesterolAdded -> dispatch(CalculateLabBasedCVDRisk(model.patientSummaryProfile!!.patient))
112115
}
113116
}
114117

115118
private fun statinPrescriptionCheckInfoLoaded(
116119
event: StatinPrescriptionCheckInfoLoaded,
117120
model: PatientSummaryModel
121+
): Next<PatientSummaryModel, PatientSummaryEffect> {
122+
return when {
123+
isLabBasedStatinNudgeEnabled -> labBasedStatinNudge(event, model)
124+
isNonLabBasedStatinNudgeEnabled -> nonLabBasedStatinNudge(event, model)
125+
else -> {
126+
throw IllegalArgumentException("Unknown case, statin prescription check info is unhandled")
127+
}
128+
}
129+
}
130+
131+
private fun labBasedStatinNudge(
132+
event: StatinPrescriptionCheckInfoLoaded,
133+
model: PatientSummaryModel
134+
): Next<PatientSummaryModel, PatientSummaryEffect> {
135+
val hasHadStroke = event.medicalHistory.hasHadStroke == Yes
136+
val hasHadHeartAttack = event.medicalHistory.hasHadHeartAttack == Yes
137+
val hasDiabetes = event.medicalHistory.diagnosedWithDiabetes == Yes
138+
139+
val hasCVD = hasHadStroke || hasHadHeartAttack
140+
val areStatinsPrescribedAlready = event.prescriptions.any { it.name.contains("statin", ignoreCase = true) }
141+
val canPrescribeStatin = event.isPatientDead.not() &&
142+
event.wasBPMeasuredWithin90Days &&
143+
areStatinsPrescribedAlready.not()
144+
145+
val isEligibleForLabBasedCvdRisk =
146+
event.age in minAgeForStatin..maxAgeForCVDRisk && isLabBasedStatinNudgeEnabled && canPrescribeStatin
147+
val shouldCalculateCVDRisk = event.cvdRiskRange == null || event.hasMedicalHistoryChanged || !event.wasCVDCalculatedWithin90Days
148+
val isCvdRiskCalculationRequired = isEligibleForLabBasedCvdRisk && shouldCalculateCVDRisk
149+
150+
return when {
151+
hasCVD -> {
152+
val updatedModel = model.updateStatinInfo(
153+
StatinInfo(
154+
canPrescribeStatin = canPrescribeStatin,
155+
hasCVD = true
156+
)
157+
)
158+
159+
next(updatedModel)
160+
}
161+
162+
hasDiabetes && event.age > maxAgeForCVDRisk -> {
163+
val updatedModel = model.updateStatinInfo(
164+
StatinInfo(
165+
canPrescribeStatin = canPrescribeStatin,
166+
hasDiabetes = true
167+
)
168+
)
169+
170+
next(updatedModel)
171+
}
172+
173+
(hasDiabetes && isCvdRiskCalculationRequired) || isCvdRiskCalculationRequired -> {
174+
dispatch(CalculateLabBasedCVDRisk(model.patientSummaryProfile!!.patient))
175+
}
176+
177+
isEligibleForLabBasedCvdRisk -> {
178+
dispatch(LoadStatinInfo(model.patientUuid))
179+
}
180+
181+
else -> {
182+
val updatedModel = model.updateStatinInfo(
183+
StatinInfo(
184+
canPrescribeStatin = false,
185+
hasCVD = false
186+
)
187+
)
188+
next(updatedModel)
189+
}
190+
}
191+
}
192+
193+
private fun nonLabBasedStatinNudge(
194+
event: StatinPrescriptionCheckInfoLoaded,
195+
model: PatientSummaryModel
118196
): Next<PatientSummaryModel, PatientSummaryEffect> {
119197
val hasHadStroke = event.medicalHistory.hasHadStroke == Yes
120198
val hasHadHeartAttack = event.medicalHistory.hasHadHeartAttack == Yes
@@ -182,19 +260,57 @@ class PatientSummaryUpdate(
182260
event: StatinInfoLoaded,
183261
model: PatientSummaryModel
184262
): Next<PatientSummaryModel, PatientSummaryEffect> {
185-
val canShowSmokingStatusDialog =
186-
event.statinInfo.canPrescribeStatin &&
187-
(event.statinInfo.cvdRisk?.level == CVDRiskLevel.LOW_HIGH ||
188-
event.statinInfo.cvdRisk?.level == CVDRiskLevel.MEDIUM_HIGH) &&
189-
event.statinInfo.isSmoker == MedicalHistoryAnswer.Unanswered &&
263+
val age = event.age
264+
val medicalHistory = event.medicalHistory
265+
val hasCVD = medicalHistory.hasHadStroke == Yes || medicalHistory.hasHadHeartAttack == Yes
266+
val hasDiabetes = medicalHistory.diagnosedWithDiabetes == Yes
267+
val cholesterol = medicalHistory.cholesterol
268+
val bmiReading = event.bmiReading
269+
val calculatedRiskRange = event.riskRange
270+
val canPrescribeStatin = if (isLabBasedStatinNudgeEnabled) {
271+
true
272+
} else {
273+
calculatedRiskRange?.canPrescribeStatin ?: false
274+
}
275+
276+
val canShowSmokingStatusDialog = canPrescribeStatin &&
277+
(calculatedRiskRange?.level == CVDRiskLevel.LOW_HIGH ||
278+
calculatedRiskRange?.level == CVDRiskLevel.MEDIUM_HIGH) &&
279+
medicalHistory.isSmoking == MedicalHistoryAnswer.Unanswered &&
190280
!model.hasShownSmokingStatusDialog
191281

282+
val riskRange = when {
283+
isLabBasedStatinNudgeEnabled -> labBasedRiskRange(calculatedRiskRange)
284+
else -> calculatedRiskRange
285+
}
286+
287+
val statinInfo = StatinInfo(
288+
canPrescribeStatin = canPrescribeStatin,
289+
cvdRisk = riskRange,
290+
isSmoker = medicalHistory.isSmoking,
291+
bmiReading = bmiReading,
292+
hasCVD = hasCVD,
293+
hasDiabetes = hasDiabetes,
294+
age = age,
295+
cholesterol = cholesterol,
296+
)
297+
192298
return if (canShowSmokingStatusDialog) {
193-
next(model.updateStatinInfo(event.statinInfo)
194-
.showSmokingStatusDialog(),
195-
ShowSmokingStatusDialog)
299+
next(model.updateStatinInfo(statinInfo).showSmokingStatusDialog(), ShowSmokingStatusDialog)
300+
} else {
301+
next(model.updateStatinInfo(statinInfo))
302+
}
303+
}
304+
305+
private fun labBasedRiskRange(calculatedRiskRange: CVDRiskRange?): CVDRiskRange? {
306+
if (calculatedRiskRange == null) return null
307+
308+
val reqMaxRiskRange = 10
309+
310+
return if (calculatedRiskRange.max < reqMaxRiskRange) {
311+
null
196312
} else {
197-
next(model.updateStatinInfo(event.statinInfo))
313+
calculatedRiskRange
198314
}
199315
}
200316

app/src/main/java/org/simple/clinic/summary/PatientSummaryViewEffectHandler.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class PatientSummaryViewEffectHandler(
4040
is ShowHypertensionDiagnosisWarning -> uiActions.showHypertensionDiagnosisWarning(viewEffect.continueToDiabetesDiagnosisWarning)
4141
is ShowSmokingStatusDialog -> uiActions.showSmokingStatusDialog()
4242
is OpenBMIEntrySheet -> uiActions.openBMIEntrySheet(viewEffect.patientUuid)
43+
is OpenCholesterolEntrySheet -> uiActions.openCholesterolEntrySheet(viewEffect.patientUuid)
4344
}.exhaustive()
4445
}
4546
}

0 commit comments

Comments
 (0)