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
2 changes: 1 addition & 1 deletion app/src/main/java/com/podometer/data/db/SensorWindow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import androidx.room.Entity
import androidx.room.PrimaryKey

/**
* Room entity storing a single 5-second classifier window of raw sensor data.
* Room entity storing a single 30-second classifier window of raw sensor data.
*
* These windows are recorded continuously by [com.podometer.service.StepTrackingService]
* and retained for 7 days. They enable retroactive recomputation of activity sessions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ package com.podometer.domain.model
* this session. Used for override/reclassification.
* @property isManualOverride True when the opening transition was manually
* overridden by the user.
* @property stepCount Total steps detected during this session.
*/
data class ActivitySession(
val activity: ActivityState,
val startTime: Long,
val endTime: Long?,
val startTransitionId: Int,
val isManualOverride: Boolean,
val stepCount: Int = 0,
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import javax.inject.Singleton
* activity sessions for a given date.
*
* This enables retroactive recomputation when classifier parameters change —
* the raw 5-second windows are retained for 7 days and can be replayed at any
* the raw 30-second windows are retained for 7 days and can be replayed at any
* time to generate updated session data.
*/
interface RecomputeActivitySessionsUseCase {
Expand Down Expand Up @@ -94,7 +94,26 @@ class RecomputeActivitySessionsUseCaseImpl @Inject constructor(
}
}

return buildActivitySessions(transitions, nowMillis)
val sessions = buildActivitySessions(transitions, nowMillis)
return attachStepCounts(sessions, windows)
}

/**
* Sums [SensorWindow.stepCount] for each session's time range and returns
* sessions with [ActivitySession.stepCount] populated.
*/
private fun attachStepCounts(
sessions: List<ActivitySession>,
windows: List<SensorWindow>,
): List<ActivitySession> {
if (sessions.isEmpty() || windows.isEmpty()) return sessions
return sessions.map { session ->
val endTime = session.endTime ?: Long.MAX_VALUE
val steps = windows
.filter { it.timestamp in session.startTime until endTime }
.sumOf { it.stepCount }
session.copy(stepCount = steps)
}
}
}
}
14 changes: 13 additions & 1 deletion app/src/main/java/com/podometer/ui/dashboard/ActivityLog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,19 @@ private fun ActivityLogItem(
text = durationText,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f),
)

if (session.stepCount > 0) {
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(R.string.activity_session_steps, session.stepCount),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}

Spacer(modifier = Modifier.weight(1f))

if (session.isManualOverride) {
Spacer(modifier = Modifier.width(8.dp))
AssistChip(
Expand Down Expand Up @@ -408,6 +418,7 @@ private fun PreviewActivityLogMultiple() {
endTime = 10L * hour + 30L * 60_000L,
startTransitionId = 1,
isManualOverride = false,
stepCount = 1247,
),
ActivitySession(
activity = ActivityState.CYCLING,
Expand All @@ -422,6 +433,7 @@ private fun PreviewActivityLogMultiple() {
endTime = null,
startTransitionId = 3,
isManualOverride = false,
stepCount = 312,
),
)
PodometerTheme {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@

<!-- ActivityLog labels -->
<string name="activity_session_ongoing">ongoing</string>
<string name="activity_session_steps">%1$d steps</string>
<string name="cd_activity_session_item">%1$s session, %2$s, %3$s</string>
<string name="cd_activity_override_options">Override options for session at %1$s</string>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ class RecomputeActivitySessionsUseCaseTest {
assertEquals(ActivityState.WALKING, sessions[0].activity)
}

@Test
fun `replayWindows attaches step counts to walking sessions`() {
val windows = (0 until 40).map { i ->
SensorWindow(
id = i.toLong(),
timestamp = 1_000_000L + i * 5_000L,
magnitudeVariance = 1.0,
stepFrequencyHz = 1.8,
stepCount = 9,
)
}

val sessions = RecomputeActivitySessionsUseCaseImpl.replayWindows(
windows = windows,
nowMillis = nowMillis,
)

assertTrue("Expected at least one session", sessions.isNotEmpty())
assertTrue("Walking session should have steps > 0", sessions[0].stepCount > 0)
}

@Test
fun `replayWindows detects cycling session from sustained cycling windows`() {
// First, we need some STILL state, then transition to cycling.
Expand Down