Skip to content

Commit

Permalink
Merge pull request #12 from voynow/2-mid-week-update
Browse files Browse the repository at this point in the history
2 mid week update
  • Loading branch information
voynow authored Aug 18, 2024
2 parents cfc2a4a + 6c0ffe9 commit 8b69fb1
Show file tree
Hide file tree
Showing 8 changed files with 432 additions and 70 deletions.
221 changes: 218 additions & 3 deletions src/email_manager.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import os
import uuid
from datetime import datetime
from typing import Dict

import sib_api_v3_sdk
from dotenv import load_dotenv

from src.types.training_week import TrainingWeekWithCoaching
from src.types.mid_week_analysis import MidWeekAnalysis
from src.types.training_week import TrainingWeekWithCoaching, TrainingWeekWithPlanning

load_dotenv()

Expand All @@ -17,6 +19,190 @@
)


def training_week_update_to_html(
mid_week_analysis: MidWeekAnalysis,
training_week_update_with_planning: TrainingWeekWithPlanning,
) -> str:
"""
Convert updated training week data to HTML content for email.
:param mid_week_analysis: MidWeekAnalysis object containing completed activities.
:param training_week_update_with_planning: TrainingWeekWithPlanning object containing updated plan.
:return: HTML content for email.
"""
uid = str(uuid.uuid4())

completed_sessions = {}
for activity in mid_week_analysis.activities:
activity_datetime = datetime.strptime(
activity.date_and_time, "%A, %B %d, %Y %I:%M %p"
)
completed_sessions[activity_datetime.strftime("%A").lower()] = activity

total_miles = round(mid_week_analysis.miles_target, 1)
miles_remaining = round(mid_week_analysis.miles_remaining, 1)

html_content = """
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
margin: 0;
padding: 0;
}
.container {
width: 100%;
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background-color: #6495ED;
color: #ffffff;
text-align: center;
padding: 20px;
}
.header h1 {
margin: 0;
font-size: 24px;
}
.content {
padding: 20px;
}
.content h2 {
color: #6495ED;
font-size: 20px;
margin-bottom: 10px;
}
.content ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.content li {
margin-bottom: 10px;
padding: 15px;
border-left: 5px solid #6495ED;
border-radius: 5px;
color: #333;
}
.content li.completed {
background-color: #f9f9f9;
border-left-color: #28a745;
}
.content li.upcoming {
background-color: #f9f9f9;
border-left-color: #6495ED;
}
.content li strong {
display: block;
font-size: 16px;
margin-bottom: 5px;
color: #333;
}
.miles-summary {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #6495ED;
padding: 20px 30px;
margin-top: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
.miles-info {
flex: 1;
}
.miles-label {
font-size: 18px;
color: #ffffff;
margin-bottom: 5px;
}
.miles-value {
font-size: 28px;
font-weight: bold;
color: #ffffff;
}
.separator {
width: 2px;
background-color: #ddd;
height: 50px;
margin: 0 20px;
}
.footer {
background-color: #f1f1f1;
text-align: center;
padding: 10px;
font-size: 9px;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Updated Training Schedule</h1>
</div>
<div class="content">
<ul>
"""
# Add completed activities
for day, activity in completed_sessions.items():
html_content += f"""
<li class="completed">
<strong>{day.capitalize()}</strong>
<span>Completed: {activity.distance_in_miles} miles</span><br>
<span>Pace: {activity.pace_minutes_per_mile} min/mile</span><br>
<span>Elevation Gain: {activity.elevation_gain_in_feet} feet</span>
</li>
"""

html_content += """
</ul>
<ul>
"""
# Add upcoming training plan
for session in training_week_update_with_planning.training_week:
html_content += f"""
<li class="upcoming">
<strong>{session.day.capitalize()}</strong>
<span>{session.session_type.value} {session.distance} miles</span><br>
<span>Planned: {session.notes}</span>
</li>
"""

html_content += f"""
</ul>
<div class="miles-summary">
<div class="miles-info">
<span class="miles-label">Total Miles Planned </span>
<span class="miles-value">{total_miles}</span>
</div>
<div class="separator"></div>
<div class="miles-info">
<span class="miles-label">Miles Remaining </span>
<span class="miles-value">{miles_remaining}</span>
</div>
</div>
</div>
<div class="footer">
<p style="font-size: 15px; color: #777;">Powered by the Strava API and OpenAI</p>
<p>{uid}</p>
</div>
</div>
</body>
</html>
"""
return html_content


def training_week_to_html(training_week_with_coaching: TrainingWeekWithCoaching) -> str:
"""
Convert a TrainingWeek object to HTML content for email
Expand Down Expand Up @@ -86,7 +272,31 @@ def training_week_to_html(training_week_with_coaching: TrainingWeekWithCoaching)
margin-bottom: 5px;
color: #333;
}
.review-section, .mileage-target-section {
.miles-summary {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #6495ED;
padding: 20px 30px;
margin-top: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
.miles-info {
flex: 1;
}
.miles-label {
font-size: 18px;
color: #ffffff;
margin-bottom: 5px;
}
.miles-value {
font-size: 28px;
font-weight: bold;
color: #ffffff;
}
.mileage-target-section {
margin-top: 30px;
padding: 20px;
background-color: #f9f9f9;
Expand Down Expand Up @@ -121,7 +331,12 @@ def training_week_to_html(training_week_with_coaching: TrainingWeekWithCoaching)
"""
html_content += f"""
</ul>
<h2>Total Miles Planned: {total_miles}</h2>
<div class="miles-summary" style="text-align: center;">
<div class="miles-info" style="margin: 0 auto;">
<span class="miles-label">Total Miles Planned </span>
<span class="miles-value">{total_miles}</span>
</div>
</div>
<div class="mileage-target-section">
<h2>Coach's Recommendation</h2>
<p>{training_week_with_coaching.weekly_mileage_target}</p>
Expand Down
33 changes: 32 additions & 1 deletion src/supabase_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from postgrest.base_request_builder import APIResponse
from supabase import Client, create_client

from src.types.training_week import TrainingWeekWithCoaching
from src.types.mid_week_analysis import MidWeekAnalysis
from src.types.training_week import TrainingWeekWithCoaching, TrainingWeekWithPlanning
from src.types.user_auth_row import UserAuthRow

load_dotenv()
Expand Down Expand Up @@ -115,3 +116,33 @@ def get_training_week_with_coaching(athlete_id: int) -> TrainingWeekWithCoaching
response_data["training_week"] = json.loads(response_data["training_week"])

return TrainingWeekWithCoaching(**response_data)


def upsert_training_week_update(
athlete_id: int,
mid_week_analysis: MidWeekAnalysis,
training_week_update_with_planning: TrainingWeekWithPlanning,
) -> APIResponse:
"""Upsert a row into the training_week_update table"""

row_data = {
"athlete_id": athlete_id,
"activities": json.dumps(
[activity.dict() for activity in mid_week_analysis.activities]
),
"training_week": json.dumps(
[session.dict() for session in mid_week_analysis.training_week]
),
"planning": training_week_update_with_planning.planning,
"training_week_update": json.dumps(
[
session.dict()
for session in training_week_update_with_planning.training_week
]
),
}

table = client.table("training_week_update")
response = table.upsert(row_data).execute()

return response
5 changes: 3 additions & 2 deletions src/training_week_generation/lambda_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
get_weekly_summaries,
)
from src.auth_manager import get_strava_client
from src.constants import COACH_ROLE
from src.email_manager import send_email, training_week_to_html
from src.supabase_client import upsert_training_week_with_coaching
from src.training_week_generation.training_week import generate_training_week


def lambda_handler(event, context):
client_preferences = "A) Training for a marathon B) This will be my second marathon C) Prefer workouts on Wednesdays and long runs on Saturdays"

sysmsg_base = f"{COACH_ROLE}\nYour client has included the following preferenced: {client_preferences}\n"
# activities setup
athlete_id = os.environ["JAMIES_ATHLETE_ID"]
strava_client = get_strava_client(athlete_id)
Expand All @@ -23,7 +24,7 @@ def lambda_handler(event, context):
day_of_week_summaries = get_day_of_week_summaries(activities_df)
weekly_summaries = get_weekly_summaries(activities_df)
training_week_with_coaching = generate_training_week(
client_preferences=client_preferences,
sysmsg_base=sysmsg_base,
weekly_summaries=weekly_summaries,
day_of_week_summaries=day_of_week_summaries,
)
Expand Down
7 changes: 1 addition & 6 deletions src/training_week_generation/training_week.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from typing import List

from src.constants import COACH_ROLE
from src.llm import get_completion, get_completion_json
from src.types.day_of_week_summary import DayOfWeekSummary
from src.types.training_week import (
Expand Down Expand Up @@ -54,14 +53,10 @@ def get_training_week(


def generate_training_week(
client_preferences: str,
sysmsg_base: str,
day_of_week_summaries: List[DayOfWeekSummary],
weekly_summaries: List[WeekSummary],
) -> TrainingWeekWithCoaching:

sysmsg_base = f"""{COACH_ROLE}
Your client has included the following preferenced: {client_preferences}\n"""

typical_week_training_review = get_typical_week_training_review(
sysmsg_base=sysmsg_base, day_of_week_summaries=day_of_week_summaries
)
Expand Down
10 changes: 10 additions & 0 deletions src/types/activity_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from pydantic import BaseModel


class ActivitySummary(BaseModel):
date_and_time: str
"""Datetime formatted as 'Monday, August 13, 2024 08:00 PM'"""

distance_in_miles: float
elevation_gain_in_feet: float
pace_minutes_per_mile: float
32 changes: 32 additions & 0 deletions src/types/mid_week_analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from datetime import datetime
from typing import List

from pydantic import BaseModel

from src.types.activity_summary import ActivitySummary
from src.types.training_week import TrainingSession


class MidWeekAnalysis(BaseModel):
activities: list[ActivitySummary]
training_week: List[TrainingSession]

@property
def training_week_future(self):
return self.training_week[datetime.now().weekday() + 1 :]

@property
def miles_ran(self):
return sum(activity.distance_in_miles for activity in self.activities)

@property
def miles_target(self):
return sum(session.distance for session in self.training_week)

@property
def miles_remaining(self):
return self.miles_target - self.miles_ran

@property
def future_miles_planned(self):
return sum(session.distance for session in self.training_week_future)
Loading

0 comments on commit 8b69fb1

Please sign in to comment.