-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #122 from voynow/121-migrate-all-frontend-routes-t…
…o-api 121 migrate all frontend routes to api
- Loading branch information
Showing
20 changed files
with
2,232 additions
and
474 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -124,6 +124,7 @@ celerybeat.pid | |
# Environments | ||
.env | ||
.venv | ||
.*venv | ||
env/ | ||
venv/ | ||
ENV/ | ||
|
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,35 @@ | ||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" | ||
|
||
[tool.poetry] | ||
name = "trackflow-api" | ||
name = "trackflow" | ||
version = "0.1.0" | ||
description = "" | ||
authors = ["voynow <[email protected]>"] | ||
readme = "README.md" | ||
|
||
[tool.poetry.dependencies] | ||
python = "^3.11" | ||
python = "^3.10" | ||
stravalib = "^v1.7" | ||
supabase = "*" | ||
python-dotenv = "^1.0.1" | ||
urllib3 = "2.2.2" | ||
openai = "1.39.0" | ||
sib-api-v3-sdk = "^7.6.0" | ||
pydantic = "1.10.16" | ||
postgrest = "^0.16.8" | ||
jinja2 = "^3.1.4" | ||
PyJWT = "^2.9.0" | ||
pyperclip = "^1.9.0" | ||
arrow = "^1.3.0" | ||
httpx = "^0.27.2" | ||
cryptography = "^43.0.3" | ||
fastapi = "^0.115.4" | ||
uvicorn = "^0.32.0" | ||
supabase = "^2.10.0" | ||
python-dotenv = "^1.0.1" | ||
pyjwt = "^2.9.0" | ||
stravalib = "^2.1" | ||
|
||
[tool.poetry.group.dev.dependencies] | ||
pytest = "^8.3.3" | ||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" | ||
ipykernel = "^6.29.4" | ||
freezegun = "^1.5.1" | ||
pytest = "^8.3.2" | ||
pytest-cov = "^6.0.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,63 @@ | ||
aiohappyeyeballs==2.4.3 ; python_version >= "3.11" and python_version < "4.0" | ||
aiohttp==3.10.10 ; python_version >= "3.11" and python_version < "4.0" | ||
aiosignal==1.3.1 ; python_version >= "3.11" and python_version < "4.0" | ||
annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "4.0" | ||
anyio==4.6.2.post1 ; python_version >= "3.11" and python_version < "4.0" | ||
arrow==1.3.0 ; python_version >= "3.11" and python_version < "4.0" | ||
attrs==24.2.0 ; python_version >= "3.11" and python_version < "4.0" | ||
certifi==2024.8.30 ; python_version >= "3.11" and python_version < "4.0" | ||
charset-normalizer==3.4.0 ; python_version >= "3.11" and python_version < "4.0" | ||
click==8.1.7 ; python_version >= "3.11" and python_version < "4.0" | ||
colorama==0.4.6 ; python_version >= "3.11" and python_version < "4.0" and platform_system == "Windows" | ||
deprecation==2.1.0 ; python_version >= "3.11" and python_version < "4.0" | ||
fastapi==0.115.4 ; python_version >= "3.11" and python_version < "4.0" | ||
flexcache==0.3 ; python_version >= "3.11" and python_version < "4.0" | ||
flexparser==0.4 ; python_version >= "3.11" and python_version < "4.0" | ||
frozenlist==1.5.0 ; python_version >= "3.11" and python_version < "4.0" | ||
gotrue==2.10.0 ; python_version >= "3.11" and python_version < "4.0" | ||
h11==0.14.0 ; python_version >= "3.11" and python_version < "4.0" | ||
h2==4.1.0 ; python_version >= "3.11" and python_version < "4.0" | ||
hpack==4.0.0 ; python_version >= "3.11" and python_version < "4.0" | ||
httpcore==1.0.6 ; python_version >= "3.11" and python_version < "4.0" | ||
httpx==0.27.2 ; python_version >= "3.11" and python_version < "4.0" | ||
httpx[http2]==0.27.2 ; python_version >= "3.11" and python_version < "4.0" | ||
hyperframe==6.0.1 ; python_version >= "3.11" and python_version < "4.0" | ||
idna==3.10 ; python_version >= "3.11" and python_version < "4.0" | ||
multidict==6.1.0 ; python_version >= "3.11" and python_version < "4.0" | ||
packaging==24.2 ; python_version >= "3.11" and python_version < "4.0" | ||
pint==0.24.4 ; python_version >= "3.11" and python_version < "4.0" | ||
platformdirs==4.3.6 ; python_version >= "3.11" and python_version < "4.0" | ||
postgrest==0.18.0 ; python_version >= "3.11" and python_version < "4.0" | ||
propcache==0.2.0 ; python_version >= "3.11" and python_version < "4.0" | ||
pydantic-core==2.23.4 ; python_version >= "3.11" and python_version < "4.0" | ||
pydantic==2.9.2 ; python_version >= "3.11" and python_version < "4.0" | ||
pyjwt==2.9.0 ; python_version >= "3.11" and python_version < "4.0" | ||
python-dateutil==2.9.0.post0 ; python_version >= "3.11" and python_version < "4.0" | ||
python-dotenv==1.0.1 ; python_version >= "3.11" and python_version < "4.0" | ||
pytz==2024.2 ; python_version >= "3.11" and python_version < "4.0" | ||
realtime==2.0.6 ; python_version >= "3.11" and python_version < "4.0" | ||
requests==2.32.3 ; python_version >= "3.11" and python_version < "4.0" | ||
six==1.16.0 ; python_version >= "3.11" and python_version < "4.0" | ||
sniffio==1.3.1 ; python_version >= "3.11" and python_version < "4.0" | ||
starlette==0.41.2 ; python_version >= "3.11" and python_version < "4.0" | ||
storage3==0.9.0 ; python_version >= "3.11" and python_version < "4.0" | ||
stravalib==2.1 ; python_version >= "3.11" and python_version < "4.0" | ||
supabase==2.10.0 ; python_version >= "3.11" and python_version < "4.0" | ||
supafunc==0.7.0 ; python_version >= "3.11" and python_version < "4.0" | ||
types-python-dateutil==2.9.0.20241003 ; python_version >= "3.11" and python_version < "4.0" | ||
typing-extensions==4.12.2 ; python_version >= "3.11" and python_version < "4.0" | ||
urllib3==2.2.3 ; python_version >= "3.11" and python_version < "4.0" | ||
uvicorn==0.32.0 ; python_version >= "3.11" and python_version < "4.0" | ||
websockets==13.1 ; python_version >= "3.11" and python_version < "4.0" | ||
yarl==1.17.1 ; python_version >= "3.11" and python_version < "4.0" | ||
aiohappyeyeballs==2.4.3 ; python_version >= "3.10" and python_version < "4.0" | ||
aiohttp==3.10.10 ; python_version >= "3.10" and python_version < "4.0" | ||
aiosignal==1.3.1 ; python_version >= "3.10" and python_version < "4.0" | ||
anyio==4.6.2.post1 ; python_version >= "3.10" and python_version < "4.0" | ||
arrow==1.3.0 ; python_version >= "3.10" and python_version < "4.0" | ||
async-timeout==4.0.3 ; python_version >= "3.10" and python_version < "3.11" | ||
attrs==24.2.0 ; python_version >= "3.10" and python_version < "4.0" | ||
certifi==2024.8.30 ; python_version >= "3.10" and python_version < "4.0" | ||
cffi==1.17.1 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy" | ||
charset-normalizer==3.4.0 ; python_version >= "3.10" and python_version < "4.0" | ||
click==8.1.7 ; python_version >= "3.10" and python_version < "4.0" | ||
colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows" | ||
cryptography==43.0.3 ; python_version >= "3.10" and python_version < "4.0" | ||
deprecation==2.1.0 ; python_version >= "3.10" and python_version < "4.0" | ||
distro==1.9.0 ; python_version >= "3.10" and python_version < "4.0" | ||
exceptiongroup==1.2.2 ; python_version >= "3.10" and python_version < "3.11" | ||
fastapi==0.115.4 ; python_version >= "3.10" and python_version < "4.0" | ||
flexcache==0.3 ; python_version >= "3.10" and python_version < "4.0" | ||
flexparser==0.4 ; python_version >= "3.10" and python_version < "4.0" | ||
frozenlist==1.5.0 ; python_version >= "3.10" and python_version < "4.0" | ||
gotrue==2.10.0 ; python_version >= "3.10" and python_version < "4.0" | ||
h11==0.14.0 ; python_version >= "3.10" and python_version < "4.0" | ||
h2==4.1.0 ; python_version >= "3.10" and python_version < "4.0" | ||
hpack==4.0.0 ; python_version >= "3.10" and python_version < "4.0" | ||
httpcore==1.0.6 ; python_version >= "3.10" and python_version < "4.0" | ||
httpx==0.27.2 ; python_version >= "3.10" and python_version < "4.0" | ||
httpx[http2]==0.27.2 ; python_version >= "3.10" and python_version < "4.0" | ||
hyperframe==6.0.1 ; python_version >= "3.10" and python_version < "4.0" | ||
idna==3.10 ; python_version >= "3.10" and python_version < "4.0" | ||
jinja2==3.1.4 ; python_version >= "3.10" and python_version < "4.0" | ||
markupsafe==3.0.2 ; python_version >= "3.10" and python_version < "4.0" | ||
multidict==6.1.0 ; python_version >= "3.10" and python_version < "4.0" | ||
openai==1.39.0 ; python_version >= "3.10" and python_version < "4.0" | ||
packaging==24.2 ; python_version >= "3.10" and python_version < "4.0" | ||
pint==0.24.4 ; python_version >= "3.10" and python_version < "4.0" | ||
platformdirs==4.3.6 ; python_version >= "3.10" and python_version < "4.0" | ||
postgrest==0.16.11 ; python_version >= "3.10" and python_version < "4.0" | ||
propcache==0.2.0 ; python_version >= "3.10" and python_version < "4.0" | ||
pycparser==2.22 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy" | ||
pydantic==1.10.16 ; python_version >= "3.10" and python_version < "4.0" | ||
pyjwt==2.9.0 ; python_version >= "3.10" and python_version < "4.0" | ||
pyperclip==1.9.0 ; python_version >= "3.10" and python_version < "4.0" | ||
python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0" | ||
python-dotenv==1.0.1 ; python_version >= "3.10" and python_version < "4.0" | ||
pytz==2024.2 ; python_version >= "3.10" and python_version < "4.0" | ||
realtime==2.0.6 ; python_version >= "3.10" and python_version < "4.0" | ||
requests==2.32.3 ; python_version >= "3.10" and python_version < "4.0" | ||
sib-api-v3-sdk==7.6.0 ; python_version >= "3.10" and python_version < "4.0" | ||
six==1.16.0 ; python_version >= "3.10" and python_version < "4.0" | ||
sniffio==1.3.1 ; python_version >= "3.10" and python_version < "4.0" | ||
starlette==0.41.2 ; python_version >= "3.10" and python_version < "4.0" | ||
storage3==0.8.2 ; python_version >= "3.10" and python_version < "4.0" | ||
stravalib==1.7 ; python_version >= "3.10" and python_version < "4.0" | ||
strenum==0.4.15 ; python_version >= "3.10" and python_version < "4.0" | ||
supabase==2.8.0 ; python_version >= "3.10" and python_version < "4.0" | ||
supafunc==0.6.2 ; python_version >= "3.10" and python_version < "4.0" | ||
tqdm==4.67.0 ; python_version >= "3.10" and python_version < "4.0" | ||
types-python-dateutil==2.9.0.20241003 ; python_version >= "3.10" and python_version < "4.0" | ||
typing-extensions==4.12.2 ; python_version >= "3.10" and python_version < "4.0" | ||
urllib3==2.2.2 ; python_version >= "3.10" and python_version < "4.0" | ||
uvicorn==0.32.0 ; python_version >= "3.10" and python_version < "4.0" | ||
websockets==13.1 ; python_version >= "3.10" and python_version < "4.0" | ||
yarl==1.17.1 ; python_version >= "3.10" and python_version < "4.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
from collections import defaultdict | ||
from datetime import datetime, timedelta | ||
from typing import List | ||
|
||
from src import constants | ||
from src.types.activity import Activity, DailyMetrics, WeekSummary | ||
from src.utils import datetime_now_est | ||
from stravalib.client import Client | ||
|
||
|
||
|
||
def add_missing_dates( | ||
activities: List[Activity], start_date: datetime, end_date: datetime | ||
) -> List[Activity]: | ||
""" | ||
Ensures that the list of activities includes placeholder activities for all dates | ||
between the start and end date. | ||
:param activities: List of Activity Pydantic models. | ||
:param start_date: The start date of the range. | ||
:param end_date: The end date of the range. | ||
:return: A list of Activity objects, with missing dates filled in as placeholder activities. | ||
""" | ||
existing_dates = {activity.start_date_local.date() for activity in activities} | ||
total_days = (end_date.date() - start_date.date()).days + 1 | ||
all_dates = {start_date.date() + timedelta(days=i) for i in range(total_days)} | ||
missing_dates = all_dates - existing_dates | ||
|
||
placeholders = [ | ||
Activity( | ||
start_date=datetime.combine(date, datetime.min.time()), | ||
start_date_local=datetime.combine(date, datetime.min.time()), | ||
) | ||
for date in missing_dates | ||
] | ||
return sorted(activities + placeholders, key=lambda x: x.start_date_local) | ||
|
||
|
||
def aggregate_daily_metrics(activities: List[Activity]) -> List[DailyMetrics]: | ||
""" | ||
Aggregates and transforms activity data to calculate daily and weekly metrics. | ||
:param activities: List of Activity Pydantic models containing activity data | ||
:return: A list of DailyMetrics objects with aggregated and transformed metrics | ||
""" | ||
|
||
results = [] | ||
activities_by_date = defaultdict(list) | ||
for activity in activities: | ||
activities_by_date[activity.start_date_local.date()].append(activity) | ||
|
||
for activity_date, daily_activities in activities_by_date.items(): | ||
total_distance = sum(a.distance for a in daily_activities) | ||
total_elevation_gain = sum(a.total_elevation_gain for a in daily_activities) | ||
total_moving_time = sum(a.moving_time.total_seconds() for a in daily_activities) | ||
activity_count = len([a for a in daily_activities if a.id != -1]) | ||
|
||
if total_distance > 0: | ||
pace_minutes_per_mile = (total_moving_time / 60) / ( | ||
total_distance / constants.METERS_PER_MILE | ||
) | ||
else: | ||
pace_minutes_per_mile = None | ||
|
||
results.append( | ||
DailyMetrics( | ||
date=activity_date, | ||
day_of_week=activity_date.strftime("%a").lower(), | ||
week_of_year=activity_date.isocalendar()[1], | ||
year=activity_date.year, | ||
distance_in_miles=total_distance / constants.METERS_PER_MILE, | ||
elevation_gain_in_feet=total_elevation_gain * constants.FEET_PER_METER, | ||
moving_time_in_minutes=total_moving_time / 60, | ||
pace_minutes_per_mile=pace_minutes_per_mile, | ||
activity_count=activity_count, | ||
) | ||
) | ||
|
||
results = sorted(results, key=lambda x: x.date) | ||
first_week = min(item.week_of_year for item in results) | ||
results = [item for item in results if item.week_of_year != first_week] | ||
|
||
return results | ||
|
||
|
||
def get_daily_activity(strava_client: Client, num_weeks: int = 8) -> List[DailyMetrics]: | ||
""" | ||
Fetches activities for a given athlete ID and returns a DataFrame with daily aggregated activities | ||
:param strava_client: The Strava client object to fetch data. | ||
:param num_weeks: The number of weeks to fetch activities for. | ||
:return: A cleaned and processed DataFrame of the athlete's daily aggregated activities. | ||
""" | ||
end_date = datetime_now_est() | ||
start_date = end_date - timedelta(weeks=num_weeks) | ||
|
||
all_strava_activities = strava_client.get_activities( | ||
after=start_date, before=end_date | ||
) | ||
|
||
# filter and convert to our Activity type | ||
activities = [ | ||
Activity(**activity.__dict__) | ||
for activity in all_strava_activities | ||
if activity.sport_type == "Run" | ||
] | ||
|
||
# add empty activities for missing dates | ||
all_dates_activities = add_missing_dates( | ||
activities=activities, start_date=start_date, end_date=end_date | ||
) | ||
|
||
# aggregate metrics | ||
return aggregate_daily_metrics(all_dates_activities) | ||
|
||
|
||
def get_weekly_summaries(strava_client: Client) -> List[WeekSummary]: | ||
""" | ||
Aggregate daily metrics by week of the year and calculate load for each week. | ||
:param strava_client: The Strava client object to fetch data. | ||
:return: A list of WeekSummary objects with summary statistics | ||
""" | ||
daily_metrics = get_daily_activity(strava_client) | ||
|
||
weekly_aggregates = defaultdict( | ||
lambda: {"total_distance": 0, "longest_run": 0, "start_of_week": None} | ||
) | ||
|
||
for metrics in daily_metrics: | ||
key = (metrics.year, metrics.week_of_year) | ||
|
||
# calculate total distance and longest run | ||
weekly_aggregates[key]["total_distance"] += metrics.distance_in_miles | ||
weekly_aggregates[key]["longest_run"] = max( | ||
weekly_aggregates[key]["longest_run"], metrics.distance_in_miles | ||
) | ||
|
||
# update start of week | ||
if ( | ||
weekly_aggregates[key]["start_of_week"] is None | ||
or metrics.date < weekly_aggregates[key]["start_of_week"] | ||
): | ||
weekly_aggregates[key]["start_of_week"] = metrics.date | ||
|
||
weekly_summaries = [ | ||
WeekSummary( | ||
year=year, | ||
week_of_year=week, | ||
week_start_date=start_of_week, | ||
longest_run=round(aggregate["longest_run"], 2), | ||
total_distance=round(aggregate["total_distance"], 2), | ||
) | ||
for (year, week), aggregate in sorted(weekly_aggregates.items()) | ||
for start_of_week in [aggregate["start_of_week"]] | ||
] | ||
|
||
return weekly_summaries |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
COACH_ROLE = "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." | ||
METERS_PER_MILE = 1609.34 | ||
FEET_PER_METER = 3.28084 |
Oops, something went wrong.