diff --git a/backend/app/routers/stats.py b/backend/app/routers/stats.py index b77905b..f7b6b99 100644 --- a/backend/app/routers/stats.py +++ b/backend/app/routers/stats.py @@ -110,43 +110,34 @@ async def get_wake_sleep_times( except ValueError: return {"days": []} - sessions, _ = await get_sessions(session, from_date=from_date, to_date=to_date) + # Fetch sessions starting from one day before to catch night sleep ending on from_date + adj_from_date = from_date - timedelta(days=1) + sessions, _ = await get_sessions(session, from_date=adj_from_date, to_date=to_date) days_data = [] current_date = from_date.date() while current_date <= to_date.date(): date_str = current_date.strftime("%Y-%m-%d") - day_sessions = [ - s - for s in sessions - if s.start_time.date() == current_date - or (s.end_time and s.end_time.date() == current_date) - ] - wakeup_min = None bedtime_min = None - if day_sessions: - # Find first wakeup after wakeup_ref - wakeups = [] - for s in day_sessions: - if s.end_time and s.end_time.time() >= w_ref_time: - diff = ( - s.end_time - datetime.combine(current_date, w_ref_time) - ).total_seconds() / 60.0 - wakeups.append(int(diff)) - if wakeups: - wakeup_min = min(wakeups) - - # Find last bedtime before bedtime_ref - bedtimes = [] - for s in day_sessions: - if s.start_time.time() <= b_ref_time: - diff = ( - s.start_time - datetime.combine(current_date, b_ref_time) - ).total_seconds() / 60.0 - bedtimes.append(int(diff)) - if bedtimes: - bedtime_min = max(bedtimes) + midnight_today = datetime.combine(current_date, time(0, 0)) + + night_session = None + for s in sessions: + # A session that runs over midnight into current_date + if s.start_time < midnight_today and s.end_time and s.end_time >= midnight_today: + night_session = s + break + + if night_session: + # Bedtime offset from b_ref_time on the day it started + s_start_date = night_session.start_time.date() + ref_datetime_bedtime = datetime.combine(s_start_date, b_ref_time) + bedtime_min = int((night_session.start_time - ref_datetime_bedtime).total_seconds() / 60.0) + + # Wakeup offset from w_ref_time on the current day + ref_datetime_wakeup = datetime.combine(current_date, w_ref_time) + wakeup_min = int((night_session.end_time - ref_datetime_wakeup).total_seconds() / 60.0) days_data.append( { diff --git a/frontend/src/components/stats/BedtimePlot.tsx b/frontend/src/components/stats/BedtimePlot.tsx index 642b6c7..9b2c2f8 100644 --- a/frontend/src/components/stats/BedtimePlot.tsx +++ b/frontend/src/components/stats/BedtimePlot.tsx @@ -18,8 +18,8 @@ interface WakeupBedtimeProps { bedtime_ref: string; } -const BedtimePlot: React.FC = ({ from_date, to_date, bedtime_ref }) => { - const { wakeupBedtime, isLoading } = useStats(from_date, to_date, '07:00', '20:00'); +const BedtimePlot: React.FC = ({ from_date, to_date, wakeup_ref, bedtime_ref }) => { + const { wakeupBedtime, isLoading } = useStats(from_date, to_date, '07:00', '20:00', wakeup_ref, bedtime_ref); const [chartData, setChartData] = useState<{ date: string; minutes: number; time: string }[]>([]); useEffect(() => { diff --git a/frontend/src/components/stats/DailySummary.tsx b/frontend/src/components/stats/DailySummary.tsx index 471ba81..0afdd18 100644 --- a/frontend/src/components/stats/DailySummary.tsx +++ b/frontend/src/components/stats/DailySummary.tsx @@ -40,9 +40,9 @@ const DailySummary: React.FC = ({ from_date, to_date, day_sta const data = dailySummary.days.map((day) => ({ date: day.date, - daySleep: day.day_minutes, - nightSleep: day.night_minutes, - total: day.total_minutes, + daySleep: day.day_minutes / 60, + nightSleep: day.night_minutes / 60, + total: day.total_minutes / 60, })); setChartData(data.reverse()); @@ -58,9 +58,9 @@ const DailySummary: React.FC = ({ from_date, to_date, day_sta ); const count = data.length; setAvgStats({ - avgTotal: Math.round(totals.total / count), - avgDay: Math.round(totals.day / count), - avgNight: Math.round(totals.night / count), + avgTotal: totals.total / count, + avgDay: totals.day / count, + avgNight: totals.night / count, count, }); } @@ -81,7 +81,12 @@ const DailySummary: React.FC = ({ from_date, to_date, day_sta margin={{ top: 5, right: 30, left: 60, bottom: 5 }} > - + `${Math.round(value)}h`} + tick={{ fontSize: 12 }} + tickLine={false} + /> = ({ from_date, to_date, day_sta /> [ - formatMinutes(value), + formatMinutes(Math.round(value * 60)), name === 'daySleep' ? 'Day' : 'Night', ]} /> @@ -102,15 +107,15 @@ const DailySummary: React.FC = ({ from_date, to_date, day_sta
Avg Total
-
{formatMinutes(avgStats.avgTotal)}
+
{formatMinutes(Math.round(avgStats.avgTotal * 60))}
Avg Day
-
{formatMinutes(avgStats.avgDay)}
+
{formatMinutes(Math.round(avgStats.avgDay * 60))}
Avg Night
-
{formatMinutes(avgStats.avgNight)}
+
{formatMinutes(Math.round(avgStats.avgNight * 60))}
diff --git a/frontend/src/components/stats/WakeupPlot.tsx b/frontend/src/components/stats/WakeupPlot.tsx index e1c1f6e..923ac83 100644 --- a/frontend/src/components/stats/WakeupPlot.tsx +++ b/frontend/src/components/stats/WakeupPlot.tsx @@ -18,8 +18,8 @@ interface WakeupBedtimeProps { bedtime_ref: string; } -const WakeupPlot: React.FC = ({ from_date, to_date, wakeup_ref }) => { - const { wakeupBedtime, isLoading } = useStats(from_date, to_date, '07:00', '20:00'); +const WakeupPlot: React.FC = ({ from_date, to_date, wakeup_ref, bedtime_ref }) => { + const { wakeupBedtime, isLoading } = useStats(from_date, to_date, '07:00', '20:00', wakeup_ref, bedtime_ref); const [chartData, setChartData] = useState<{ date: string; minutes: number; time: string }[]>([]); useEffect(() => { diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..d800a65 --- /dev/null +++ b/plan.md @@ -0,0 +1,34 @@ +# Investigation Plan: Bedtime Graph Issue + +## Objective +Investigate why the "Bedtime" graph shows unexpected times when a specific `bedtime_ref` is set. + +## Current Understanding +- The `BedtimePlot.tsx` component displays a graph where the Y-axis (or values) are formatted using `formatMinutesAfter(value, bedtime_ref)`. +- The data comes from the `/api/stats/wake-sleep-times` endpoint. +- The backend calculates `bedtime_minutes` based on sessions that started on or before the `bedtime_ref`. + +## Investigation Steps + +### Step 1: Analyze Backend Logic +- [ ] Examine `get_wake_sleep_times` in `backend/app/routers/stats.py` to understand how `bedtime_minutes` is calculated. +- [ ] Verify if the logic correctly identifies "bedtime" for various sleep session scenarios (e.g., sessions crossing midnight). +- [ ] Identify potential edge cases where the `s.start_time.time() <= b_ref_time` condition might be problematic. + +### Step 2: Analyze Frontend Logic +- [ ] Examine `BedtimePlot.tsx` and `useStats.ts` to see how the data is consumed and formatted. +- [ ] Understand how `formatMinutesAfter` interacts with the values returned by the backend. + +### Step 3: Data Simulation & Verification +- [ ] Create a set of test cases (mocked data) representing different sleep scenarios. +- [ ] Manually calculate the expected `bedtime_minutes` for these scenarios using the current logic. +- [ ] Compare with what the user reported to confirm the bug's nature. + +### Step 4: Propose and Implement Fixes +- [ ] Refine the backend logic to better represent "bedtime" if current logic is insufficient. +- [ ] Ensure the frontend correctly displays the intended time. +- [ ] Verify that changes do not break other parts of the stats page (e.g., Wakeup times). + +## Future Tasks +- [ ] Run tests to ensure correctness after implementation. +- [ ] Finalize documentation/explanation of the fix.