Skip to content

Commit

Permalink
Updated preference context & better activity descriptions/notes
Browse files Browse the repository at this point in the history
  • Loading branch information
voynow committed Nov 6, 2024
1 parent d34e1b2 commit 6d34833
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 31 deletions.
28 changes: 24 additions & 4 deletions src/apn.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,28 @@ 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"],
private_key=base64.b64decode(os.environ["APN_PRIVATE_KEY"]).decode(),
)

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}",
Expand Down Expand Up @@ -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}")
2 changes: 1 addition & 1 deletion src/types/training_week.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
4 changes: 2 additions & 2 deletions src/update_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
82 changes: 58 additions & 24 deletions test.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -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=<Day.WED: 'Wed'>, session_type=<SessionType.MODERATE: 'moderate run'>), TheoreticalTrainingSession(day=<Day.SUN: 'Sun'>, session_type=<SessionType.LONG: 'long run'>), TheoreticalTrainingSession(day=<Day.THURS: 'Thurs'>, session_type=<SessionType.MODERATE: 'moderate run'>)]\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=<RaceDistance.MARATHON: 'marathon'> ideal_training_week=[TheoreticalTrainingSession(day=<Day.MON: 'Mon'>, session_type=<SessionType.EASY: 'easy run'>), TheoreticalTrainingSession(day=<Day.TUES: 'Tues'>, session_type=<SessionType.EASY: 'easy run'>), TheoreticalTrainingSession(day=<Day.WED: 'Wed'>, session_type=<SessionType.EASY: 'easy run'>), TheoreticalTrainingSession(day=<Day.THURS: 'Thurs'>, session_type=<SessionType.EASY: 'easy run'>), TheoreticalTrainingSession(day=<Day.FRI: 'Fri'>, session_type=<SessionType.REST: 'rest day'>), TheoreticalTrainingSession(day=<Day.SAT: 'Sat'>, session_type=<SessionType.LONG: 'long run'>), TheoreticalTrainingSession(day=<Day.SUN: 'Sun'>, session_type=<SessionType.REST: 'rest day'>)]\n",
"race_distance=None ideal_training_week=[]\n"
"sessions=[TrainingSession(day=<Day.MON: 'Mon'>, session_type=<SessionType.EASY: 'easy run'>, distance=8.37, notes='easy pace', completed=True), TrainingSession(day=<Day.TUES: 'Tues'>, session_type=<SessionType.MODERATE: 'moderate run'>, distance=5.0, notes='Maintain a steady but comfortable pace.', completed=False), TrainingSession(day=<Day.WED: 'Wed'>, session_type=<SessionType.SPEED: 'speed workout'>, distance=6.0, notes='2x2mi at 7m 30s pace with rest intervals.', completed=False), TrainingSession(day=<Day.THURS: 'Thurs'>, session_type=<SessionType.REST: 'rest day'>, distance=0.0, notes='Complete rest to recover.', completed=False), TrainingSession(day=<Day.FRI: 'Fri'>, session_type=<SessionType.EASY: 'easy run'>, distance=5.63, notes='Easy pace, focus on form and breathing.', completed=False), TrainingSession(day=<Day.SAT: 'Sat'>, session_type=<SessionType.REST: 'rest day'>, distance=0.0, notes='Use this day to fully rest before the long run.', completed=False), TrainingSession(day=<Day.SUN: 'Sun'>, session_type=<SessionType.LONG: 'long run'>, 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=<RaceDistance.MARATHON: 'marathon'> ideal_training_week=[TheoreticalTrainingSession(day=<Day.SAT: 'Sat'>, session_type=<SessionType.LONG: 'long run'>), TheoreticalTrainingSession(day=<Day.SUN: 'Sun'>, session_type=<SessionType.REST: 'rest day'>)]\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=<Day.MON: 'Mon'>, session_type=<SessionType.EASY: 'easy run'>, 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=<Day.TUES: 'Tues'>, session_type=<SessionType.EASY: 'easy run'>, distance=1.02, notes='Short recovery run with no elevation gain. Pace was 10m 1s per mile.', completed=True), TrainingSession(day=<Day.WED: 'Wed'>, session_type=<SessionType.SPEED: 'speed workout'>, 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=<Day.THURS: 'Thurs'>, session_type=<SessionType.REST: 'rest day'>, distance=0.0, notes='Complete rest to recover.', completed=False), TrainingSession(day=<Day.FRI: 'Fri'>, session_type=<SessionType.EASY: 'easy run'>, 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=<Day.SAT: 'Sat'>, session_type=<SessionType.REST: 'rest day'>, distance=0.0, notes='Use this day to fully rest before the long run.', completed=False), TrainingSession(day=<Day.SUN: 'Sun'>, session_type=<SessionType.LONG: 'long run'>, 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)"
]
},
{
Expand Down

0 comments on commit 6d34833

Please sign in to comment.