From 9a9c5971873b40268bb0f97fe76d85bdae2677d3 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 22 Mar 2026 12:09:25 +0100 Subject: [PATCH] fix: sleep import groups segments by gap instead of date boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Segments crossing midnight were split into different nights - 22:30-23:15 (21.03) → assigned to 21.03 - 00:30-02:45 (22.03) → assigned to 22.03 But both belong to the same night (21/22.03)! Solution: Gap-based grouping - Sort segments chronologically - Group segments with gap < 2 hours - Night date = wake_time.date() (last segment's end date) Co-Authored-By: Claude Opus 4.6 --- backend/routers/sleep.py | 46 ++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/backend/routers/sleep.py b/backend/routers/sleep.py index 916fa9f..ec8f056 100644 --- a/backend/routers/sleep.py +++ b/backend/routers/sleep.py @@ -484,13 +484,20 @@ async def import_apple_health_sleep( 'phase': phase_en }) - # Group by night (wake date) - nights = {} - for seg in segments: - wake_date = seg['end'].date() # Date of waking up + # Sort segments chronologically + segments.sort(key=lambda s: s['start']) - if wake_date not in nights: - nights[wake_date] = { + # Group segments into nights (gap-based) + # If gap between segments > 2 hours → new night + nights = [] + current_night = None + + for seg in segments: + # Start new night if: + # 1. First segment + # 2. Gap > 2 hours since last segment + if current_night is None or (seg['start'] - current_night['wake_time']).total_seconds() > 7200: + current_night = { 'bedtime': seg['start'], 'wake_time': seg['end'], 'segments': [], @@ -499,21 +506,28 @@ async def import_apple_health_sleep( 'light_minutes': 0, 'awake_minutes': 0 } + nights.append(current_night) - night = nights[wake_date] - night['segments'].append(seg) - night['wake_time'] = max(night['wake_time'], seg['end']) # Latest wake time - night['bedtime'] = min(night['bedtime'], seg['start']) # Earliest bed time + # Add segment to current night + current_night['segments'].append(seg) + current_night['wake_time'] = max(current_night['wake_time'], seg['end']) + current_night['bedtime'] = min(current_night['bedtime'], seg['start']) # Sum phases if seg['phase'] == 'deep': - night['deep_minutes'] += seg['duration_min'] + current_night['deep_minutes'] += seg['duration_min'] elif seg['phase'] == 'rem': - night['rem_minutes'] += seg['duration_min'] + current_night['rem_minutes'] += seg['duration_min'] elif seg['phase'] == 'light': - night['light_minutes'] += seg['duration_min'] + current_night['light_minutes'] += seg['duration_min'] elif seg['phase'] == 'awake': - night['awake_minutes'] += seg['duration_min'] + current_night['awake_minutes'] += seg['duration_min'] + + # Convert nights list to dict with wake_date as key + nights_dict = {} + for night in nights: + wake_date = night['wake_time'].date() # Date when you woke up + nights_dict[wake_date] = night # Insert nights imported = 0 @@ -522,7 +536,7 @@ async def import_apple_health_sleep( with get_db() as conn: cur = get_cursor(conn) - for date, night in nights.items(): + for date, night in nights_dict.items(): # Calculate total duration (sum of all phases) duration_minutes = ( night['deep_minutes'] + @@ -593,6 +607,6 @@ async def import_apple_health_sleep( return { "imported": imported, "skipped": skipped, - "total_nights": len(nights), + "total_nights": len(nights_dict), "message": f"{imported} Nächte importiert, {skipped} übersprungen (manuelle Einträge)" }