From 6d34833c844a475363a98b8ab955ea754b10d5be Mon Sep 17 00:00:00 2001 From: voynow Date: Tue, 5 Nov 2024 19:15:52 -0500 Subject: [PATCH] Updated preference context & better activity descriptions/notes --- src/apn.py | 28 +++++++++++-- src/types/training_week.py | 2 +- src/update_pipeline.py | 4 +- test.ipynb | 82 +++++++++++++++++++++++++++----------- 4 files changed, 85 insertions(+), 31 deletions(-) diff --git a/src/apn.py b/src/apn.py index 8fb04b9..78894d1 100644 --- a/src/apn.py +++ b/src/apn.py @@ -38,7 +38,21 @@ def send_push_notification(device_token: str, title: str, body: str): :param device_token: User's device token :param title: Notification title :param body: Notification body + :raises ValueError: If device token is invalid """ + # Validate device token format + clean_token = device_token.strip().replace(" ", "") + if ( + not clean_token + or len(clean_token) != 64 + or not all(c in "0123456789abcdefABCDEF" for c in clean_token) + ): + raise ValueError(f"Invalid device token format: {device_token}") + + logging.info( + f"Sending push notification with token: {clean_token[:8]}..." + ) # Log first 8 chars for debugging + auth_token = generate_jwt_token( key_id=os.environ["APN_KEY_ID"], team_id=os.environ["APN_TEAM_ID"], @@ -46,7 +60,6 @@ def send_push_notification(device_token: str, title: str, body: str): ) base_url = "api.push.apple.com" - clean_token = device_token.strip().replace(" ", "") url = f"https://{base_url}/3/device/{clean_token}" headers = { "Authorization": f"Bearer {auth_token}", @@ -78,12 +91,19 @@ def send_push_notification(device_token: str, title: str, body: str): def send_push_notif_wrapper(user: UserRow): + """Send push notification to user if they have a valid device token.""" user_auth = get_user_auth(user.athlete_id) - if user_auth.device_token: + if not user_auth.device_token: + logger.info(f"No device token for user {user.athlete_id}") + return + + try: send_push_notification( device_token=user_auth.device_token, title="TrackFlow 🏃‍♂️🎯", body="Your training week has been updated!", ) - else: - logger.info(f"Skipping push notification for {user.athlete_id=}") + except ValueError as e: + logger.error(f"Invalid device token for user {user.athlete_id}: {e}") + except Exception as e: + logger.error(f"Failed to send push notification to user {user.athlete_id}: {e}") diff --git a/src/types/training_week.py b/src/types/training_week.py index 4d13244..d00489f 100644 --- a/src/types/training_week.py +++ b/src/types/training_week.py @@ -29,7 +29,7 @@ class TrainingSession(BaseModel): session_type: SessionType distance: float = Field(description="Distance in miles") notes: str = Field( - description="Concise notes about the session, e.g. '2x2mi @ 10k pace' or 'easy pace'" + description="Detailed yet concise notes about the session from the coach's perspective" ) completed: bool = Field(description="Whether the session has been completed") diff --git a/src/update_pipeline.py b/src/update_pipeline.py index e7d3cab..cf072ca 100644 --- a/src/update_pipeline.py +++ b/src/update_pipeline.py @@ -33,14 +33,14 @@ def update_training_week(user: UserRow, exe_type: ExeType) -> dict: if exe_type == ExeType.NEW_WEEK: weekly_summaries = get_weekly_summaries(strava_client) training_week = generate_new_training_week( - sysmsg_base=f"{COACH_ROLE}\n{user.preferences}", + sysmsg_base=f"{COACH_ROLE}\nClient Preferences: {user.preferences}", weekly_summaries=weekly_summaries, ) else: # ExeType.MID_WEEK current_week = get_training_week(user.athlete_id) activity_summaries = get_activity_summaries(strava_client, num_weeks=1) training_week = generate_mid_week_update( - sysmsg_base=f"{COACH_ROLE}\n{user.preferences}", + sysmsg_base=f"{COACH_ROLE}\nClient Preferences: {user.preferences}", training_week=current_week, completed_activities=activity_summaries, ) diff --git a/test.ipynb b/test.ipynb index 1226ceb..7ab79cb 100644 --- a/test.ipynb +++ b/test.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -12,40 +12,74 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:httpx:HTTP Request: GET https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/user?select=%2A&athlete_id=eq.98390356 \"HTTP/2 200 OK\"\n", + "INFO:httpx:HTTP Request: GET https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/user_auth?select=%2A&athlete_id=eq.98390356 \"HTTP/2 200 OK\"\n", + "INFO:httpx:HTTP Request: GET https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/training_week?select=training_week&athlete_id=eq.98390356&order=created_at.desc&limit=1 \"HTTP/2 200 OK\"\n", + "INFO:stravalib.protocol.ApiV3:GET 'https://www.strava.com/api/v3/athlete/activities' with params {'before': 1730851992, 'after': 1730243592, 'page': 1, 'per_page': 200}\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=)]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance=None ideal_training_week=[]\n", - "race_distance= ideal_training_week=[TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=)]\n", - "race_distance=None ideal_training_week=[]\n" + "sessions=[TrainingSession(day=, session_type=, distance=8.37, notes='easy pace', completed=True), TrainingSession(day=, session_type=, distance=5.0, notes='Maintain a steady but comfortable pace.', completed=False), TrainingSession(day=, session_type=, distance=6.0, notes='2x2mi at 7m 30s pace with rest intervals.', completed=False), TrainingSession(day=, session_type=, distance=0.0, notes='Complete rest to recover.', completed=False), TrainingSession(day=, session_type=, distance=5.63, notes='Easy pace, focus on form and breathing.', completed=False), TrainingSession(day=, session_type=, distance=0.0, notes='Use this day to fully rest before the long run.', completed=False), TrainingSession(day=, session_type=, distance=13.0, notes='Maintain a comfortable pace, ensure hydration and nutrition.', completed=False)]\n", + "[ActivitySummary(date='Monday, November 04, 2024', distance_in_miles=8.37, elevation_gain_in_feet=173.88, pace_minutes_per_mile=9.66), ActivitySummary(date='Tuesday, November 05, 2024', distance_in_miles=1.02, elevation_gain_in_feet=0.0, pace_minutes_per_mile=10.02)]\n", + "You are a talented running coach with years of experience. You have been hired by a client to help them improve their running performance. Note: convert pace values where applicable e.g. 7.5 -> 7m 30s.\n", + "Client Preferences: race_distance= ideal_training_week=[TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=)]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n", + "INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n", + "INFO:httpx:HTTP Request: POST https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/training_week \"HTTP/2 201 Created\"\n" ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sessions=[TrainingSession(day=, session_type=, distance=8.37, notes='Completed an easy run with an elevation gain of 173.88 feet. Pace was 9m 40s per mile.', completed=True), TrainingSession(day=, session_type=, distance=1.02, notes='Short recovery run with no elevation gain. Pace was 10m 1s per mile.', completed=True), TrainingSession(day=, session_type=, distance=7.5, notes='2x2mi at 7m 30s pace with rest intervals, plus an additional 1.5 miles at an easy pace.', completed=False), TrainingSession(day=, session_type=, distance=0.0, notes='Complete rest to recover.', completed=False), TrainingSession(day=, session_type=, distance=7.11, notes='Easy pace, focus on form and breathing, including an additional 1.48 miles at an easy pace.', completed=False), TrainingSession(day=, session_type=, distance=0.0, notes='Use this day to fully rest before the long run.', completed=False), TrainingSession(day=, session_type=, distance=13.0, notes='Maintain a comfortable pace, ensure hydration and nutrition.', completed=False)]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:httpx:HTTP Request: GET https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/user_auth?select=%2A&athlete_id=eq.98390356 \"HTTP/2 200 OK\"\n", + "INFO:root:Sending push notification with token: ff27dd2c...\n", + "INFO:httpx:HTTP Request: POST https://api.push.apple.com/3/device/ff27dd2c813efb25194f614c3be1829dce114f288dd03bde0e86169fae8f3d1f \"HTTP/2 200 OK\"\n" + ] + }, + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "from src.supabase_client import list_users\n", + "import os\n", + "\n", + "from src.update_pipeline import update_training_week\n", + "from src.types.update_pipeline import ExeType\n", + "from src.supabase_client import get_user\n", "\n", - "for user in list_users():\n", - " print(user.preferences)" + "USER = get_user(os.environ[\"JAMIES_ATHLETE_ID\"])\n", + "update_training_week(USER, ExeType.MID_WEEK)" ] }, {