Project 5: Does training cost you sleep?
**Goal:** a script of your own that puts two lists side by side, your workouts and your nights, and answers a question your apps answer separately: do you sleep less after a hard day?
Goal: a script of your own that puts two lists side by side, your workouts and your nights, and answers a question your apps answer separately: do you sleep less after a hard day?
Time: 30 minutes. Level: some Python. Needs: project 2, with workouts from a watch, Strava, Polar, Suunto, intervals.icu or the Health app, and sleep from any source. They do not need to come from the same device.
1. Start from the connection block
awk '/^def nights_by_app/{exit} {print}' recipes/sleep-report.py > training-vs-sleep.py2. Join workouts and nights
Paste this at the end of training-vs-sleep.py:
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
def local(stamp, record):
"""A UTC time from the API, on the clock of the place it was recorded."""
t = datetime.fromisoformat(stamp[:19]).replace(tzinfo=timezone.utc)
zone = record["metadata"].get("timezone")
return t.astimezone(ZoneInfo(zone)) if zone else t.astimezone()
since = (datetime.now() - timedelta(days=56)).date()
# Minutes of training per day.
training = {}
for w in records("workouts", start=since.isoformat()):
day = local(w["start_date"], w).date()
training[day] = training.get(day, 0) + (w["duration_s"] or 0) / 60
# Hours of sleep per night, named by the morning it ends on.
sleep = {}
for s in records("sleep", start=since.isoformat()):
if s["bedtime_end"]:
morning = (local(s["bedtime_end"], s) + timedelta(hours=10)).date()
sleep[morning] = sleep.get(morning, 0) + (s["total_sleep_seconds"] or 0) / 3600
# The night after each day: trained 45 minutes or more, or rested.
groups = {"after 45+ min of training": [], "after a rest day": []}
for morning, hours in sleep.items():
minutes = training.get(morning - timedelta(days=1), 0)
if minutes >= 45:
groups["after 45+ min of training"].append(hours)
elif minutes == 0:
groups["after a rest day"].append(hours)
print(f"Sleep since {since:%d %b}")
for name, nights in groups.items():
if nights:
print(f" {name:27} {sum(nights) / len(nights):.2f} h ({len(nights)} nights)")python3 training-vs-sleep.pySleep since 18 Aug
after 45+ min of training 6.76 h (18 nights)
after a rest day 7.16 h (30 nights)How it works.
records("workouts")andrecords("sleep")read the two lists. A workout from Strava and a night from an Oura ring have the same shape here, so the script never asks where they came from.- Both are put on your local clock. The API sends UTC;
metadata.timezonesays where a record was made when the source knows it, otherwise the script uses your computer's zone. - Each night is matched with the day before it: the night that ends on Tuesday morning follows Monday's training.
- Days with a short workout (under 45 minutes) are left out of both groups, so the comparison is between clearly hard days and clearly easy ones.
Read it with care. A difference over a handful of nights can be chance, and a hard day can fall on the day you also went out. The count next to each average is there to remind you: the more nights, the more it means.
Make it yours
- Late sessions. Split the training days by the hour the workout started: before or after 18:00. Evening sessions are often the ones that shorten a night.
- HRV, not hours. Use
records("daily", start=...)and itshrv_avg_ms(rings and bands) in place of sleep hours, matched the same way. - By sport. Each workout has an
activity_type(running,cycling,swimming...): keep one and see if the answer changes.
Share it
Your script and what you found, even "no difference at all", in Show and tell. A finding that holds over months is worth a post of its own.
Project 4: Ask an AI about your training
**Goal:** an AI assistant such as Claude answering questions about your sleep, heart and training from your real numbers, through a token that can only read.
Project 6: Your own dashboard
**Goal:** one page with your steps, sleep, resting heart rate and HRV over the last months, every device together, and a training chart on it that you added yourself.