Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import streamlit as st
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import plotly.express as px
|
| 5 |
+
from stravalib.client import Client
|
| 6 |
+
import google.generativeai as genai
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
|
| 9 |
+
# Load environment variables
|
| 10 |
+
load_dotenv()
|
| 11 |
+
|
| 12 |
+
# API Keys and Secrets
|
| 13 |
+
STRAVA_CLIENT_ID = os.getenv('STRAVA_CLIENT_ID')
|
| 14 |
+
STRAVA_CLIENT_SECRET = os.getenv('STRAVA_CLIENT_SECRET')
|
| 15 |
+
STRAVA_REDIRECT_URI = os.getenv('STRAVA_REDIRECT_URI')
|
| 16 |
+
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
|
| 17 |
+
|
| 18 |
+
# Strava API setup
|
| 19 |
+
client = Client()
|
| 20 |
+
|
| 21 |
+
# Gemini API setup
|
| 22 |
+
genai.configure(api_key=GEMINI_API_KEY)
|
| 23 |
+
model = genai.GenerativeModel('gemini-pro')
|
| 24 |
+
|
| 25 |
+
# Streamlit app
|
| 26 |
+
st.set_page_config(page_title="Strava Analysis App", layout="wide")
|
| 27 |
+
st.title("Strava Analysis App")
|
| 28 |
+
|
| 29 |
+
# Strava authentication
|
| 30 |
+
if 'access_token' not in st.session_state:
|
| 31 |
+
auth_url = client.authorization_url(
|
| 32 |
+
client_id=STRAVA_CLIENT_ID,
|
| 33 |
+
redirect_uri=STRAVA_REDIRECT_URI,
|
| 34 |
+
scope=['read_all', 'profile:read_all', 'activity:read_all']
|
| 35 |
+
)
|
| 36 |
+
st.write(f"[Authorize with Strava]({auth_url})")
|
| 37 |
+
|
| 38 |
+
code = st.text_input("Enter the code from the redirect URL:")
|
| 39 |
+
if code:
|
| 40 |
+
token_response = client.exchange_code_for_token(
|
| 41 |
+
client_id=STRAVA_CLIENT_ID,
|
| 42 |
+
client_secret=STRAVA_CLIENT_SECRET,
|
| 43 |
+
code=code
|
| 44 |
+
)
|
| 45 |
+
st.session_state.access_token = token_response['access_token']
|
| 46 |
+
st.experimental_rerun()
|
| 47 |
+
|
| 48 |
+
if 'access_token' in st.session_state:
|
| 49 |
+
client.access_token = st.session_state.access_token
|
| 50 |
+
athlete = client.get_athlete()
|
| 51 |
+
st.write(f"Welcome, {athlete.firstname} {athlete.lastname}!")
|
| 52 |
+
|
| 53 |
+
# Fetch activities
|
| 54 |
+
@st.cache_data(ttl=3600)
|
| 55 |
+
def fetch_activities():
|
| 56 |
+
activities = list(client.get_activities(limit=100))
|
| 57 |
+
df = pd.DataFrame([{
|
| 58 |
+
'name': activity.name,
|
| 59 |
+
'distance': activity.distance.num,
|
| 60 |
+
'moving_time': activity.moving_time.total_seconds() / 3600,
|
| 61 |
+
'elevation_gain': activity.total_elevation_gain.num,
|
| 62 |
+
'type': activity.type,
|
| 63 |
+
'start_date': activity.start_date.date()
|
| 64 |
+
} for activity in activities])
|
| 65 |
+
return df
|
| 66 |
+
|
| 67 |
+
df = fetch_activities()
|
| 68 |
+
|
| 69 |
+
# Display recent activities
|
| 70 |
+
st.subheader("Recent Activities")
|
| 71 |
+
st.dataframe(df)
|
| 72 |
+
|
| 73 |
+
# Basic stats
|
| 74 |
+
st.subheader("Activity Stats")
|
| 75 |
+
total_distance = df['distance'].sum() / 1000 # Convert to km
|
| 76 |
+
total_time = df['moving_time'].sum()
|
| 77 |
+
total_elevation = df['elevation_gain'].sum()
|
| 78 |
+
|
| 79 |
+
col1, col2, col3 = st.columns(3)
|
| 80 |
+
col1.metric("Total Distance", f"{total_distance:.2f} km")
|
| 81 |
+
col2.metric("Total Time", f"{total_time:.2f} hours")
|
| 82 |
+
col3.metric("Total Elevation Gain", f"{total_elevation:.0f} m")
|
| 83 |
+
|
| 84 |
+
# Visualizations
|
| 85 |
+
st.subheader("Activity Visualizations")
|
| 86 |
+
|
| 87 |
+
col1, col2 = st.columns(2)
|
| 88 |
+
|
| 89 |
+
with col1:
|
| 90 |
+
# Distance by activity type
|
| 91 |
+
fig_distance = px.bar(df.groupby('type').sum().reset_index(),
|
| 92 |
+
x='type', y='distance',
|
| 93 |
+
title="Total Distance by Activity Type")
|
| 94 |
+
st.plotly_chart(fig_distance, use_container_width=True)
|
| 95 |
+
|
| 96 |
+
with col2:
|
| 97 |
+
# Activity count over time
|
| 98 |
+
df['start_date'] = pd.to_datetime(df['start_date'])
|
| 99 |
+
fig_timeline = px.bar(df.groupby('start_date').size().reset_index(name='count'),
|
| 100 |
+
x='start_date', y='count',
|
| 101 |
+
title="Activity Count Over Time")
|
| 102 |
+
st.plotly_chart(fig_timeline, use_container_width=True)
|
| 103 |
+
|
| 104 |
+
# Training Plan Generation
|
| 105 |
+
st.subheader("Generate Training Plan")
|
| 106 |
+
|
| 107 |
+
col1, col2, col3 = st.columns(3)
|
| 108 |
+
with col1:
|
| 109 |
+
level = st.selectbox("Select your level:", ["Beginner", "Intermediate", "Advanced"])
|
| 110 |
+
with col2:
|
| 111 |
+
race_distance = st.selectbox("Select race distance:", ["5K", "10K", "Half Marathon", "Marathon"])
|
| 112 |
+
with col3:
|
| 113 |
+
plan_duration = st.selectbox("Select plan duration:", ["8 weeks", "10 weeks", "12 weeks"])
|
| 114 |
+
|
| 115 |
+
if st.button("Generate Training Plan"):
|
| 116 |
+
prompt = f"""Create a {plan_duration} training plan for a {level} runner preparing for a {race_distance} race.
|
| 117 |
+
Include weekly mileage and key workouts. Format the plan week by week, with each week on a new line."""
|
| 118 |
+
response = model.generate_content(prompt)
|
| 119 |
+
st.markdown(response.text)
|
| 120 |
+
|
| 121 |
+
# Natural Language Insights
|
| 122 |
+
st.subheader("Get Insights")
|
| 123 |
+
user_question = st.text_input("Ask a question about your running data:")
|
| 124 |
+
if user_question:
|
| 125 |
+
context = f"""Based on the runner's data:
|
| 126 |
+
Total distance: {total_distance:.2f} km
|
| 127 |
+
Total time: {total_time:.2f} hours
|
| 128 |
+
Total elevation gain: {total_elevation:.0f} m
|
| 129 |
+
Number of activities: {len(df)}
|
| 130 |
+
Date range: {df['start_date'].min()} to {df['start_date'].max()}
|
| 131 |
+
"""
|
| 132 |
+
prompt = f"{context}\n\nUser question: {user_question}\n\nProvide a concise, insightful answer:"
|
| 133 |
+
response = model.generate_content(prompt)
|
| 134 |
+
st.write(response.text)
|
| 135 |
+
|
| 136 |
+
# Performance Analysis
|
| 137 |
+
st.subheader("Performance Analysis")
|
| 138 |
+
|
| 139 |
+
# Calculate pace
|
| 140 |
+
df['pace'] = df['moving_time'] / (df['distance'] / 1000) # min/km
|
| 141 |
+
|
| 142 |
+
# Filter for runs only
|
| 143 |
+
runs_df = df[df['type'] == 'Run']
|
| 144 |
+
|
| 145 |
+
if not runs_df.empty:
|
| 146 |
+
fig_pace = px.scatter(runs_df, x='start_date', y='pace',
|
| 147 |
+
title="Running Pace Over Time",
|
| 148 |
+
labels={'pace': 'Pace (min/km)', 'start_date': 'Date'})
|
| 149 |
+
st.plotly_chart(fig_pace, use_container_width=True)
|
| 150 |
+
|
| 151 |
+
# Weekly mileage
|
| 152 |
+
weekly_mileage = runs_df.resample('W', on='start_date')['distance'].sum() / 1000
|
| 153 |
+
fig_weekly = px.bar(weekly_mileage, title="Weekly Running Mileage",
|
| 154 |
+
labels={'value': 'Distance (km)', 'start_date': 'Week'})
|
| 155 |
+
st.plotly_chart(fig_weekly, use_container_width=True)
|
| 156 |
+
else:
|
| 157 |
+
st.write("No running data available for performance analysis.")
|
| 158 |
+
|
| 159 |
+
else:
|
| 160 |
+
st.write("Please authorize with Strava to view your data and analytics.")
|