From 0a29e4c1560d0bb198b911fb7fbb6f769aaf9d1b Mon Sep 17 00:00:00 2001 From: sagarwal Date: Thu, 19 Mar 2026 15:49:17 +0530 Subject: [PATCH 1/5] Add `OverdueAppointmentSorter` --- .../java/org/simple/clinic/feature/Feature.kt | 1 + .../home/overdue/OverdueAppointmentSorter.kt | 66 +++++ .../simple/clinic/returnscore/ScoreType.kt | 9 +- .../overdue/OverdueAppointmentSorterTest.kt | 277 ++++++++++++++++++ .../kotlin/org/simple/clinic/TestData.kt | 10 +- 5 files changed, 357 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSorter.kt create mode 100644 app/src/test/java/org/simple/clinic/home/overdue/OverdueAppointmentSorterTest.kt diff --git a/app/src/main/java/org/simple/clinic/feature/Feature.kt b/app/src/main/java/org/simple/clinic/feature/Feature.kt index e08e558b7aa..488cecd8d28 100644 --- a/app/src/main/java/org/simple/clinic/feature/Feature.kt +++ b/app/src/main/java/org/simple/clinic/feature/Feature.kt @@ -28,4 +28,5 @@ enum class Feature( LabBasedStatinNudge(false, "lab_based_statin_nudge"), Screening(false, "screening_feature_v0"), ShowDiagnosisButton(false, "show_diagnosis_button"), + SortOverdueBasedOnReturnScore(false, "sort_overdue_based_on_return_score"), } diff --git a/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSorter.kt b/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSorter.kt new file mode 100644 index 00000000000..7c2e984265d --- /dev/null +++ b/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSorter.kt @@ -0,0 +1,66 @@ +package org.simple.clinic.home.overdue + +import org.simple.clinic.feature.Feature +import org.simple.clinic.feature.Features +import org.simple.clinic.returnscore.LikelyToReturnIfCalledScoreType +import org.simple.clinic.returnscore.ReturnScore +import java.util.UUID +import javax.inject.Inject +import kotlin.math.max +import kotlin.random.Random + +class OverdueAppointmentSorter @Inject constructor( + private val returnScoreDao: ReturnScore.RoomDao, + private val features: Features, + private val random: Random = Random.Default +) { + + fun sort(overdueAppointments: List): List { + if (!features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) { + return overdueAppointments + } + + val scores = returnScoreDao.getAllImmediate() + .filter { it.scoreType == LikelyToReturnIfCalledScoreType } + + val scoreMap: Map = scores.associate { + it.patientUuid to it.scoreValue + } + + val withScores = overdueAppointments.map { overdueAppointment -> + val score = scoreMap[overdueAppointment.appointment.patientUuid] ?: 0f + overdueAppointment to score + } + + val sorted = withScores.sortedByDescending { it.second } + + val total = sorted.size + if (total == 0) return overdueAppointments + + val top20End = max((total * 0.2).toInt(), 1) + val next30End = max((total * 0.5).toInt(), top20End) + + val top20 = sorted.take(top20End) + val next30 = sorted.subList(top20End, next30End) + val rest = sorted.drop(next30End) + + val topPickCount = max((top20.size * 0.5).toInt(), 1) + val nextPickCount = max((next30.size * 0.5).toInt(), 1) + + val topPicked = top20.shuffled(random).take(topPickCount) + val nextPicked = next30.shuffled(random).take(nextPickCount) + + val selectedSet = (topPicked + nextPicked).toSet() + + val topRemaining = top20.filterNot { it in selectedSet } + val nextRemaining = next30.filterNot { it in selectedSet } + + return ( + topPicked + + nextPicked + + topRemaining + + nextRemaining + + rest + ).map { it.first } + } +} diff --git a/app/src/main/java/org/simple/clinic/returnscore/ScoreType.kt b/app/src/main/java/org/simple/clinic/returnscore/ScoreType.kt index 1dcd3d5643b..781936c5b96 100644 --- a/app/src/main/java/org/simple/clinic/returnscore/ScoreType.kt +++ b/app/src/main/java/org/simple/clinic/returnscore/ScoreType.kt @@ -11,7 +11,8 @@ import org.simple.clinic.util.room.SafeEnumTypeAdapter sealed class ScoreType : Parcelable { object TypeAdapter : SafeEnumTypeAdapter( knownMappings = mapOf( - LikelyToReturnScoreType to "likely_to_return", + LikelyToReturnIfCalledScoreType to "likely_to_return_if_called", + LikelyToReturnIfNotCalledIn15DaysScoreType to "likely_to_return_if_not_called_in_15_days", ), unknownStringToEnumConverter = ::Unknown, unknownEnumToStringConverter = { (it as Unknown).actualValue } @@ -42,7 +43,11 @@ sealed class ScoreType : Parcelable { } @Parcelize -data object LikelyToReturnScoreType : ScoreType() +data object LikelyToReturnIfCalledScoreType : ScoreType() + +@Parcelize +data object LikelyToReturnIfNotCalledIn15DaysScoreType : ScoreType() + @Parcelize data class Unknown(val actualValue: String) : ScoreType() diff --git a/app/src/test/java/org/simple/clinic/home/overdue/OverdueAppointmentSorterTest.kt b/app/src/test/java/org/simple/clinic/home/overdue/OverdueAppointmentSorterTest.kt new file mode 100644 index 00000000000..7d074756040 --- /dev/null +++ b/app/src/test/java/org/simple/clinic/home/overdue/OverdueAppointmentSorterTest.kt @@ -0,0 +1,277 @@ +package org.simple.clinic.home.overdue + +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.kotlin.whenever +import org.simple.clinic.TestData +import org.simple.clinic.TestData.overdueAppointment +import org.simple.clinic.feature.Feature +import org.simple.clinic.feature.Features +import org.simple.clinic.returnscore.LikelyToReturnIfNotCalledIn15DaysScoreType +import org.simple.clinic.returnscore.ReturnScore +import org.simple.clinic.storage.Timestamps +import java.time.Instant +import java.util.UUID +import kotlin.random.Random + +class OverdueAppointmentSorterTest { + + private lateinit var returnScoreDao: ReturnScore.RoomDao + private lateinit var features: Features + private lateinit var sorter: OverdueAppointmentSorter + + @Before + fun setup() { + returnScoreDao = mock() + features = mock() + + sorter = OverdueAppointmentSorter( + returnScoreDao = returnScoreDao, + features = features, + random = Random(123) + ) + } + + @Test + fun `returns original list when feature disabled`() { + val list = (1..5).map { overdueAppointment(UUID.randomUUID()) } + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(false) + + val result = sorter.sort(list) + + assertThat(result).containsExactlyElementsIn(list).inOrder() + } + + @Test + fun `does not lose or duplicate patients`() { + val list = (1..20).map { overdueAppointment(UUID.randomUUID()) } + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(true) + + whenever(returnScoreDao.getAllImmediate()).thenReturn(emptyList()) + + val result = sorter.sort(list) + + val input = list.map { it.appointment.patientUuid } + val output = result.map { it.appointment.patientUuid } + + assertThat(output).containsExactlyElementsIn(input) + } + + @Test + fun `higher score patients appear before lower score patients`() { + val p1 = UUID.randomUUID() + val p2 = UUID.randomUUID() + + val list = listOf( + overdueAppointment(patientUuid = p1), + overdueAppointment(patientUuid = p2) + ) + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(true) + + whenever(returnScoreDao.getAllImmediate()).thenReturn( + listOf( + TestData.returnScore(patientUuid = p1, scoreValue = 10f), + TestData.returnScore(patientUuid = p2, scoreValue = 50f), + ) + ) + + val result = sorter.sort(list) + val uuids = result.map { it.appointment.patientUuid } + + assertThat(uuids.indexOf(p2)).isLessThan(uuids.indexOf(p1)) + } + + @Test + fun `same seed produces same result`() { + val uuids = (1..10).map { UUID.randomUUID() } + val list = uuids.map { overdueAppointment(patientUuid = it) } + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(true) + + val scores = uuids.mapIndexed { index, uuid -> + TestData.returnScore(patientUuid = uuid, scoreValue = (100 - index).toFloat()) + } + + whenever(returnScoreDao.getAllImmediate()).thenReturn(scores) + + val sorter1 = OverdueAppointmentSorter(returnScoreDao, features, Random(123)) + val sorter2 = OverdueAppointmentSorter(returnScoreDao, features, Random(123)) + + val result1 = sorter1.sort(list) + val result2 = sorter2.sort(list) + + assertThat(result1.map { it.appointment.patientUuid }) + .containsExactlyElementsIn(result2.map { it.appointment.patientUuid }) + .inOrder() + } + + @Test + fun `sorts by score descending`() { + val p1 = UUID.randomUUID() + val p2 = UUID.randomUUID() + val p3 = UUID.randomUUID() + + val list = listOf( + overdueAppointment(patientUuid = p1), + overdueAppointment(patientUuid = p2), + overdueAppointment(patientUuid = p3) + ) + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(true) + + whenever(returnScoreDao.getAllImmediate()).thenReturn( + listOf( + TestData.returnScore(patientUuid = p1, scoreValue = 10f), + TestData.returnScore(patientUuid = p2, scoreValue = 50f), + TestData.returnScore(patientUuid = p3, scoreValue = 30f), + ) + ) + + val result = sorter.sort(list) + + val uuids = result.map { it.appointment.patientUuid } + + assertThat(uuids.indexOf(p2)).isLessThan(uuids.indexOf(p1)) + assertThat(uuids.indexOf(p3)).isLessThan(uuids.indexOf(p1)) + } + + @Test + fun `ignores non LikelyToReturnIfCalled score types`() { + val uuid = UUID.randomUUID() + + val list = listOf(overdueAppointment(patientUuid = uuid)) + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(true) + + whenever(returnScoreDao.getAllImmediate()).thenReturn( + listOf( + ReturnScore( + uuid = UUID.randomUUID(), + patientUuid = uuid, + scoreType = LikelyToReturnIfNotCalledIn15DaysScoreType, + scoreValue = 100f, + timestamps = Timestamps( + createdAt = Instant.now(), + updatedAt = Instant.now(), + deletedAt = null + ) + ), + TestData.returnScore(uuid, scoreValue = 10f) + ) + ) + + val result = sorter.sort(list) + + val first = result.first().appointment.patientUuid + + assertThat(first).isEqualTo(uuid) + } + + @Test + fun `uses default score when missing`() { + val p1 = UUID.randomUUID() + val p2 = UUID.randomUUID() + + val list = listOf( + overdueAppointment(patientUuid = p1), + overdueAppointment(patientUuid = p2) + ) + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(true) + + whenever(returnScoreDao.getAllImmediate()).thenReturn( + listOf(TestData.returnScore(patientUuid = p1, scoreValue = 50f)) + ) + + val result = sorter.sort(list) + + val sorted = result.map { it.appointment.patientUuid } + + assertThat(sorted.first()).isEqualTo(p1) + } + + @Test + fun `picks patients only from top 20 and next 30 buckets`() { + val uuids = (1..10).map { UUID.randomUUID() } + val list = uuids.map { overdueAppointment(patientUuid = it) } + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(true) + + val scores = uuids.mapIndexed { index, uuid -> + TestData.returnScore(patientUuid = uuid, scoreValue = (100 - index).toFloat()) + } + + whenever(returnScoreDao.getAllImmediate()).thenReturn(scores) + + val result = sorter.sort(list) + + val top20 = uuids.take(2) + val next30 = uuids.subList(2, 5) + + val picked = result.take(2).map { it.appointment.patientUuid } + + picked.forEach { + assertThat(it in (top20 + next30)).isTrue() + } + } + + @Test + fun `handles single item safely`() { + val uuid = UUID.randomUUID() + + val list = listOf(overdueAppointment(patientUuid = uuid)) + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(true) + + whenever(returnScoreDao.getAllImmediate()).thenReturn( + listOf(TestData.returnScore(patientUuid = uuid, scoreValue = 10f)) + ) + + val result = sorter.sort(list) + + assertThat(result).hasSize(1) + } + + @Test + fun `all patients get default score when no valid score type`() { + val uuids = (1..5).map { UUID.randomUUID() } + val list = uuids.map { overdueAppointment(patientUuid = it) } + + whenever(features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) + .thenReturn(true) + + whenever(returnScoreDao.getAllImmediate()).thenReturn( + uuids.map { + ReturnScore( + uuid = UUID.randomUUID(), + patientUuid = it, + scoreType = LikelyToReturnIfNotCalledIn15DaysScoreType, + scoreValue = 100f, + timestamps = Timestamps( + createdAt = Instant.now(), + updatedAt = Instant.now(), + deletedAt = null + ) + ) + } + ) + + val result = sorter.sort(list) + + assertThat(result).hasSize(list.size) + } +} diff --git a/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt b/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt index 50cc180c753..91e57da9e7b 100644 --- a/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt +++ b/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt @@ -96,7 +96,7 @@ import org.simple.clinic.questionnaire.component.properties.InputFieldValidation import org.simple.clinic.questionnaire.component.properties.IntegerType import org.simple.clinic.questionnaire.component.properties.StringType import org.simple.clinic.questionnaireresponse.QuestionnaireResponse -import org.simple.clinic.returnscore.LikelyToReturnScoreType +import org.simple.clinic.returnscore.LikelyToReturnIfCalledScoreType import org.simple.clinic.returnscore.ReturnScore import org.simple.clinic.returnscore.ScoreType import org.simple.clinic.scanid.IndiaNHIDDateOfBirth @@ -2010,15 +2010,17 @@ object TestData { fun returnScore( uuid: UUID = UUID.fromString("f9a42c9f-01fe-40c5-b625-64b3e9868d5e"), - scoreType: ScoreType = LikelyToReturnScoreType, + patientUuid: UUID = UUID.fromString("f9a42c9f-01fe-40c5-b625-64b3e9868d5e"), + scoreType: ScoreType = LikelyToReturnIfCalledScoreType, + scoreValue: Float = 0f, createdAt: Instant = Instant.now(), updatedAt: Instant = Instant.now(), deletedAt: Instant? = null, ) = ReturnScore( uuid = uuid, - patientUuid = UUID.fromString("7ac2d657-6868-441c-9c0c-5c4a5dba87d7"), + patientUuid = patientUuid, scoreType = scoreType, - scoreValue = 9f, + scoreValue = scoreValue, timestamps = Timestamps( createdAt = createdAt, updatedAt = updatedAt, From d6581df17b35d1725632ae4f04ba5a0424334e0c Mon Sep 17 00:00:00 2001 From: sagarwal Date: Mon, 23 Mar 2026 17:55:37 +0530 Subject: [PATCH 2/5] Add sorter to the pending appointments --- .../simple/clinic/home/overdue/OverdueEffectHandler.kt | 8 +++++++- .../java/org/simple/clinic/overdue/AppointmentModule.kt | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/simple/clinic/home/overdue/OverdueEffectHandler.kt b/app/src/main/java/org/simple/clinic/home/overdue/OverdueEffectHandler.kt index fc06766d5a6..ab5260729c7 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/OverdueEffectHandler.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/OverdueEffectHandler.kt @@ -27,6 +27,7 @@ class OverdueEffectHandler @AssistedInject constructor( private val overdueDownloadScheduler: OverdueDownloadScheduler, private val userClock: UserClock, private val overdueAppointmentSelector: OverdueAppointmentSelector, + private val overdueAppointmentSorter: OverdueAppointmentSorter, @Assisted private val viewEffectsConsumer: Consumer ) { @@ -82,8 +83,13 @@ class OverdueEffectHandler @AssistedInject constructor( overdueAppointments = overdueAppointments ) val overdueSections = overdueAppointmentsWithInYear.groupBy { it.callResult?.outcome } + + val pendingAppointments = overdueSections[null].orEmpty() + + val sortedPendingAppointments = overdueAppointmentSorter.sort(pendingAppointments) + val overdueAppointmentSections = OverdueAppointmentSections( - pendingAppointments = overdueSections[null].orEmpty(), + pendingAppointments = sortedPendingAppointments, agreedToVisitAppointments = overdueSections[Outcome.AgreedToVisit].orEmpty(), remindToCallLaterAppointments = overdueSections[Outcome.RemindToCallLater].orEmpty(), removedFromOverdueAppointments = overdueSections[Outcome.RemovedFromOverdueList].orEmpty(), diff --git a/app/src/main/java/org/simple/clinic/overdue/AppointmentModule.kt b/app/src/main/java/org/simple/clinic/overdue/AppointmentModule.kt index 8b8d8e7bc66..b44a40f314a 100644 --- a/app/src/main/java/org/simple/clinic/overdue/AppointmentModule.kt +++ b/app/src/main/java/org/simple/clinic/overdue/AppointmentModule.kt @@ -10,12 +10,14 @@ import org.simple.clinic.overdue.TimeToAppointment.Days import org.simple.clinic.overdue.TimeToAppointment.Months import org.simple.clinic.overdue.TimeToAppointment.Weeks import org.simple.clinic.remoteconfig.ConfigReader +import org.simple.clinic.returnscore.ReturnScore import org.simple.clinic.util.preference.StringPreferenceConverter import org.simple.clinic.util.preference.getOptional import retrofit2.Retrofit import java.time.Period import java.util.Optional import javax.inject.Named +import kotlin.random.Random @Module class AppointmentModule { @@ -133,4 +135,9 @@ class AppointmentModule { @Provides fun providePendingAppointmentsConfig(configReader: ConfigReader) = PendingAppointmentsConfig.read(configReader) + + @Provides + fun provideRandom(): Random { + return Random.Default + } } From 1e72bd1afa1aab961d4172291d71bebc05ec1e27 Mon Sep 17 00:00:00 2001 From: sagarwal Date: Thu, 26 Mar 2026 12:25:25 +0530 Subject: [PATCH 3/5] Add return score debug values to list item --- .../java/org/simple/clinic/feature/Feature.kt | 1 + .../overdue/OverdueAppointmentSections.kt | 2 + .../home/overdue/OverdueAppointmentSorter.kt | 52 +++++++++++++------ .../clinic/home/overdue/OverdueBucket.kt | 7 +++ .../home/overdue/OverdueEffectHandler.kt | 6 ++- .../clinic/home/overdue/OverdueScreen.kt | 2 + .../home/overdue/SortedOverdueAppointment.kt | 7 +++ .../compose/OverdueAppointmentSections.kt | 3 ++ .../overdue/compose/OverduePatientListItem.kt | 33 ++++++++++++ .../home/overdue/compose/OverdueUiModel.kt | 4 ++ .../overdue/compose/OverdueUiModelMapper.kt | 20 +++++++ 11 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/org/simple/clinic/home/overdue/OverdueBucket.kt create mode 100644 app/src/main/java/org/simple/clinic/home/overdue/SortedOverdueAppointment.kt diff --git a/app/src/main/java/org/simple/clinic/feature/Feature.kt b/app/src/main/java/org/simple/clinic/feature/Feature.kt index 488cecd8d28..77666331dd0 100644 --- a/app/src/main/java/org/simple/clinic/feature/Feature.kt +++ b/app/src/main/java/org/simple/clinic/feature/Feature.kt @@ -29,4 +29,5 @@ enum class Feature( Screening(false, "screening_feature_v0"), ShowDiagnosisButton(false, "show_diagnosis_button"), SortOverdueBasedOnReturnScore(false, "sort_overdue_based_on_return_score"), + ShowReturnScoreDebugValues(false, "show_return_score_debug_values"), } diff --git a/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSections.kt b/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSections.kt index ea2457bd48b..619e5621d88 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSections.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSections.kt @@ -2,10 +2,12 @@ package org.simple.clinic.home.overdue import android.os.Parcelable import kotlinx.parcelize.Parcelize +import java.util.UUID @Parcelize data class OverdueAppointmentSections( val pendingAppointments: List, + val pendingDebugInfo: Map>, val agreedToVisitAppointments: List, val remindToCallLaterAppointments: List, val removedFromOverdueAppointments: List, diff --git a/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSorter.kt b/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSorter.kt index 7c2e984265d..2b9a08da00e 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSorter.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/OverdueAppointmentSorter.kt @@ -15,9 +15,16 @@ class OverdueAppointmentSorter @Inject constructor( private val random: Random = Random.Default ) { - fun sort(overdueAppointments: List): List { + fun sort(overdueAppointments: List): List { + if (!features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) { - return overdueAppointments + return overdueAppointments.map { + SortedOverdueAppointment( + appointment = it, + score = 0f, + bucket = OverdueBucket.REMAINING + ) + } } val scores = returnScoreDao.getAllImmediate() @@ -27,15 +34,15 @@ class OverdueAppointmentSorter @Inject constructor( it.patientUuid to it.scoreValue } - val withScores = overdueAppointments.map { overdueAppointment -> - val score = scoreMap[overdueAppointment.appointment.patientUuid] ?: 0f - overdueAppointment to score + val withScores = overdueAppointments.map { appointment -> + val score = scoreMap[appointment.appointment.patientUuid] ?: 0f + appointment to score } val sorted = withScores.sortedByDescending { it.second } val total = sorted.size - if (total == 0) return overdueAppointments + if (total == 0) return emptyList() val top20End = max((total * 0.2).toInt(), 1) val next30End = max((total * 0.5).toInt(), top20End) @@ -50,17 +57,30 @@ class OverdueAppointmentSorter @Inject constructor( val topPicked = top20.shuffled(random).take(topPickCount) val nextPicked = next30.shuffled(random).take(nextPickCount) - val selectedSet = (topPicked + nextPicked).toSet() + val selectedAppointments = (topPicked + nextPicked) + .map { it.first } + .toSet() + + val topRemaining = top20.filterNot { it.first in selectedAppointments } + val nextRemaining = next30.filterNot { it.first in selectedAppointments } - val topRemaining = top20.filterNot { it in selectedSet } - val nextRemaining = next30.filterNot { it in selectedSet } + fun mapToSorted( + list: List>, + bucket: OverdueBucket + ) = list.map { (appointment, score) -> + SortedOverdueAppointment( + appointment = appointment, + score = score, + bucket = bucket + ) + } - return ( - topPicked + - nextPicked + - topRemaining + - nextRemaining + - rest - ).map { it.first } + return buildList { + addAll(mapToSorted(topPicked, OverdueBucket.TOP_20)) + addAll(mapToSorted(nextPicked, OverdueBucket.NEXT_30)) + addAll(mapToSorted(topRemaining, OverdueBucket.TOP_20)) + addAll(mapToSorted(nextRemaining, OverdueBucket.NEXT_30)) + addAll(mapToSorted(rest, OverdueBucket.REMAINING)) + } } } diff --git a/app/src/main/java/org/simple/clinic/home/overdue/OverdueBucket.kt b/app/src/main/java/org/simple/clinic/home/overdue/OverdueBucket.kt new file mode 100644 index 00000000000..8533a5621a6 --- /dev/null +++ b/app/src/main/java/org/simple/clinic/home/overdue/OverdueBucket.kt @@ -0,0 +1,7 @@ +package org.simple.clinic.home.overdue + +enum class OverdueBucket { + TOP_20, + NEXT_30, + REMAINING +} diff --git a/app/src/main/java/org/simple/clinic/home/overdue/OverdueEffectHandler.kt b/app/src/main/java/org/simple/clinic/home/overdue/OverdueEffectHandler.kt index ab5260729c7..34be7ee1a00 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/OverdueEffectHandler.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/OverdueEffectHandler.kt @@ -87,9 +87,13 @@ class OverdueEffectHandler @AssistedInject constructor( val pendingAppointments = overdueSections[null].orEmpty() val sortedPendingAppointments = overdueAppointmentSorter.sort(pendingAppointments) + val debugMap = sortedPendingAppointments.associate { + it.appointment.appointment.patientUuid to (it.score to it.bucket) + } val overdueAppointmentSections = OverdueAppointmentSections( - pendingAppointments = sortedPendingAppointments, + pendingAppointments = sortedPendingAppointments.map { it.appointment }, + pendingDebugInfo = debugMap, agreedToVisitAppointments = overdueSections[Outcome.AgreedToVisit].orEmpty(), remindToCallLaterAppointments = overdueSections[Outcome.RemindToCallLater].orEmpty(), removedFromOverdueAppointments = overdueSections[Outcome.RemovedFromOverdueList].orEmpty(), diff --git a/app/src/main/java/org/simple/clinic/home/overdue/OverdueScreen.kt b/app/src/main/java/org/simple/clinic/home/overdue/OverdueScreen.kt index 8b08140c870..2dd9ebd68c1 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/OverdueScreen.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/OverdueScreen.kt @@ -31,6 +31,7 @@ import org.simple.clinic.databinding.ScreenOverdueBinding import org.simple.clinic.di.injector import org.simple.clinic.feature.Feature.OverdueInstantSearch import org.simple.clinic.feature.Feature.PatientReassignment +import org.simple.clinic.feature.Feature.ShowReturnScoreDebugValues import org.simple.clinic.feature.Features import org.simple.clinic.home.HomeScreen import org.simple.clinic.home.overdue.compose.OverdueScreenView @@ -249,6 +250,7 @@ class OverdueScreen : BaseScreen< isOverdueSelectAndDownloadEnabled = country.isoCountryCode == Country.INDIA, selectedOverdueAppointments = selectedOverdueAppointments, isPatientReassignmentFeatureEnabled = features.isEnabled(PatientReassignment), + showDebugValues = features.isEnabled(ShowReturnScoreDebugValues), locale = locale, ) diff --git a/app/src/main/java/org/simple/clinic/home/overdue/SortedOverdueAppointment.kt b/app/src/main/java/org/simple/clinic/home/overdue/SortedOverdueAppointment.kt new file mode 100644 index 00000000000..b5766f28075 --- /dev/null +++ b/app/src/main/java/org/simple/clinic/home/overdue/SortedOverdueAppointment.kt @@ -0,0 +1,7 @@ +package org.simple.clinic.home.overdue + +data class SortedOverdueAppointment( + val appointment: OverdueAppointment, + val score: Float, + val bucket: OverdueBucket +) diff --git a/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueAppointmentSections.kt b/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueAppointmentSections.kt index b1db2547633..5f9b2a74de7 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueAppointmentSections.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueAppointmentSections.kt @@ -60,6 +60,9 @@ fun OverdueAppointmentSections( isOverdueSelectAndDownloadEnabled = model.isOverdueSelectAndDownloadEnabled, isAppointmentSelected = model.isAppointmentSelected, isEligibleForReassignment = model.isEligibleForReassignment, + showDebugValues = model.showDebugValues, + returnScore = model.returnScore, + bucket = model.bucket, onCallClicked = onCallClicked, onRowClicked = onRowClicked, onCheckboxClicked = onCheckboxClicked diff --git a/app/src/main/java/org/simple/clinic/home/overdue/compose/OverduePatientListItem.kt b/app/src/main/java/org/simple/clinic/home/overdue/compose/OverduePatientListItem.kt index 2d4ed3fcc05..e3c80b086ac 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/compose/OverduePatientListItem.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/compose/OverduePatientListItem.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.simple.clinic.R import org.simple.clinic.common.ui.theme.SimpleTheme +import org.simple.clinic.home.overdue.OverdueBucket import org.simple.clinic.patient.Gender import org.simple.clinic.patient.displayIconRes import java.util.UUID @@ -44,6 +45,9 @@ fun OverduePatientListItem( isOverdueSelectAndDownloadEnabled: Boolean, isAppointmentSelected: Boolean, isEligibleForReassignment: Boolean, + showDebugValues: Boolean, + returnScore: Float?, + bucket: OverdueBucket?, onCallClicked: (UUID) -> Unit, onRowClicked: (UUID) -> Unit, onCheckboxClicked: (UUID) -> Unit @@ -97,6 +101,10 @@ fun OverduePatientListItem( style = SimpleTheme.typography.material.body2, color = SimpleTheme.colors.material.error, ) + + if (showDebugValues) { + DebugScoreView(returnScore = returnScore, bucket = bucket) + } } OverduePatientListItemRightButton( @@ -210,6 +218,28 @@ fun OverduePatientListItemRightButton( } } +@Composable +private fun DebugScoreView( + returnScore: Float?, + bucket: OverdueBucket? +) { + if (returnScore != null && bucket != null) { + + val bucketText = when (bucket) { + OverdueBucket.TOP_20 -> "Top 20%" + OverdueBucket.NEXT_30 -> "Next 30%" + OverdueBucket.REMAINING -> "Remaining" + } + + Text( + modifier = Modifier.padding(top = 4.dp), + text = "Score: ${"%.1f".format(returnScore)} | $bucketText", + style = SimpleTheme.typography.material.caption, + color = Color.Gray + ) + } +} + @Preview @Composable private fun OverduePatientListItemPreview() { @@ -226,6 +256,9 @@ private fun OverduePatientListItemPreview() { isOverdueSelectAndDownloadEnabled = false, isAppointmentSelected = false, isEligibleForReassignment = true, + showDebugValues = true, + returnScore = 9.2f, + bucket = OverdueBucket.TOP_20, onCallClicked = {}, onRowClicked = {}, onCheckboxClicked = {} diff --git a/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueUiModel.kt b/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueUiModel.kt index 6ee2e1ab260..34c0ee92e54 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueUiModel.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueUiModel.kt @@ -2,6 +2,7 @@ package org.simple.clinic.home.overdue.compose import androidx.annotation.StringRes import org.simple.clinic.home.overdue.OverdueAppointmentSectionTitle +import org.simple.clinic.home.overdue.OverdueBucket import org.simple.clinic.home.overdue.PendingListState import org.simple.clinic.patient.Gender import java.util.Locale @@ -20,6 +21,9 @@ sealed class OverdueUiModel { val isOverdueSelectAndDownloadEnabled: Boolean, val isAppointmentSelected: Boolean, val isEligibleForReassignment: Boolean, + val showDebugValues: Boolean, + val returnScore: Float? = null, + val bucket: OverdueBucket? = null ) : OverdueUiModel() data class Header( diff --git a/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueUiModelMapper.kt b/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueUiModelMapper.kt index 215c3ed96f0..ddf58307494 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueUiModelMapper.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/compose/OverdueUiModelMapper.kt @@ -8,6 +8,7 @@ import org.simple.clinic.home.overdue.OverdueAppointmentSectionTitle.PENDING_TO_ import org.simple.clinic.home.overdue.OverdueAppointmentSectionTitle.REMIND_TO_CALL import org.simple.clinic.home.overdue.OverdueAppointmentSectionTitle.REMOVED_FROM_OVERDUE import org.simple.clinic.home.overdue.OverdueAppointmentSections +import org.simple.clinic.home.overdue.OverdueBucket import org.simple.clinic.home.overdue.OverdueListSectionStates import org.simple.clinic.home.overdue.PendingListState.SEE_ALL import org.simple.clinic.home.overdue.PendingListState.SEE_LESS @@ -31,6 +32,7 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled: Boolean, selectedOverdueAppointments: Set, isPatientReassignmentFeatureEnabled: Boolean, + showDebugValues: Boolean, locale: Locale, ): List { val searchOverduePatientsButtonListItem = searchOverduePatientItem( @@ -45,6 +47,7 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled, selectedOverdueAppointments, isPatientReassignmentFeatureEnabled, + showDebugValues, locale, ) @@ -236,6 +239,7 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled: Boolean, selectedOverdueAppointments: Set, isPatientReassignmentFeatureEnabled: Boolean, + showDebugValues: Boolean, locale: Locale, ): List { val pendingAppointments = overdueAppointmentSections.pendingAppointments @@ -256,6 +260,7 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled, selectedOverdueAppointments, isPatientReassignmentFeatureEnabled, + showDebugValues ) val showPendingListFooter = pendingAppointments.size > pendingListDefaultStateSize && overdueListSectionStates.isPendingHeaderExpanded @@ -278,6 +283,7 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled: Boolean, selectedOverdueAppointments: Set, isPatientReassignmentFeatureEnabled: Boolean, + showDebugValues: Boolean, ): List { val pendingAppointmentsList = when (overdueListSectionStates.pendingListState) { SEE_LESS -> overdueAppointmentSections.pendingAppointments.take(pendingListDefaultStateSize) @@ -291,6 +297,8 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled, selectedOverdueAppointments, isPatientReassignmentFeatureEnabled, + showDebugValues, + overdueAppointmentSections.pendingDebugInfo ) return if (pendingAppointmentsList.isEmpty() && overdueListSectionStates.isPendingHeaderExpanded) { @@ -307,6 +315,8 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled: Boolean, selectedOverdueAppointments: Set, isPatientReassignmentFeatureEnabled: Boolean, + showDebugValues: Boolean = false, + debugMap: Map> = emptyMap() ): List { return if (isListExpanded) { overdueAppointment.map { @@ -317,6 +327,8 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled, isAppointmentSelected, isPatientReassignmentFeatureEnabled, + showDebugValues, + debugMap ) } } else { @@ -330,7 +342,12 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled: Boolean, isAppointmentSelected: Boolean, isPatientReassignmentFeatureEnabled: Boolean, + showDebugValues: Boolean, + debugMap: Map> ): OverdueUiModel { + val patientUuid = overdueAppointment.appointment.patientUuid + val debugInfo = debugMap[patientUuid] + return OverdueUiModel.Patient( appointmentUuid = overdueAppointment.appointment.uuid, patientUuid = overdueAppointment.appointment.patientUuid, @@ -343,6 +360,9 @@ class OverdueUiModelMapper { isOverdueSelectAndDownloadEnabled = isOverdueSelectAndDownloadEnabled, isAppointmentSelected = isAppointmentSelected, isEligibleForReassignment = (overdueAppointment.eligibleForReassignment == Answer.Yes) && isPatientReassignmentFeatureEnabled, + showDebugValues = showDebugValues, + returnScore = debugInfo?.first, + bucket = debugInfo?.second, ) } From 16294eac1ae63b8202e5d5f15cc8d6577cc93861 Mon Sep 17 00:00:00 2001 From: sagarwal Date: Thu, 26 Mar 2026 14:09:20 +0530 Subject: [PATCH 4/5] Fix failing test case --- .../overdue/OverdueAppointmentSorterTest.kt | 16 +++++----- .../home/overdue/OverdueEffectHandlerTest.kt | 11 +++++-- .../home/overdue/OverdueUiRendererTest.kt | 30 ++++++++++++------- .../clinic/home/overdue/OverdueUpdateTest.kt | 3 +- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/app/src/test/java/org/simple/clinic/home/overdue/OverdueAppointmentSorterTest.kt b/app/src/test/java/org/simple/clinic/home/overdue/OverdueAppointmentSorterTest.kt index 7d074756040..c15e8c26b48 100644 --- a/app/src/test/java/org/simple/clinic/home/overdue/OverdueAppointmentSorterTest.kt +++ b/app/src/test/java/org/simple/clinic/home/overdue/OverdueAppointmentSorterTest.kt @@ -58,7 +58,7 @@ class OverdueAppointmentSorterTest { val result = sorter.sort(list) val input = list.map { it.appointment.patientUuid } - val output = result.map { it.appointment.patientUuid } + val output = result.map { it.appointment.appointment.patientUuid } assertThat(output).containsExactlyElementsIn(input) } @@ -84,7 +84,7 @@ class OverdueAppointmentSorterTest { ) val result = sorter.sort(list) - val uuids = result.map { it.appointment.patientUuid } + val uuids = result.map { it.appointment.appointment.patientUuid } assertThat(uuids.indexOf(p2)).isLessThan(uuids.indexOf(p1)) } @@ -109,8 +109,8 @@ class OverdueAppointmentSorterTest { val result1 = sorter1.sort(list) val result2 = sorter2.sort(list) - assertThat(result1.map { it.appointment.patientUuid }) - .containsExactlyElementsIn(result2.map { it.appointment.patientUuid }) + assertThat(result1.map { it.appointment.appointment.patientUuid }) + .containsExactlyElementsIn(result2.map { it.appointment.appointment.patientUuid }) .inOrder() } @@ -139,7 +139,7 @@ class OverdueAppointmentSorterTest { val result = sorter.sort(list) - val uuids = result.map { it.appointment.patientUuid } + val uuids = result.map { it.appointment.appointment.patientUuid } assertThat(uuids.indexOf(p2)).isLessThan(uuids.indexOf(p1)) assertThat(uuids.indexOf(p3)).isLessThan(uuids.indexOf(p1)) @@ -173,7 +173,7 @@ class OverdueAppointmentSorterTest { val result = sorter.sort(list) - val first = result.first().appointment.patientUuid + val first = result.first().appointment.appointment.patientUuid assertThat(first).isEqualTo(uuid) } @@ -197,7 +197,7 @@ class OverdueAppointmentSorterTest { val result = sorter.sort(list) - val sorted = result.map { it.appointment.patientUuid } + val sorted = result.map { it.appointment.appointment.patientUuid } assertThat(sorted.first()).isEqualTo(p1) } @@ -221,7 +221,7 @@ class OverdueAppointmentSorterTest { val top20 = uuids.take(2) val next30 = uuids.subList(2, 5) - val picked = result.take(2).map { it.appointment.patientUuid } + val picked = result.take(2).map { it.appointment.appointment.patientUuid } picked.forEach { assertThat(it in (top20 + next30)).isTrue() diff --git a/app/src/test/java/org/simple/clinic/home/overdue/OverdueEffectHandlerTest.kt b/app/src/test/java/org/simple/clinic/home/overdue/OverdueEffectHandlerTest.kt index a3c0913a71f..4fa7221ecfd 100644 --- a/app/src/test/java/org/simple/clinic/home/overdue/OverdueEffectHandlerTest.kt +++ b/app/src/test/java/org/simple/clinic/home/overdue/OverdueEffectHandlerTest.kt @@ -9,6 +9,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever +import org.simple.clinic.TestData import org.simple.clinic.analytics.NetworkCapabilitiesProvider import org.simple.clinic.facility.FacilityConfig import org.simple.clinic.mobius.EffectHandlerTestCase @@ -19,9 +20,8 @@ import org.simple.clinic.overdue.callresult.Outcome import org.simple.clinic.overdue.download.OverdueDownloadScheduler import org.simple.clinic.overdue.download.OverdueListFileFormat.CSV import org.simple.clinic.util.PagerFactory -import org.simple.clinic.util.scheduler.TestSchedulersProvider -import org.simple.clinic.TestData import org.simple.clinic.util.TestUserClock +import org.simple.clinic.util.scheduler.TestSchedulersProvider import java.time.Instant import java.time.LocalDate import java.util.UUID @@ -48,6 +48,8 @@ class OverdueEffectHandlerTest { private val viewEffectHandler = OverdueViewEffectHandler(uiActions) private val appointmentRepository = mock() private val overdueAppointmentSelector = mock() + private val overdueAppointmentSorter = mock() + private val effectHandler = OverdueEffectHandler( schedulers = TestSchedulersProvider.trampoline(), appointmentRepository = appointmentRepository, @@ -57,6 +59,7 @@ class OverdueEffectHandlerTest { overdueDownloadScheduler = overdueDownloadScheduler, userClock = TestUserClock(Instant.parse("2018-01-01T00:00:00Z")), overdueAppointmentSelector = overdueAppointmentSelector, + overdueAppointmentSorter = overdueAppointmentSorter, viewEffectsConsumer = viewEffectHandler::handle ).build() private val effectHandlerTestCase = EffectHandlerTestCase(effectHandler) @@ -160,7 +163,8 @@ class OverdueEffectHandlerTest { overdueDownloadScheduler = overdueDownloadScheduler, userClock = TestUserClock(Instant.parse("2018-03-01T00:00:00Z")), overdueAppointmentSelector = overdueAppointmentSelector, - viewEffectsConsumer = viewEffectHandler::handle + viewEffectsConsumer = viewEffectHandler::handle, + overdueAppointmentSorter = overdueAppointmentSorter, ).build() val effectHandlerTestCase = EffectHandlerTestCase(effectHandler) @@ -252,6 +256,7 @@ class OverdueEffectHandlerTest { effectHandlerTestCase.assertOutgoingEvents(OverdueAppointmentsLoaded( overdueAppointmentSections = OverdueAppointmentSections( pendingAppointments = listOf(pendingAppointment), + pendingDebugInfo = emptyMap(), agreedToVisitAppointments = listOf(agreedToVisitAppointment), remindToCallLaterAppointments = emptyList(), removedFromOverdueAppointments = listOf(removedAppointment), diff --git a/app/src/test/java/org/simple/clinic/home/overdue/OverdueUiRendererTest.kt b/app/src/test/java/org/simple/clinic/home/overdue/OverdueUiRendererTest.kt index 46c2ebd50f9..0ca1b55c795 100644 --- a/app/src/test/java/org/simple/clinic/home/overdue/OverdueUiRendererTest.kt +++ b/app/src/test/java/org/simple/clinic/home/overdue/OverdueUiRendererTest.kt @@ -1,18 +1,18 @@ package org.simple.clinic.home.overdue +import org.junit.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions -import org.junit.Test -import org.simple.clinic.home.overdue.PendingListState.SEE_LESS import org.simple.clinic.TestData +import org.simple.clinic.home.overdue.PendingListState.SEE_LESS import java.util.UUID class OverdueUiRendererTest { private val ui = mock() private val uiRenderer = OverdueUiRenderer( - ui = ui + ui = ui ) private val defaultModel = OverdueModel.create() @@ -31,7 +31,8 @@ class OverdueUiRendererTest { agreedToVisitAppointments = agreedToVisitAppointments, remindToCallLaterAppointments = emptyList(), removedFromOverdueAppointments = removedFromOverdueAppointments, - moreThanAnYearOverdueAppointments = emptyList() + moreThanAnYearOverdueAppointments = emptyList(), + pendingDebugInfo = emptyMap() ) ) .selectedOverdueAppointmentsChanged(selectedAppointments) @@ -54,7 +55,8 @@ class OverdueUiRendererTest { agreedToVisitAppointments = agreedToVisitAppointments, remindToCallLaterAppointments = emptyList(), removedFromOverdueAppointments = removedFromOverdueAppointments, - moreThanAnYearOverdueAppointments = emptyList() + moreThanAnYearOverdueAppointments = emptyList(), + pendingDebugInfo = emptyMap() ), selectedAppointments, overdueListSectionStates = overdueListSectionStates @@ -76,7 +78,8 @@ class OverdueUiRendererTest { agreedToVisitAppointments = emptyList(), remindToCallLaterAppointments = emptyList(), removedFromOverdueAppointments = emptyList(), - moreThanAnYearOverdueAppointments = emptyList() + moreThanAnYearOverdueAppointments = emptyList(), + pendingDebugInfo = emptyMap() ) ) val overdueListSectionStates = OverdueListSectionStates( @@ -99,7 +102,8 @@ class OverdueUiRendererTest { agreedToVisitAppointments = emptyList(), remindToCallLaterAppointments = emptyList(), removedFromOverdueAppointments = emptyList(), - moreThanAnYearOverdueAppointments = emptyList() + moreThanAnYearOverdueAppointments = emptyList(), + pendingDebugInfo = emptyMap() ), emptySet(), overdueListSectionStates = overdueListSectionStates @@ -143,7 +147,8 @@ class OverdueUiRendererTest { agreedToVisitAppointments = emptyList(), remindToCallLaterAppointments = emptyList(), removedFromOverdueAppointments = emptyList(), - moreThanAnYearOverdueAppointments = emptyList() + moreThanAnYearOverdueAppointments = emptyList(), + pendingDebugInfo = emptyMap() )) .selectedOverdueAppointmentsChanged(selectedAppointments) @@ -157,7 +162,8 @@ class OverdueUiRendererTest { agreedToVisitAppointments = emptyList(), remindToCallLaterAppointments = emptyList(), removedFromOverdueAppointments = emptyList(), - moreThanAnYearOverdueAppointments = emptyList() + moreThanAnYearOverdueAppointments = emptyList(), + pendingDebugInfo = emptyMap() ), selectedOverdueAppointments = selectedAppointments, overdueListSectionStates = selectedAppointmentsModel.overdueListSectionStates @@ -183,7 +189,8 @@ class OverdueUiRendererTest { agreedToVisitAppointments = emptyList(), remindToCallLaterAppointments = emptyList(), removedFromOverdueAppointments = emptyList(), - moreThanAnYearOverdueAppointments = emptyList() + moreThanAnYearOverdueAppointments = emptyList(), + pendingDebugInfo = emptyMap() )) .selectedOverdueAppointmentsChanged(emptySet()) @@ -197,7 +204,8 @@ class OverdueUiRendererTest { agreedToVisitAppointments = emptyList(), remindToCallLaterAppointments = emptyList(), removedFromOverdueAppointments = emptyList(), - moreThanAnYearOverdueAppointments = emptyList() + moreThanAnYearOverdueAppointments = emptyList(), + pendingDebugInfo = emptyMap() ), selectedOverdueAppointments = emptySet(), overdueListSectionStates = selectedAppointmentsModel.overdueListSectionStates diff --git a/app/src/test/java/org/simple/clinic/home/overdue/OverdueUpdateTest.kt b/app/src/test/java/org/simple/clinic/home/overdue/OverdueUpdateTest.kt index e2a7b42b09d..997e41980da 100644 --- a/app/src/test/java/org/simple/clinic/home/overdue/OverdueUpdateTest.kt +++ b/app/src/test/java/org/simple/clinic/home/overdue/OverdueUpdateTest.kt @@ -7,6 +7,7 @@ import com.spotify.mobius.test.NextMatchers.hasNoModel import com.spotify.mobius.test.UpdateSpec import com.spotify.mobius.test.UpdateSpec.assertThatNext import org.junit.Test +import org.simple.clinic.TestData import org.simple.clinic.analytics.NetworkConnectivityStatus.ACTIVE import org.simple.clinic.analytics.NetworkConnectivityStatus.INACTIVE import org.simple.clinic.facility.FacilityConfig @@ -18,7 +19,6 @@ import org.simple.clinic.home.overdue.OverdueAppointmentSectionTitle.REMOVED_FRO import org.simple.clinic.home.overdue.PendingListState.SEE_ALL import org.simple.clinic.home.overdue.PendingListState.SEE_LESS import org.simple.clinic.overdue.download.OverdueListFileFormat.CSV -import org.simple.clinic.TestData import java.time.LocalDate import java.util.Optional import java.util.UUID @@ -161,6 +161,7 @@ class OverdueUpdateTest { val overdueAppointmentSections = OverdueAppointmentSections( pendingAppointments = pendingAppointments, + pendingDebugInfo = emptyMap(), agreedToVisitAppointments = agreedToVisitAppointments, remindToCallLaterAppointments = remindToCallLaterAppointments, removedFromOverdueAppointments = removedFromOverdueAppointments, From 5fc7457b27d5620ae1ac4819135c543c93175ecb Mon Sep 17 00:00:00 2001 From: sagarwal Date: Thu, 26 Mar 2026 14:13:23 +0530 Subject: [PATCH 5/5] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index edc61569ceb..728cb847334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ - Bump Jackson Core to v2.21.1 - Bump Compose BOM to v2026.03.00 +### Changes + +- Sort Overdue list based on return score when feature `sort_overdue_based_on_return_score` is enabled + ## 2026.03.02 ### Internal