Prashanthsrn commited on
Commit
35f2f2e
Β·
verified Β·
1 Parent(s): 079fded

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -69
app.py CHANGED
@@ -2,9 +2,11 @@ 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()
@@ -22,7 +24,7 @@ genai.configure(api_key=GEMINI_API_KEY)
22
  model = genai.GenerativeModel('gemini-pro')
23
 
24
  # Streamlit app
25
- st.set_page_config(page_title="Strava Analysis App", layout="wide")
26
 
27
  # Custom CSS for better UI
28
  st.markdown("""
@@ -31,44 +33,64 @@ st.markdown("""
31
  background-color: #f0f2f6;
32
  }
33
  .stButton>button {
34
- background-color: #fc5200;
35
  color: white;
 
 
 
 
 
 
 
36
  }
37
  .stSelectbox {
38
  color: #333333;
39
  }
40
  .stPlotlyChart {
41
  background-color: white;
42
- border-radius: 5px;
43
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
 
 
44
  }
45
- .success-message {
46
- padding: 10px;
47
- background-color: #d4edda;
48
- color: #155724;
49
- border-radius: 5px;
50
- margin-bottom: 10px;
51
  }
52
- .error-message {
53
- padding: 10px;
54
- background-color: #f8d7da;
55
- color: #721c24;
56
- border-radius: 5px;
57
- margin-bottom: 10px;
 
 
 
 
 
 
 
 
 
 
58
  }
59
  </style>
60
  """, unsafe_allow_html=True)
61
 
62
- st.title("πŸƒβ€β™‚οΈ Strava Analysis App")
63
 
64
  # Strava manual authentication
65
  if 'access_token' not in st.session_state:
66
  st.write("To use this app, you need to authorize it with Strava. Follow these steps:")
67
- st.write("1. Click this link to go to Strava's authorization page:")
68
- st.write(f"[Strava Authorization](https://www.strava.com/oauth/authorize?client_id={STRAVA_CLIENT_ID}&response_type=code&redirect_uri=http://localhost&approval_prompt=force&scope=read_all,profile:read_all,activity:read_all)")
 
69
  st.write("2. Log in to Strava if needed and click 'Authorize'")
70
- st.write("3. After authorizing, you'll see a page that says 'Authorization Error' or 'localhost refused to connect'. This is expected!")
71
- st.write("4. In the URL of that page, you'll see a code parameter. Copy the value of that code (it's a long string of letters and numbers).")
72
  st.write("5. Paste that code below:")
73
 
74
  auth_code = st.text_input("Paste the authorization code here:")
@@ -80,10 +102,10 @@ if 'access_token' not in st.session_state:
80
  code=auth_code
81
  )
82
  st.session_state.access_token = token_response['access_token']
83
- st.markdown('<div class="success-message">Authorization successful! Refreshing the app...</div>', unsafe_allow_html=True)
84
  st.rerun()
85
  except Exception as e:
86
- st.markdown(f'<div class="error-message">An error occurred: {str(e)}. Please try authorizing again.</div>', unsafe_allow_html=True)
87
 
88
  if 'access_token' in st.session_state:
89
  client.access_token = st.session_state.access_token
@@ -91,61 +113,91 @@ if 'access_token' in st.session_state:
91
  athlete = client.get_athlete()
92
  st.write(f"Welcome, {athlete.firstname} {athlete.lastname}! πŸ‘‹")
93
  except Exception as e:
94
- st.markdown(f'<div class="error-message">Error fetching athlete data: {str(e)}. Please try reauthorizing.</div>', unsafe_allow_html=True)
95
  if st.button("Reauthorize"):
96
  del st.session_state.access_token
97
  st.rerun()
98
 
99
- # Fetch activities
100
  @st.cache_data(ttl=3600)
101
- def fetch_activities():
102
- activities = list(client.get_activities(limit=100))
 
103
  df = pd.DataFrame([{
104
- 'name': activity.name,
105
- 'distance': float(activity.distance) if activity.distance is not None else 0,
106
- 'moving_time': float(activity.moving_time) if activity.moving_time is not None else 0,
107
- 'elevation_gain': float(activity.total_elevation_gain) if activity.total_elevation_gain is not None else 0,
108
- 'type': str(activity.type),
109
- } for activity in activities])
 
 
 
110
  return df
111
 
112
  try:
113
- df = fetch_activities()
114
  except Exception as e:
115
- st.markdown(f'<div class="error-message">Error fetching activities: {str(e)}. Please try again later.</div>', unsafe_allow_html=True)
116
  st.stop()
117
 
118
- # Display recent activities
119
- st.subheader("πŸ“Š Recent Activities")
120
- st.dataframe(df.style.format({
121
- 'distance': '{:.2f} km',
122
- 'moving_time': '{:.2f} hours',
123
- 'elevation_gain': '{:.0f} m'
124
- }))
125
-
126
  # Basic stats
127
- st.subheader("πŸ… Activity Stats")
128
- total_distance = df['distance'].sum() / 1000 # Convert to km
129
- total_time = df['moving_time'].sum() / 3600 # Convert to hours
130
- total_elevation = df['elevation_gain'].sum()
131
 
132
- col1, col2, col3 = st.columns(3)
133
- col1.metric("Total Distance", f"{total_distance:.2f} km")
134
- col2.metric("Total Time", f"{total_time:.2f} hours")
135
- col3.metric("Total Elevation Gain", f"{total_elevation:.0f} m")
 
 
 
 
 
 
136
 
137
  # Visualizations
138
- st.subheader("πŸ“ˆ Activity Visualizations")
139
 
140
- # Distance by activity type
141
- fig_distance = px.bar(df.groupby('type').sum().reset_index(),
142
- x='type', y='distance',
143
- title="Total Distance by Activity Type")
144
- fig_distance.update_layout(xaxis_title="Activity Type", yaxis_title="Distance (m)")
145
- st.plotly_chart(fig_distance, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
  # Training Plan Generation
148
- st.subheader("πŸ‹οΈβ€β™€οΈ Generate Training Plan")
149
 
150
  col1, col2, col3 = st.columns(3)
151
  with col1:
@@ -159,18 +211,12 @@ if 'access_token' in st.session_state:
159
  with st.spinner("Generating your personalized training plan..."):
160
  try:
161
  prompt = f"""Create a {plan_duration} training plan for a {level} runner preparing for a {race_distance} race.
162
- Include weekly mileage and key workouts. Format the plan week by week, with each week on a new line."""
 
163
  response = model.generate_content(prompt)
164
  st.markdown(response.text)
165
  except Exception as e:
166
- st.markdown(f'<div class="error-message">Error generating training plan: {str(e)}. Please try again later.</div>', unsafe_allow_html=True)
167
-
168
- # Simple Insights
169
- st.subheader("πŸ’‘ Activity Insights")
170
- activity_types = df['type'].value_counts()
171
- st.write(f"You have {len(df)} recorded activities.")
172
- st.write(f"Your most common activity type is {activity_types.index[0]} with {activity_types.iloc[0]} activities.")
173
- st.write(f"Your average activity distance is {df['distance'].mean() / 1000:.2f} km.")
174
 
175
  else:
176
- st.info("Please complete the authorization process to view your Strava data and analytics.")
 
2
  import streamlit as st
3
  import pandas as pd
4
  import plotly.express as px
5
+ import plotly.graph_objects as go
6
  from stravalib.client import Client
7
  import google.generativeai as genai
8
  from dotenv import load_dotenv
9
+ from datetime import datetime, timedelta
10
 
11
  # Load environment variables
12
  load_dotenv()
 
24
  model = genai.GenerativeModel('gemini-pro')
25
 
26
  # Streamlit app
27
+ st.set_page_config(page_title="Strava Run Analysis", layout="wide")
28
 
29
  # Custom CSS for better UI
30
  st.markdown("""
 
33
  background-color: #f0f2f6;
34
  }
35
  .stButton>button {
36
+ background-color: #fc4c02;
37
  color: white;
38
+ font-weight: bold;
39
+ border-radius: 5px;
40
+ border: none;
41
+ padding: 0.5rem 1rem;
42
+ }
43
+ .stButton>button:hover {
44
+ background-color: #e34402;
45
  }
46
  .stSelectbox {
47
  color: #333333;
48
  }
49
  .stPlotlyChart {
50
  background-color: white;
51
+ border-radius: 10px;
52
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
53
+ padding: 1rem;
54
+ margin-bottom: 2rem;
55
  }
56
+ .stat-card {
57
+ background-color: white;
58
+ border-radius: 10px;
59
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
60
+ padding: 1.5rem;
61
+ text-align: center;
62
  }
63
+ .stat-card h3 {
64
+ color: #fc4c02;
65
+ font-size: 2.5rem;
66
+ margin-bottom: 0.5rem;
67
+ }
68
+ .stat-card p {
69
+ color: #666;
70
+ font-size: 1rem;
71
+ margin: 0;
72
+ }
73
+ .section-header {
74
+ color: #333;
75
+ font-size: 1.8rem;
76
+ font-weight: bold;
77
+ margin-top: 2rem;
78
+ margin-bottom: 1rem;
79
  }
80
  </style>
81
  """, unsafe_allow_html=True)
82
 
83
+ st.title("πŸƒβ€β™‚οΈ Strava Run Analysis")
84
 
85
  # Strava manual authentication
86
  if 'access_token' not in st.session_state:
87
  st.write("To use this app, you need to authorize it with Strava. Follow these steps:")
88
+ st.write("1. Click the button below to go to Strava's authorization page:")
89
+ auth_url = f"https://www.strava.com/oauth/authorize?client_id={STRAVA_CLIENT_ID}&response_type=code&redirect_uri=http://localhost&approval_prompt=force&scope=read_all,profile:read_all,activity:read_all"
90
+ st.markdown(f"<a href='{auth_url}' target='_blank'><button style='background-color: #fc4c02; color: white; padding: 0.5rem 1rem; border: none; border-radius: 5px; cursor: pointer;'>Authorize Strava</button></a>", unsafe_allow_html=True)
91
  st.write("2. Log in to Strava if needed and click 'Authorize'")
92
+ st.write("3. After authorizing, you'll be redirected to a page that may show an error. This is expected!")
93
+ st.write("4. Copy the 'code' parameter from the URL of that page.")
94
  st.write("5. Paste that code below:")
95
 
96
  auth_code = st.text_input("Paste the authorization code here:")
 
102
  code=auth_code
103
  )
104
  st.session_state.access_token = token_response['access_token']
105
+ st.success("Authorization successful! Refreshing the app...")
106
  st.rerun()
107
  except Exception as e:
108
+ st.error(f"An error occurred: {str(e)}. Please try authorizing again.")
109
 
110
  if 'access_token' in st.session_state:
111
  client.access_token = st.session_state.access_token
 
113
  athlete = client.get_athlete()
114
  st.write(f"Welcome, {athlete.firstname} {athlete.lastname}! πŸ‘‹")
115
  except Exception as e:
116
+ st.error(f"Error fetching athlete data: {str(e)}. Please try reauthorizing.")
117
  if st.button("Reauthorize"):
118
  del st.session_state.access_token
119
  st.rerun()
120
 
121
+ # Fetch running activities
122
  @st.cache_data(ttl=3600)
123
+ def fetch_run_activities():
124
+ activities = list(client.get_activities(limit=200))
125
+ runs = [activity for activity in activities if activity.type == 'Run']
126
  df = pd.DataFrame([{
127
+ 'name': run.name,
128
+ 'distance': float(run.distance) / 1000, # Convert to km
129
+ 'moving_time': run.moving_time.total_seconds() / 60, # Convert to minutes
130
+ 'total_elevation_gain': float(run.total_elevation_gain),
131
+ 'average_speed': float(run.average_speed) * 3.6, # Convert to km/h
132
+ 'average_heartrate': float(run.average_heartrate) if run.average_heartrate else None,
133
+ 'start_date': run.start_date.replace(tzinfo=None)
134
+ } for run in runs])
135
+ df['pace'] = df['moving_time'] / df['distance'] # Calculate pace (min/km)
136
  return df
137
 
138
  try:
139
+ df = fetch_run_activities()
140
  except Exception as e:
141
+ st.error(f"Error fetching activities: {str(e)}. Please try again later.")
142
  st.stop()
143
 
 
 
 
 
 
 
 
 
144
  # Basic stats
145
+ st.markdown("<h2 class='section-header'>πŸ“Š Run Statistics</h2>", unsafe_allow_html=True)
 
 
 
146
 
147
+ col1, col2, col3, col4 = st.columns(4)
148
+ with col1:
149
+ st.markdown("<div class='stat-card'><h3>{:.0f}</h3><p>Total Runs</p></div>".format(len(df)), unsafe_allow_html=True)
150
+ with col2:
151
+ st.markdown("<div class='stat-card'><h3>{:.0f} km</h3><p>Total Distance</p></div>".format(df['distance'].sum()), unsafe_allow_html=True)
152
+ with col3:
153
+ total_time = df['moving_time'].sum()
154
+ st.markdown("<div class='stat-card'><h3>{:.0f}h {:.0f}m</h3><p>Total Time</p></div>".format(total_time // 60, total_time % 60), unsafe_allow_html=True)
155
+ with col4:
156
+ st.markdown("<div class='stat-card'><h3>{:.0f} m</h3><p>Total Elevation Gain</p></div>".format(df['total_elevation_gain'].sum()), unsafe_allow_html=True)
157
 
158
  # Visualizations
159
+ st.markdown("<h2 class='section-header'>πŸ“ˆ Run Analysis</h2>", unsafe_allow_html=True)
160
 
161
+ # Weekly distance
162
+ weekly_distance = df.resample('W', on='start_date')['distance'].sum().reset_index()
163
+ fig_weekly = px.bar(weekly_distance, x='start_date', y='distance',
164
+ title="Weekly Running Distance",
165
+ labels={'distance': 'Distance (km)', 'start_date': 'Week'})
166
+ fig_weekly.update_layout(xaxis_title="Week", yaxis_title="Distance (km)")
167
+ st.plotly_chart(fig_weekly, use_container_width=True)
168
+
169
+ # Pace improvement
170
+ df_sorted = df.sort_values('start_date')
171
+ fig_pace = px.scatter(df_sorted, x='start_date', y='pace',
172
+ title="Running Pace Over Time",
173
+ labels={'pace': 'Pace (min/km)', 'start_date': 'Date'})
174
+ fig_pace.add_trace(go.Scatter(x=df_sorted['start_date'], y=df_sorted['pace'].rolling(window=10).mean(),
175
+ mode='lines', name='10-run moving average'))
176
+ fig_pace.update_layout(yaxis_title="Pace (min/km)")
177
+ st.plotly_chart(fig_pace, use_container_width=True)
178
+
179
+ # Heart rate improvement (if data available)
180
+ if df['average_heartrate'].notna().any():
181
+ df_hr = df_sorted[df_sorted['average_heartrate'].notna()]
182
+ fig_hr = px.scatter(df_hr, x='start_date', y='average_heartrate',
183
+ title="Average Heart Rate Over Time",
184
+ labels={'average_heartrate': 'Average Heart Rate (bpm)', 'start_date': 'Date'})
185
+ fig_hr.add_trace(go.Scatter(x=df_hr['start_date'], y=df_hr['average_heartrate'].rolling(window=10).mean(),
186
+ mode='lines', name='10-run moving average'))
187
+ fig_hr.update_layout(yaxis_title="Average Heart Rate (bpm)")
188
+ st.plotly_chart(fig_hr, use_container_width=True)
189
+ else:
190
+ st.info("No heart rate data available for analysis.")
191
+
192
+ # Distance vs. Elevation gain
193
+ fig_elev = px.scatter(df, x='distance', y='total_elevation_gain',
194
+ title="Distance vs. Elevation Gain",
195
+ labels={'distance': 'Distance (km)', 'total_elevation_gain': 'Elevation Gain (m)'})
196
+ fig_elev.update_layout(xaxis_title="Distance (km)", yaxis_title="Elevation Gain (m)")
197
+ st.plotly_chart(fig_elev, use_container_width=True)
198
 
199
  # Training Plan Generation
200
+ st.markdown("<h2 class='section-header'>πŸ‹οΈβ€β™€οΈ Generate Training Plan</h2>", unsafe_allow_html=True)
201
 
202
  col1, col2, col3 = st.columns(3)
203
  with col1:
 
211
  with st.spinner("Generating your personalized training plan..."):
212
  try:
213
  prompt = f"""Create a {plan_duration} training plan for a {level} runner preparing for a {race_distance} race.
214
+ Include weekly mileage and key workouts. Format the plan week by week, with each week on a new line.
215
+ Consider the runner's current weekly mileage of {weekly_distance['distance'].iloc[-1]:.1f} km."""
216
  response = model.generate_content(prompt)
217
  st.markdown(response.text)
218
  except Exception as e:
219
+ st.error(f"Error generating training plan: {str(e)}. Please try again later.")
 
 
 
 
 
 
 
220
 
221
  else:
222
+ st.info("Please complete the authorization process to view your Strava running data and analytics.")