Hi R2Tom
I have encountered similar error. I created a basic GUI that will analyze the normalized power and average power of a certain distance of a ride e.g. kilometer 3 to 23.
import tkinter as tk
from tkinter import ttk
import requests
import numpy as np
from requests.auth import HTTPBasicAuth
API_KEY = “my api key” # Replace with your actual API key
— MODIFIED FUNCTION —
def get_activity_data(api_key, activity_id):
“”"
Fetches and processes activity stream data from Intervals.icu.
“”"
# 1. Use the correct endpoint for streams
url = f"https://intervals.icu/api/v1/activities/{activity_id}/streams"
auth = HTTPBasicAuth(‘API_KEY’, api_key)
response = requests.get(url, auth=auth)
response.raise_for_status() # This will raise an error for bad responses (e.g., 404 Not Found)
# 2. Process the list of streams into a single dictionary
streams_list = response.json()
data_dict = {}
for stream in streams_list:
# The API uses 'watts' for power, let's rename it to 'power' for consistency
if stream['type'] == 'watts':
data_dict['power'] = stream['data']
else:
data_dict[stream['type']] = stream['data']
# Check if essential data is present
if 'power' not in data_dict or 'time' not in data_dict or 'distance' not in data_dict:
# This can happen if the activity doesn't have power data, for example.
raise ValueError("Activity is missing required data streams (power, time, or distance).")
return data_dict
def hhmmss_to_seconds(hhmmss):
“”“Convert hh:mm:ss string to seconds.”“”
try:
h, m, s = map(int, hhmmss.strip().split(“:”))
return h3600 + m60 + s
except Exception:
raise ValueError(“Time must be in hh:mm:ss format.”)
def analyze_power(data, analysis_type, start, end):
# This function now works correctly because data is a dictionary
powers = np.array(data[“power”])
if analysis_type == “time”:
times = np.array(data[“time”])
mask = (times >= start) & (times <= end)
else:
distances = np.array(data[“distance”])
mask = (distances >= start) & (distances <= end)
selected_powers = powers[mask]
if selected_powers.size == 0:
return 0.0, 0.0
avg_power = selected_powers.mean()
# Calculate Normalized Power (NP)
# Use a 30-second rolling average for the calculation
if len(selected_powers) >= 30:
rolling_avg = np.convolve(selected_powers, np.ones(30)/30, mode='valid')
norm_power = (np.mean(rolling_avg ** 4)) ** 0.25
else:
# Can't calculate NP for durations shorter than 30 seconds
norm_power = 0.0
return avg_power, norm_power
class IntervalsAnalyzerApp(tk.Tk):
def init(self):
super().init()
self.title(“Intervals.icu Analyzer”)
# Username dropdown (for now, just your own)
ttk.Label(self, text="Username:").grid(row=0, column=0, sticky="w", padx=5, pady=2)
self.user_var = tk.StringVar(value="i95385")
self.user_dropdown = ttk.Combobox(self, textvariable=self.user_var, state="readonly", values=["i95385"])
self.user_dropdown.grid(row=0, column=1, sticky="ew", columnspan=2, padx=5, pady=2)
# Activity ID
ttk.Label(self, text="Activity ID:").grid(row=1, column=0, sticky="w", padx=5, pady=2)
self.activity_id_entry = ttk.Entry(self)
self.activity_id_entry.grid(row=1, column=1, sticky="ew", columnspan=2, padx=5, pady=2)
# Analysis type
self.analysis_type = tk.StringVar(value="time")
ttk.Label(self, text="Analysis Type:").grid(row=2, column=0, sticky="w", padx=5, pady=2)
self.time_radio = ttk.Radiobutton(self, text="Time", variable=self.analysis_type, value="time", command=self.update_fields)
self.time_radio.grid(row=2, column=1, sticky="w", padx=5, pady=2)
self.dist_radio = ttk.Radiobutton(self, text="Distance", variable=self.analysis_type, value="distance", command=self.update_fields)
self.dist_radio.grid(row=2, column=2, sticky="w", padx=5, pady=2)
# Start/End fields
self.start_label = ttk.Label(self, text="Start Time (hh:mm:ss):")
self.start_label.grid(row=3, column=0, sticky="w", padx=5, pady=2)
self.start_entry = ttk.Entry(self)
self.start_entry.grid(row=3, column=1, sticky="ew", columnspan=2, padx=5, pady=2)
self.end_label = ttk.Label(self, text="End Time (hh:mm:ss):")
self.end_label.grid(row=4, column=0, sticky="w", padx=5, pady=2)
self.end_entry = ttk.Entry(self)
self.end_entry.grid(row=4, column=1, sticky="ew", columnspan=2, padx=5, pady=2)
# Analyze button
self.analyze_btn = ttk.Button(self, text="Analyze", command=self.run_analysis)
self.analyze_btn.grid(row=5, column=0, columnspan=3, pady=10)
# Output
self.output_label = ttk.Label(self, text="", font=("Helvetica", 10))
self.output_label.grid(row=6, column=0, columnspan=3, padx=5, pady=5)
self.update_fields() # Set initial fields
def update_fields(self):
if self.analysis_type.get() == "time":
self.start_label.config(text="Start Time (hh:mm:ss):")
self.end_label.config(text="End Time (hh:mm:ss):")
else:
self.start_label.config(text="Start Distance (km):")
self.end_label.config(text="End Distance (km):")
def run_analysis(self):
activity_id = self.activity_id_entry.get().strip()
if not activity_id:
self.output_label.config(text="Error: Please enter an Activity ID.")
return
analysis_type = self.analysis_type.get()
try:
if analysis_type == "time":
start = hhmmss_to_seconds(self.start_entry.get())
end = hhmmss_to_seconds(self.end_entry.get())
else: # distance
start = float(self.start_entry.get()) * 1000 # km -> m
end = float(self.end_entry.get()) * 1000 # km -> m
self.output_label.config(text="Fetching and analyzing data...")
self.update_idletasks() # Update the GUI to show the message
data = get_activity_data(API_KEY, activity_id)
avg_power, norm_power = analyze_power(data, analysis_type, start, end)
# Show a more helpful message if no data was found in the range
if avg_power == 0 and norm_power == 0:
result_text = "No data found in the specified range."
else:
result_text = f"Average Power: {avg_power:.2f} W\nNormalized Power: {norm_power:.2f} W"
self.output_label.config(text=result_text)
except requests.exceptions.HTTPError as e:
self.output_label.config(text=f"API Error: {e.response.status_code}. Check Activity ID.")
except Exception as e:
self.output_label.config(text=f"Error: {e}")
if name == “main”:
app = IntervalsAnalyzerApp()
app.mainloop()
i have entered the right API key and also the activity id straight from the address bar of intervals. i even removed “i” but still got the same error:
API error 404: Check activity ID. is there something wrong with this?