find_strava_activities
ChatGPTRemote fallback and discovery tool — search Strava directly for activities absent from or incomplete in local storage. IMPORTANT: always check local storage first. For targeted historical activity lookups (e.g. "my 2022 marathon", "a trail run in September 2023"), call search_local_activities first. For aggregation and rankings over synced history, call get_local_stats first. Call find_strava_activities only when: (1) search_local_activities returned 0 results and the activity may not yet be in local storage; (2) local history is known to be incomplete for the required date range. Distance-based discovery when not found locally: marathon min_distance_m 40000 max_distance_m 43500, half-marathon min_distance_m 20000 max_distance_m 22000. Strava only supports date bounds and pagination remotely; distance, name, type, and grouping are applied locally after fetching pages. IMPORTANT — historical year-specific searches: always set explicit after and before for the target year — e.g. after: "2022-01-01", before: "2023-01-01". The default 365-day window will miss any year more than 12 months ago. Never rely on the default window for multi-year lookback searches. IMPORTANT — all-time grouping queries: when group_by is used and no explicit dates are provided, always set after and before explicitly (e.g. after: "2010-01-01") — never rely on the default window for historical ranking or trend queries. IMPORTANT: if no candidate is found and the user is certain the activity exists, do not conclude it does not exist — narrow the date range first, then increase max_pages. Check meta.has_more_remote_pages: true means more Strava pages may exist; false means the search window is exhausted. IMPORTANT — group_by is a fallback capability: use group_by only when local history is absent or incomplete and get_local_stats cannot be used. When group_by is used and meta.has_more_remote_pages is true, warn the user that the ranking is based on partial data.
get_account_status
ChatGPTGet the status of the user's Performance Signal account: whether setup is complete, whether their Strava account is connected, and whether any action is required. This tool is read-only and does not start a Strava connection. Call this tool when any data tool returns meta.strava_connected: false, or when the user asks about their setup or connection status. Read the action field to decide what to say: action "complete_setup" or "account_verification_required" includes a dashboard_url — direct the user there to finish non-billing account/Strava setup (do not attempt to construct or navigate to any Strava OAuth URL yourself; the website dashboard is the only place where Strava can be connected). action "access_inactive" has no dashboard_url and no next step to suggest — simply tell the user this feature is not currently available on their account; do not mention a subscription, membership, purchase, billing, or any URL. action "account_pending_deletion" means training data is unavailable while deletion is pending — state that plainly with no further action. If action_required is false and strava_connected is true, proceed with data tools. IMPORTANT — session expiry: if ChatGPT is prompting the user to re-authenticate with Performance Signal, the ChatGPT-to-Performance Signal session has expired — this is unrelated to Strava connection or account access. Instruct the user: open ChatGPT Settings, find Performance Signal under Connected Apps, disconnect it, reconnect it, and sign in again. Do not tell them to reconnect Strava or take any purchase-related action.
get_local_stats
ChatGPTUse this tool to answer aggregation and ranking questions about the user's synced activity history. Example queries that should use this tool: "which year did I run the most kilometres", "what were my top 5 highest mileage months", "show my mileage by year", "how did my yearly mileage compare", "best year ever", "monthly mileage leaderboard", "how many km did I run each year", "what was my biggest month". IMPORTANT: check meta.strava_connected in the response — if false, call get_account_status immediately and direct the user to their Performance Signal dashboard at https://performancesignal.ai/dashboard to complete setup. This tool queries the local Supabase database directly — zero Strava API calls, no rate limit risk, scales to any history size. Results are sorted by total_distance_km descending; groups[0] is always the highest-mileage period, so "top 5" queries read groups.slice(0, 5) without further sorting. Use group_by: "year" for yearly comparisons (period format "2022"), group_by: "month" for monthly rankings (period format "2022-03"). Optionally narrow with after and before (ISO date strings, e.g. "2022-01-01"). activity_type defaults to "Run"; use "TrailRun", "VirtualRun", or "Ride" to query other types. Only returns activities already synced to local storage. If meta.strava_connected is false, call get_account_status. If summary.total_activities_in_db is 0 or much lower than expected and Strava is connected, local history may not have been imported — inform the user and suggest reconnecting Strava to trigger a full history sync. Do not use find_strava_activities for aggregation when local history is complete — use this tool instead.
get_run_detail
ChatGPTUse this after get_runs when analysing a specific run in detail. Returns structured activity metadata: distance, duration, average pace, average heart rate, elevation, laps, splits, segment efforts, and best efforts where available in the raw Strava data. IMPORTANT: get_run_detail does NOT provide per-second time-series streams. It does not contain per-second cadence, per-second pace, per-second heart rate, or any point-in-run data. Do not use get_run_detail alone to answer questions about fading, heart rate drift, cadence drift, stride mechanics, or where something happened in a run. For all per-second stream analysis — including exact point-in-run questions, fade detection, HR drift, cadence analysis, pacing consistency, or stride mechanics — use get_run_streams instead.
get_run_streams
ChatGPTUse this for all stream-level intra-run analysis. Returns per-second time-series arrays: time, distance, heartrate, velocity_smooth, cadence, altitude, grade_smooth, and moving. This is the only tool that provides per-second cadence, pace, HR, and position data. Use this — not get_run_detail — for: fading, HR drift, cadence drift, stride mechanics, pacing consistency, surging, effort distribution, late-run fatigue, hill analysis, and any question about where something happened in a run. To analyse a specific distance point (e.g. "what happened at 11.5km"): find the index i where distance[i] is closest to the target in metres (e.g. 11500), then read heartrate[i], velocity_smooth[i], cadence[i], altitude[i], grade_smooth[i], and time[i] at that index. Report specific values at that point, not just qualitative patterns across the run. For cadence and fade questions: analyse cadence[] and velocity_smooth[] together across the run — do not infer cadence stability from average_cadence alone. Use the streams to distinguish between cadence drop, stride-length shortening (speed falls while cadence holds), HR drift, terrain effects (grade_smooth), and pacing decisions. If this tool returns an error indicating stream data was not found, the activity exists in local storage but its stream data has not been synced yet. Call sync_strava_activities with the activity_id and include_streams: true, then retry get_run_streams.
get_runs
ChatGPTList recent runs and retrieve activity IDs from locally stored data. Use a small limit (1–5) for explicit recent-run queries ("last run", "last 3 runs"). For trend analysis, training progression, or consistency checks spanning weeks or months, use a broader limit (50–200). IMPORTANT: for searching by date range, distance type (marathon, half-marathon, 10k), or activity name, use search_local_activities instead — it applies server-side filters and does not require a large limit. Do not use get_runs with a high limit to scan for a specific historical activity. IMPORTANT: check meta.strava_connected in the response — if false, call get_account_status immediately and direct the user to their Performance Signal dashboard at https://performancesignal.ai/dashboard to complete setup. If meta.has_more is true and you need broader coverage, increase the limit or switch to search_local_activities with appropriate date filters. find_strava_activities is a remote fallback for activities not yet in local storage — do not use it when local history is sufficient.
search_local_activities
ChatGPTSearch locally synced activities by date range, distance, name, or type. Use this when the user refers to a specific historical activity — for example: "my marathon from 2022", "my trail run in September 2023", "the Berlin race", "my longest run last year", "a 10k I ran in 2021". Always call this before find_strava_activities for history-based lookups — it queries local Supabase storage with zero Strava API calls. Set after/before for year-specific queries (e.g. after: "2022-01-01", before: "2023-01-01"). Use min_distance_m/max_distance_m for race distances: marathon 40000–43500, half-marathon 20000–22000, 10k 9500–10500. Default activity_type is "run" (includes Run, TrailRun, VirtualRun only — no Golf, Ride, or Walk). Use activity_type: "all" to search across all sport types. Use order_by: "distance_m", order_dir: "desc" to find the longest run in a date range. Returns activity_id and strava_activity_id values ready for get_run_detail or get_run_streams. If this returns no results and the user is certain the activity exists (e.g. it predates the Strava connection or local sync), call find_strava_activities as a remote fallback.
sync_strava_activities
ChatGPTUse after find_strava_activities to import one or more Strava activities (and optionally their streams) into local storage. Call this before get_run_detail or get_run_streams if the activity was found remotely but already_synced is false. Supports multiple IDs in one call. Also use this when get_run_streams returns an error indicating stream data was not found for an activity that already exists in local storage — call with the activity_id and include_streams: true to fetch the missing stream data, then retry get_run_streams.