otf-api 0.12.0__py3-none-any.whl → 0.13.0__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 +35 -3
- otf_api/api/__init__.py +3 -0
- otf_api/api/_compat.py +77 -0
- otf_api/api/api.py +80 -0
- otf_api/api/bookings/__init__.py +3 -0
- otf_api/api/bookings/booking_api.py +541 -0
- otf_api/api/bookings/booking_client.py +112 -0
- otf_api/api/client.py +203 -0
- otf_api/api/members/__init__.py +3 -0
- otf_api/api/members/member_api.py +187 -0
- otf_api/api/members/member_client.py +112 -0
- otf_api/api/studios/__init__.py +3 -0
- otf_api/api/studios/studio_api.py +173 -0
- otf_api/api/studios/studio_client.py +120 -0
- otf_api/api/utils.py +307 -0
- otf_api/api/workouts/__init__.py +3 -0
- otf_api/api/workouts/workout_api.py +333 -0
- otf_api/api/workouts/workout_client.py +140 -0
- otf_api/auth/__init__.py +1 -1
- otf_api/auth/auth.py +155 -89
- otf_api/auth/user.py +5 -17
- otf_api/auth/utils.py +27 -2
- otf_api/cache.py +132 -0
- otf_api/exceptions.py +18 -6
- otf_api/models/__init__.py +25 -21
- otf_api/models/bookings/__init__.py +23 -0
- otf_api/models/bookings/bookings.py +134 -0
- otf_api/models/{bookings_v2.py → bookings/bookings_v2.py} +72 -31
- otf_api/models/bookings/classes.py +124 -0
- otf_api/models/{enums.py → bookings/enums.py} +7 -81
- otf_api/{filters.py → models/bookings/filters.py} +39 -11
- otf_api/models/{ratings.py → bookings/ratings.py} +2 -6
- otf_api/models/members/__init__.py +5 -0
- otf_api/models/members/member_detail.py +149 -0
- otf_api/models/members/member_membership.py +26 -0
- otf_api/models/members/member_purchases.py +29 -0
- otf_api/models/members/notifications.py +17 -0
- otf_api/models/mixins.py +48 -1
- otf_api/models/studios/__init__.py +5 -0
- otf_api/models/studios/enums.py +11 -0
- otf_api/models/studios/studio_detail.py +93 -0
- otf_api/models/studios/studio_services.py +36 -0
- otf_api/models/workouts/__init__.py +31 -0
- otf_api/models/{body_composition_list.py → workouts/body_composition_list.py} +140 -71
- otf_api/models/workouts/challenge_tracker_content.py +50 -0
- otf_api/models/workouts/challenge_tracker_detail.py +99 -0
- otf_api/models/workouts/enums.py +70 -0
- otf_api/models/workouts/lifetime_stats.py +96 -0
- otf_api/models/workouts/out_of_studio_workout_history.py +32 -0
- otf_api/models/{performance_summary.py → workouts/performance_summary.py} +19 -5
- otf_api/models/workouts/telemetry.py +88 -0
- otf_api/models/{workout.py → workouts/workout.py} +34 -20
- {otf_api-0.12.0.dist-info → otf_api-0.13.0.dist-info}/METADATA +4 -2
- otf_api-0.13.0.dist-info/RECORD +59 -0
- {otf_api-0.12.0.dist-info → otf_api-0.13.0.dist-info}/WHEEL +1 -1
- otf_api/api.py +0 -1682
- otf_api/logging.py +0 -19
- otf_api/models/bookings.py +0 -109
- otf_api/models/challenge_tracker_content.py +0 -59
- otf_api/models/challenge_tracker_detail.py +0 -88
- otf_api/models/classes.py +0 -70
- otf_api/models/lifetime_stats.py +0 -78
- otf_api/models/member_detail.py +0 -121
- otf_api/models/member_membership.py +0 -26
- otf_api/models/member_purchases.py +0 -29
- otf_api/models/notifications.py +0 -17
- otf_api/models/out_of_studio_workout_history.py +0 -32
- otf_api/models/studio_detail.py +0 -71
- otf_api/models/studio_services.py +0 -36
- otf_api/models/telemetry.py +0 -84
- otf_api/utils.py +0 -164
- otf_api-0.12.0.dist-info/RECORD +0 -38
- {otf_api-0.12.0.dist-info → otf_api-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {otf_api-0.12.0.dist-info → otf_api-0.13.0.dist-info}/top_level.txt +0 -0
@@ -35,6 +35,15 @@ class BodyFatPercentIndicator(StrEnum):
|
|
35
35
|
def get_percent_body_fat_descriptor(
|
36
36
|
percent_body_fat: float, body_fat_percent_dividers: list[float]
|
37
37
|
) -> BodyFatPercentIndicator:
|
38
|
+
"""Get the body fat percent descriptor based on the percent body fat and dividers.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
percent_body_fat (float): The percent body fat to check
|
42
|
+
body_fat_percent_dividers (list[float]): The dividers for the percent body fat
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
BodyFatPercentIndicator: The body fat percent descriptor
|
46
|
+
"""
|
38
47
|
if not percent_body_fat or not body_fat_percent_dividers[3]:
|
39
48
|
return BodyFatPercentIndicator.NO_INDICATOR
|
40
49
|
|
@@ -48,6 +57,15 @@ def get_percent_body_fat_descriptor(
|
|
48
57
|
|
49
58
|
|
50
59
|
def get_relative_descriptor(in_body_value: float, in_body_dividers: list[float]) -> AverageType:
|
60
|
+
"""Get the relative descriptor for the InBody value.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
in_body_value (float): The InBody value to check
|
64
|
+
in_body_dividers (list[float]): The dividers for the InBody value
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
AverageType: The relative descriptor for the InBody value
|
68
|
+
"""
|
51
69
|
if in_body_value <= in_body_dividers[2]:
|
52
70
|
return AverageType.BELOW_AVERAGE
|
53
71
|
|
@@ -58,6 +76,17 @@ def get_relative_descriptor(in_body_value: float, in_body_dividers: list[float])
|
|
58
76
|
|
59
77
|
|
60
78
|
def get_body_fat_percent_dividers(age: int, gender: Literal["M", "F"]) -> list[float]:
|
79
|
+
"""Get the body fat percent dividers based on age and gender.
|
80
|
+
|
81
|
+
Converted more or less directly from the Java code in the OTF app.
|
82
|
+
|
83
|
+
Args:
|
84
|
+
age (int): The age of the person
|
85
|
+
gender (Literal["M", "F"]): The gender from the member details
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
list[float]: The body fat percent dividers
|
89
|
+
"""
|
61
90
|
if gender == "M":
|
62
91
|
return get_body_fat_percent_dividers_male(age)
|
63
92
|
|
@@ -65,6 +94,16 @@ def get_body_fat_percent_dividers(age: int, gender: Literal["M", "F"]) -> list[f
|
|
65
94
|
|
66
95
|
|
67
96
|
def get_body_fat_percent_dividers_male(age: int) -> list[float]:
|
97
|
+
"""Get the body fat percent dividers for males based on age.
|
98
|
+
|
99
|
+
Converted more or less directly from the Java code in the OTF app.
|
100
|
+
|
101
|
+
Args:
|
102
|
+
age (int): The age of the person
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
list[float]: The body fat percent dividers
|
106
|
+
"""
|
68
107
|
match age:
|
69
108
|
case age if 0 <= age < 30:
|
70
109
|
return [0.0, 13.1, 21.1, 100.0]
|
@@ -81,6 +120,16 @@ def get_body_fat_percent_dividers_male(age: int) -> list[float]:
|
|
81
120
|
|
82
121
|
|
83
122
|
def get_body_fat_percent_dividers_female(age: int) -> list[float]:
|
123
|
+
"""Get the body fat percent dividers for females based on age.
|
124
|
+
|
125
|
+
Converted more or less directly from the Java code in the OTF app.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
age (int): The age of the person
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
list[float]: The body fat percent dividers
|
132
|
+
"""
|
84
133
|
match age:
|
85
134
|
case age if 0 <= age < 30:
|
86
135
|
return [0.0, 19.1, 26.1, 100.0]
|
@@ -97,106 +146,110 @@ def get_body_fat_percent_dividers_female(age: int) -> list[float]:
|
|
97
146
|
|
98
147
|
|
99
148
|
class LeanBodyMass(OtfItemBase):
|
100
|
-
left_arm: float = Field(...,
|
101
|
-
left_leg: float = Field(...,
|
102
|
-
right_arm: float = Field(...,
|
103
|
-
right_leg: float = Field(...,
|
104
|
-
trunk: float = Field(...,
|
149
|
+
left_arm: float = Field(..., validation_alias="lbmOfLeftArm")
|
150
|
+
left_leg: float = Field(..., validation_alias="lbmOfLeftLeg")
|
151
|
+
right_arm: float = Field(..., validation_alias="lbmOfRightArm")
|
152
|
+
right_leg: float = Field(..., validation_alias="lbmOfRightLeg")
|
153
|
+
trunk: float = Field(..., validation_alias="lbmOfTrunk")
|
105
154
|
|
106
155
|
|
107
156
|
class LeanBodyMassPercent(OtfItemBase):
|
108
|
-
left_arm: float = Field(...,
|
109
|
-
left_leg: float = Field(...,
|
110
|
-
right_arm: float = Field(...,
|
111
|
-
right_leg: float = Field(...,
|
112
|
-
trunk: float = Field(...,
|
157
|
+
left_arm: float = Field(..., validation_alias="lbmPercentOfLeftArm")
|
158
|
+
left_leg: float = Field(..., validation_alias="lbmPercentOfLeftLeg")
|
159
|
+
right_arm: float = Field(..., validation_alias="lbmPercentOfRightArm")
|
160
|
+
right_leg: float = Field(..., validation_alias="lbmPercentOfRightLeg")
|
161
|
+
trunk: float = Field(..., validation_alias="lbmPercentOfTrunk")
|
113
162
|
|
114
163
|
|
115
164
|
class BodyFatMass(OtfItemBase):
|
116
|
-
control: float = Field(...,
|
117
|
-
left_arm: float = Field(...,
|
118
|
-
left_leg: float = Field(...,
|
119
|
-
right_arm: float = Field(...,
|
120
|
-
right_leg: float = Field(...,
|
121
|
-
trunk: float = Field(...,
|
165
|
+
control: float = Field(..., validation_alias="bfmControl")
|
166
|
+
left_arm: float = Field(..., validation_alias="bfmOfLeftArm")
|
167
|
+
left_leg: float = Field(..., validation_alias="bfmOfLeftLeg")
|
168
|
+
right_arm: float = Field(..., validation_alias="bfmOfRightArm")
|
169
|
+
right_leg: float = Field(..., validation_alias="bfmOfRightLeg")
|
170
|
+
trunk: float = Field(..., validation_alias="bfmOfTrunk")
|
122
171
|
|
123
172
|
|
124
173
|
class BodyFatMassPercent(OtfItemBase):
|
125
|
-
left_arm: float = Field(...,
|
126
|
-
left_leg: float = Field(...,
|
127
|
-
right_arm: float = Field(...,
|
128
|
-
right_leg: float = Field(...,
|
129
|
-
trunk: float = Field(...,
|
174
|
+
left_arm: float = Field(..., validation_alias="bfmPercentOfLeftArm")
|
175
|
+
left_leg: float = Field(..., validation_alias="bfmPercentOfLeftLeg")
|
176
|
+
right_arm: float = Field(..., validation_alias="bfmPercentOfRightArm")
|
177
|
+
right_leg: float = Field(..., validation_alias="bfmPercentOfRightLeg")
|
178
|
+
trunk: float = Field(..., validation_alias="bfmPercentOfTrunk")
|
130
179
|
|
131
180
|
|
132
181
|
class TotalBodyWeight(OtfItemBase):
|
133
|
-
right_arm: float = Field(...,
|
134
|
-
left_arm: float = Field(...,
|
135
|
-
trunk: float = Field(...,
|
136
|
-
right_leg: float = Field(...,
|
137
|
-
left_leg: float = Field(...,
|
182
|
+
right_arm: float = Field(..., validation_alias="tbwOfRightArm")
|
183
|
+
left_arm: float = Field(..., validation_alias="tbwOfLeftArm")
|
184
|
+
trunk: float = Field(..., validation_alias="tbwOfTrunk")
|
185
|
+
right_leg: float = Field(..., validation_alias="tbwOfRightLeg")
|
186
|
+
left_leg: float = Field(..., validation_alias="tbwOfLeftLeg")
|
138
187
|
|
139
188
|
|
140
189
|
class IntraCellularWater(OtfItemBase):
|
141
|
-
right_arm: float = Field(...,
|
142
|
-
left_arm: float = Field(...,
|
143
|
-
trunk: float = Field(...,
|
144
|
-
right_leg: float = Field(...,
|
145
|
-
left_leg: float = Field(...,
|
190
|
+
right_arm: float = Field(..., validation_alias="icwOfRightArm")
|
191
|
+
left_arm: float = Field(..., validation_alias="icwOfLeftArm")
|
192
|
+
trunk: float = Field(..., validation_alias="icwOfTrunk")
|
193
|
+
right_leg: float = Field(..., validation_alias="icwOfRightLeg")
|
194
|
+
left_leg: float = Field(..., validation_alias="icwOfLeftLeg")
|
146
195
|
|
147
196
|
|
148
197
|
class ExtraCellularWater(OtfItemBase):
|
149
|
-
right_arm: float = Field(...,
|
150
|
-
left_arm: float = Field(...,
|
151
|
-
trunk: float = Field(...,
|
152
|
-
right_leg: float = Field(...,
|
153
|
-
left_leg: float = Field(...,
|
198
|
+
right_arm: float = Field(..., validation_alias="ecwOfRightArm")
|
199
|
+
left_arm: float = Field(..., validation_alias="ecwOfLeftArm")
|
200
|
+
trunk: float = Field(..., validation_alias="ecwOfTrunk")
|
201
|
+
right_leg: float = Field(..., validation_alias="ecwOfRightLeg")
|
202
|
+
left_leg: float = Field(..., validation_alias="ecwOfLeftLeg")
|
154
203
|
|
155
204
|
|
156
205
|
class ExtraCellularWaterOverTotalBodyWater(OtfItemBase):
|
157
|
-
right_arm: float = Field(...,
|
158
|
-
left_arm: float = Field(...,
|
159
|
-
trunk: float = Field(...,
|
160
|
-
right_leg: float = Field(...,
|
161
|
-
left_leg: float = Field(...,
|
206
|
+
right_arm: float = Field(..., validation_alias="ecwOverTBWOfRightArm")
|
207
|
+
left_arm: float = Field(..., validation_alias="ecwOverTBWOfLeftArm")
|
208
|
+
trunk: float = Field(..., validation_alias="ecwOverTBWOfTrunk")
|
209
|
+
right_leg: float = Field(..., validation_alias="ecwOverTBWOfRightLeg")
|
210
|
+
left_leg: float = Field(..., validation_alias="ecwOverTBWOfLeftLeg")
|
162
211
|
|
163
212
|
|
164
213
|
class BodyCompositionData(OtfItemBase):
|
165
214
|
# NOTE: weight is hardcoded to be pounds here, regardless of the unit shown in the member details
|
166
215
|
|
167
|
-
member_uuid: str = Field(...,
|
168
|
-
member_id: str | int = Field(...,
|
169
|
-
scan_result_uuid: str = Field(...,
|
170
|
-
inbody_id: str = Field(
|
216
|
+
member_uuid: str = Field(..., validation_alias="memberUUId")
|
217
|
+
member_id: str | int = Field(..., validation_alias="memberId")
|
218
|
+
scan_result_uuid: str = Field(..., validation_alias="scanResultUUId")
|
219
|
+
inbody_id: str = Field(
|
220
|
+
..., validation_alias="id", exclude=True, repr=False, description="InBody ID, same as email address"
|
221
|
+
)
|
171
222
|
email: str
|
172
223
|
height: str = Field(..., description="Height in cm")
|
173
224
|
gender: Literal["M", "F"]
|
174
225
|
age: int
|
175
|
-
scan_datetime: datetime = Field(...,
|
226
|
+
scan_datetime: datetime = Field(..., validation_alias="testDatetime")
|
176
227
|
provided_weight: float = Field(
|
177
|
-
...,
|
228
|
+
..., validation_alias="weight", description="Weight in pounds, provided by member at time of scan"
|
178
229
|
)
|
179
230
|
|
180
231
|
lean_body_mass_details: LeanBodyMass
|
181
232
|
lean_body_mass_percent_details: LeanBodyMassPercent
|
182
233
|
|
183
|
-
total_body_weight: float = Field(
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
234
|
+
total_body_weight: float = Field(
|
235
|
+
..., validation_alias="tbw", description="Total body weight in pounds, based on scan results"
|
236
|
+
)
|
237
|
+
dry_lean_mass: float = Field(..., validation_alias="dlm")
|
238
|
+
body_fat_mass: float = Field(..., validation_alias="bfm")
|
239
|
+
lean_body_mass: float = Field(..., validation_alias="lbm")
|
240
|
+
skeletal_muscle_mass: float = Field(..., validation_alias="smm")
|
241
|
+
body_mass_index: float = Field(..., validation_alias="bmi")
|
242
|
+
percent_body_fat: float = Field(..., validation_alias="pbf")
|
243
|
+
basal_metabolic_rate: float = Field(..., validation_alias="bmr")
|
244
|
+
in_body_type: str = Field(..., validation_alias="inBodyType")
|
192
245
|
|
193
246
|
# excluded because they are only useful for end result of calculations
|
194
|
-
body_fat_mass_dividers: list[float] = Field(...,
|
195
|
-
body_fat_mass_plot_point: float = Field(...,
|
196
|
-
skeletal_muscle_mass_dividers: list[float] = Field(...,
|
197
|
-
skeletal_muscle_mass_plot_point: float = Field(...,
|
198
|
-
weight_dividers: list[float] = Field(...,
|
199
|
-
weight_plot_point: float = Field(...,
|
247
|
+
body_fat_mass_dividers: list[float] = Field(..., validation_alias="bfmGraphScale", exclude=True, repr=False)
|
248
|
+
body_fat_mass_plot_point: float = Field(..., validation_alias="pfatnew", exclude=True, repr=False)
|
249
|
+
skeletal_muscle_mass_dividers: list[float] = Field(..., validation_alias="smmGraphScale", exclude=True, repr=False)
|
250
|
+
skeletal_muscle_mass_plot_point: float = Field(..., validation_alias="psmm", exclude=True, repr=False)
|
251
|
+
weight_dividers: list[float] = Field(..., validation_alias="wtGraphScale", exclude=True, repr=False)
|
252
|
+
weight_plot_point: float = Field(..., validation_alias="pwt", exclude=True, repr=False)
|
200
253
|
|
201
254
|
# excluded due to 0 values
|
202
255
|
body_fat_mass_details: BodyFatMass = Field(..., exclude=True, repr=False)
|
@@ -207,13 +260,13 @@ class BodyCompositionData(OtfItemBase):
|
|
207
260
|
extra_cellular_water_over_total_body_water_details: ExtraCellularWaterOverTotalBodyWater = Field(
|
208
261
|
..., exclude=True, repr=False
|
209
262
|
)
|
210
|
-
visceral_fat_level: float = Field(...,
|
211
|
-
visceral_fat_area: float = Field(...,
|
212
|
-
body_comp_measurement: float = Field(...,
|
213
|
-
total_body_weight_over_lean_body_mass: float = Field(...,
|
214
|
-
intracellular_water: float = Field(...,
|
215
|
-
extracellular_water: float = Field(...,
|
216
|
-
lean_body_mass_control: float = Field(...,
|
263
|
+
visceral_fat_level: float = Field(..., validation_alias="vfl", exclude=True, repr=False)
|
264
|
+
visceral_fat_area: float = Field(..., validation_alias="vfa", exclude=True, repr=False)
|
265
|
+
body_comp_measurement: float = Field(..., validation_alias="bcm", exclude=True, repr=False)
|
266
|
+
total_body_weight_over_lean_body_mass: float = Field(..., validation_alias="tbwOverLBM", exclude=True, repr=False)
|
267
|
+
intracellular_water: float = Field(..., validation_alias="icw", exclude=True, repr=False)
|
268
|
+
extracellular_water: float = Field(..., validation_alias="ecw", exclude=True, repr=False)
|
269
|
+
lean_body_mass_control: float = Field(..., validation_alias="lbmControl", exclude=True, repr=False)
|
217
270
|
|
218
271
|
def __init__(self, **data):
|
219
272
|
# Convert the nested dictionaries to the appropriate classes
|
@@ -235,12 +288,28 @@ class BodyCompositionData(OtfItemBase):
|
|
235
288
|
|
236
289
|
@field_validator("skeletal_muscle_mass_dividers", "weight_dividers", "body_fat_mass_dividers", mode="before")
|
237
290
|
@classmethod
|
238
|
-
def convert_dividers_to_float_list(cls, v: str):
|
291
|
+
def convert_dividers_to_float_list(cls, v: str) -> list[float]:
|
292
|
+
"""Convert the dividers from a string to a list of floats.
|
293
|
+
|
294
|
+
Args:
|
295
|
+
v (str): The dividers as a string, separated by semicolons.
|
296
|
+
|
297
|
+
Returns:
|
298
|
+
list[float]: The dividers as a list of floats.
|
299
|
+
"""
|
239
300
|
return [float(i) for i in v.split(";")]
|
240
301
|
|
241
302
|
@field_validator("total_body_weight", mode="before")
|
242
303
|
@classmethod
|
243
|
-
def convert_body_weight_from_kg_to_pounds(cls, v: float):
|
304
|
+
def convert_body_weight_from_kg_to_pounds(cls, v: float) -> float:
|
305
|
+
"""Convert the body weight from kg to pounds.
|
306
|
+
|
307
|
+
Args:
|
308
|
+
v (float): The body weight in kg.
|
309
|
+
|
310
|
+
Returns:
|
311
|
+
float: The body weight in pounds.
|
312
|
+
"""
|
244
313
|
return ureg.Quantity(v, ureg.kilogram).to(ureg.pound).magnitude
|
245
314
|
|
246
315
|
@property
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from pydantic import Field
|
2
|
+
|
3
|
+
from otf_api.models.base import OtfItemBase
|
4
|
+
|
5
|
+
from .enums import EquipmentType
|
6
|
+
|
7
|
+
|
8
|
+
class Year(OtfItemBase):
|
9
|
+
year: int | None = Field(None, validation_alias="Year")
|
10
|
+
is_participated: bool | None = Field(None, validation_alias="IsParticipated")
|
11
|
+
in_progress: bool | None = Field(None, validation_alias="InProgress")
|
12
|
+
|
13
|
+
|
14
|
+
class Program(OtfItemBase):
|
15
|
+
"""A program represents multi-day/week challenges that members can participate in."""
|
16
|
+
|
17
|
+
# NOTE: These ones do seem to match the ChallengeType enums in the OTF app.
|
18
|
+
# Leaving them as int for now though in case older data or other user's
|
19
|
+
# data doesn't match up.
|
20
|
+
challenge_category_id: int | None = Field(None, validation_alias="ChallengeCategoryId")
|
21
|
+
challenge_sub_category_id: int | None = Field(None, validation_alias="ChallengeSubCategoryId")
|
22
|
+
challenge_name: str | None = Field(None, validation_alias="ChallengeName")
|
23
|
+
years: list[Year] = Field(default_factory=list, validation_alias="Years")
|
24
|
+
|
25
|
+
|
26
|
+
class Challenge(OtfItemBase):
|
27
|
+
"""A challenge represents a single day or event that members can participate in."""
|
28
|
+
|
29
|
+
# NOTE: The challenge category/subcategory ids here do not seem to be at
|
30
|
+
# all related to the ChallengeType enums or the few SubCategory enums I've
|
31
|
+
# been able to puzzle out. I haven't been able to link them to any code
|
32
|
+
# in the OTF app. Due to that, they are being excluded from the model for now.
|
33
|
+
challenge_category_id: int | None = Field(None, validation_alias="ChallengeCategoryId")
|
34
|
+
challenge_sub_category_id: int | None = Field(None, validation_alias="ChallengeSubCategoryId")
|
35
|
+
challenge_name: str | None = Field(None, validation_alias="ChallengeName")
|
36
|
+
years: list[Year] = Field(default_factory=list, validation_alias="Years")
|
37
|
+
|
38
|
+
|
39
|
+
class Benchmark(OtfItemBase):
|
40
|
+
"""A benchmark represents a specific workout that members can participate in."""
|
41
|
+
|
42
|
+
equipment_id: EquipmentType | None = Field(None, validation_alias="EquipmentId")
|
43
|
+
equipment_name: str | None = Field(None, validation_alias="EquipmentName")
|
44
|
+
years: list[Year] = Field(default_factory=list, validation_alias="Years")
|
45
|
+
|
46
|
+
|
47
|
+
class ChallengeTracker(OtfItemBase):
|
48
|
+
programs: list[Program] = Field(default_factory=list, validation_alias="Programs")
|
49
|
+
challenges: list[Challenge] = Field(default_factory=list, validation_alias="Challenges")
|
50
|
+
benchmarks: list[Benchmark] = Field(default_factory=list, validation_alias="Benchmarks")
|
@@ -0,0 +1,99 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from pydantic import Field
|
5
|
+
|
6
|
+
from otf_api.models.base import OtfItemBase
|
7
|
+
|
8
|
+
from .enums import EquipmentType
|
9
|
+
|
10
|
+
|
11
|
+
class MetricEntry(OtfItemBase):
|
12
|
+
title: str | None = Field(None, validation_alias="Title")
|
13
|
+
equipment_id: EquipmentType | None = Field(None, validation_alias="EquipmentId")
|
14
|
+
entry_type: str | None = Field(None, validation_alias="EntryType")
|
15
|
+
metric_key: str | None = Field(None, validation_alias="MetricKey")
|
16
|
+
min_value: str | None = Field(None, validation_alias="MinValue")
|
17
|
+
max_value: str | None = Field(None, validation_alias="MaxValue")
|
18
|
+
|
19
|
+
|
20
|
+
class BenchmarkHistory(OtfItemBase):
|
21
|
+
studio_name: str | None = Field(None, validation_alias="StudioName")
|
22
|
+
equipment_id: EquipmentType | None = Field(None, validation_alias="EquipmentId")
|
23
|
+
class_time: datetime | None = Field(None, validation_alias="ClassTime")
|
24
|
+
challenge_sub_category_id: int | None = Field(None, validation_alias="ChallengeSubCategoryId")
|
25
|
+
weight_lbs: int | None = Field(None, validation_alias="WeightLBS")
|
26
|
+
class_name: str | None = Field(None, validation_alias="ClassName")
|
27
|
+
coach_name: str | None = Field(None, validation_alias="CoachName")
|
28
|
+
result: float | str | None = Field(None, validation_alias="Result")
|
29
|
+
workout_type_id: int | None = Field(None, validation_alias="WorkoutTypeId", exclude=True, repr=False)
|
30
|
+
workout_id: int | None = Field(None, validation_alias="WorkoutId", exclude=True, repr=False)
|
31
|
+
linked_challenges: list[Any] | None = Field(None, validation_alias="LinkedChallenges", exclude=True, repr=False)
|
32
|
+
|
33
|
+
date_created: datetime | None = Field(
|
34
|
+
None,
|
35
|
+
validation_alias="DateCreated",
|
36
|
+
exclude=True,
|
37
|
+
repr=False,
|
38
|
+
description="When the entry was created in database, not useful to users",
|
39
|
+
)
|
40
|
+
date_updated: datetime | None = Field(
|
41
|
+
None,
|
42
|
+
validation_alias="DateUpdated",
|
43
|
+
exclude=True,
|
44
|
+
repr=False,
|
45
|
+
description="When the entry was updated in database, not useful to users",
|
46
|
+
)
|
47
|
+
class_id: int | None = Field(
|
48
|
+
None, validation_alias="ClassId", exclude=True, repr=False, description="Not used by API"
|
49
|
+
)
|
50
|
+
substitute_id: int | None = Field(
|
51
|
+
None,
|
52
|
+
validation_alias="SubstituteId",
|
53
|
+
exclude=True,
|
54
|
+
repr=False,
|
55
|
+
description="Not used by API, also always seems to be 0",
|
56
|
+
)
|
57
|
+
|
58
|
+
|
59
|
+
class ChallengeHistory(OtfItemBase):
|
60
|
+
studio_name: str | None = Field(None, validation_alias="StudioName")
|
61
|
+
start_date: datetime | None = Field(None, validation_alias="StartDate")
|
62
|
+
end_date: datetime | None = Field(None, validation_alias="EndDate")
|
63
|
+
total_result: float | str | None = Field(None, validation_alias="TotalResult")
|
64
|
+
is_finished: bool | None = Field(None, validation_alias="IsFinished")
|
65
|
+
benchmark_histories: list[BenchmarkHistory] = Field(default_factory=list, validation_alias="BenchmarkHistories")
|
66
|
+
|
67
|
+
challenge_id: int | None = Field(
|
68
|
+
None, validation_alias="ChallengeId", exclude=True, repr=False, description="Not used by API"
|
69
|
+
)
|
70
|
+
studio_id: int | None = Field(
|
71
|
+
None, validation_alias="StudioId", exclude=True, repr=False, description="Not used by API"
|
72
|
+
)
|
73
|
+
challenge_objective: str | None = Field(
|
74
|
+
None, validation_alias="ChallengeObjective", exclude=True, repr=False, description="Always the string 'None'"
|
75
|
+
)
|
76
|
+
|
77
|
+
|
78
|
+
class Goal(OtfItemBase):
|
79
|
+
goal: int | None = Field(None, validation_alias="Goal")
|
80
|
+
goal_period: str | None = Field(None, validation_alias="GoalPeriod")
|
81
|
+
overall_goal: int | None = Field(None, validation_alias="OverallGoal")
|
82
|
+
overall_goal_period: str | None = Field(None, validation_alias="OverallGoalPeriod")
|
83
|
+
min_overall: int | None = Field(None, validation_alias="MinOverall")
|
84
|
+
min_overall_period: str | None = Field(None, validation_alias="MinOverallPeriod")
|
85
|
+
|
86
|
+
|
87
|
+
class FitnessBenchmark(OtfItemBase):
|
88
|
+
challenge_category_id: int | None = Field(None, validation_alias="ChallengeCategoryId")
|
89
|
+
challenge_sub_category_id: int | None = Field(None, validation_alias="ChallengeSubCategoryId")
|
90
|
+
equipment_id: EquipmentType | None = Field(None, validation_alias="EquipmentId")
|
91
|
+
equipment_name: str | None = Field(None, validation_alias="EquipmentName")
|
92
|
+
metric_entry: MetricEntry | None = Field(None, validation_alias="MetricEntry")
|
93
|
+
challenge_name: str | None = Field(None, validation_alias="ChallengeName")
|
94
|
+
best_record: float | str | None = Field(None, validation_alias="BestRecord")
|
95
|
+
last_record: float | str | None = Field(None, validation_alias="LastRecord")
|
96
|
+
previous_record: float | str | None = Field(None, validation_alias="PreviousRecord")
|
97
|
+
unit: str | None = Field(None, validation_alias="Unit")
|
98
|
+
goals: Goal | None = Field(None, validation_alias="Goals")
|
99
|
+
challenge_histories: list[ChallengeHistory] = Field(default_factory=list, validation_alias="ChallengeHistories")
|
@@ -0,0 +1,70 @@
|
|
1
|
+
from enum import IntEnum, StrEnum
|
2
|
+
|
3
|
+
|
4
|
+
class StatsTime(StrEnum):
|
5
|
+
"""Enum representing the different time periods for workout statistics."""
|
6
|
+
|
7
|
+
LastYear = "lastYear"
|
8
|
+
ThisYear = "thisYear"
|
9
|
+
LastMonth = "lastMonth"
|
10
|
+
ThisMonth = "thisMonth"
|
11
|
+
LastWeek = "lastWeek"
|
12
|
+
ThisWeek = "thisWeek"
|
13
|
+
AllTime = "allTime"
|
14
|
+
|
15
|
+
|
16
|
+
class EquipmentType(IntEnum):
|
17
|
+
"""Enum representing the type of equipment used in workouts."""
|
18
|
+
|
19
|
+
Treadmill = 2
|
20
|
+
Strider = 3
|
21
|
+
Rower = 4
|
22
|
+
Bike = 5
|
23
|
+
WeightFloor = 6
|
24
|
+
PowerWalker = 7
|
25
|
+
|
26
|
+
|
27
|
+
class ChallengeCategory(IntEnum):
|
28
|
+
"""Enum representing the different challenge categories."""
|
29
|
+
|
30
|
+
Other = 0
|
31
|
+
DriTri = 2
|
32
|
+
Infinity = 3
|
33
|
+
MarathonMonth = 5
|
34
|
+
OrangeEverest = 9
|
35
|
+
CatchMeIfYouCan = 10
|
36
|
+
TwoHundredMeterRow = 15
|
37
|
+
FiveHundredMeterRow = 16
|
38
|
+
TwoThousandMeterRow = 17
|
39
|
+
TwelveMinuteTreadmill = 18
|
40
|
+
OneMileTreadmill = 19
|
41
|
+
TenMinuteRow = 20
|
42
|
+
HellWeek = 52
|
43
|
+
Inferno = 55
|
44
|
+
Mayhem = 58
|
45
|
+
BackAtIt = 60
|
46
|
+
FourteenMinuteRow = 61
|
47
|
+
TwelveDaysOfFitness = 63
|
48
|
+
TransformationChallenge = 64
|
49
|
+
RemixInSix = 65
|
50
|
+
Push = 66
|
51
|
+
QuarterMileTreadmill = 69
|
52
|
+
OneThousandMeterRow = 70
|
53
|
+
|
54
|
+
|
55
|
+
class DriTriChallengeSubCategory(IntEnum):
|
56
|
+
"""Enum representing the subcategories of the DriTri challenge."""
|
57
|
+
|
58
|
+
FullRun = 1
|
59
|
+
SprintRun = 3
|
60
|
+
Relay = 4
|
61
|
+
StrengthRun = 1500
|
62
|
+
|
63
|
+
|
64
|
+
class MarathonMonthChallengeSubCategory(IntEnum):
|
65
|
+
"""Enum representing the subcategories of the Marathon Month challenge."""
|
66
|
+
|
67
|
+
Original = 1
|
68
|
+
Full = 14
|
69
|
+
Half = 15
|
70
|
+
Ultra = 16
|
@@ -0,0 +1,96 @@
|
|
1
|
+
from typing import Generic, TypeVar
|
2
|
+
|
3
|
+
from pydantic import Field, field_serializer
|
4
|
+
|
5
|
+
from otf_api.models.base import OtfItemBase
|
6
|
+
|
7
|
+
from .enums import StatsTime
|
8
|
+
|
9
|
+
T = TypeVar("T", bound=OtfItemBase)
|
10
|
+
|
11
|
+
|
12
|
+
class OutStudioMixin:
|
13
|
+
walking_distance: float | None = Field(None, validation_alias="walkingDistance")
|
14
|
+
running_distance: float | None = Field(None, validation_alias="runningDistance")
|
15
|
+
cycling_distance: float | None = Field(None, validation_alias="cyclingDistance")
|
16
|
+
|
17
|
+
@field_serializer("walking_distance", "running_distance", "cycling_distance")
|
18
|
+
@staticmethod
|
19
|
+
def limit_floats(value: float | int | None) -> float | None:
|
20
|
+
"""Limit the float values to 2 decimal places."""
|
21
|
+
if value is not None:
|
22
|
+
return round(value, 2)
|
23
|
+
return value
|
24
|
+
|
25
|
+
|
26
|
+
class InStudioMixin:
|
27
|
+
treadmill_distance: float | None = Field(None, validation_alias="treadmillDistance")
|
28
|
+
treadmill_elevation_gained: float | None = Field(None, validation_alias="treadmillElevationGained")
|
29
|
+
rower_distance: float | None = Field(None, validation_alias="rowerDistance")
|
30
|
+
rower_watt: float | None = Field(None, validation_alias="rowerWatt")
|
31
|
+
|
32
|
+
@field_serializer("treadmill_distance", "treadmill_elevation_gained", "rower_distance", "rower_watt")
|
33
|
+
@staticmethod
|
34
|
+
def limit_floats(value: float | int | None) -> float | None:
|
35
|
+
"""Limit the float values to 2 decimal places."""
|
36
|
+
if value is not None:
|
37
|
+
return round(value, 2)
|
38
|
+
return value
|
39
|
+
|
40
|
+
|
41
|
+
class BaseStatsData(OtfItemBase):
|
42
|
+
calories: float | None = None
|
43
|
+
splat_point: float | None = Field(None, validation_alias="splatPoint")
|
44
|
+
total_black_zone: float | None = Field(None, validation_alias="totalBlackZone")
|
45
|
+
total_blue_zone: float | None = Field(None, validation_alias="totalBlueZone")
|
46
|
+
total_green_zone: float | None = Field(None, validation_alias="totalGreenZone")
|
47
|
+
total_orange_zone: float | None = Field(None, validation_alias="totalOrangeZone")
|
48
|
+
total_red_zone: float | None = Field(None, validation_alias="totalRedZone")
|
49
|
+
workout_duration: float | None = Field(None, validation_alias="workoutDuration")
|
50
|
+
step_count: float | None = Field(None, validation_alias="stepCount")
|
51
|
+
|
52
|
+
|
53
|
+
class InStudioStatsData(InStudioMixin, BaseStatsData):
|
54
|
+
pass
|
55
|
+
|
56
|
+
|
57
|
+
class OutStudioStatsData(OutStudioMixin, BaseStatsData):
|
58
|
+
pass
|
59
|
+
|
60
|
+
|
61
|
+
class AllStatsData(OutStudioMixin, InStudioMixin, BaseStatsData):
|
62
|
+
pass
|
63
|
+
|
64
|
+
|
65
|
+
class TimeStats(OtfItemBase, Generic[T]):
|
66
|
+
last_year: T = Field(..., validation_alias="lastYear")
|
67
|
+
this_year: T = Field(..., validation_alias="thisYear")
|
68
|
+
last_month: T = Field(..., validation_alias="lastMonth")
|
69
|
+
this_month: T = Field(..., validation_alias="thisMonth")
|
70
|
+
last_week: T = Field(..., validation_alias="lastWeek")
|
71
|
+
this_week: T = Field(..., validation_alias="thisWeek")
|
72
|
+
all_time: T = Field(..., validation_alias="allTime")
|
73
|
+
|
74
|
+
def get_by_time(self, stats_time: StatsTime) -> T:
|
75
|
+
"""Get the stats data by time."""
|
76
|
+
match stats_time:
|
77
|
+
case StatsTime.LastYear:
|
78
|
+
return self.last_year
|
79
|
+
case StatsTime.ThisYear:
|
80
|
+
return self.this_year
|
81
|
+
case StatsTime.LastMonth:
|
82
|
+
return self.last_month
|
83
|
+
case StatsTime.ThisMonth:
|
84
|
+
return self.this_month
|
85
|
+
case StatsTime.LastWeek:
|
86
|
+
return self.last_week
|
87
|
+
case StatsTime.ThisWeek:
|
88
|
+
return self.this_week
|
89
|
+
case StatsTime.AllTime:
|
90
|
+
return self.all_time
|
91
|
+
|
92
|
+
|
93
|
+
class StatsResponse(OtfItemBase):
|
94
|
+
all_stats: TimeStats[AllStatsData] = Field(..., validation_alias="allStats")
|
95
|
+
in_studio: TimeStats[InStudioStatsData] = Field(..., validation_alias="inStudio")
|
96
|
+
out_studio: TimeStats[OutStudioStatsData] = Field(..., validation_alias="outStudio")
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
|
3
|
+
from pydantic import AliasPath, Field
|
4
|
+
|
5
|
+
from otf_api.models.base import OtfItemBase
|
6
|
+
|
7
|
+
|
8
|
+
class OutOfStudioWorkoutHistory(OtfItemBase):
|
9
|
+
member_uuid: str = Field(..., validation_alias="memberUUId")
|
10
|
+
workout_uuid: str = Field(..., validation_alias="workoutUUId")
|
11
|
+
|
12
|
+
workout_date: datetime | None = Field(None, validation_alias="workoutDate")
|
13
|
+
start_time: datetime | None = Field(None, validation_alias="startTime")
|
14
|
+
end_time: datetime | None = Field(None, validation_alias="endTime")
|
15
|
+
duration: float | None = None
|
16
|
+
duration_unit: str | None = Field(None, validation_alias="durationUnit")
|
17
|
+
total_calories: int | None = Field(None, validation_alias="totalCalories")
|
18
|
+
hr_percent_max: int | None = Field(None, validation_alias="hrPercentMax")
|
19
|
+
distance_unit: str | None = Field(None, validation_alias="distanceUnit")
|
20
|
+
total_distance: float | None = Field(None, validation_alias="totalDistance")
|
21
|
+
splat_points: int | None = Field(None, validation_alias="splatPoints")
|
22
|
+
target_heart_rate: int | None = Field(None, validation_alias="targetHeartRate")
|
23
|
+
total_steps: int | None = Field(None, validation_alias="totalSteps")
|
24
|
+
has_detailed_data: bool | None = Field(None, validation_alias="hasDetailedData")
|
25
|
+
avg_heartrate: int | None = Field(None, validation_alias="avgHeartrate")
|
26
|
+
max_heartrate: int | None = Field(None, validation_alias="maxHeartrate")
|
27
|
+
workout_type: str | None = Field(None, validation_alias=AliasPath("workoutType", "displayName"))
|
28
|
+
red_zone_seconds: int | None = Field(None, validation_alias="redZoneSeconds")
|
29
|
+
orange_zone_seconds: int | None = Field(None, validation_alias="orangeZoneSeconds")
|
30
|
+
green_zone_seconds: int | None = Field(None, validation_alias="greenZoneSeconds")
|
31
|
+
blue_zone_seconds: int | None = Field(None, validation_alias="blueZoneSeconds")
|
32
|
+
grey_zone_seconds: int | None = Field(None, validation_alias="greyZoneSeconds")
|