Write an application that allows me to track my baby sleep
Drop files here
or click to upload
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
import numpy as np
import os
# Set page config
st.set_page_config(page_title="Baby Sleep Tracker", layout="wide")
# Initialize database
from database import (
create_tables,
add_sleep_record,
get_all_sleep_records,
delete_sleep_record,
)
create_tables()
# Custom CSS
st.markdown("""
<style>
.main-header {
font-size: 2.5rem;
color: #1E90FF;
text-align: center;
margin-bottom: 1rem;
}
.sub-header {
font-size: 1.5rem;
color: #4169E1;
margin-top: 1.5rem;
margin-bottom: 1rem;
}
.card {
background-color: #f0f8ff;
border-radius: 10px;
padding: 15px;
margin-bottom: 20px;
}
</style>
""", unsafe_allow_html=True)
st.markdown("<h1 class='main-header'>Baby Sleep Tracker</h1>", unsafe_allow_html=True)
# Create tabs
tab1, tab2, tab3 = st.tabs(["Add Sleep Record", "View Sleep Data", "Analytics"])
with tab1:
st.markdown("<h2 class='sub-header'>Add New Sleep Record</h2>", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
st.markdown("<div class='card'>", unsafe_allow_html=True)
sleep_date = st.date_input("Date", datetime.now().date())
sleep_start_time = st.time_input("Sleep Start Time", datetime.now().time())
sleep_end_time = st.time_input("Sleep End Time", (datetime.now() + timedelta(hours=1)).time())
sleep_quality = st.slider("Sleep Quality (1-5)", 1, 5, 3)
sleep_location = st.selectbox(
"Sleep Location",
["Crib", "Bassinet", "Parent's Bed", "Stroller", "Car Seat", "Other"]
)
notes = st.text_area("Notes (optional)")
st.markdown("</div>", unsafe_allow_html=True)
with col2:
st.markdown("<div class='card'>", unsafe_allow_html=True)
st.markdown("### Sleep Environment")
col_a, col_b = st.columns(2)
with col_a:
room_temp = st.number_input("Room Temperature (°F)", 60, 85, 72)
white_noise = st.checkbox("White Noise Machine")
with col_b:
night_light = st.checkbox("Night Light")
swaddled = st.checkbox("Swaddled")
sleep_position = st.radio(
"Sleep Position",
["Back", "Side", "Stomach"],
index=0,
horizontal=True
)
feeding_before_sleep = st.checkbox("Feeding before sleep")
st.markdown("</div>", unsafe_allow_html=True)
if st.button("Save Sleep Record", use_container_width=True):
# Calculate duration
start_datetime = datetime.combine(sleep_date, sleep_start_time)
end_datetime = datetime.combine(sleep_date, sleep_end_time)
# Handle overnight sleep
if end_datetime <= start_datetime:
end_datetime = end_datetime + timedelta(days=1)
duration_minutes = (end_datetime - start_datetime).total_seconds() / 60
# Create environment info
environment = {
"room_temp": room_temp,
"white_noise": white_noise,
"night_light": night_light,
"swaddled": swaddled,
"sleep_position": sleep_position,
"feeding_before_sleep": feeding_before_sleep
}
# Add to database
add_sleep_record(
sleep_date.strftime("%Y-%m-%d"),
sleep_start_time.strftime("%H:%M"),
sleep_end_time.strftime("%H:%M"),
duration_minutes,
sleep_quality,
sleep_location,
notes,
environment
)
st.success("Sleep record added successfully!")
with tab2:
st.markdown("<h2 class='sub-header'>View Sleep Records</h2>", unsafe_allow_html=True)
# Get all records
records = get_all_sleep_records()
if records:
# Convert to DataFrame
df = pd.DataFrame(records)
# Convert environment from string to dict
df['environment'] = df['environment'].apply(eval)
# Add formatted duration
df['duration_formatted'] = df['duration_minutes'].apply(
lambda x: f"{int(x // 60)}h {int(x % 60)}m"
)
# Format date and times
df['formatted_date'] = pd.to_datetime(df['date']).dt.strftime('%a, %b %d, %Y')
# Display options
col1, col2 = st.columns([1, 3])
with col1:
st.markdown("<div class='card'>", unsafe_allow_html=True)
st.subheader("Filter Options")
# Date range filter
date_range = st.date_input(
"Date Range",
[
pd.to_datetime(df['date']).min().date(),
pd.to_datetime(df['date']).max().date()
],
key="date_range"
)
# Location filter
locations = df['location'].unique().tolist()
selected_locations = st.multiselect(
"Sleep Locations",
options=locations,
default=locations
)
# Quality filter
min_quality, max_quality = st.select_slider(
"Sleep Quality Range",
options=list(range(1, 6)),
value=(1, 5)
)
st.markdown("</div>", unsafe_allow_html=True)
with col2:
# Apply filters
filtered_df = df.copy()
if len(date_range) == 2:
start_date, end_date = date_range
mask = (pd.to_datetime(filtered_df['date']).dt.date >= start_date) & \
(pd.to_datetime(filtered_df['date']).dt.date <= end_date)
filtered_df = filtered_df[mask]
filtered_df = filtered_df[filtered_df['location'].isin(selected_locations)]
filtered_df = filtered_df[(filtered_df['sleep_quality'] >= min_quality) &
(filtered_df['sleep_quality'] <= max_quality)]
# Show filtered records
if not filtered_df.empty:
st.subheader(f"Sleep Records ({len(filtered_df)} records)")
# Create display columns
display_df = filtered_df[['id', 'formatted_date', 'start_time', 'end_time',
'duration_formatted', 'sleep_quality', 'location']].copy()
display_df.columns = ['ID', 'Date', 'Start Time', 'End Time', 'Duration', 'Quality', 'Location']
st.dataframe(
display_df,
use_container_width=True,
height=400,
hide_index=True
)
# Record detail section
st.subheader("Record Details")
selected_id = st.selectbox("Select Record ID to View Details", filtered_df['id'])
if selected_id:
record = filtered_df[filtered_df['id'] == selected_id].iloc[0]
col_a, col_b, col_c = st.columns(3)
with col_a:
st.markdown("#### Basic Info")
st.write(f"**Date:** {record['formatted_date']}")
st.write(f"**Time:** {record['start_time']} - {record['end_time']}")
st.write(f"**Duration:** {record['duration_formatted']}")
st.write(f"**Quality:** {'⭐' * record['sleep_quality']}")
st.write(f"**Location:** {record['location']}")
with col_b:
st.markdown("#### Environment")
env = record['environment']
st.write(f"**Room Temperature:** {env['room_temp']}°F")
st.write(f"**White Noise:** {'Yes' if env['white_noise'] else 'No'}")
st.write(f"**Night Light:** {'Yes' if env['night_light'] else 'No'}")
st.write(f"**Swaddled:** {'Yes' if env['swaddled'] else 'No'}")
st.write(f"**Sleep Position:** {env['sleep_position']}")
st.write(f"**Feeding Before Sleep:** {'Yes' if env['feeding_before_sleep'] else 'No'}")
with col_c:
st.markdown("#### Notes")
st.write(record['notes'] if record['notes'] else "No notes recorded")
# Delete button
if st.button("Delete This Record", key=f"delete_{selected_id}"):
delete_sleep_record(selected_id)
st.success("Record deleted successfully!")
st.rerun()
else:
st.info("No records match the selected filters.")
else:
st.info("No sleep records found. Add some records in the 'Add Sleep Record' tab.")
with tab3:
st.markdown("<h2 class='sub-header'>Sleep Analytics</h2>", unsafe_allow_html=True)
# Get all records
records = get_all_sleep_records()
if records:
df = pd.DataFrame(records)
df['date'] = pd.to_datetime(df['date'])
df['environment'] = df['environment'].apply(eval)
# Time period selector
time_period = st.radio(
"Select Time Period",
["Last 7 Days", "Last 30 Days", "All Time"],
horizontal=True
)
if time_period == "Last 7 Days":
cutoff_date = datetime.now().date() - timedelta(days=7)
df_period = df[df['date'].dt.date >= cutoff_date]
elif time_period == "Last 30 Days":
cutoff_date = datetime.now().date() - timedelta(days=30)
df_period = df[df['date'].dt.date >= cutoff_date]
else:
df_period = df
if len(df_period) > 0:
col1, col2 = st.columns(2)
with col1:
# Total sleep over time
st.markdown("<div class='card'>", unsafe_allow_html=True)
st.subheader("Daily Sleep Duration")
# Group by date and sum duration
daily_sleep = df_period.groupby(df_period['date'].dt.date)['duration_minutes'].sum().reset_index()
daily_sleep['duration_hours'] = daily_sleep['duration_minutes'] / 60
fig = px.bar(
daily_sleep,
x='date',
y='duration_hours',
labels={'date': 'Date', 'duration_hours': 'Sleep Duration (hours)'},
color_discrete_sequence=['#4169E1']
)
# Add horizontal line for recommended sleep
fig.add_hline(
y=14, # Recommended hours for baby
line_dash="dash",
line_color="red",
annotation_text="Recommended Sleep (14h)",
annotation_position="bottom right"
)
fig.update_layout(
height=300,
margin=dict(l=20, r=20, t=30, b=20),
)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
# Sleep quality distribution
st.markdown("<div class='card'>", unsafe_allow_html=True)
st.subheader("Sleep Quality Distribution")
quality_counts = df_period['sleep_quality'].value_counts().sort_index().reset_index()
quality_counts.columns = ['Quality', 'Count']
fig = px.pie(
quality_counts,
values='Count',
names='Quality',
color='Quality',
color_discrete_sequence=px.colors.sequential.Blues,
hole=0.4
)
fig.update_layout(
height=300,
margin=dict(l=20, r=20, t=30, b=20),
)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
with col2:
# Sleep location distribution
st.markdown("<div class='card'>", unsafe_allow_html=True)
st.subheader("Sleep Location Distribution")
location_counts = df_period['location'].value_counts().reset_index()
location_counts.columns = ['Location', 'Count']
fig = px.bar(
location_counts,
x='Location',
y='Count',
color='Location',
color_discrete_sequence=px.colors.qualitative.Pastel
)
fig.update_layout(
height=300,
margin=dict(l=20, r=20, t=30, b=20),
showlegend=False
)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
# Sleep environment impact
st.markdown("<div class='card'>", unsafe_allow_html=True)
st.subheader("Sleep Environment Impact")
# Extract environment factors
df_period['white_noise'] = df_period['environment'].apply(lambda x: x['white_noise'])
df_period['night_light'] = df_period['environment'].apply(lambda x: x['night_light'])
df_period['swaddled'] = df_period['environment'].apply(lambda x: x['swaddled'])
df_period['feeding_before_sleep'] = df_period['environment'].apply(lambda x: x['feeding_before_sleep'])
# Select factor to analyze
factor = st.selectbox(
"Select Factor to Analyze",
["white_noise", "night_light", "swaddled", "feeding_before_sleep"]
)
# Group by factor and calculate average quality and duration
factor_analysis = df_period.groupby(factor)[['sleep_quality', 'duration_minutes']].agg({
'sleep_quality': 'mean',
'duration_minutes': 'mean'
}).reset_index()
factor_analysis['duration_hours'] = factor_analysis['duration_minutes'] / 60
factor_analysis[factor] = factor_analysis[factor].map({True: 'Yes', False: 'No'})
col_a, col_b = st.columns(2)
with col_a:
# Quality by factor
fig = px.bar(
factor_analysis,
x=factor,
y='sleep_quality',
color=factor,
title=f"Avg. Quality by {factor.replace('_', ' ').title()}",
labels={'sleep_quality': 'Avg. Quality (1-5)'},
color_discrete_sequence=['#4169E1', '#87CEEB']
)
fig.update_layout(
height=200,
margin=dict(l=20, r=20, t=40, b=20),
showlegend=False
)
st.plotly_chart(fig, use_container_width=True)
with col_b:
# Duration by factor
fig = px.bar(
factor_analysis,
x=factor,
y='duration_hours',
color=factor,
title=f"Avg. Duration by {factor.replace('_', ' ').title()}",
labels={'duration_hours': 'Avg. Hours'},
color_discrete_sequence=['#4169E1', '#87CEEB']
)
fig.update_layout(
height=200,
margin=dict(l=20, r=20, t=40, b=20),
showlegend=False
)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
# Sleep patterns
st.markdown("<div class='card'>", unsafe_allow_html=True)
st.subheader("Sleep Patterns by Time of Day")
# Create time bins
df_period['start_hour'] = df_period['start_time'].apply(
lambda x: int(x.split(':')[0])
)
# Define time periods
def get_time_period(hour):
if 20 <= hour <= 23 or 0 <= hour < 6:
return "Night (8PM-6AM)"
elif 6 <= hour < 12:
return "Morning (6AM-12PM)"
elif 12 <= hour < 17:
return "Afternoon (12PM-5PM)"
else:
return "Evening (5PM-8PM)"
df_period['time_period'] = df_period['start_hour'].apply(get_time_period)
# Group by time period
time_period_stats = df_period.groupby('time_period').agg({
'id': 'count',
'duration_minutes': 'mean',
'sleep_quality': 'mean'
}).reset_index()
time_period_stats.columns = ['Time Period', 'Count', 'Avg Duration (min)', 'Avg Quality']
time_period_stats['Avg Duration (hrs)'] = time_period_stats['Avg Duration (min)'] / 60
# Define custom order for time periods
custom_order = ["Morning (6AM-12PM)", "Afternoon (12PM-5PM)",
"Evening (5PM-8PM)", "Night (8PM-6AM)"]
time_period_stats['Time Period'] = pd.Categorical(
time_period_stats['Time Period'],
categories=custom_order,
ordered=True
)
time_period_stats = time_period_stats.sort_values('Time Period')
col_a, col_b = st.columns([3, 2])
with col_a:
# Create a figure with secondary y-axis
fig = go.Figure()
# Add bars for count
fig.add_trace(
go.Bar(
x=time_period_stats['Time Period'],
y=time_period_stats['Count'],
name='Number of Naps',
marker_color='#1E90FF'
)
)
# Add line for duration
fig.add_trace(
go.Scatter(
x=time_period_stats['Time Period'],
y=time_period_stats['Avg Duration (hrs)'],
name='Avg Duration (hrs)',
mode='lines+markers',
marker=dict(color='#FF7F50'),
line=dict(width=3),
yaxis='y2'
)
)
# Add line for quality
fig.add_trace(
go.Scatter(
x=time_period_stats['Time Period'],
y=time_period_stats['Avg Quality'],
name='Avg Quality',
mode='lines+markers',
marker=dict(color='#32CD32'),
line=dict(width=3, dash='dot'),
yaxis='y3'
)
)
# Update layout with multiple y-axes
fig.update_layout(
yaxis=dict(
title='Number of Naps',
titlefont=dict(color='#1E90FF'),
tickfont=dict(color='#1E90FF')
),
yaxis2=dict(
title='Avg Duration (hrs)',
titlefont=dict(color='#FF7F50'),
tickfont=dict(color='#FF7F50'),
anchor='x',
overlaying='y',
side='right',
position=0.85
),
yaxis3=dict(
title='Avg Quality',
titlefont=dict(color='#32CD32'),
tickfont=dict(color='#32CD32'),
anchor='free',
overlaying='y',
side='right',
position=1
),
legend=dict(
orientation='h',
yanchor='bottom',
y=1.02,
xanchor='center',
x=0.5
),
height=400,
margin=dict(l=20, r=70, t=70, b=20),
)
st.plotly_chart(fig, use_container_width=True)
with col_b:
st.subheader("Sleep Metrics by Time Period")
st.dataframe(
time_period_stats[['Time Period', 'Count', 'Avg Duration (hrs)', 'Avg Quality']],
use_container_width=True,
hide_index=True
)
# Summary stats
st.subheader("Summary Statistics")
total_sleep = df_period['duration_minutes'].sum() / 60
avg_quality = df_period['sleep_quality'].mean()
avg_duration = df_period['duration_minutes'].mean() / 60
col_x, col_y, col_z = st.columns(3)
with col_x:
st.metric(
"Total Sleep",
f"{total_sleep:.1f} hrs",
f"{total_sleep/len(df_period.groupby(df_period['date'].dt.date)):.1f} hrs/day"
)
with col_y:
st.metric(
"Avg. Quality",
f"{avg_quality:.1f}/5"
)
with col_z:
st.metric(
"Avg. Nap Duration",
f"{avg_duration:.1f} hrs"
)
st.markdown("</div>", unsafe_allow_html=True)
else:
st.info(f"No data available for the selected time period ({time_period}).")
else:
st.info("No sleep records found. Add some records in the 'Add Sleep Record' tab.")
# Footer
st.markdown("---")
st.markdown("© 2023 Baby Sleep Tracker | Made with ❤️ for tired parents")
Hi! I can help you with any questions about Streamlit and Python. What would you like to know?