Skip to content

Commit

Permalink
Get training plans basically working
Browse files Browse the repository at this point in the history
  • Loading branch information
rustybrooks committed Nov 11, 2024
1 parent 59d4685 commit 4473f06
Show file tree
Hide file tree
Showing 24 changed files with 798 additions and 385 deletions.
61 changes: 61 additions & 0 deletions src/api/bikes/models/season.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import datetime
from typing import Optional

from django.contrib.auth.models import User
from django.db import models

from bikes.plans import CTBv1


class Season(models.Model):
# LIMIT_FACTOR_CHOICES = (
Expand Down Expand Up @@ -59,6 +64,62 @@ def zone_power(self, zone):

return int(self.ftp_watts * zones[zone])

@staticmethod
def generate_weeks(
user: Optional[User],
season_start_date: datetime.date,
season_end_date: datetime.date,
params,
):
from bikes.models import TrainingWeek

ctb = CTBv1(params)
progression = ctb.progression()

weeks: list[TrainingWeek] = []

season = Season(
user=user,
season_start_date=season_start_date,
season_end_date=season_end_date,
training_plan="CTB",
params=params,
)

entries = []
this_day = season_start_date or datetime.date.today()
prog_index = 0
prog_ct = 1
week_prog = progression[prog_index]
while season_start_date:
if prog_ct > week_prog[1]:
prog_ct = 1
prog_index += 1

if prog_index >= len(progression):
break

if season_end_date and this_day > season_end_date:
break

week_prog = progression[prog_index]

w = TrainingWeek(
season=season,
week_start_date=this_day,
week_type=week_prog[0],
week_type_num=prog_ct,
)
entries.extend(w.populate_entries(save=False))
weeks.append(w)

next_day = this_day + datetime.timedelta(days=7)

this_day = next_day
prog_ct += 1

return [season, weeks, entries]

# def entries(self):
# entries = []
# for entry in (
Expand Down
4 changes: 2 additions & 2 deletions src/api/bikes/models/strava_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,10 @@ def update_curves(cls):
StravaSpeedCurve, # type: ignore
)

all = cls.objects.filter()
all_activities = cls.objects.filter()

updated = 0
for activity in all:
for activity in all_activities:
streams1 = StravaPowerCurve.objects.filter(activity=activity)
streams2 = StravaSpeedCurve.objects.filter(activity=activity)
if len(streams1) and len(streams2):
Expand Down
6 changes: 3 additions & 3 deletions src/api/bikes/models/strava_activity_segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ def sync_one(cls, activity, segment):

# logger.info("sync one segment effort id=%r", segment["id"])

id = segment["id"]
segs = cls.objects.filter(activity_segment_id=id)
segment_id = segment["id"]
segs = cls.objects.filter(activity_segment_id=segment_id)
if len(segs):
sege = segs[0]
else:
sege = StravaActivitySegmentEffort()
sege.activity_segment_id = id
sege.activity_segment_id = segment_id

sege.activity = activity
sege.start_datetime = segment.get("start_date")
Expand Down
8 changes: 5 additions & 3 deletions src/api/bikes/models/strava_segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ class StravaSegment(models.Model):

@classmethod
def sync_one(cls, segment):
id = segment["id"]
segs = StravaSegment.objects.filter(segment_id=id)
seg: Self = cast(Self, segs[0] if len(segs) else StravaSegment(segment_id=id))
segment_id = segment["id"]
segs = StravaSegment.objects.filter(segment_id=segment_id)
seg: Self = cast(
Self, segs[0] if len(segs) else StravaSegment(segment_id=segment_id)
)

for key in [
"resource_state",
Expand Down
3 changes: 3 additions & 0 deletions src/api/bikes/models/strava_segment_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ def sync_one(cls, user, segment_id, athlete_id):
def sync_all(cls, user, athlete_id):
import time

from bikes.libs import stravaapi
from bikes.models import StravaSegment

segments = StravaSegment.objects.filter()
for s in segments:
try:
Expand Down
28 changes: 2 additions & 26 deletions src/api/bikes/models/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@


def tp_from_season(s):
# logger.error(
# "tp_from_season - season = %r, params = %r, tp = %r",
# s,
# s.params,
# s.training_plan,
# )
return tpmap[s.training_plan](s.params)


Expand All @@ -42,8 +36,7 @@ def __unicode__(self):
return "%s:%s-%s" % (self.week_start_date, self.week_type, self.week_type_num)

def json(self, cal):
output: dict[str, Any] = {}
output["entries"] = []
output: dict[str, Any] = {"entries": []}
for elist in cal.entries_by_week(self):
output["entries"].append([e.json() for e in elist])

Expand Down Expand Up @@ -126,7 +119,7 @@ class TrainingEntry(models.Model):
"Season", unique_for_date="entry_date", on_delete=models.DO_NOTHING
)
week = models.ForeignKey(TrainingWeek, on_delete=models.DO_NOTHING)
workout_type = models.CharField(max_length=50) # , choices=NAME_CHOICES
workout_type = models.CharField(max_length=50)
activity_type = models.CharField(max_length=50)
scheduled_dow = models.IntegerField()
scheduled_length = models.FloatField()
Expand All @@ -137,23 +130,6 @@ class TrainingEntry(models.Model):
def __unicode__(self):
return "%s" % (self.entry_date,)

def json(self):
output = {}

for el in (
"id",
"entry_date",
"workout_type",
"scheduled_dow",
"scheduled_length",
"actual_length",
):
output[el] = getattr(self, el)

output["workout_types"] = self.workout_type_list()

return output

def workout_type_list(self):
tp = tp_from_season(self.season)
return tp.workout_types(self)
Expand Down
25 changes: 17 additions & 8 deletions src/api/bikes/plans/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ def __init__(self, params):


class TCC(TrainingPlan):
def progression(self):
@classmethod
def progression(cls):
return tccdata.progression

def plan_entries(self, week):
Expand Down Expand Up @@ -51,21 +52,25 @@ def plan_entries(self, week):

return entries

def weekly_hours(self, week):
@classmethod
def weekly_hours(cls, _week):
# pass
return 7

def workout_description(self, wo_type):
@classmethod
def workout_description(cls, _wo_type):
return "temp"

def workout_types(self, entry):
@classmethod
def workout_types(cls, entry):
key = "%s" % (entry.week.week_type,)
patterns = tccdata.workout_patterns[key]
return patterns[entry.scheduled_dow]


class CTBv1(TrainingPlan):
def progression(self):
@classmethod
def progression(cls):
return tbv1data.progresssion

def weekly_hours(self, week):
Expand All @@ -81,7 +86,8 @@ def weekly_hours(self, week):

return hours

def plan_entries(self, week):
@classmethod
def plan_entries(cls, week):
try:
key = "%s-%s" % (week.week_type, week.week_type_num)
patterns = tbv1data.workout_patterns[key]
Expand Down Expand Up @@ -112,16 +118,19 @@ def plan_entries(self, week):
activity_type="Ride", # FIXME if we add other types
scheduled_dow=dow_ind,
scheduled_length=week_hours[hpatterns[dow_ind] - 1],
scheduled_length2=0,
actual_length=week_hours[hpatterns[dow_ind] - 1],
)
)

return entries

def workout_description(self, wo_type):
@classmethod
def workout_description(cls, wo_type):
return tbv1data.workouts[wo_type]

def workout_types(self, entry):
@classmethod
def workout_types(cls, entry):
try:
key = "%s-%s" % (entry.week.week_type, entry.week.week_type_num)
patterns = tbv1data.workout_patterns[key]
Expand Down
1 change: 0 additions & 1 deletion src/api/bikes/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ def __call__(self, request):
"rest_framework.parsers.MultiPartParser",
],
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.BasicAuthentication",
"rest_framework.authentication.SessionAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
Expand Down
12 changes: 9 additions & 3 deletions src/api/bikes/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@
from django.urls import include, path, re_path # type: ignore
from rest_framework.routers import SimpleRouter # type: ignore

from bikes.views.activities import ActivitiesViewSet # type: ignore
from bikes.views.seasons import SeasonViewSet # type: ignore
from bikes.views.activity import ActivitiesViewSet # type: ignore
from bikes.views.season import SeasonViewSet # type: ignore
from bikes.views.swagger import SwaggerView # type: ignore
from bikes.views.users import UserViewSet # type: ignore
from bikes.views.training_entry import TrainingEntryViewSet
from bikes.views.training_week import TrainingWeekViewSet
from bikes.views.user import UserViewSet # type: ignore

router = SimpleRouter()
router.register(r"api/seasons", SeasonViewSet, basename="season")
router.register(r"api/users", UserViewSet, basename="user")
router.register(r"api/activities", ActivitiesViewSet, basename="activity")
router.register(
r"api/training_entries", TrainingEntryViewSet, basename="training_entry"
)
router.register(r"api/training_weeks", TrainingWeekViewSet, basename="training_week")

urlpatterns = [
path("health/", include("health_check.urls")),
Expand Down
File renamed without changes.
62 changes: 62 additions & 0 deletions src/api/bikes/views/season.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import logging

from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers, status # type: ignore
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet # type: ignore

from bikes import models, plans # type: ignore
from bikes.models import Season
from bikes.views.training_entry import TrainingEntryOut

logger = logging.getLogger(__name__)


class SeasonSerializer(serializers.ModelSerializer):
class Meta:
model = Season
exclude: list[str] = []


class TrainingBibleV1In(serializers.Serializer):
season_start_date = serializers.DateField(allow_null=True)
season_end_date = serializers.DateField(allow_null=True)
params = serializers.JSONField()


class TrainingBiblePreviewOut(serializers.Serializer):
entries = serializers.ListField(child=TrainingEntryOut())
hour_selection = serializers.ListField(child=serializers.IntegerField())


class SeasonViewSet(ModelViewSet):
queryset = Season.objects.all()
serializer_class = SeasonSerializer
ordering_fields = ["season_start_date", "season_end_date"]

@swagger_auto_schema(
request_body=TrainingBibleV1In,
responses={200: openapi.Response("", TrainingBiblePreviewOut(many=False))},
)
@action(detail=False, methods=["post"])
def preview_training_bible_v1(self, request: Request):
serializer = TrainingBibleV1In(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

season, weeks, entries = Season.generate_weeks(
user=None, **serializer.validated_data
)

training_serialized = TrainingEntryOut(data=entries, many=True)
training_serialized.is_valid()
training_entries_data = training_serialized.data

data = {
"entries": training_entries_data,
"hour_selection": plans.training_bible_v1.annual_hours_lookup["Yearly"],
}
return Response(data)
Loading

0 comments on commit 4473f06

Please sign in to comment.