otf-api 0.8.2__py3-none-any.whl → 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- otf_api/__init__.py +7 -4
- otf_api/api.py +699 -480
- otf_api/auth/__init__.py +4 -0
- otf_api/auth/auth.py +234 -0
- otf_api/auth/user.py +66 -0
- otf_api/auth/utils.py +129 -0
- otf_api/exceptions.py +38 -5
- otf_api/filters.py +97 -0
- otf_api/logging.py +19 -0
- otf_api/models/__init__.py +27 -38
- otf_api/models/body_composition_list.py +47 -50
- otf_api/models/bookings.py +63 -87
- otf_api/models/challenge_tracker_content.py +42 -21
- otf_api/models/challenge_tracker_detail.py +68 -48
- otf_api/models/classes.py +53 -62
- otf_api/models/enums.py +108 -30
- otf_api/models/lifetime_stats.py +59 -45
- otf_api/models/member_detail.py +95 -115
- otf_api/models/member_membership.py +18 -17
- otf_api/models/member_purchases.py +21 -127
- otf_api/models/mixins.py +37 -33
- otf_api/models/notifications.py +17 -0
- otf_api/models/out_of_studio_workout_history.py +22 -31
- otf_api/models/performance_summary_detail.py +47 -42
- otf_api/models/performance_summary_list.py +19 -37
- otf_api/models/studio_detail.py +51 -98
- otf_api/models/studio_services.py +27 -48
- otf_api/models/telemetry.py +14 -5
- otf_api/utils.py +134 -0
- {otf_api-0.8.2.dist-info → otf_api-0.9.1.dist-info}/METADATA +21 -10
- otf_api-0.9.1.dist-info/RECORD +35 -0
- {otf_api-0.8.2.dist-info → otf_api-0.9.1.dist-info}/WHEEL +1 -1
- otf_api/auth.py +0 -316
- otf_api/models/book_class.py +0 -89
- otf_api/models/cancel_booking.py +0 -49
- otf_api/models/favorite_studios.py +0 -106
- otf_api/models/latest_agreement.py +0 -21
- otf_api/models/telemetry_hr_history.py +0 -34
- otf_api/models/telemetry_max_hr.py +0 -13
- otf_api/models/total_classes.py +0 -8
- otf_api-0.8.2.dist-info/AUTHORS.md +0 -9
- otf_api-0.8.2.dist-info/RECORD +0 -36
- {otf_api-0.8.2.dist-info → otf_api-0.9.1.dist-info}/LICENSE +0 -0
@@ -4,65 +4,85 @@ from typing import Any
|
|
4
4
|
from pydantic import Field
|
5
5
|
|
6
6
|
from otf_api.models.base import OtfItemBase
|
7
|
+
from otf_api.models.enums import ChallengeCategory, EquipmentType
|
7
8
|
|
8
9
|
|
9
10
|
class MetricEntry(OtfItemBase):
|
10
|
-
title: str = Field(
|
11
|
-
equipment_id:
|
12
|
-
entry_type: str = Field(
|
13
|
-
metric_key: str = Field(
|
14
|
-
min_value: str = Field(
|
15
|
-
max_value: str = Field(
|
11
|
+
title: str | None = Field(None, alias="Title")
|
12
|
+
equipment_id: EquipmentType | None = Field(None, alias="EquipmentId")
|
13
|
+
entry_type: str | None = Field(None, alias="EntryType")
|
14
|
+
metric_key: str | None = Field(None, alias="MetricKey")
|
15
|
+
min_value: str | None = Field(None, alias="MinValue")
|
16
|
+
max_value: str | None = Field(None, alias="MaxValue")
|
16
17
|
|
17
18
|
|
18
19
|
class BenchmarkHistory(OtfItemBase):
|
19
|
-
studio_name: str = Field(
|
20
|
-
equipment_id:
|
21
|
-
|
22
|
-
date_created: datetime = Field(..., alias="DateCreated")
|
23
|
-
date_updated: datetime = Field(..., alias="DateUpdated")
|
24
|
-
class_time: datetime = Field(..., alias="ClassTime")
|
20
|
+
studio_name: str | None = Field(None, alias="StudioName")
|
21
|
+
equipment_id: EquipmentType | None = Field(None, alias="EquipmentId")
|
22
|
+
class_time: datetime | None = Field(None, alias="ClassTime")
|
25
23
|
challenge_sub_category_id: int | None = Field(None, alias="ChallengeSubCategoryId")
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
None,
|
36
|
-
|
24
|
+
weight_lbs: int | None = Field(None, alias="WeightLBS")
|
25
|
+
class_name: str | None = Field(None, alias="ClassName")
|
26
|
+
coach_name: str | None = Field(None, alias="CoachName")
|
27
|
+
result: float | str | None = Field(None, alias="Result")
|
28
|
+
workout_type_id: int | None = Field(None, alias="WorkoutTypeId", exclude=True, repr=False)
|
29
|
+
workout_id: int | None = Field(None, alias="WorkoutId", exclude=True, repr=False)
|
30
|
+
linked_challenges: list[Any] | None = Field(None, alias="LinkedChallenges", exclude=True, repr=False)
|
31
|
+
|
32
|
+
date_created: datetime | None = Field(
|
33
|
+
None,
|
34
|
+
alias="DateCreated",
|
35
|
+
exclude=True,
|
36
|
+
repr=False,
|
37
|
+
description="When the entry was created in database, not useful to users",
|
38
|
+
)
|
39
|
+
date_updated: datetime | None = Field(
|
40
|
+
None,
|
41
|
+
alias="DateUpdated",
|
42
|
+
exclude=True,
|
43
|
+
repr=False,
|
44
|
+
description="When the entry was updated in database, not useful to users",
|
45
|
+
)
|
46
|
+
class_id: int | None = Field(None, alias="ClassId", exclude=True, repr=False, description="Not used by API")
|
47
|
+
substitute_id: int | None = Field(
|
48
|
+
None, alias="SubstituteId", exclude=True, repr=False, description="Not used by API, also always seems to be 0"
|
49
|
+
)
|
37
50
|
|
38
51
|
|
39
52
|
class ChallengeHistory(OtfItemBase):
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
total_result: float | str = Field(..., alias="TotalResult")
|
47
|
-
is_finished: bool = Field(..., alias="IsFinished")
|
48
|
-
benchmark_histories: list[BenchmarkHistory] = Field(..., alias="BenchmarkHistories")
|
53
|
+
studio_name: str | None = Field(None, alias="StudioName")
|
54
|
+
start_date: datetime | None = Field(None, alias="StartDate")
|
55
|
+
end_date: datetime | None = Field(None, alias="EndDate")
|
56
|
+
total_result: float | str | None = Field(None, alias="TotalResult")
|
57
|
+
is_finished: bool | None = Field(None, alias="IsFinished")
|
58
|
+
benchmark_histories: list[BenchmarkHistory] = Field(default_factory=list, alias="BenchmarkHistories")
|
49
59
|
|
60
|
+
challenge_id: int | None = Field(None, alias="ChallengeId", exclude=True, repr=False, description="Not used by API")
|
61
|
+
studio_id: int | None = Field(None, alias="StudioId", exclude=True, repr=False, description="Not used by API")
|
62
|
+
challenge_objective: str | None = Field(
|
63
|
+
None, alias="ChallengeObjective", exclude=True, repr=False, description="Always the string 'None'"
|
64
|
+
)
|
50
65
|
|
51
|
-
class ChallengeTrackerDetail(OtfItemBase):
|
52
|
-
challenge_category_id: int = Field(..., alias="ChallengeCategoryId")
|
53
|
-
challenge_sub_category_id: int | None = Field(None, alias="ChallengeSubCategoryId")
|
54
|
-
equipment_id: int = Field(..., alias="EquipmentId")
|
55
|
-
equipment_name: str = Field(..., alias="EquipmentName")
|
56
|
-
metric_entry: MetricEntry = Field(..., alias="MetricEntry")
|
57
|
-
challenge_name: str = Field(..., alias="ChallengeName")
|
58
|
-
logo_url: str = Field(..., alias="LogoUrl")
|
59
|
-
best_record: float | str = Field(..., alias="BestRecord")
|
60
|
-
last_record: float | str = Field(..., alias="LastRecord")
|
61
|
-
previous_record: float | str = Field(..., alias="PreviousRecord")
|
62
|
-
unit: str | None = Field(None, alias="Unit")
|
63
|
-
goals: None = Field(..., alias="Goals")
|
64
|
-
challenge_histories: list[ChallengeHistory] = Field(..., alias="ChallengeHistories")
|
65
66
|
|
67
|
+
class Goal(OtfItemBase):
|
68
|
+
goal: int | None = Field(None, alias="Goal")
|
69
|
+
goal_period: str | None = Field(None, alias="GoalPeriod")
|
70
|
+
overall_goal: int | None = Field(None, alias="OverallGoal")
|
71
|
+
overall_goal_period: str | None = Field(None, alias="OverallGoalPeriod")
|
72
|
+
min_overall: int | None = Field(None, alias="MinOverall")
|
73
|
+
min_overall_period: str | None = Field(None, alias="MinOverallPeriod")
|
66
74
|
|
67
|
-
|
68
|
-
|
75
|
+
|
76
|
+
class FitnessBenchmark(OtfItemBase):
|
77
|
+
challenge_category_id: ChallengeCategory | None = Field(None, alias="ChallengeCategoryId")
|
78
|
+
challenge_sub_category_id: int | None = Field(None, alias="ChallengeSubCategoryId")
|
79
|
+
equipment_id: EquipmentType = Field(None, alias="EquipmentId")
|
80
|
+
equipment_name: str | None = Field(None, alias="EquipmentName")
|
81
|
+
metric_entry: MetricEntry | None = Field(None, alias="MetricEntry")
|
82
|
+
challenge_name: str | None = Field(None, alias="ChallengeName")
|
83
|
+
best_record: float | str | None = Field(None, alias="BestRecord")
|
84
|
+
last_record: float | str | None = Field(None, alias="LastRecord")
|
85
|
+
previous_record: float | str | None = Field(None, alias="PreviousRecord")
|
86
|
+
unit: str | None = Field(None, alias="Unit")
|
87
|
+
goals: Goal | None = Field(None, alias="Goals")
|
88
|
+
challenge_histories: list[ChallengeHistory] = Field(default_factory=list, alias="ChallengeHistories")
|
otf_api/models/classes.py
CHANGED
@@ -1,65 +1,65 @@
|
|
1
1
|
from datetime import datetime
|
2
2
|
|
3
|
-
from pydantic import Field
|
3
|
+
from pydantic import AliasPath, Field
|
4
4
|
|
5
5
|
from otf_api.models.base import OtfItemBase
|
6
|
-
from otf_api.models.enums import DoW
|
7
|
-
from otf_api.models.
|
6
|
+
from otf_api.models.enums import ClassType, DoW
|
7
|
+
from otf_api.models.studio_detail import StudioDetail
|
8
8
|
|
9
9
|
|
10
|
-
class
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
class OtfClass(OtfItemBase):
|
11
|
+
class_uuid: str = Field(alias="ot_base_class_uuid", description="The OTF class UUID")
|
12
|
+
name: str | None = Field(None, description="The name of the class")
|
13
|
+
class_type: ClassType = Field(alias="type")
|
14
|
+
coach: str | None = Field(None, alias=AliasPath("coach", "first_name"))
|
15
|
+
ends_at: datetime = Field(
|
16
|
+
alias="ends_at_local",
|
17
|
+
description="The end time of the class. Reflects local time, but the object does not have a timezone.",
|
18
|
+
)
|
19
|
+
starts_at: datetime = Field(
|
20
|
+
alias="starts_at_local",
|
21
|
+
description="The start time of the class. Reflects local time, but the object does not have a timezone.",
|
22
|
+
)
|
23
|
+
studio: StudioDetail
|
16
24
|
|
25
|
+
# capacity/status fields
|
26
|
+
booking_capacity: int | None = None
|
27
|
+
full: bool | None = None
|
28
|
+
max_capacity: int | None = None
|
29
|
+
waitlist_available: bool | None = None
|
30
|
+
waitlist_size: int | None = None
|
31
|
+
is_booked: bool | None = Field(None, description="Custom helper field to determine if class is already booked")
|
32
|
+
is_cancelled: bool | None = Field(None, alias="canceled")
|
33
|
+
is_home_studio: bool | None = Field(None, description="Custom helper field to determine if at home studio")
|
17
34
|
|
18
|
-
|
19
|
-
|
20
|
-
name: str
|
21
|
-
mbo_studio_id: str
|
22
|
-
time_zone: str
|
23
|
-
currency_code: str
|
24
|
-
address: Address
|
25
|
-
phone_number: str
|
26
|
-
latitude: float
|
27
|
-
longitude: float
|
35
|
+
# unused fields
|
36
|
+
class_id: str | None = Field(None, alias="id", exclude=True, repr=False, description="Not used by API")
|
28
37
|
|
38
|
+
created_at: datetime | None = Field(None, exclude=True, repr=False)
|
39
|
+
ends_at_utc: datetime | None = Field(None, alias="ends_at", exclude=True, repr=False)
|
40
|
+
mbo_class_description_id: str | None = Field(None, exclude=True, repr=False, description="MindBody attr")
|
41
|
+
mbo_class_id: str | None = Field(None, exclude=True, repr=False, description="MindBody attr")
|
42
|
+
mbo_class_schedule_id: str | None = Field(None, exclude=True, repr=False, description="MindBody attr")
|
43
|
+
starts_at_utc: datetime | None = Field(None, alias="starts_at", exclude=True, repr=False)
|
44
|
+
updated_at: datetime | None = Field(None, exclude=True, repr=False)
|
29
45
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
46
|
+
@property
|
47
|
+
def day_of_week(self) -> DoW:
|
48
|
+
dow = self.starts_at.strftime("%A")
|
49
|
+
return DoW(dow)
|
34
50
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
type: str
|
48
|
-
studio: Studio
|
49
|
-
coach: Coach
|
50
|
-
max_capacity: int
|
51
|
-
booking_capacity: int
|
52
|
-
waitlist_size: int
|
53
|
-
full: bool
|
54
|
-
waitlist_available: bool
|
55
|
-
canceled: bool
|
56
|
-
mbo_class_id: str
|
57
|
-
mbo_class_schedule_id: str
|
58
|
-
mbo_class_description_id: str
|
59
|
-
created_at: datetime
|
60
|
-
updated_at: datetime
|
61
|
-
is_home_studio: bool | None = Field(None, description="Custom helper field to determine if at home studio")
|
62
|
-
is_booked: bool | None = Field(None, description="Custom helper field to determine if class is already booked")
|
51
|
+
def __str__(self) -> str:
|
52
|
+
starts_at_str = self.starts_at.strftime("%a %b %d, %I:%M %p")
|
53
|
+
booked_str = ""
|
54
|
+
if self.is_booked:
|
55
|
+
booked_str = "Booked"
|
56
|
+
elif self.has_availability:
|
57
|
+
booked_str = "Available"
|
58
|
+
elif self.waitlist_available:
|
59
|
+
booked_str = "Waitlist Available"
|
60
|
+
else:
|
61
|
+
booked_str = "Full"
|
62
|
+
return f"Class: {starts_at_str} {self.name} - {self.coach} ({booked_str})"
|
63
63
|
|
64
64
|
@property
|
65
65
|
def has_availability(self) -> bool:
|
@@ -67,14 +67,5 @@ class OtfClass(OtfItemBase, OtfClassTimeMixin):
|
|
67
67
|
|
68
68
|
@property
|
69
69
|
def day_of_week_enum(self) -> DoW:
|
70
|
-
dow = self.
|
71
|
-
return DoW
|
72
|
-
|
73
|
-
@property
|
74
|
-
def actual_class_uuid(self) -> str:
|
75
|
-
"""The UUID used to book the class"""
|
76
|
-
return self.ot_class_uuid
|
77
|
-
|
78
|
-
|
79
|
-
class OtfClassList(OtfItemBase):
|
80
|
-
classes: list[OtfClass]
|
70
|
+
dow = self.starts_at.strftime("%A").upper()
|
71
|
+
return DoW(dow)
|
otf_api/models/enums.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
from enum import
|
1
|
+
from enum import IntEnum, StrEnum
|
2
2
|
|
3
3
|
|
4
|
-
class StudioStatus(
|
4
|
+
class StudioStatus(StrEnum):
|
5
5
|
OTHER = "OTHER"
|
6
6
|
ACTIVE = "Active"
|
7
7
|
INACTIVE = "Inactive"
|
@@ -10,7 +10,7 @@ class StudioStatus(str, Enum):
|
|
10
10
|
PERM_CLOSED = "Permanently Closed"
|
11
11
|
|
12
12
|
|
13
|
-
class BookingStatus(
|
13
|
+
class BookingStatus(StrEnum):
|
14
14
|
CheckedIn = "Checked In"
|
15
15
|
CancelCheckinPending = "Cancel Checkin Pending"
|
16
16
|
CancelCheckinRequested = "Cancel Checkin Requested"
|
@@ -23,39 +23,84 @@ class BookingStatus(str, Enum):
|
|
23
23
|
CheckinCancelled = "Checkin Cancelled"
|
24
24
|
|
25
25
|
|
26
|
-
class DoW(
|
27
|
-
MONDAY = "
|
28
|
-
TUESDAY = "
|
29
|
-
WEDNESDAY = "
|
30
|
-
THURSDAY = "
|
31
|
-
FRIDAY = "
|
32
|
-
SATURDAY = "
|
33
|
-
SUNDAY = "
|
26
|
+
class DoW(StrEnum):
|
27
|
+
MONDAY = "Monday"
|
28
|
+
TUESDAY = "Tuesday"
|
29
|
+
WEDNESDAY = "Wednesday"
|
30
|
+
THURSDAY = "Thursday"
|
31
|
+
FRIDAY = "Friday"
|
32
|
+
SATURDAY = "Saturday"
|
33
|
+
SUNDAY = "Sunday"
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
|
36
|
+
class Orange60ClassType(StrEnum):
|
37
|
+
Enterprise60 = "Enterprise 60"
|
38
|
+
ExplicitOrange60 = "Explicit Orange 60"
|
39
|
+
OpenStudio60_3G = "Open Studio 60 3G"
|
40
|
+
Orange3G = "Orange 3G"
|
41
|
+
Orange3Group = "Orange 3 Group"
|
42
|
+
Orange60 = "Orange 60"
|
43
|
+
Orange60Min2G = "Orange 60 Min 2G"
|
44
|
+
Orange60Min2GMaskOptional = "Orange 60 Min 2G Mask Optional"
|
45
|
+
Orange60Min3G = "Orange 60 Min 3G"
|
46
|
+
Orange60Tornado = "Orange 60 - Tornado"
|
47
|
+
Tornado60Minute = "Tornado 60 Minute"
|
48
|
+
|
49
|
+
|
50
|
+
class Strength50ClassType(StrEnum):
|
51
|
+
Strength50Lower = "Strength 50 (Lower)"
|
52
|
+
Strength50Total = "Strength 50 (Total)"
|
53
|
+
Strength50Upper = "Strength 50 (Upper)"
|
54
|
+
|
55
|
+
|
56
|
+
class Tread50ClassType(StrEnum):
|
57
|
+
Tread50 = "Tread 50"
|
58
|
+
|
59
|
+
|
60
|
+
class OtherClassType(StrEnum):
|
61
|
+
InterpretingInbody = "Interpreting Inbody"
|
62
|
+
OpenStudio60 = "Open Studio 60"
|
63
|
+
Orangetheory101Workshop = "Orangetheory 101 Workshop"
|
64
|
+
OrangeTornado = "Orange Tornado"
|
65
|
+
OTFPopUp = "OTF Pop-Up"
|
66
|
+
PrivateClass = "Private Class"
|
67
|
+
RowingClinic = "Rowing Clinic"
|
68
|
+
Tornado = "Tornado"
|
69
|
+
VIPClass = "VIP Class"
|
70
|
+
|
71
|
+
|
72
|
+
class Orange90ClassType(StrEnum):
|
73
|
+
Orange90Min3G = "Orange 90 Min 3G"
|
74
|
+
Orange90Min2G = "Orange 90 Min 2G"
|
75
|
+
LifeIsWhyWeGive90 = "Life is Why We Give 90"
|
39
76
|
|
40
77
|
|
41
|
-
class ClassType(
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
ORANGE_TORNADO = "Orange Tornado"
|
48
|
-
ORANGE_90_MIN_3G = "Orange 90 Min 3G"
|
49
|
-
VIP_CLASS = "VIP Class"
|
50
|
-
OTHER = "Other"
|
78
|
+
class ClassType(StrEnum):
|
79
|
+
ORANGE_60 = "ORANGE_60"
|
80
|
+
ORANGE_90 = "ORANGE_90"
|
81
|
+
OTHER = "OTHER"
|
82
|
+
STRENGTH_50 = "STRENGTH_50"
|
83
|
+
TREAD_50 = "TREAD_50"
|
51
84
|
|
52
85
|
@classmethod
|
53
86
|
def get_case_insensitive(cls, value: str) -> str:
|
87
|
+
value = (value or "").strip()
|
88
|
+
value = value.replace(" ", "_")
|
54
89
|
lcase_to_actual = {item.value.lower(): item.value for item in cls}
|
55
90
|
return lcase_to_actual[value.lower()]
|
56
91
|
|
92
|
+
@staticmethod
|
93
|
+
def get_standard_class_types() -> list["ClassType"]:
|
94
|
+
"""Returns 2G/3G/Tornado - 60/90 minute classes"""
|
95
|
+
return [ClassType.ORANGE_60, ClassType.ORANGE_90]
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def get_tread_strength_class_types() -> list["ClassType"]:
|
99
|
+
"""Returns Tread/Strength 50 minute classes"""
|
100
|
+
return [ClassType.TREAD_50, ClassType.STRENGTH_50]
|
101
|
+
|
57
102
|
|
58
|
-
class StatsTime(
|
103
|
+
class StatsTime(StrEnum):
|
59
104
|
LastYear = "lastYear"
|
60
105
|
ThisYear = "thisYear"
|
61
106
|
LastMonth = "lastMonth"
|
@@ -65,7 +110,7 @@ class StatsTime(str, Enum):
|
|
65
110
|
AllTime = "allTime"
|
66
111
|
|
67
112
|
|
68
|
-
class EquipmentType(
|
113
|
+
class EquipmentType(IntEnum):
|
69
114
|
Treadmill = 2
|
70
115
|
Strider = 3
|
71
116
|
Rower = 4
|
@@ -74,14 +119,47 @@ class EquipmentType(int, Enum):
|
|
74
119
|
PowerWalker = 7
|
75
120
|
|
76
121
|
|
77
|
-
class
|
122
|
+
class ChallengeCategory(IntEnum):
|
78
123
|
Other = 0
|
79
124
|
DriTri = 2
|
125
|
+
Infinity = 3
|
80
126
|
MarathonMonth = 5
|
127
|
+
OrangeEverest = 9
|
128
|
+
CatchMeIfYouCan = 10
|
129
|
+
TwoHundredMeterRow = 15
|
130
|
+
FiveHundredMeterRow = 16
|
131
|
+
TwoThousandMeterRow = 17
|
132
|
+
TwelveMinuteTreadmill = 18
|
133
|
+
OneMileTreadmill = 19
|
134
|
+
TenMinuteRow = 20
|
81
135
|
HellWeek = 52
|
136
|
+
Inferno = 55
|
82
137
|
Mayhem = 58
|
138
|
+
BackAtIt = 60
|
139
|
+
FourteenMinuteRow = 61
|
83
140
|
TwelveDaysOfFitness = 63
|
84
|
-
|
141
|
+
TransformationChallenge = 64
|
85
142
|
RemixInSix = 65
|
86
143
|
Push = 66
|
87
|
-
|
144
|
+
QuarterMileTreadmill = 69
|
145
|
+
|
146
|
+
|
147
|
+
class DriTriChallengeSubCategory(IntEnum):
|
148
|
+
FullRun = 1
|
149
|
+
SprintRun = 3
|
150
|
+
Relay = 4
|
151
|
+
StrengthRun = 1500
|
152
|
+
|
153
|
+
|
154
|
+
class MarathonMonthChallengeSubCategory(IntEnum):
|
155
|
+
Original = 1
|
156
|
+
Full = 14
|
157
|
+
Half = 15
|
158
|
+
Ultra = 16
|
159
|
+
|
160
|
+
|
161
|
+
# only Other, DriTri, and MarathonMonth have subcategories
|
162
|
+
|
163
|
+
# BackAtIt and Transformation are multi-week challenges
|
164
|
+
|
165
|
+
# RemixInSix, Mayhem, HellWeek, Push, and TwelveDaysOfFitness are multi-day challenges
|
otf_api/models/lifetime_stats.py
CHANGED
@@ -1,31 +1,36 @@
|
|
1
|
-
from
|
1
|
+
from typing import Generic, TypeVar
|
2
|
+
|
3
|
+
from pydantic import BaseModel, Field
|
2
4
|
|
3
5
|
from otf_api.models.base import OtfItemBase
|
6
|
+
from otf_api.models.enums import StatsTime
|
7
|
+
|
8
|
+
T = TypeVar("T", bound=BaseModel)
|
4
9
|
|
5
10
|
|
6
11
|
class OutStudioMixin(OtfItemBase):
|
7
|
-
walking_distance: float = Field(
|
8
|
-
running_distance: float = Field(
|
9
|
-
cycling_distance: float = Field(
|
12
|
+
walking_distance: float | None = Field(None, alias="walkingDistance")
|
13
|
+
running_distance: float | None = Field(None, alias="runningDistance")
|
14
|
+
cycling_distance: float | None = Field(None, alias="cyclingDistance")
|
10
15
|
|
11
16
|
|
12
17
|
class InStudioMixin(OtfItemBase):
|
13
|
-
treadmill_distance: float = Field(
|
14
|
-
treadmill_elevation_gained: float = Field(
|
15
|
-
rower_distance: float = Field(
|
16
|
-
rower_watt: float = Field(
|
18
|
+
treadmill_distance: float | None = Field(None, alias="treadmillDistance")
|
19
|
+
treadmill_elevation_gained: float | None = Field(None, alias="treadmillElevationGained")
|
20
|
+
rower_distance: float | None = Field(None, alias="rowerDistance")
|
21
|
+
rower_watt: float | None = Field(None, alias="rowerWatt")
|
17
22
|
|
18
23
|
|
19
24
|
class BaseStatsData(OtfItemBase):
|
20
|
-
calories: float
|
21
|
-
splat_point: float = Field(
|
22
|
-
total_black_zone: float = Field(
|
23
|
-
total_blue_zone: float = Field(
|
24
|
-
total_green_zone: float = Field(
|
25
|
-
total_orange_zone: float = Field(
|
26
|
-
total_red_zone: float = Field(
|
27
|
-
workout_duration: float = Field(
|
28
|
-
step_count: float = Field(
|
25
|
+
calories: float | None = None
|
26
|
+
splat_point: float | None = Field(None, alias="splatPoint")
|
27
|
+
total_black_zone: float | None = Field(None, alias="totalBlackZone")
|
28
|
+
total_blue_zone: float | None = Field(None, alias="totalBlueZone")
|
29
|
+
total_green_zone: float | None = Field(None, alias="totalGreenZone")
|
30
|
+
total_orange_zone: float | None = Field(None, alias="totalOrangeZone")
|
31
|
+
total_red_zone: float | None = Field(None, alias="totalRedZone")
|
32
|
+
workout_duration: float | None = Field(None, alias="workoutDuration")
|
33
|
+
step_count: float | None = Field(None, alias="stepCount")
|
29
34
|
|
30
35
|
|
31
36
|
class InStudioStatsData(InStudioMixin, BaseStatsData):
|
@@ -40,34 +45,43 @@ class AllStatsData(OutStudioMixin, InStudioMixin, BaseStatsData):
|
|
40
45
|
pass
|
41
46
|
|
42
47
|
|
43
|
-
class
|
44
|
-
last_year:
|
45
|
-
this_year:
|
46
|
-
last_month:
|
47
|
-
this_month:
|
48
|
-
last_week:
|
49
|
-
this_week:
|
50
|
-
all_time:
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
48
|
+
class TimeStats(OtfItemBase, Generic[T]):
|
49
|
+
last_year: T = Field(..., alias="lastYear")
|
50
|
+
this_year: T = Field(..., alias="thisYear")
|
51
|
+
last_month: T = Field(..., alias="lastMonth")
|
52
|
+
this_month: T = Field(..., alias="thisMonth")
|
53
|
+
last_week: T = Field(..., alias="lastWeek")
|
54
|
+
this_week: T = Field(..., alias="thisWeek")
|
55
|
+
all_time: T = Field(..., alias="allTime")
|
56
|
+
|
57
|
+
def get_by_time(self, stats_time: StatsTime) -> T:
|
58
|
+
match stats_time:
|
59
|
+
case StatsTime.LastYear:
|
60
|
+
return self.last_year
|
61
|
+
case StatsTime.ThisYear:
|
62
|
+
return self.this_year
|
63
|
+
case StatsTime.LastMonth:
|
64
|
+
return self.last_month
|
65
|
+
case StatsTime.ThisMonth:
|
66
|
+
return self.this_month
|
67
|
+
case StatsTime.LastWeek:
|
68
|
+
return self.last_week
|
69
|
+
case StatsTime.ThisWeek:
|
70
|
+
return self.this_week
|
71
|
+
case StatsTime.AllTime:
|
72
|
+
return self.all_time
|
73
|
+
|
74
|
+
|
75
|
+
class OutStudioTimeStats(TimeStats[OutStudioStatsData]):
|
76
|
+
pass
|
77
|
+
|
78
|
+
|
79
|
+
class InStudioTimeStats(TimeStats[InStudioStatsData]):
|
80
|
+
pass
|
81
|
+
|
82
|
+
|
83
|
+
class AllStatsTimeStats(TimeStats[AllStatsData]):
|
84
|
+
pass
|
71
85
|
|
72
86
|
|
73
87
|
class StatsResponse(OtfItemBase):
|