Skip to content

Commit 615b7df

Browse files
authored
Add Suspected flag to recent patient list item (#5663)
1 parent b23bc27 commit 615b7df

File tree

10 files changed

+292
-178
lines changed

10 files changed

+292
-178
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
- Handle Screening feature visibility based on feature flag `Screening`
4040
- Add `hypertensionDiagnosedAt` and `diabetesDiagnosedAt` in `MedicalHistory` table
4141
- Hide schedule appointment sheet for suspected patients
42+
- Add `Suspected` flag to recent patient list item
4243

4344
## 2025.09.09
4445

app/src/androidTest/java/org/simple/clinic/patient/PatientRepositoryAndroidTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,8 @@ class PatientRepositoryAndroidTest {
922922
patientRecordedAt = this.recordedAt,
923923
updatedAt = recordedAt,
924924
eligibleForReassignment = eligibleForReassignment,
925+
diagnosedWithDiabetes = Yes,
926+
diagnosedWithHypertension = Yes
925927
)
926928

927929
private fun verifyRecentPatientOrder(
@@ -977,6 +979,8 @@ class PatientRepositoryAndroidTest {
977979
patientRecordedAt = this.recordedAt,
978980
updatedAt = updatedAt,
979981
eligibleForReassignment = eligibleForReassignment,
982+
diagnosedWithDiabetes = Yes,
983+
diagnosedWithHypertension = Yes
980984
)
981985
}
982986
}
@@ -1119,6 +1123,8 @@ class PatientRepositoryAndroidTest {
11191123
patientRecordedAt = this.recordedAt,
11201124
updatedAt = createdAt,
11211125
eligibleForReassignment = eligibleForReassignment,
1126+
diagnosedWithDiabetes = Yes,
1127+
diagnosedWithHypertension = Yes
11221128
)
11231129
}
11241130
}

app/src/main/java/org/simple/clinic/patient/RecentPatient.kt

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.simple.clinic.overdue.Appointment.Status
1212
import org.simple.clinic.util.Unicode
1313
import java.time.Instant
1414
import java.util.UUID
15+
import org.simple.clinic.medicalhistory.Answer as MedicalHistoryAnswer
1516

1617
@Parcelize
1718
data class RecentPatient(
@@ -30,6 +31,10 @@ data class RecentPatient(
3031
val updatedAt: Instant,
3132

3233
val eligibleForReassignment: Answer,
34+
35+
val diagnosedWithHypertension: MedicalHistoryAnswer?,
36+
37+
val diagnosedWithDiabetes: MedicalHistoryAnswer?
3338
) : Parcelable {
3439

3540
override fun toString(): String {
@@ -43,51 +48,65 @@ data class RecentPatient(
4348

4449
const val RECENT_PATIENT_QUERY = """
4550
SELECT P.uuid, P.fullName, P.gender, P.dateOfBirth, P.age_value,
46-
P.age_updatedAt, P.recordedAt patientRecordedAt, P.eligibleForReassignment,
47-
MAX(
48-
IFNULL(BP.latestRecordedAt, '0'),
49-
IFNULL(PD.latestUpdatedAt, '0'),
50-
IFNULL(AP.latestCreatedAt, '0'),
51-
IFNULL(BloodSugar.latestRecordedAt, '0')
52-
) updatedAt
51+
P.age_updatedAt, P.recordedAt patientRecordedAt, P.eligibleForReassignment,
52+
(
53+
SELECT diagnosedWithHypertension
54+
FROM MedicalHistory
55+
WHERE patientUuid = P.uuid
56+
ORDER BY updatedAt DESC
57+
LIMIT 1
58+
) diagnosedWithHypertension,
59+
(
60+
SELECT hasDiabetes
61+
FROM MedicalHistory
62+
WHERE patientUuid = P.uuid
63+
ORDER BY updatedAt DESC
64+
LIMIT 1
65+
) diagnosedWithDiabetes,
66+
MAX(
67+
IFNULL(BP.latestRecordedAt, '0'),
68+
IFNULL(PD.latestUpdatedAt, '0'),
69+
IFNULL(AP.latestCreatedAt, '0'),
70+
IFNULL(BloodSugar.latestRecordedAt, '0')
71+
) updatedAt
5372
FROM Patient P
5473
LEFT JOIN (
5574
SELECT MAX(recordedAt) latestRecordedAt, patientUuid, facilityUuid
56-
FROM BloodPressureMeasurement
57-
WHERE facilityUuid = :facilityUuid
75+
FROM BloodPressureMeasurement
76+
WHERE facilityUuid = :facilityUuid
5877
AND deletedAt IS NULL
59-
GROUP BY patientUuid
78+
GROUP BY patientUuid
6079
) BP ON P.uuid = BP.patientUuid
6180
LEFT JOIN (
6281
SELECT MAX(updatedAt) latestUpdatedAt, patientUuid, facilityUuid
63-
FROM PrescribedDrug
64-
WHERE facilityUuid = :facilityUuid
82+
FROM PrescribedDrug
83+
WHERE facilityUuid = :facilityUuid
6584
AND deletedAt IS NULL
66-
GROUP BY patientUuid
85+
GROUP BY patientUuid
6786
) PD ON P.uuid = PD.patientUuid
6887
LEFT JOIN (
6988
SELECT MAX(createdAt) latestCreatedAt, uuid, patientUuid, facilityUuid, creationFacilityUuid
70-
FROM Appointment
71-
WHERE creationFacilityUuid = :facilityUuid
89+
FROM Appointment
90+
WHERE creationFacilityUuid = :facilityUuid
7291
AND deletedAt IS NULL
7392
AND status = :appointmentStatus
7493
AND appointmentType = :appointmentType
75-
GROUP BY patientUuid
94+
GROUP BY patientUuid
7695
) AP ON P.uuid = AP.patientUuid
7796
LEFT JOIN (
7897
SELECT MAX(recordedAt) latestRecordedAt, patientUuid, facilityUuid
79-
FROM BloodSugarMeasurements
80-
WHERE facilityUuid = :facilityUuid
98+
FROM BloodSugarMeasurements
99+
WHERE facilityUuid = :facilityUuid
81100
AND deletedAt IS NULL
82-
GROUP BY patientUuid
101+
GROUP BY patientUuid
83102
) BloodSugar ON P.uuid = BloodSugar.patientUuid
84103
WHERE (
85104
(
86105
BP.facilityUuid = :facilityUuid OR
87106
PD.facilityUuid = :facilityUuid OR
88107
AP.creationFacilityUuid = :facilityUuid OR
89108
BloodSugar.facilityUuid = :facilityUuid
90-
)
109+
)
91110
AND P.deletedAt IS NULL
92111
AND P.status = :patientStatus
93112
)
@@ -96,14 +115,12 @@ data class RecentPatient(
96115
}
97116

98117
/**
99-
Goal: Fetch a list of patients with 10 most recent changes.
100-
There are tables like BloodPressureMeasurement (BP), PrescribedDrug (PD), Appointment (AP), etc. Let’s call each table T1, T2, T3, etc.
101-
102-
Algo:
103-
1. Get a list of all patients
104-
2. For each patient, from each table T, get the latest change for them. Columns: T1.latestUpdatedAt, T2.latestUpdatedAt, etc.
105-
3. Pick latestUpdatedAt for each patient
106-
4. Order by updatedAt from final list and cap it to 10 entries.
118+
* Goal: Fetch a list of patients with 10 most recent changes.
119+
* Algo:
120+
* 1. Get a list of all patients
121+
* 2. For each patient, from each table T, get the latest change for them.
122+
* 3. Pick latestUpdatedAt for each patient
123+
* 4. Order by updatedAt from final list and cap it to the limit.
107124
*/
108125
@Query("$RECENT_PATIENT_QUERY LIMIT :limit")
109126
fun recentPatientsWithLimit(

app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt

Lines changed: 12 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,17 @@ import androidx.paging.PagingData
44
import androidx.paging.map
55
import androidx.recyclerview.widget.DiffUtil
66
import io.reactivex.subjects.Subject
7-
import org.simple.clinic.R
8-
import org.simple.clinic.databinding.RecentPatientItemViewBinding
9-
import org.simple.clinic.patient.Gender
107
import org.simple.clinic.patient.RecentPatient
11-
import org.simple.clinic.patient.displayIconRes
128
import org.simple.clinic.util.UserClock
13-
import org.simple.clinic.util.toLocalDateAtZone
149
import org.simple.clinic.widgets.PagingItemAdapter
1510
import org.simple.clinic.widgets.UiEvent
1611
import org.simple.clinic.widgets.recyclerview.BindingViewHolder
17-
import org.simple.clinic.widgets.visibleOrGone
18-
import java.time.Instant
1912
import java.time.LocalDate
2013
import java.time.format.DateTimeFormatter
2114
import java.util.UUID
2215

2316
data class RecentPatientItem(
24-
val uuid: UUID,
25-
val name: String,
26-
val age: Int,
27-
val gender: Gender,
28-
val updatedAt: Instant,
29-
val dateFormatter: DateTimeFormatter,
30-
val clock: UserClock,
31-
val isNewRegistration: Boolean
17+
private val model: RecentPatientUiModel
3218
) : PagingItemAdapter.Item<UiEvent> {
3319

3420
companion object {
@@ -38,7 +24,6 @@ data class RecentPatientItem(
3824
dateFormatter: DateTimeFormatter
3925
): PagingData<RecentPatientItem> {
4026
val today = LocalDate.now(userClock)
41-
4227
return recentPatients.map { recentPatientItem(it, today, userClock, dateFormatter) }
4328
}
4429

@@ -48,36 +33,26 @@ data class RecentPatientItem(
4833
userClock: UserClock,
4934
dateFormatter: DateTimeFormatter
5035
): RecentPatientItem {
51-
val patientRegisteredOnDate = recentPatient.patientRecordedAt.toLocalDateAtZone(userClock.zone)
52-
val isNewRegistration = today == patientRegisteredOnDate
53-
54-
return RecentPatientItem(
55-
uuid = recentPatient.uuid,
56-
name = recentPatient.fullName,
57-
age = recentPatient.ageDetails.estimateAge(userClock),
58-
gender = recentPatient.gender,
59-
updatedAt = recentPatient.updatedAt,
36+
val model = RecentPatientUiModel.from(
37+
recentPatient = recentPatient,
38+
today = today,
39+
userClock = userClock,
6040
dateFormatter = dateFormatter,
61-
clock = userClock,
62-
isNewRegistration = isNewRegistration
41+
isEligibleForReassignment = false
6342
)
43+
44+
return RecentPatientItem(model)
6445
}
6546
}
6647

67-
override fun layoutResId(): Int = R.layout.recent_patient_item_view
48+
val uuid: UUID get() = model.uuid
6849

69-
override fun render(holder: BindingViewHolder, subject: Subject<UiEvent>) {
70-
val context = holder.itemView.context
71-
val binding = holder.binding as RecentPatientItemViewBinding
50+
override fun layoutResId(): Int = org.simple.clinic.R.layout.recent_patient_item_view
7251

73-
holder.itemView.setOnClickListener {
52+
override fun render(holder: BindingViewHolder, subject: Subject<UiEvent>) {
53+
RecentPatientViewBinder.bind(holder, model) {
7454
subject.onNext(RecentPatientItemClicked(patientUuid = uuid))
7555
}
76-
77-
binding.newRegistrationTextView.visibleOrGone(isNewRegistration)
78-
binding.patientNameTextView.text = context.resources.getString(R.string.patients_recentpatients_nameage, name, age.toString())
79-
binding.genderImageView.setImageResource(gender.displayIconRes)
80-
binding.lastSeenTextView.text = dateFormatter.format(updatedAt.toLocalDateAtZone(clock.zone))
8156
}
8257
}
8358

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package org.simple.clinic.recentpatient
2+
3+
import org.simple.clinic.R
4+
import org.simple.clinic.databinding.RecentPatientItemViewBinding
5+
import org.simple.clinic.medicalhistory.Answer.Suspected
6+
import org.simple.clinic.patient.Gender
7+
import org.simple.clinic.patient.RecentPatient
8+
import org.simple.clinic.patient.displayIconRes
9+
import org.simple.clinic.util.UserClock
10+
import org.simple.clinic.util.toLocalDateAtZone
11+
import org.simple.clinic.widgets.recyclerview.BindingViewHolder
12+
import org.simple.clinic.widgets.visibleOrGone
13+
import java.time.Instant
14+
import java.time.LocalDate
15+
import java.time.format.DateTimeFormatter
16+
import java.util.UUID
17+
18+
/**
19+
* Shared UI model used by both paged and non-paged adapters.
20+
*/
21+
data class RecentPatientUiModel(
22+
val uuid: UUID,
23+
val name: String,
24+
val age: Int,
25+
val gender: Gender,
26+
val updatedAt: Instant,
27+
val dateFormatter: DateTimeFormatter,
28+
val clock: UserClock,
29+
val isNewRegistration: Boolean,
30+
val isEligibleForReassignment: Boolean = false,
31+
val isSuspectedForHypertension: Boolean = false,
32+
val isSuspectedForDiabetes: Boolean = false
33+
) {
34+
companion object {
35+
fun from(
36+
recentPatient: RecentPatient,
37+
today: LocalDate,
38+
userClock: UserClock,
39+
dateFormatter: DateTimeFormatter,
40+
isEligibleForReassignment: Boolean = false
41+
): RecentPatientUiModel {
42+
val patientRegisteredOnDate = recentPatient.patientRecordedAt.toLocalDateAtZone(userClock.zone)
43+
val isNewRegistration = today == patientRegisteredOnDate
44+
45+
return RecentPatientUiModel(
46+
uuid = recentPatient.uuid,
47+
name = recentPatient.fullName,
48+
age = recentPatient.ageDetails.estimateAge(userClock),
49+
gender = recentPatient.gender,
50+
updatedAt = recentPatient.updatedAt,
51+
dateFormatter = dateFormatter,
52+
clock = userClock,
53+
isNewRegistration = isNewRegistration,
54+
isEligibleForReassignment = isEligibleForReassignment,
55+
isSuspectedForHypertension = recentPatient.diagnosedWithHypertension == Suspected,
56+
isSuspectedForDiabetes = recentPatient.diagnosedWithDiabetes == Suspected
57+
)
58+
}
59+
}
60+
}
61+
62+
object RecentPatientViewBinder {
63+
64+
fun bind(
65+
holder: BindingViewHolder,
66+
model: RecentPatientUiModel,
67+
onClick: (UUID) -> Unit
68+
) {
69+
val context = holder.itemView.context
70+
val binding = holder.binding as RecentPatientItemViewBinding
71+
72+
holder.itemView.setOnClickListener {
73+
onClick(model.uuid)
74+
}
75+
76+
val statusText: String? = when {
77+
model.isSuspectedForHypertension && model.isSuspectedForDiabetes ->
78+
context.getString(R.string.recent_patients_itemview_suspected_for_hypertension_and_diabetes)
79+
80+
model.isSuspectedForHypertension ->
81+
context.getString(R.string.recent_patients_itemview_suspected_for_hypertension)
82+
83+
model.isSuspectedForDiabetes ->
84+
context.getString(R.string.recent_patients_itemview_suspected_for_diabetes)
85+
86+
model.isNewRegistration ->
87+
context.getString(R.string.recent_patients_itemview_new_registration)
88+
89+
else -> null
90+
}
91+
92+
binding.patientStatusTextView.visibleOrGone(statusText != null)
93+
binding.patientStatusTextView.text = statusText
94+
binding.facilityReassignmentView.root.visibleOrGone(model.isEligibleForReassignment)
95+
96+
binding.patientNameTextView.text =
97+
context.resources.getString(R.string.patients_recentpatients_nameage, model.name, model.age.toString())
98+
99+
binding.genderImageView.setImageResource(model.gender.displayIconRes)
100+
binding.lastSeenTextView.text = model.dateFormatter.format(model.updatedAt.toLocalDateAtZone(model.clock.zone))
101+
}
102+
}

0 commit comments

Comments
 (0)