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 matplotlib.pyplot as plt
import plotly.express as px
from datetime import datetime, timedelta
import sqlite3
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Float
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
# Set page config
st.set_page_config(
page_title="Baby Sleep Tracker",
page_icon="👶",
layout="wide"
)
# Database setup
engine = create_engine('sqlite:///baby_sleep.db')
Base = declarative_base()
class SleepRecord(Base):
__tablename__ = 'sleep_records'
id = Column(Integer, primary_key=True)
baby_name = Column(String, nullable=False)
sleep_start = Column(DateTime, nullable=False)
sleep_end = Column(DateTime, nullable=False)
duration = Column(Float, nullable=False) # in hours
sleep_quality = Column(String)
notes = Column(String)
# Create tables
Base.metadata.create_all(engine)
SessionLocal = sessionmaker(bind=engine)
# App title and description
st.title("👶 Baby Sleep Tracker")
st.markdown("""
Keep track of your baby's sleep patterns, analyze trends, and identify sleep habits to help establish a healthy sleep routine.
""")
# Sidebar for navigation
st.sidebar.title("Navigation")
page = st.sidebar.radio("Go to", ["Record Sleep", "View Records", "Analytics"])
# Function to calculate sleep duration
def calculate_duration(start, end):
duration = end - start
return duration.total_seconds() / 3600 # Convert to hours
# Function to load all sleep records
def load_all_records():
with Session(engine) as session:
records = session.query(SleepRecord).all()
if not records:
return pd.DataFrame()
data = [{
"id": record.id,
"baby_name": record.baby_name,
"sleep_start": record.sleep_start,
"sleep_end": record.sleep_end,
"duration": record.duration,
"sleep_quality": record.sleep_quality,
"notes": record.notes
} for record in records]
return pd.DataFrame(data)
# Initialize session state for baby names
if 'baby_names' not in st.session_state:
# Try to get unique baby names from database
df = load_all_records()
if not df.empty:
st.session_state.baby_names = df['baby_name'].unique().tolist()
else:
st.session_state.baby_names = []
# Record Sleep page
if page == "Record Sleep":
st.header("Record Sleep Session")
# Input form
with st.form("sleep_record_form"):
# Baby selection or new baby
existing_babies = st.session_state.baby_names
use_existing = st.checkbox("Use existing baby name", value=len(existing_babies) > 0)
if use_existing and existing_babies:
baby_name = st.selectbox("Select Baby", options=existing_babies)
else:
baby_name = st.text_input("Baby Name")
# Sleep time inputs
col1, col2 = st.columns(2)
with col1:
sleep_date = st.date_input("Sleep Date", datetime.now().date())
sleep_start_time = st.time_input("Sleep Start Time", datetime.now().time())
with col2:
wake_date = st.date_input("Wake Date", datetime.now().date())
sleep_end_time = st.time_input("Sleep End Time", (datetime.now() + timedelta(hours=1)).time())
# Combine date and time
sleep_start = datetime.combine(sleep_date, sleep_start_time)
sleep_end = datetime.combine(wake_date, sleep_end_time)
# Calculate duration
if sleep_end > sleep_start:
duration = calculate_duration(sleep_start, sleep_end)
duration_display = f"{duration:.2f} hours ({int(duration * 60)} minutes)"
else:
duration = 0
duration_display = "End time must be after start time"
st.info(f"Sleep Duration: {duration_display}")
# Additional information
sleep_quality = st.selectbox(
"Sleep Quality",
options=["Excellent", "Good", "Fair", "Poor", "Very Poor"]
)
notes = st.text_area("Notes", placeholder="Any additional notes (feeding, diaper changes, etc.)")
submit_button = st.form_submit_button("Save Sleep Record")
if submit_button:
if not baby_name:
st.error("Please enter a baby name")
elif sleep_end <= sleep_start:
st.error("Sleep end time must be after start time")
else:
# Save to database
try:
with Session(engine) as session:
new_record = SleepRecord(
baby_name=baby_name,
sleep_start=sleep_start,
sleep_end=sleep_end,
duration=duration,
sleep_quality=sleep_quality,
notes=notes
)
session.add(new_record)
session.commit()
# Add baby name to session state if it's new
if baby_name not in st.session_state.baby_names:
st.session_state.baby_names.append(baby_name)
st.success("Sleep record saved successfully!")
except Exception as e:
st.error(f"Error saving record: {str(e)}")
# View Records page
elif page == "View Records":
st.header("Sleep Records")
# Load data
df = load_all_records()
if df.empty:
st.info("No sleep records found. Add some records first!")
else:
# Filter options
st.subheader("Filter Records")
col1, col2, col3 = st.columns(3)
with col1:
baby_filter = st.multiselect(
"Baby",
options=df['baby_name'].unique(),
default=df['baby_name'].unique()
)
with col2:
date_range = st.date_input(
"Date Range",
value=(
df['sleep_start'].min().date(),
df['sleep_start'].max().date()
),
key="date_range_filter"
)
with col3:
quality_filter = st.multiselect(
"Sleep Quality",
options=df['sleep_quality'].unique(),
default=df['sleep_quality'].unique()
)
# Apply filters
filtered_df = df.copy()
if baby_filter:
filtered_df = filtered_df[filtered_df['baby_name'].isin(baby_filter)]
if len(date_range) == 2:
start_date, end_date = date_range
end_date = datetime.combine(end_date, datetime.max.time())
filtered_df = filtered_df[
(filtered_df['sleep_start'] >= datetime.combine(start_date, datetime.min.time())) &
(filtered_df['sleep_start'] <= end_date)
]
if quality_filter:
filtered_df = filtered_df[filtered_df['sleep_quality'].isin(quality_filter)]
# Display filtered records
if filtered_df.empty:
st.warning("No records match the selected filters.")
else:
display_df = filtered_df.copy()
# Format datetime columns for display
display_df['sleep_start'] = display_df['sleep_start'].dt.strftime('%Y-%m-%d %H:%M')
display_df['sleep_end'] = display_df['sleep_end'].dt.strftime('%Y-%m-%d %H:%M')
display_df['duration'] = display_df['duration'].round(2).astype(str) + ' hours'
st.dataframe(
display_df,
column_order=["id", "baby_name", "sleep_start", "sleep_end", "duration", "sleep_quality", "notes"],
hide_index=True
)
# Delete record functionality
with st.expander("Delete Records"):
record_to_delete = st.number_input("Enter record ID to delete", min_value=1, step=1)
if st.button("Delete Record"):
try:
with Session(engine) as session:
record = session.query(SleepRecord).filter(SleepRecord.id == record_to_delete).first()
if record:
session.delete(record)
session.commit()
st.success(f"Record {record_to_delete} deleted successfully!")
st.experimental_rerun()
else:
st.error("Record not found!")
except Exception as e:
st.error(f"Error deleting record: {str(e)}")
# Analytics page
elif page == "Analytics":
st.header("Sleep Analytics")
# Load data
df = load_all_records()
if df.empty:
st.info("No sleep records found. Add some records first!")
else:
# Baby selection for analytics
selected_baby = st.selectbox(
"Select Baby for Analysis",
options=df['baby_name'].unique()
)
baby_df = df[df['baby_name'] == selected_baby].copy()
# Time period selection
time_period = st.radio(
"Select Time Period",
options=["Last 7 days", "Last 30 days", "All time"],
horizontal=True
)
if time_period == "Last 7 days":
start_date = datetime.now() - timedelta(days=7)
baby_df = baby_df[baby_df['sleep_start'] >= start_date]
elif time_period == "Last 30 days":
start_date = datetime.now() - timedelta(days=30)
baby_df = baby_df[baby_df['sleep_start'] >= start_date]
if baby_df.empty:
st.warning(f"No data available for {selected_baby} in the selected time period.")
else:
# Create tabs for different analytics
tab1, tab2, tab3 = st.tabs(["Sleep Duration", "Sleep Patterns", "Sleep Quality"])
with tab1:
st.subheader("Sleep Duration Analysis")
# Calculate statistics
avg_duration = baby_df['duration'].mean()
max_duration = baby_df['duration'].max()
min_duration = baby_df['duration'].min()
total_sleep = baby_df['duration'].sum()
# Display metrics
col1, col2, col3, col4 = st.columns(4)
col1.metric("Average Sleep", f"{avg_duration:.2f} hours")
col2.metric("Longest Sleep", f"{max_duration:.2f} hours")
col3.metric("Shortest Sleep", f"{min_duration:.2f} hours")
col4.metric("Total Sleep", f"{total_sleep:.2f} hours")
# Duration over time chart
baby_df['date'] = baby_df['sleep_start'].dt.date
daily_sleep = baby_df.groupby('date')['duration'].sum().reset_index()
fig = px.line(
daily_sleep,
x='date',
y='duration',
title=f"Daily Sleep Duration for {selected_baby}",
labels={'duration': 'Sleep Duration (hours)', 'date': 'Date'}
)
fig.update_layout(height=400)
st.plotly_chart(fig, use_container_width=True)
with tab2:
st.subheader("Sleep Patterns")
# Extract hour of day
baby_df['hour_start'] = baby_df['sleep_start'].dt.hour
baby_df['hour_end'] = baby_df['sleep_end'].dt.hour
# Sleep start time distribution
fig1 = px.histogram(
baby_df,
x='hour_start',
title=f"Sleep Start Time Distribution for {selected_baby}",
labels={'hour_start': 'Hour of Day (24h)', 'count': 'Frequency'},
nbins=24,
range_x=[0, 24]
)
fig1.update_layout(bargap=0.1, height=300)
st.plotly_chart(fig1, use_container_width=True)
# Sleep end time distribution
fig2 = px.histogram(
baby_df,
x='hour_end',
title=f"Wake Time Distribution for {selected_baby}",
labels={'hour_end': 'Hour of Day (24h)', 'count': 'Frequency'},
nbins=24,
range_x=[0, 24]
)
fig2.update_layout(bargap=0.1, height=300)
st.plotly_chart(fig2, use_container_width=True)
# Day of week analysis
baby_df['day_of_week'] = baby_df['sleep_start'].dt.day_name()
day_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
daily_avg = baby_df.groupby('day_of_week')['duration'].mean().reset_index()
daily_avg['day_of_week'] = pd.Categorical(daily_avg['day_of_week'], categories=day_order, ordered=True)
daily_avg = daily_avg.sort_values('day_of_week')
fig3 = px.bar(
daily_avg,
x='day_of_week',
y='duration',
title=f"Average Sleep Duration by Day of Week for {selected_baby}",
labels={'duration': 'Avg Sleep Duration (hours)', 'day_of_week': 'Day of Week'}
)
fig3.update_layout(height=300)
st.plotly_chart(fig3, use_container_width=True)
with tab3:
st.subheader("Sleep Quality Analysis")
# Quality distribution
quality_counts = baby_df['sleep_quality'].value_counts().reset_index()
quality_counts.columns = ['sleep_quality', 'count']
# Create a quality order
quality_order = ['Excellent', 'Good', 'Fair', 'Poor', 'Very Poor']
quality_counts['sleep_quality'] = pd.Categorical(
quality_counts['sleep_quality'],
categories=quality_order,
ordered=True
)
quality_counts = quality_counts.sort_values('sleep_quality')
fig = px.pie(
quality_counts,
values='count',
names='sleep_quality',
title=f"Sleep Quality Distribution for {selected_baby}",
color='sleep_quality',
color_discrete_map={
'Excellent': '#28a745',
'Good': '#5cb85c',
'Fair': '#ffc107',
'Poor': '#ff8f00',
'Very Poor': '#dc3545'
}
)
fig.update_layout(height=400)
st.plotly_chart(fig, use_container_width=True)
# Quality vs. Duration
fig2 = px.box(
baby_df,
x='sleep_quality',
y='duration',
title=f"Sleep Duration by Quality for {selected_baby}",
labels={'duration': 'Sleep Duration (hours)', 'sleep_quality': 'Sleep Quality'},
category_orders={"sleep_quality": quality_order}
)
fig2.update_layout(height=400)
st.plotly_chart(fig2, use_container_width=True)
# Quality over time
baby_df['quality_numeric'] = baby_df['sleep_quality'].map({
'Excellent': 5,
'Good': 4,
'Fair': 3,
'Poor': 2,
'Very Poor': 1
})
fig3 = px.scatter(
baby_df,
x='sleep_start',
y='quality_numeric',
size='duration',
hover_data=['sleep_quality', 'duration', 'notes'],
title=f"Sleep Quality Over Time for {selected_baby}",
labels={
'sleep_start': 'Date',
'quality_numeric': 'Sleep Quality',
'duration': 'Sleep Duration (hours)'
}
)
fig3.update_layout(
height=400,
yaxis=dict(
tickmode='array',
tickvals=[1, 2, 3, 4, 5],
ticktext=['Very Poor', 'Poor', 'Fair', 'Good', 'Excellent']
)
)
st.plotly_chart(fig3, use_container_width=True)
# Footer
st.markdown("---")
st.markdown("Baby Sleep Tracker - Helping you understand your baby's sleep patterns")
Hi! I can help you with any questions about Streamlit and Python. What would you like to know?