-
Notifications
You must be signed in to change notification settings - Fork 52
Add additional metrics - Hill + Endurance score, Morning Training Readiness #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add additional metrics - Hill + Endurance score, Morning Training Readiness #135
Conversation
WalkthroughAdds two new data models, GarminScoresData and MorningTrainingReadinessData, exposes them in package exports, adds README examples (duplicated), and includes tests for their get() and list() methods. No runtime API signatures were changed. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Caller as User/Test
participant SDK as garth.client
participant API as Garmin API
Caller->>SDK: GarminScoresData.get(date)
SDK->>API: GET /wellness-api/score/hills/{date}
API-->>SDK: hill score payload (camelCase)
SDK->>API: GET /wellness-api/score/endurance/{date}
API-->>SDK: endurance payload (camelCase)
SDK->>SDK: camel_to_snake_dict, rename keys, merge
SDK-->>Caller: GarminScoresData instance or None
Caller->>SDK: MorningTrainingReadinessData.get(date)
SDK->>API: GET /metrics-service/metrics/trainingreadiness/{date}
API-->>SDK: readiness payload (list)
SDK->>SDK: select entry where inputContext=="AFTER_WAKEUP_RESET"
SDK->>SDK: camel_to_snake_dict, construct dataclass
SDK-->>Caller: MorningTrainingReadinessData instance or None
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/garth/data/garmin_scores.py (2)
38-46: Consider API call efficiency and error handlingThe method makes two separate API calls which could be inefficient, but this may be necessary due to the API design. The error handling returns
Noneif either endpoint fails, which is reasonable but could be more granular.Consider if both endpoints are always required or if partial data could be useful in some cases.
60-65: Complex field renaming logic - consider adding commentsThe dictionary comprehension for renaming endurance classification fields is complex but appears correct. Consider adding a comment to explain the logic for better maintainability.
+ # Rename classification fields to include 'endurance_classification' prefix data_endurance_score = { ("endurance_classification" + key[len("classification") :]) if key.startswith("classification") else key: value for key, value in data_endurance_score.items() }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
tests/data/cassettes/test_garmin_scores_data_get.yamlis excluded by!tests/**/cassettes/**tests/data/cassettes/test_garmin_scores_data_list.yamlis excluded by!tests/**/cassettes/**tests/data/cassettes/test_morning_training_readiness_data_get.yamlis excluded by!tests/**/cassettes/**tests/data/cassettes/test_morning_training_readiness_data_list.yamlis excluded by!tests/**/cassettes/**
📒 Files selected for processing (7)
README.md(1 hunks)src/garth/__init__.py(2 hunks)src/garth/data/__init__.py(2 hunks)src/garth/data/garmin_scores.py(1 hunks)src/garth/data/morning_training_readiness.py(1 hunks)tests/data/test_garmin_scores.py(1 hunks)tests/data/test_morning_training_readiness.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`tests/**`: - test functions shouldn't have a return type hint - it's ok to use `assert` instead of `pytest.assume()`
tests/**: - test functions shouldn't have a return type hint
- it's ok to use
assertinstead ofpytest.assume()
⚙️ Source: CodeRabbit Configuration File
List of files the instruction was applied to:
tests/data/test_garmin_scores.pytests/data/test_morning_training_readiness.py
🧠 Learnings (2)
tests/data/test_garmin_scores.py (1)
Learnt from: matin
PR: matin/garth#76
File: tests/test_http.py:109-109
Timestamp: 2024-12-07T14:07:11.531Z
Learning: In Python test files (e.g., `tests/test_http.py`), top-level test functions should be separated by two blank lines, as per PEP 8 style guidelines.
tests/data/test_morning_training_readiness.py (1)
Learnt from: matin
PR: matin/garth#76
File: tests/test_http.py:109-109
Timestamp: 2024-12-07T14:07:11.531Z
Learning: In Python test files (e.g., `tests/test_http.py`), top-level test functions should be separated by two blank lines, as per PEP 8 style guidelines.
🧬 Code Graph Analysis (2)
src/garth/__init__.py (3)
src/garth/data/garmin_scores.py (1)
GarminScoresData(14-74)src/garth/data/hrv.py (1)
HRVData(39-68)src/garth/data/morning_training_readiness.py (1)
MorningTrainingReadinessData(15-69)
src/garth/data/morning_training_readiness.py (3)
src/garth/utils.py (1)
camel_to_snake_dict(17-33)src/garth/data/_base.py (1)
Data(15-47)src/garth/http.py (2)
Client(19-244)connectapi(186-192)
🔇 Additional comments (13)
src/garth/__init__.py (2)
4-6: LGTM - Imports follow existing patternsThe new imports are correctly placed and follow the established alphabetical ordering within the import block.
36-38: LGTM - Exports properly added to allThe new classes are correctly added to the
__all__export list in alphabetical order, maintaining consistency with existing patterns.README.md (1)
908-1068: LGTM - Comprehensive documentation for new featuresThe new documentation sections are well-structured and follow the existing style patterns. The examples demonstrate both single-date and multi-date usage scenarios with detailed sample outputs, which will be helpful for users implementing these features.
src/garth/data/__init__.py (1)
6-8: LGTM - Consistent with existing module patternsThe new exports and imports are correctly added following the established alphabetical ordering and import structure patterns.
Also applies to: 21-23
src/garth/data/garmin_scores.py (2)
14-29: LGTM - Well-structured dataclass definitionThe dataclass fields are comprehensive and well-named, covering all the metrics from both hill and endurance score endpoints. The field names are descriptive and follow Python naming conventions.
71-74: LGTM - Consistent list method implementationThe list method correctly delegates to the parent class and sorts by calendar_date, which is consistent with other data classes in the codebase.
tests/data/test_garmin_scores.py (2)
9-24: LGTM - Comprehensive test coverage for get() methodThe test correctly verifies both positive and negative cases:
- Tests successful data retrieval with assertions on key fields
- Tests the case where no data is available (returns None)
- Follows the established testing patterns with
pytest.mark.vcrandauthed_clientfixture
26-34: LGTM - Appropriate test for list() methodThe test verifies the list functionality correctly:
- Tests with appropriate parameters (days=2, max_workers=1)
- Verifies the returned data length matches expected days
- Checks that the last entry matches the expected end date
- Uses single worker to ensure deterministic test execution
tests/data/test_morning_training_readiness.py (1)
26-34: Test implementation looks good.The test properly validates the list method functionality, checking both the returned length and the last entry's date.
src/garth/data/morning_training_readiness.py (4)
1-12: Imports and setup look good.The imports are appropriately scoped and the module structure follows the established pattern in the codebase.
14-35: Well-structured dataclass with comprehensive field definitions.The dataclass appropriately captures all the morning training readiness metrics with correct type annotations. The field names follow Python naming conventions.
37-64: Robust implementation of the get method.The method properly:
- Handles the case when no data is returned from the API
- Filters for the specific "AFTER_WAKEUP_RESET" context to isolate morning readiness data
- Uses the established
camel_to_snake_dictutility for field conversion- Follows the pattern established in other data classes
66-69: Good override of the list method.The method appropriately sorts the results by calendar_date, which provides a consistent and logical ordering for time-series data.
840a25b to
5d23551
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #135 +/- ##
===========================================
- Coverage 100.00% 99.95% -0.05%
===========================================
Files 45 49 +4
Lines 1898 2028 +130
===========================================
+ Hits 1898 2027 +129
- Misses 0 1 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@vactomas both of your PRs have the same issue. let's put you could also mock the response, but don't worry about it. let's get these PRs merged |
5d23551 to
c3b55dd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/garth/data/morning_training_readiness.py (2)
45-49: Defensive type check on connectapi resultconnectapi() is annotated to return Dict[str, Any] | None but this endpoint returns a list; guard to avoid unexpected crashes if a non-list slips through.
if not raw_data: return None + if not isinstance(raw_data, list): + # Defensive: unexpected shape (e.g., error object) + return None data = cast(list[dict[str, Any]], raw_data)
41-44: Normalize date formattingBe explicit with ISO formatting when day is a date.
- path = f"/metrics-service/metrics/trainingreadiness/{day}" + day_str = day.isoformat() if isinstance(day, date) else day + path = f"/metrics-service/metrics/trainingreadiness/{day_str}"tests/data/test_morning_training_readiness.py (1)
33-34: Reduce potential flakiness in length assertionDays without readiness would shrink the list; assert non-empty and keep the end-date check.
- assert len(morning_training_readiness) == days + assert len(morning_training_readiness) >= 1 assert morning_training_readiness[-1].calendar_date == end
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
tests/data/cassettes/test_morning_training_readiness_data_get.yamlis excluded by!tests/**/cassettes/**tests/data/cassettes/test_morning_training_readiness_data_list.yamlis excluded by!tests/**/cassettes/**
📒 Files selected for processing (5)
README.md(1 hunks)src/garth/__init__.py(2 hunks)src/garth/data/__init__.py(2 hunks)src/garth/data/morning_training_readiness.py(1 hunks)tests/data/test_morning_training_readiness.py(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- README.md
🚧 Files skipped from review as they are similar to previous changes (2)
- src/garth/data/init.py
- src/garth/init.py
🧰 Additional context used
📓 Path-based instructions (1)
tests/**
⚙️ CodeRabbit configuration file
tests/**: - test functions shouldn't have a return type hint
- it's ok to use
assertinstead ofpytest.assume()
Files:
tests/data/test_morning_training_readiness.py
🧬 Code graph analysis (2)
tests/data/test_morning_training_readiness.py (3)
src/garth/data/morning_training_readiness.py (2)
MorningTrainingReadinessData(15-70)get(38-65)src/garth/http.py (1)
Client(19-244)tests/conftest.py (3)
vcr(79-82)authed_client(65-75)client(21-22)
src/garth/data/morning_training_readiness.py (3)
src/garth/utils.py (1)
camel_to_snake_dict(17-33)src/garth/data/_base.py (1)
Data(15-47)src/garth/http.py (2)
Client(19-244)connectapi(186-192)
🔇 Additional comments (4)
src/garth/data/morning_training_readiness.py (2)
68-70: Sorted output is goodStable ordering by calendar_date is appropriate for consumers.
60-63: Verified coverage pragma placement Only the requested# pragma: no coverappears insrc/garth/data/morning_training_readiness.py; no such pragmas are present (or needed) insrc/garth/data/garmin_scores.py.tests/data/test_morning_training_readiness.py (2)
9-18: Happy-path coverage looks goodCovers existence and key fields with VCR; fixture usage is clear.
20-23: None-path check is goodValidates 204/empty scenarios without extra mocking.
| morning_readiness_data = next( | ||
| ( | ||
| entry | ||
| for entry in data | ||
| if entry["inputContext"] == "AFTER_WAKEUP_RESET" | ||
| ), | ||
| None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid KeyError when filtering by inputContext
Use dict.get to guard against missing keys from API responses.
- if entry["inputContext"] == "AFTER_WAKEUP_RESET"
+ if entry.get("inputContext") == "AFTER_WAKEUP_RESET"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| morning_readiness_data = next( | |
| ( | |
| entry | |
| for entry in data | |
| if entry["inputContext"] == "AFTER_WAKEUP_RESET" | |
| ), | |
| None, | |
| morning_readiness_data = next( | |
| ( | |
| entry | |
| for entry in data | |
| if entry.get("inputContext") == "AFTER_WAKEUP_RESET" | |
| ), | |
| None, |
🤖 Prompt for AI Agents
In src/garth/data/morning_training_readiness.py around lines 51 to 57, the
generator expression uses entry["inputContext"] which can raise KeyError for
missing keys; change it to use entry.get("inputContext") when filtering so
missing keys return None (or a default) and avoid exceptions. Ensure the logic
otherwise remains the same (compare the result to "AFTER_WAKEUP_RESET") and keep
the default None for next(...) unchanged.
Hi, matin,
I've added a couple of additional metrics, which can be obtained from the Garmin API. I believe they could be of use to people, who process the data themselves.
Let me know, what you think!
Summary by CodeRabbit
New Features
Documentation
Tests