otf-api 0.9.4__py3-none-any.whl → 0.10.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.
@@ -3,39 +3,45 @@ from .bookings import Booking
3
3
  from .challenge_tracker_content import ChallengeTracker
4
4
  from .challenge_tracker_detail import FitnessBenchmark
5
5
  from .classes import OtfClass
6
- from .enums import BookingStatus, ChallengeCategory, ClassType, DoW, EquipmentType, StatsTime, StudioStatus
6
+ from .enums import (
7
+ HISTORICAL_BOOKING_STATUSES,
8
+ BookingStatus,
9
+ ChallengeCategory,
10
+ ClassType,
11
+ DoW,
12
+ EquipmentType,
13
+ StatsTime,
14
+ StudioStatus,
15
+ )
7
16
  from .lifetime_stats import StatsResponse, TimeStats
8
17
  from .member_detail import MemberDetail
9
18
  from .member_membership import MemberMembership
10
19
  from .member_purchases import MemberPurchase
11
20
  from .notifications import EmailNotificationSettings, SmsNotificationSettings
12
21
  from .out_of_studio_workout_history import OutOfStudioWorkoutHistory
13
- from .performance_summary_detail import PerformanceSummaryDetail
14
- from .performance_summary_list import PerformanceSummaryEntry
22
+ from .performance_summary import PerformanceSummary
15
23
  from .studio_detail import StudioDetail
16
24
  from .studio_services import StudioService
17
25
  from .telemetry import Telemetry, TelemetryHistoryItem
18
26
 
19
27
  __all__ = [
28
+ "HISTORICAL_BOOKING_STATUSES",
20
29
  "BodyCompositionData",
21
30
  "Booking",
22
31
  "BookingStatus",
23
32
  "ChallengeCategory",
24
- "ChallengeParticipation",
25
33
  "ChallengeTracker",
26
34
  "ClassType",
27
35
  "DoW",
28
36
  "EmailNotificationSettings",
29
37
  "EquipmentType",
30
38
  "FitnessBenchmark",
31
- "LatestAgreement",
32
39
  "MemberDetail",
33
40
  "MemberMembership",
34
41
  "MemberPurchase",
35
42
  "OtfClass",
36
43
  "OutOfStudioWorkoutHistory",
37
- "PerformanceSummaryDetail",
38
- "PerformanceSummaryEntry",
44
+ "PerformanceSummary",
39
45
  "SmsNotificationSettings",
40
46
  "StatsResponse",
41
47
  "StatsTime",
otf_api/models/enums.py CHANGED
@@ -8,19 +8,34 @@ class StudioStatus(StrEnum):
8
8
  COMING_SOON = "Coming Soon"
9
9
  TEMP_CLOSED = "Temporarily Closed"
10
10
  PERM_CLOSED = "Permanently Closed"
11
+ UNKNOWN = "Unknown"
11
12
 
12
13
 
13
14
  class BookingStatus(StrEnum):
14
- CheckedIn = "Checked In"
15
- CancelCheckinPending = "Cancel Checkin Pending"
16
- CancelCheckinRequested = "Cancel Checkin Requested"
15
+ Pending = "Pending"
16
+ Requested = "Requested"
17
+ Booked = "Booked"
17
18
  Cancelled = "Cancelled"
18
19
  LateCancelled = "Late Cancelled"
19
- Booked = "Booked"
20
20
  Waitlisted = "Waitlisted"
21
+ CheckedIn = "Checked In"
21
22
  CheckinPending = "Checkin Pending"
22
23
  CheckinRequested = "Checkin Requested"
24
+ Confirmed = "Confirmed"
23
25
  CheckinCancelled = "Checkin Cancelled"
26
+ CancelCheckinPending = "Cancel Checkin Pending"
27
+ CancelCheckinRequested = "Cancel Checkin Requested"
28
+
29
+
30
+ HISTORICAL_BOOKING_STATUSES = [
31
+ BookingStatus.CheckedIn,
32
+ BookingStatus.CancelCheckinPending,
33
+ BookingStatus.CancelCheckinRequested,
34
+ BookingStatus.LateCancelled,
35
+ BookingStatus.CheckinPending,
36
+ BookingStatus.CheckinRequested,
37
+ BookingStatus.CheckinCancelled,
38
+ ]
24
39
 
25
40
 
26
41
  class DoW(StrEnum):
@@ -0,0 +1,169 @@
1
+ from datetime import datetime, time
2
+ from typing import Any
3
+
4
+ from pydantic import AliasPath, Field, field_validator
5
+
6
+ from otf_api.models.base import OtfItemBase
7
+ from otf_api.models.enums import ClassType
8
+ from otf_api.models.studio_detail import StudioDetail
9
+
10
+
11
+ class ZoneTimeMinutes(OtfItemBase):
12
+ gray: int
13
+ blue: int
14
+ green: int
15
+ orange: int
16
+ red: int
17
+
18
+
19
+ class CoachRating(OtfItemBase):
20
+ id: str
21
+ description: str
22
+ value: int
23
+
24
+
25
+ class ClassRating(OtfItemBase):
26
+ id: str
27
+ description: str
28
+ value: int
29
+
30
+
31
+ class HeartRate(OtfItemBase):
32
+ max_hr: int
33
+ peak_hr: int
34
+ peak_hr_percent: int
35
+ avg_hr: int
36
+ avg_hr_percent: int
37
+
38
+
39
+ class Class(OtfItemBase):
40
+ class_uuid: str | None = Field(None, description="Only populated if class is ratable", alias="ot_base_class_uuid")
41
+ starts_at: datetime | None = Field(None, alias="starts_at_local")
42
+ type: ClassType | None = None
43
+ studio: StudioDetail | None = None
44
+ name: str | None = None
45
+
46
+
47
+ class PerformanceMetric(OtfItemBase):
48
+ display_value: time | float
49
+ display_unit: str
50
+ metric_value: float
51
+
52
+ @field_validator("display_value", mode="before")
53
+ @classmethod
54
+ def convert_to_time_format(cls, value) -> time | float:
55
+ if not value:
56
+ return value
57
+
58
+ if isinstance(value, float | int):
59
+ return value
60
+
61
+ if isinstance(value, str) and ":" in value:
62
+ if value.count(":") == 1:
63
+ minutes, seconds = value.split(":")
64
+ return time(minute=int(minutes), second=int(seconds))
65
+ if value.count(":") == 2:
66
+ hours, minutes, seconds = value.split(":")
67
+ return time(hour=int(hours), minute=int(minutes), second=int(seconds))
68
+
69
+ return value
70
+
71
+
72
+ class BaseEquipment(OtfItemBase):
73
+ avg_pace: PerformanceMetric
74
+ avg_speed: PerformanceMetric
75
+ max_pace: PerformanceMetric
76
+ max_speed: PerformanceMetric
77
+ moving_time: PerformanceMetric
78
+ total_distance: PerformanceMetric
79
+
80
+
81
+ class Treadmill(BaseEquipment):
82
+ avg_incline: PerformanceMetric
83
+ elevation_gained: PerformanceMetric
84
+ max_incline: PerformanceMetric
85
+
86
+
87
+ class Rower(BaseEquipment):
88
+ avg_cadence: PerformanceMetric
89
+ avg_power: PerformanceMetric
90
+ max_cadence: PerformanceMetric
91
+
92
+
93
+ class PerformanceSummary(OtfItemBase):
94
+ """Represents a workout performance summary - the same data that is shown in the OTF app after a workout"
95
+
96
+ This data comes from two different endpoints that do not actually match, because of course not.
97
+ The summary endpoint returns one distinct set of data plus some detailed data - this is the only place we can get
98
+ the studio information. The detail endpoint returns more performance data but does not have much class data and does
99
+ not have the studio.
100
+
101
+ * The summary endpoint data is missing step_count, the value is always 0.
102
+ * The detail endpoint data is missing active_time_seconds, the value is always 0.
103
+ * The detail endpoint class name is more generic than the summary endpoint class name.
104
+
105
+
106
+ """
107
+
108
+ performance_summary_id: str = Field(..., alias="id", description="Unique identifier for this performance summary")
109
+ class_history_uuid: str = Field(..., alias="id", description="Same as performance_summary_id")
110
+ ratable: bool | None = None
111
+ otf_class: Class | None = Field(None, alias="class")
112
+ coach: str | None = Field(None, alias=AliasPath("class", "coach", "first_name"))
113
+ coach_rating: CoachRating | None = Field(None, alias=AliasPath("ratings", "coach"))
114
+ class_rating: ClassRating | None = Field(None, alias=AliasPath("ratings", "class"))
115
+
116
+ active_time_seconds: int | None = Field(None, alias=AliasPath("details", "active_time_seconds"))
117
+ calories_burned: int | None = Field(None, alias=AliasPath("details", "calories_burned"))
118
+ splat_points: int | None = Field(None, alias=AliasPath("details", "splat_points"))
119
+ step_count: int | None = Field(None, alias=AliasPath("details", "step_count"))
120
+ zone_time_minutes: ZoneTimeMinutes | None = Field(None, alias=AliasPath("details", "zone_time_minutes"))
121
+ heart_rate: HeartRate | None = Field(None, alias=AliasPath("details", "heart_rate"))
122
+
123
+ rower_data: Rower | None = Field(None, alias=AliasPath("details", "equipment_data", "rower"))
124
+ treadmill_data: Treadmill | None = Field(None, alias=AliasPath("details", "equipment_data", "treadmill"))
125
+
126
+ @property
127
+ def is_rated(self) -> bool:
128
+ return self.coach_rating is not None or self.class_rating is not None
129
+
130
+ @property
131
+ def studio(self) -> StudioDetail | None:
132
+ return self.otf_class.studio if self.otf_class else None
133
+
134
+ def __init__(self, **kwargs) -> None:
135
+ summary_detail = kwargs.pop("details", {}) or {}
136
+ true_detail = kwargs.pop("detail", {}) or {}
137
+
138
+ summary_class = kwargs.pop("class", {})
139
+ detail_class = true_detail.pop("class", {})
140
+
141
+ kwargs["class"] = combine_class_data(summary_class, detail_class)
142
+ kwargs["details"] = combine_detail_data(summary_detail, true_detail.pop("details", {}))
143
+
144
+ super().__init__(**kwargs)
145
+
146
+
147
+ def combine_class_data(summary_class: dict[str, str], detail_class: dict[str, str]) -> dict[str, str]:
148
+ class_data = {}
149
+
150
+ class_data["ot_base_class_uuid"] = summary_class.get("ot_base_class_uuid")
151
+ class_data["starts_at_local"] = summary_class.get("starts_at_local") or detail_class.get("starts_at_local")
152
+ class_data["type"] = summary_class.get("type")
153
+ class_data["studio"] = summary_class.get("studio")
154
+ class_data["name"] = detail_class.get("name") or summary_class.get("name")
155
+ class_data["coach"] = summary_class.get("coach")
156
+
157
+ return class_data
158
+
159
+
160
+ def combine_detail_data(summary_detail: dict[str, Any], true_detail: dict[str, Any]) -> dict[str, Any]:
161
+ # active time seconds always 0 in detail
162
+ del true_detail["active_time_seconds"]
163
+
164
+ # step count always 0 in summary
165
+ summary_detail["step_count"] = true_detail["step_count"]
166
+
167
+ combined_details = summary_detail | true_detail
168
+
169
+ return combined_details
@@ -9,8 +9,8 @@ from otf_api.models.mixins import AddressMixin
9
9
 
10
10
  class StudioLocation(AddressMixin):
11
11
  phone_number: str | None = Field(None, alias=AliasChoices("phone", "phoneNumber"))
12
- latitude: float = Field(..., alias=AliasChoices("latitude"))
13
- longitude: float = Field(..., alias=AliasChoices("longitude"))
12
+ latitude: float | None = Field(None, alias=AliasChoices("latitude"))
13
+ longitude: float | None = Field(None, alias=AliasChoices("longitude"))
14
14
 
15
15
  physical_region: str | None = Field(None, alias="physicalRegion", exclude=True, repr=False)
16
16
  physical_country_id: int | None = Field(None, alias="physicalCountryId", exclude=True, repr=False)
@@ -24,8 +24,10 @@ class StudioDetail(OtfItemBase):
24
24
  None,
25
25
  description="Distance from latitude and longitude provided to `search_studios_by_geo` method,\
26
26
  NULL if that method was not used",
27
+ exclude=True,
28
+ repr=False,
27
29
  )
28
- location: StudioLocation = Field(..., alias="studioLocation")
30
+ location: StudioLocation = Field(..., alias="studioLocation", default_factory=StudioLocation)
29
31
  name: str | None = Field(None, alias="studioName")
30
32
  status: StudioStatus | None = Field(
31
33
  None, alias="studioStatus", description="Active, Temporarily Closed, Coming Soon"
@@ -49,7 +49,10 @@ class TelemetryItem(OtfItemBase):
49
49
 
50
50
  class Telemetry(OtfItemBase):
51
51
  member_uuid: str = Field(..., alias="memberUuid")
52
- class_history_uuid: str = Field(..., alias="classHistoryUuid")
52
+ performance_summary_id: str = Field(
53
+ ..., alias="classHistoryUuid", description="The ID of the performance summary this telemetry item belongs to."
54
+ )
55
+ class_history_uuid: str = Field(..., alias="classHistoryUuid", description="The same as performance_summary_id.")
53
56
  class_start_time: datetime | None = Field(None, alias="classStartTime")
54
57
  max_hr: int | None = Field(None, alias="maxHr")
55
58
  zones: Zones
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: otf-api
3
- Version: 0.9.4
3
+ Version: 0.10.1
4
4
  Summary: Python OrangeTheory Fitness API Client
5
5
  License: MIT
6
6
  Author: Jessica Smith
@@ -19,6 +19,7 @@ Classifier: Topic :: Software Development :: Libraries
19
19
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Requires-Dist: attrs (>=24.3.0,<25.0.0)
22
+ Requires-Dist: cachetools (>=5.5.0)
22
23
  Requires-Dist: httpx (>=0.27.2)
23
24
  Requires-Dist: humanize (>=4.9.0,<5.0.0)
24
25
  Requires-Dist: inflection (==0.5.*)
@@ -26,6 +27,7 @@ Requires-Dist: mypy-boto3-cognito-idp (>=1.35.93,<2.0.0)
26
27
  Requires-Dist: pint (==0.24.*)
27
28
  Requires-Dist: pycognito (==2024.5.1)
28
29
  Requires-Dist: pydantic (>=2.7.3)
30
+ Requires-Dist: tenacity (>=9.0.0,<10.0.0)
29
31
  Requires-Dist: yarl (>=1.18.3,<2.0.0)
30
32
  Project-URL: Documentation, https://otf-api.readthedocs.io/en/stable/
31
33
  Description-Content-Type: text/markdown
@@ -1,5 +1,5 @@
1
- otf_api/__init__.py,sha256=YX1QZJ1_zpa6hu-28ypuaxCzL19ExrQkzHt9CahYrrM,204
2
- otf_api/api.py,sha256=UNuMgJ6RnEj1rU8V6Funtnv3WQlxp2ShOJKCkfI-iC0,52570
1
+ otf_api/__init__.py,sha256=jmIypN9FJ7JVYl5DU1tyUooGkPfpx-vsQ-kzOi36G1w,205
2
+ otf_api/api.py,sha256=eAAkmgE7nySpHEe52O9SWv6hC3hLy1Aiy5jDz6ySoV0,60312
3
3
  otf_api/auth/__init__.py,sha256=Pu6ugbEXhgjJEpYKr7K2CqEac53Wq9FRF3F16GLQ_ww,133
4
4
  otf_api/auth/auth.py,sha256=m9qzUn0hdAo6AZHm2Mn054nLqUWPxEyP3y3DOBVkvYA,9010
5
5
  otf_api/auth/user.py,sha256=HgggfneeVMnKFWSUMnlikfZyC__HE8vVaKSpBS4_sIg,2256
@@ -7,14 +7,14 @@ otf_api/auth/utils.py,sha256=jUH_A1-DU3KfY-XrymCuQoud79o9qfu5P9Ws9QA2aWs,3211
7
7
  otf_api/exceptions.py,sha256=GISekwF5dPt0Ol0WCU55kE5ODc5VxicNEEhmlguuE0U,1815
8
8
  otf_api/filters.py,sha256=fk2bFGi3srjS96qZlaDx-ARZRaj93NUTUdMJ01TX420,3702
9
9
  otf_api/logging.py,sha256=PRZpCaJ1F1Xya3L9Efkt3mKS5_QNr3sXjEUERSxYjvE,563
10
- otf_api/models/__init__.py,sha256=czt_OYsU4igeudRAi8kqOQro6fjNrbE9aLr--FA76Sg,1588
10
+ otf_api/models/__init__.py,sha256=Ak1Wph5Yn9Npu3m3vvG-n5CsMH6j7NCuzoOBd0b_OlI,1524
11
11
  otf_api/models/base.py,sha256=KJlIxl_sRj6f-g5vKYPw4yV6fGDk-fwZ93EO0JGPYMw,202
12
12
  otf_api/models/body_composition_list.py,sha256=jGdR-9ScvIOtULJNB99aYh2INk2ihoHAnTWtbQCIea4,12202
13
13
  otf_api/models/bookings.py,sha256=8zgLnQ40ci0c7AimGeoAI-Raw8d2byvKoXkJXyW1xUE,4260
14
14
  otf_api/models/challenge_tracker_content.py,sha256=zukbda38DloiBHW9HUEKQnumGhgVqpjjbxo7G4bDIc0,2630
15
15
  otf_api/models/challenge_tracker_detail.py,sha256=KDfLRDjr8quVhDPVj9x_tuLnkJPua5y8C3WtGIVzXZw,4356
16
16
  otf_api/models/classes.py,sha256=DZ7oo_MNK7m3ul2gK929maGBksekG9RtLzYa-Di8skk,3005
17
- otf_api/models/enums.py,sha256=qXpnJwUyR5fZxr_z1VIlgo_uOGFdGNqoaITVpiA2z4E,4214
17
+ otf_api/models/enums.py,sha256=VyG3w-kcpDMTsHvX7s6xp-cI61mqvsmN_A76jsnmKj0,4604
18
18
  otf_api/models/lifetime_stats.py,sha256=sQw0oaT5uSlCTZyMSEEhYJg7IdOzuxelTI3mzDBHNls,3055
19
19
  otf_api/models/member_detail.py,sha256=CDDZg3Ow07U57yRqbm3i-BVha0cvCNOZ8QhN0pliU_A,6598
20
20
  otf_api/models/member_membership.py,sha256=jZwHzwtVyMUr8dWGlFbMYj9qveCbiOblWW5szXDUFFo,1338
@@ -22,14 +22,13 @@ otf_api/models/member_purchases.py,sha256=Ne7ByEbGTqTJhuEyCgywWe8I3nc-D46qw09up7
22
22
  otf_api/models/mixins.py,sha256=VR5EeweySHhzaiqqnCr853Cpe1uK97cxY0IFEdf5T8w,2262
23
23
  otf_api/models/notifications.py,sha256=AkmIfiIiU6wox_7puyenbhCX10SFvBD5eBAovcurRgY,833
24
24
  otf_api/models/out_of_studio_workout_history.py,sha256=El5i0K2Td_sMReyfUKP-Iv1L1WgRx0ijjjnHzYvlCeY,1703
25
- otf_api/models/performance_summary_detail.py,sha256=U-Z-6-1rJHhtMuzCAnKZtAE0VPRCPUB8IzldINI8JqA,2917
26
- otf_api/models/performance_summary_list.py,sha256=ydOVYRwdI_U0yxW4USrDQilLafwxMzUy80AHDmJvmdQ,1708
27
- otf_api/models/studio_detail.py,sha256=4HZXP6khjuFs7J7lokr3rMEDDpCwcb7OYJVJvzgly7U,3639
25
+ otf_api/models/performance_summary.py,sha256=0pg1TTnYEk7HW6exA3Czclt1YMtYRBusgPQFWN_udGo,6066
26
+ otf_api/models/studio_detail.py,sha256=DZicrQWee8iexyK5I8UN1wBYevTB_1dciz2EKBhPids,3729
28
27
  otf_api/models/studio_services.py,sha256=aGLQMQmjGVpI6YxzAl-mcp3Y9cHPXuH9dIqrl6E-78E,1665
29
- otf_api/models/telemetry.py,sha256=_g-wtJDbaXENk1DqH0bbpJUsGepWDYraRIsIvpvDBU8,2531
28
+ otf_api/models/telemetry.py,sha256=LuoQ-W7wWQ-trjQqfITQsUpkWL1X3TXdsHiDhyIDvUk,2748
30
29
  otf_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
30
  otf_api/utils.py,sha256=-9JBjRmoS6xpua_lHccJYjEX2tWp2-5QQrbbuOYMyIo,3945
32
- otf_api-0.9.4.dist-info/LICENSE,sha256=UaPT9ynYigC3nX8n22_rC37n-qmTRKLFaHrtUwF9ktE,1071
33
- otf_api-0.9.4.dist-info/METADATA,sha256=k_gb2f8juCNMLwrF95l_8M7zkXSZ8UUS17jV0YkDoqY,2296
34
- otf_api-0.9.4.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
35
- otf_api-0.9.4.dist-info/RECORD,,
31
+ otf_api-0.10.1.dist-info/LICENSE,sha256=UaPT9ynYigC3nX8n22_rC37n-qmTRKLFaHrtUwF9ktE,1071
32
+ otf_api-0.10.1.dist-info/METADATA,sha256=vOdLR2wtWuJGfjPCKnvjLzqziKPZvnh1DJKR47-QGsI,2375
33
+ otf_api-0.10.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
34
+ otf_api-0.10.1.dist-info/RECORD,,
@@ -1,89 +0,0 @@
1
- from datetime import datetime, time
2
-
3
- from pydantic import AliasPath, Field, field_validator
4
-
5
- from otf_api.models.base import OtfItemBase
6
-
7
-
8
- class ZoneTimeMinutes(OtfItemBase):
9
- gray: int
10
- blue: int
11
- green: int
12
- orange: int
13
- red: int
14
-
15
-
16
- class HeartRate(OtfItemBase):
17
- max_hr: int
18
- peak_hr: int
19
- peak_hr_percent: int
20
- avg_hr: int
21
- avg_hr_percent: int
22
-
23
-
24
- class PerformanceMetric(OtfItemBase):
25
- display_value: time | float
26
- display_unit: str
27
- metric_value: float
28
-
29
- @field_validator("display_value", mode="before")
30
- @classmethod
31
- def convert_to_time_format(cls, value) -> time | float:
32
- if not value:
33
- return value
34
-
35
- if isinstance(value, float | int):
36
- return value
37
-
38
- if isinstance(value, str) and ":" in value:
39
- if value.count(":") == 1:
40
- minutes, seconds = value.split(":")
41
- return time(minute=int(minutes), second=int(seconds))
42
- if value.count(":") == 2:
43
- hours, minutes, seconds = value.split(":")
44
- return time(hour=int(hours), minute=int(minutes), second=int(seconds))
45
-
46
- return value
47
-
48
-
49
- class BaseEquipment(OtfItemBase):
50
- avg_pace: PerformanceMetric
51
- avg_speed: PerformanceMetric
52
- max_pace: PerformanceMetric
53
- max_speed: PerformanceMetric
54
- moving_time: PerformanceMetric
55
- total_distance: PerformanceMetric
56
-
57
-
58
- class Treadmill(BaseEquipment):
59
- avg_incline: PerformanceMetric
60
- elevation_gained: PerformanceMetric
61
- max_incline: PerformanceMetric
62
-
63
-
64
- class Rower(BaseEquipment):
65
- avg_cadence: PerformanceMetric
66
- avg_power: PerformanceMetric
67
- max_cadence: PerformanceMetric
68
-
69
-
70
- class PerformanceSummaryDetail(OtfItemBase):
71
- class_history_uuid: str = Field(..., alias="id")
72
- class_name: str | None = Field(None, alias=AliasPath("class", "name"))
73
- class_starts_at: datetime | None = Field(None, alias=AliasPath("class", "starts_at_local"))
74
-
75
- ratable: bool | None = Field(
76
- None,
77
- exclude=True,
78
- repr=False,
79
- description="Seems to be inaccurate, not reflecting ratable from `PerformanceSummaryEntry`",
80
- )
81
- calories_burned: int | None = Field(None, alias=AliasPath("details", "calories_burned"))
82
- splat_points: int | None = Field(None, alias=AliasPath("details", "splat_points"))
83
- step_count: int | None = Field(None, alias=AliasPath("details", "step_count"))
84
- active_time_seconds: int | None = Field(None, alias=AliasPath("details", "active_time_seconds"))
85
- zone_time_minutes: ZoneTimeMinutes | None = Field(None, alias=AliasPath("details", "zone_time_minutes"))
86
- heart_rate: HeartRate | None = Field(None, alias=AliasPath("details", "heart_rate"))
87
-
88
- rower_data: Rower | None = Field(None, alias=AliasPath("details", "equipment_data", "rower"))
89
- treadmill_data: Treadmill | None = Field(None, alias=AliasPath("details", "equipment_data", "treadmill"))
@@ -1,50 +0,0 @@
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 ZoneTimeMinutes(OtfItemBase):
9
- gray: int
10
- blue: int
11
- green: int
12
- orange: int
13
- red: int
14
-
15
-
16
- class Class(OtfItemBase):
17
- class_uuid: str | None = Field(None, description="Only populated if class is ratable", alias="ot_base_class_uuid")
18
- starts_at: datetime | None = Field(None, alias="starts_at_local")
19
- name: str | None = None
20
- type: str | None = None
21
-
22
-
23
- class CoachRating(OtfItemBase):
24
- id: str
25
- description: str
26
- value: int
27
-
28
-
29
- class ClassRating(OtfItemBase):
30
- id: str
31
- description: str
32
- value: int
33
-
34
-
35
- class PerformanceSummaryEntry(OtfItemBase):
36
- class_history_uuid: str = Field(..., alias="id")
37
- calories_burned: int | None = Field(None, alias=AliasPath("details", "calories_burned"))
38
- splat_points: int | None = Field(None, alias=AliasPath("details", "splat_points"))
39
- step_count: int | None = Field(None, alias=AliasPath("details", "step_count"))
40
- active_time_seconds: int | None = Field(None, alias=AliasPath("details", "active_time_seconds"))
41
- zone_time_minutes: ZoneTimeMinutes | None = Field(None, alias=AliasPath("details", "zone_time_minutes"))
42
- ratable: bool | None = None
43
- otf_class: Class | None = Field(None, alias="class")
44
- coach: str | None = Field(None, alias=AliasPath("class", "coach", "first_name"))
45
- coach_rating: CoachRating | None = Field(None, alias=AliasPath("ratings", "coach"))
46
- class_rating: ClassRating | None = Field(None, alias=AliasPath("ratings", "class"))
47
-
48
- @property
49
- def is_rated(self) -> bool:
50
- return self.coach_rating is not None or self.class_rating is not None