-
Notifications
You must be signed in to change notification settings - Fork 72
Sort Overdue list based on return_score #5764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| 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<OverdueAppointment>): List<SortedOverdueAppointment> { | ||
|
|
||
| if (!features.isEnabled(Feature.SortOverdueBasedOnReturnScore)) { | ||
| return overdueAppointments.map { | ||
| SortedOverdueAppointment( | ||
| appointment = it, | ||
| score = 0f, | ||
| bucket = OverdueBucket.REMAINING | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| val scores = returnScoreDao.getAllImmediate() | ||
| .filter { it.scoreType == LikelyToReturnIfCalledScoreType } | ||
|
|
||
| val scoreMap: Map<UUID, Float> = scores.associate { | ||
| it.patientUuid to it.scoreValue | ||
| } | ||
|
|
||
| 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 emptyList() | ||
|
|
||
| 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 selectedAppointments = (topPicked + nextPicked) | ||
| .map { it.first } | ||
| .toSet() | ||
|
|
||
| val topRemaining = top20.filterNot { it.first in selectedAppointments } | ||
| val nextRemaining = next30.filterNot { it.first in selectedAppointments } | ||
|
|
||
| fun mapToSorted( | ||
| list: List<Pair<OverdueAppointment, Float>>, | ||
| bucket: OverdueBucket | ||
| ) = list.map { (appointment, score) -> | ||
| SortedOverdueAppointment( | ||
| appointment = appointment, | ||
| score = score, | ||
| bucket = bucket | ||
| ) | ||
| } | ||
|
|
||
| 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)) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package org.simple.clinic.home.overdue | ||
|
|
||
| enum class OverdueBucket { | ||
| TOP_20, | ||
| NEXT_30, | ||
| REMAINING | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package org.simple.clinic.home.overdue | ||
|
|
||
| data class SortedOverdueAppointment( | ||
| val appointment: OverdueAppointment, | ||
| val score: Float, | ||
| val bucket: OverdueBucket | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| val bucketText = when (bucket) { | ||
| OverdueBucket.TOP_20 -> "Top 20%" | ||
| OverdueBucket.NEXT_30 -> "Next 30%" | ||
| OverdueBucket.REMAINING -> "Remaining" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| 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 = {} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<UUID>, | ||
| isPatientReassignmentFeatureEnabled: Boolean, | ||
| showDebugValues: Boolean, | ||
| locale: Locale, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ): List<OverdueUiModel> { | ||
| val searchOverduePatientsButtonListItem = searchOverduePatientItem( | ||
|
|
@@ -45,6 +47,7 @@ class OverdueUiModelMapper { | |
| isOverdueSelectAndDownloadEnabled, | ||
| selectedOverdueAppointments, | ||
| isPatientReassignmentFeatureEnabled, | ||
| showDebugValues, | ||
| locale, | ||
| ) | ||
|
|
||
|
|
@@ -236,6 +239,7 @@ class OverdueUiModelMapper { | |
| isOverdueSelectAndDownloadEnabled: Boolean, | ||
| selectedOverdueAppointments: Set<UUID>, | ||
| isPatientReassignmentFeatureEnabled: Boolean, | ||
| showDebugValues: Boolean, | ||
| locale: Locale, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ): List<OverdueUiModel> { | ||
| 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<UUID>, | ||
| isPatientReassignmentFeatureEnabled: Boolean, | ||
| showDebugValues: Boolean, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ): List<OverdueUiModel> { | ||
| 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<UUID>, | ||
| isPatientReassignmentFeatureEnabled: Boolean, | ||
| showDebugValues: Boolean = false, | ||
| debugMap: Map<UUID, Pair<Float, OverdueBucket>> = emptyMap() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ): List<OverdueUiModel> { | ||
| 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<UUID, Pair<Float, OverdueBucket>> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ): 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, | ||
| ) | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function with many returns (count = 3): sort [qlty:return-statements]