fix: prevent double-counting of sleep sessions in stats by grouping by start date

This commit is contained in:
2026-06-03 09:20:33 +02:00
parent c371a3d8e8
commit 7e3bd0a16b
3 changed files with 76 additions and 84 deletions
+23 -18
View File
@@ -113,22 +113,27 @@ async def update_session(
return sleep_session
async def get_all_time_stats(session: AsyncSession) -> tuple[int, float]:
statement = select(func.count(), func.min(SleepSession.start_time))
async def get_sleep_counts_by_start_date(
session: AsyncSession,
from_date: Optional[datetime] = None,
to_date: Optional[datetime] = None,
) -> List[tuple]:
if to_date and to_date.hour == 0 and to_date.minute == 0 and to_date.second == 0 and to_date.microsecond == 0:
to_date = to_date.replace(hour=23, minute=59, second=59, microsecond=999999)
query_to_date = to_date.replace(tzinfo=None) if to_date and to_date.tzinfo else to_date
query_from_date = from_date.replace(tzinfo=None) if from_date and from_date.tzinfo else from_date
statement = (
select(func.date(SleepSession.start_time), func.count())
.group_by(func.date(SleepSession.start_time))
.order_by(func.date(SleepSession.start_time).desc())
)
if query_from_date:
statement = statement.where(SleepSession.start_time >= query_from_date)
if query_to_date:
statement = statement.where(SleepSession.start_time <= query_to_date)
result = await session.exec(statement)
total_sessions, first_session_time = result.one()
if total_sessions == 0 or first_session_time is None:
return 0, 0.0
# DB has naive datetimes
first_session_time_naive = first_session_time.replace(tzinfo=None)
# Get current date (naive for comparison)
today_date = datetime.now().date()
first_date = first_session_time_naive.date()
num_days = (today_date - first_date).days + 1
avg_sleeps = total_sessions / num_days if num_days > 0 else 0.0
return total_sessions, avg_sleeps
return list(result.all())
+19 -7
View File
@@ -3,7 +3,7 @@ from typing import List, Optional
from fastapi import APIRouter, Depends, Query
from sqlmodel.ext.asyncio.session import AsyncSession
from app.database import get_session
from app.crud import get_sessions, get_all_time_stats
from app.crud import get_sessions, get_sleep_counts_by_start_date
from app.schemas import DailySummaryResponse, TimetableResponse, WakeupBedtimeResponse, SummaryResponse
from app.utils.time_utils import get_day_night_splits
@@ -106,14 +106,26 @@ async def get_summary(
to_date: datetime,
session: AsyncSession = Depends(get_session),
):
sessions, _ = await get_sessions(session, from_date=from_date, to_date=to_date)
total_sleeps = len(sessions)
# Period Stats
period_counts = await get_sleep_counts_by_start_date(session, from_date=from_date, to_date=to_date)
total_sleeps = sum(count for _, count in period_counts)
num_days = (to_date.date() - from_date.date()).days + 1
avg_sleeps_per_day = total_sleeps / num_days if num_days > 0 else 0.0
total_sleeps_all_time, avg_sleeps_per_day_all_time = await get_all_time_stats(session)
# All-Time Stats
all_counts = await get_sleep_counts_by_start_date(session)
total_sleeps_all_time = sum(count for _, count in all_counts)
avg_sleeps_per_day_all_time = 0.0
if all_counts:
# counts are ordered by desc date (from crud.py)
# so last one is earliest.
earliest_date_str = all_counts[-1][0]
# SQLite date() returns YYYY-MM-DD string
earliest_date = datetime.strptime(earliest_date_str, "%Y-%m-%d").date()
today = datetime.now(timezone.utc).date()
days_all_time = (today - earliest_date).days + 1
avg_sleeps_per_day_all_time = total_sleeps_all_time / days_all_time if days_all_time > 0 else 0.0
return {
"total_sleeps": total_sleeps,
"avg_sleeps_per_day": avg_sleeps_per_day,