F2: Streak no longer counts aborted sessions

getDistinctSessionDays() now filters on status = 'completed' like every
other stat query, so starting and immediately exiting a session does not
extend the streak. Adds a regression test (completed today + aborted
yesterday => streak 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
David 2026-07-09 15:04:24 +02:00
parent 13f3b305bd
commit cd8636546a
2 changed files with 30 additions and 2 deletions

View file

@ -158,7 +158,7 @@ export const executionLogRepository = {
async getDistinctSessionDays() { async getDistinctSessionDays() {
return db.selectAll( return db.selectAll(
`SELECT DATE(started_at) AS day FROM execution_log GROUP BY DATE(started_at) ORDER BY day DESC`, `SELECT DATE(started_at) AS day FROM execution_log WHERE status = 'completed' GROUP BY DATE(started_at) ORDER BY day DESC`,
) )
}, },

View file

@ -16,6 +16,7 @@ Step 9 — Statistics
- test_stats_volume_chart_renders: Volume chart canvas renders when sessions have weight data - test_stats_volume_chart_renders: Volume chart canvas renders when sessions have weight data
- test_stats_progression_chart_renders: Progression chart renders after selecting an exercise - test_stats_progression_chart_renders: Progression chart renders after selecting an exercise
- test_stats_streak_consecutive_days: Streak counts consecutive days with sessions - test_stats_streak_consecutive_days: Streak counts consecutive days with sessions
- test_stats_streak_ignores_aborted_sessions: Aborted sessions do not extend the streak
""" """
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
@ -115,6 +116,7 @@ def _create_log_with_items(
reps: int = 10, reps: int = 10,
weight: float = 0.0, weight: float = 0.0,
completed: bool = True, completed: bool = True,
status: str = 'completed',
) -> str: ) -> str:
"""Seed one execution log with one item.""" """Seed one execution log with one item."""
finished_at = started_at # same timestamp is fine for tests finished_at = started_at # same timestamp is fine for tests
@ -123,7 +125,8 @@ def _create_log_with_items(
const logId = await window.__repos.executionLogs.create({{ const logId = await window.__repos.executionLogs.create({{
session_id: '{session_id}', session_id: '{session_id}',
started_at: '{started_at}', started_at: '{started_at}',
finished_at: '{finished_at}' finished_at: '{finished_at}',
status: {repr(status)}
}}) }})
await window.__repos.executionLogs.addItem({{ await window.__repos.executionLogs.addItem({{
log_id: logId, log_id: logId,
@ -510,3 +513,28 @@ def test_stats_streak_consecutive_days(page: Page, app_url: str):
streak_val = page.locator('[data-testid="stats-streak"] .stat-value').text_content() streak_val = page.locator('[data-testid="stats-streak"] .stat-value').text_content()
assert int(streak_val) >= 3, f"Expected streak ≥ 3, got {streak_val}" assert int(streak_val) >= 3, f"Expected streak ≥ 3, got {streak_val}"
def test_stats_streak_ignores_aborted_sessions(page: Page, app_url: str):
"""Aborted sessions do not extend the streak: one completed log today plus
one aborted log yesterday must give a streak of 1, not 2."""
_wait_for_db(page, app_url)
_delete_all_logs(page)
_delete_all_exercises(page)
_delete_all_sessions(page)
ex_id = _create_exercise(page, 'Aborted Streak Exercise')
sess_id = _create_session(page, 'Aborted Streak Session')
_create_log_with_items(page, sess_id, ex_id, _iso_days_ago(0), status='completed')
_create_log_with_items(page, sess_id, ex_id, _iso_days_ago(1), status='aborted')
page.reload()
page.wait_for_function(
"document.documentElement.getAttribute('data-db-ready') === 'true'",
timeout=15000,
)
_go_to_stats(page)
streak_val = page.locator('[data-testid="stats-streak"] .stat-value').text_content()
assert int(streak_val) == 1, f"Expected streak 1 (aborted must not count), got {streak_val}"