Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
- Update GH Actions `setup-jdk` to to v5
- Bump Gradle to v9.1.0
- Bump Mockito Kotlin to v6.0.0
- Add helper function `diagnosedAt` to update the htn and dm diagnosed at fields in medical history

### Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.simple.clinic.medicalhistory

import io.reactivex.Completable
import io.reactivex.Observable
import org.simple.clinic.medicalhistory.Answer.Suspected
import org.simple.clinic.medicalhistory.Answer.Unanswered
import org.simple.clinic.medicalhistory.sync.MedicalHistoryPayload
import org.simple.clinic.patient.PatientUuid
Expand Down Expand Up @@ -109,8 +110,18 @@ class MedicalHistoryRepository @Inject constructor(
isSmoking = historyEntry.isSmoking,
isUsingSmokelessTobacco = historyEntry.isUsingSmokelessTobacco,
cholesterol = null,
hypertensionDiagnosedAt = diagnosedAt(historyEntry.diagnosedWithHypertension, now),
diabetesDiagnosedAt = diagnosedAt(historyEntry.hasDiabetes, now),
hypertensionDiagnosedAt = diagnosedAt(
existingAnswer = null,
newAnswer = historyEntry.diagnosedWithHypertension,
now = now,
existingTimestamp = null
),
diabetesDiagnosedAt = diagnosedAt(
existingAnswer = null,
newAnswer = historyEntry.hasDiabetes,
now = now,
existingTimestamp = null
),
syncStatus = SyncStatus.PENDING,
createdAt = now,
updatedAt = now,
Expand All @@ -119,16 +130,20 @@ class MedicalHistoryRepository @Inject constructor(
}

fun save(history: MedicalHistory, updateTime: Instant) {
val existing = dao.getOne(history.uuid)

val htnDiagnosedAt = diagnosedAt(
existingAnswer = existing?.diagnosedWithHypertension,
newAnswer = history.diagnosedWithHypertension,
now = updateTime,
existingTimestamp = history.hypertensionDiagnosedAt
existingTimestamp = existing?.hypertensionDiagnosedAt
)

val diabetesDiagnosedAt = diagnosedAt(
existingAnswer = existing?.diagnosedWithDiabetes,
newAnswer = history.diagnosedWithDiabetes,
now = updateTime,
existingTimestamp = history.diabetesDiagnosedAt
existingTimestamp = existing?.diabetesDiagnosedAt
)

val dirtyHistory = history.copy(
Expand Down Expand Up @@ -202,14 +217,6 @@ class MedicalHistoryRepository @Inject constructor(
}
}

fun diagnosedAt(newAnswer: Answer, now: Instant, existingTimestamp: Instant? = null): Instant? {
return if (!newAnswer.isAnsweredWithYesOrNo) {
null
} else {
existingTimestamp ?: now
}
}

override fun pendingSyncRecordCount(): Observable<Int> {
return dao
.countWithStatus(SyncStatus.PENDING)
Expand All @@ -225,3 +232,20 @@ class MedicalHistoryRepository @Inject constructor(
)
}
}

fun diagnosedAt(
existingAnswer: Answer?,
newAnswer: Answer,
existingTimestamp: Instant?,
now: Instant
): Instant? {
if (newAnswer == Suspected) return null

if (existingTimestamp != null) return existingTimestamp

if (existingAnswer == null || !existingAnswer.isAnsweredWithYesOrNo) {
Comment thread
siddh1004 marked this conversation as resolved.
return if (newAnswer.isAnsweredWithYesOrNo) now else null
Comment thread
siddh1004 marked this conversation as resolved.
}

return null
Comment thread
siddh1004 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.simple.clinic.medicalhistory


import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.simple.clinic.medicalhistory.Answer.No
import org.simple.clinic.medicalhistory.Answer.Suspected
import org.simple.clinic.medicalhistory.Answer.Unanswered
import org.simple.clinic.medicalhistory.Answer.Yes
import java.time.Instant

class DiagnosedAtTest {
private val now = Instant.parse("2025-11-25T10:15:30Z")
private val existingTs = Instant.parse("2025-01-01T00:00:00Z")

private fun call(
existingAnswer: Answer?,
newAnswer: Answer,
existingTimestamp: Instant?,
now: Instant
): Instant? {
return diagnosedAt(existingAnswer, newAnswer, existingTimestamp, now)
}

@Test
fun `when new answer is Suspected then returns null even if existing timestamp present`() {
assertNull(call(existingAnswer = Yes, newAnswer = Suspected, existingTimestamp = existingTs, now = now))
assertNull(call(existingAnswer = null, newAnswer = Suspected, existingTimestamp = existingTs, now = now))
}

@Test
fun `when existing timestamp is present and new answer is definitive then preserve the existing timestamp`() {
// If timestamp exists it should be returned (write-once).
assertEquals(existingTs, call(existingAnswer = Yes, newAnswer = Yes, existingTimestamp = existingTs, now = now))
assertEquals(existingTs, call(existingAnswer = Suspected, newAnswer = No, existingTimestamp = existingTs, now = now))
}

@Test
fun `when existingAnswer is null and new answer is yes or no then return now`() {
assertEquals(now, call(existingAnswer = null, newAnswer = Yes, existingTimestamp = null, now = now))
assertEquals(now, call(existingAnswer = null, newAnswer = No, existingTimestamp = null, now = now))
}

@Test
fun `when existingAnswer is null and new answer is Suspected or Unanswered then return null`() {
assertNull(call(existingAnswer = null, newAnswer = Suspected, existingTimestamp = null, now = now))
assertNull(call(existingAnswer = null, newAnswer = Unanswered, existingTimestamp = null, now = now))
}

@Test
fun `when existingAnswer is suspected or unanswered and new answer is yes or no then return now`() {
// Previously Suspected -> new definitive should stamp now
assertEquals(now, call(existingAnswer = Suspected, newAnswer = Yes, existingTimestamp = null, now = now))
assertEquals(now, call(existingAnswer = Unanswered, newAnswer = No, existingTimestamp = null, now = now))
}

@Test
fun `when existingAnswer is yes or no with no timestamp and new answer is Yes or No then return null`() {
assertNull(call(existingAnswer = Yes, newAnswer = No, existingTimestamp = null, now = now))
assertNull(call(existingAnswer = No, newAnswer = Yes, existingTimestamp = null, now = now))
assertNull(call(existingAnswer = Yes, newAnswer = Yes, existingTimestamp = null, now = now))
}

@Test
fun `When existing timestamp is present then always preserve it except when suspected where return null`() {
assertEquals(existingTs, call(existingAnswer = Yes, newAnswer = Yes, existingTimestamp = existingTs, now = now))
assertNull(call(existingAnswer = Yes, newAnswer = Suspected, existingTimestamp = existingTs, now = now))
}
}