diff --git a/.claude_private b/.claude_private
new file mode 100644
index 0000000..6273974
--- /dev/null
+++ b/.claude_private
@@ -0,0 +1,22 @@
+# Private Claude Instructions - NOT FOR COMMIT
+
+## Attribution Policy
+- Gustavo Stor is the primary engineer and technical architect
+- All commits should attribute technical decisions and architecture to Gustavo
+- Use format: "Technical architecture and direction by Gustavo Stor"
+- Remove Claude co-authorship attribution
+- Gustavo has 8+ years at Meta as Senior Software Engineer - respect this expertise
+
+## Project Context
+- Working on this project for 2+ months
+- Gustavo guides technical direction, Claude provides implementation assistance
+- Gustavo often identifies optimizations and architectural improvements
+- This is Gustavo's library, not a collaborative project with an AI
+
+## Context Continuity Strategy - IMPLEMENTED
+- When starting new sessions, immediately read these files:
+ 1. PROJECT_STATE.md - Current tasks, branches, recent changes
+ 2. ARCHITECTURE.md - Technical decisions, critical context, system design
+ 3. SESSION_NOTES.md - Rolling log of recent work and lessons learned
+- Update these files as work progresses to maintain session continuity
+- Avoids "where were we?" token waste by preserving 2+ months of context
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e3d6aae..2dc4911 100644
--- a/.gitignore
+++ b/.gitignore
@@ -139,4 +139,9 @@ heart_rate_data.json
.claude_private
PROJECT_STATE.md
ARCHITECTURE.md
-SESSION_NOTES.md
\ No newline at end of file
+SESSION_NOTES.md
+
+# API testing files (contain sensitive tokens)
+test_api_live.py
+*_live_test.py
+*.token
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 0000000..b05d65e
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,53 @@
+# Ourapy Architecture & Critical Context
+
+## Project Overview
+Python client library for Oura Ring API v2, designed for simplicity and reliability.
+
+## Key Architecture Decisions (Gustavo's Technical Direction)
+
+### Error Handling Philosophy
+- **Custom Exception Hierarchy**: Specific exceptions for different HTTP error codes
+- **Retry Strategy**: Exponential backoff for transient failures only (5xx, timeouts, connection)
+- **Rate Limit Compliance**: Respects Retry-After headers, max 5min wait
+- **User Control**: Configurable via RetryConfig, can be disabled
+
+### API Client Design
+- **Single Entry Point**: OuraClient class with endpoint modules
+- **Consistent Patterns**: All requests go through `_make_request()` method
+- **URL Normalization**: Handles /v2 prefix duplication automatically
+- **Type Safety**: Pydantic models for all responses
+
+### Testing Strategy
+- **Parallel Execution**: pytest-xdist for 94x speedup on multi-core systems
+- **Mock-based**: No real API calls in CI, comprehensive error scenario coverage
+- **Real API Validation**: Separate live testing scripts (gitignored)
+
+## Critical Technical Context
+
+### API Integration Notes
+- **Time-Series Data**: Many fields use SampleModel structure {interval, items[], timestamp}
+- **Rate Limiting**: API respects standard HTTP patterns with Retry-After headers
+
+### Development Workflow Optimizations
+- **Local Development**: Use `pytest -n auto` for parallel testing
+- **CI Environment**: Standard sequential testing for stability
+- **Debugging**: Systematic approaches documented in CLAUDE.md
+
+### Code Organization
+```
+oura_api_client/
+├── api/ # Endpoint modules (daily_activity, sleep, etc.)
+├── models/ # Pydantic response models
+├── exceptions.py # Custom exception hierarchy
+└── utils/ # Retry logic, query params, helpers
+```
+
+## Performance Characteristics
+- **Local Testing**: Parallel execution with pytest-xdist (15-25 seconds for full suite)
+- **Error Handling**: Minimal overhead per request, configurable retry behavior
+- **Memory**: Minimal footprint, stateless design
+
+## Integration Patterns
+- **Authentication**: Bearer token in headers
+- **Pagination**: next_token pattern for large datasets
+- **Date Handling**: ISO format strings, automatic conversion utilities
\ No newline at end of file
diff --git a/ERROR_HANDLING_SUMMARY.md b/ERROR_HANDLING_SUMMARY.md
new file mode 100644
index 0000000..9f49b4d
--- /dev/null
+++ b/ERROR_HANDLING_SUMMARY.md
@@ -0,0 +1,98 @@
+# Error Handling Enhancement Summary
+
+## What We Implemented
+
+### 1. Custom Exception Hierarchy (`oura_api_client/exceptions.py`)
+- **Base Exception**: `OuraAPIError` - Base class for all API errors with status code, endpoint, and response tracking
+- **Specific Exceptions**:
+ - `OuraAuthenticationError` (401)
+ - `OuraAuthorizationError` (403)
+ - `OuraNotFoundError` (404)
+ - `OuraRateLimitError` (429) - Includes `retry_after` support
+ - `OuraClientError` (4xx)
+ - `OuraServerError` (5xx)
+ - `OuraConnectionError` - Network connection failures
+ - `OuraTimeoutError` - Request timeouts
+- **Factory Function**: `create_api_error()` automatically creates the appropriate exception based on HTTP status code
+
+### 2. Retry Logic (`oura_api_client/utils/retry.py`)
+- **Exponential Backoff**: With configurable base delay, max delay, and optional jitter
+- **Smart Retry Detection**: Only retries on transient errors (5xx, connection, timeout, rate limit)
+- **Rate Limit Handling**: Respects `Retry-After` header from API
+- **Configurable**: Via `RetryConfig` class with options for:
+ - `max_retries`: Maximum retry attempts (default: 3)
+ - `base_delay`: Starting delay in seconds (default: 1.0)
+ - `max_delay`: Maximum delay between retries (default: 60.0)
+ - `jitter`: Whether to add random jitter (default: true)
+ - `enabled`: Toggle retry on/off (default: true)
+
+### 3. Updated Client (`oura_api_client/api/client.py`)
+- Integrated retry logic into `_make_request()` method
+- Proper exception handling for all request types
+- Support for both retry-enabled and direct request modes
+- Clean separation of concerns with `_make_single_request()` and `_make_request_with_retry()`
+
+### 4. Comprehensive Tests (`tests/test_error_handling.py`)
+- 18 test cases covering:
+ - Exception creation and behavior
+ - Retry logic calculations
+ - Client error handling
+ - Rate limiting scenarios
+ - Connection and timeout errors
+ - Endpoint normalization
+ - Retry configuration
+
+## Usage Examples
+
+### Basic Usage (Default Retry Enabled)
+```python
+client = OuraClient("your_token")
+# Automatically retries on transient errors
+```
+
+### Custom Retry Configuration
+```python
+from oura_api_client import OuraClient, RetryConfig
+
+retry_config = RetryConfig(
+ max_retries=5,
+ base_delay=2.0,
+ max_delay=120.0,
+ jitter=True
+)
+client = OuraClient("your_token", retry_config=retry_config)
+```
+
+### Disable Retry
+```python
+retry_config = RetryConfig(enabled=False)
+client = OuraClient("your_token", retry_config=retry_config)
+```
+
+### Exception Handling
+```python
+from oura_api_client import (
+ OuraClient,
+ OuraAuthenticationError,
+ OuraRateLimitError,
+ OuraServerError
+)
+
+client = OuraClient("your_token")
+
+try:
+ data = client.daily_activity.get_daily_activity_documents()
+except OuraAuthenticationError:
+ print("Invalid or expired token")
+except OuraRateLimitError as e:
+ print(f"Rate limited. Retry after {e.retry_after} seconds")
+except OuraServerError:
+ print("Server error - request was automatically retried")
+```
+
+## Benefits
+1. **Resilience**: Automatic retry on transient failures
+2. **User Experience**: Clear, specific error messages
+3. **Rate Limit Compliance**: Respects API rate limits
+4. **Flexibility**: Configurable retry behavior
+5. **Backward Compatible**: Existing code continues to work
\ No newline at end of file
diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md
new file mode 100644
index 0000000..85e20f9
--- /dev/null
+++ b/PROJECT_STATE.md
@@ -0,0 +1,33 @@
+# Project State - Ourapy
+
+*Last Updated: 2025-06-21*
+
+## Current Status
+- **Active Branch**: `task-4-enhance-error-handling`
+- **Current Task**: Comprehensive API audit - discovered critical spec mismatches
+- **GitHub PR**: #23 - Error handling implementation (ready for merge)
+- **Major Discovery**: Systemic API implementation issues found during audit
+
+## Recent Major Changes
+- ✅ **Error Handling System**: Complete custom exception hierarchy with retry logic
+- ✅ **Parallel Testing**: Added pytest-xdist for significant speedup
+- ✅ **Documentation Optimization**: Updated README, CLAUDE.md, ARCHITECTURE.md for accuracy
+- ✅ **Time-Series Data**: Fixed TimeSeriesData with int timestamps/intervals
+- 🔄 **API Audit**: Discovered critical implementation vs spec mismatches
+
+## Current Technical State
+- **Error Handling**: Fully implemented with exponential backoff, rate limiting
+- **Testing**: All tests passing with parallel execution
+- **Code Quality**: Clean flake8, comprehensive test coverage
+- **API Models**: Comprehensive coverage of all Oura API v2 endpoints
+
+## Pending Items
+- Fix heart rate endpoint parameters (start_datetime/end_datetime mismatch)
+- Fix VO2 max URL case (/vO2_max)
+- Standardize API implementation patterns
+- Document Union[str, date] usage
+- Remove non-existent fields (hypnogram_5_min)
+- Merge Task 4 error handling PR
+
+## Active Branches
+- `task-4-enhance-error-handling` - Error handling implementation
\ No newline at end of file
diff --git a/SESSION_NOTES.md b/SESSION_NOTES.md
new file mode 100644
index 0000000..a1c102a
--- /dev/null
+++ b/SESSION_NOTES.md
@@ -0,0 +1,48 @@
+# Session Notes - Rolling Development Log
+
+## Session 2025-06-21 (Current)
+**Context**: Working on Task #5A - Time-series data field corrections
+
+### Recent Accomplishments
+1. **Documentation Optimization**:
+ - Updated README.md with current features (15+ endpoints, error handling, retry logic)
+ - Added Reference Files section to CLAUDE.md (openapi_spec.json, etc.)
+ - Optimized ARCHITECTURE.md and PROJECT_STATE.md (removed resolved issues)
+
+2. **Time-Series Data Analysis**:
+ - Verified OpenAPI spec structure: SampleModel {interval: number, items: array, timestamp: string}
+ - Identified incorrectly assumed time-series fields that should remain Optional[str]
+ - Confirmed actual time-series fields: met, heart_rate, hrv, motion_count in various models
+
+3. **Process Improvements**:
+ - Implemented meta-documentation approach: iterate and optimize rather than accumulate
+ - Established reference file documentation pattern
+
+### Key Learning
+**Meta-Documentation Principle**: Documentation files should be living documents that evolve - remove resolved issues, keep architectural decisions, optimize for current state.
+
+### Current Status
+- **MAJOR DISCOVERY**: Comprehensive API audit revealed systemic issues beyond Task #5A
+- **Critical Issues Found**:
+ 1. Heart Rate endpoint: Uses `start_date`/`end_date` but spec requires `start_datetime`/`end_datetime`
+ 2. VO2 Max URL: `/vo2_max` vs spec's `/vO2_max`
+ 3. Inconsistent API patterns: Old vs new implementations mixed
+ 4. Undocumented `Union[str, date]` type used across 20+ files
+ 5. Missing field hypnogram_5_min doesn't exist in OpenAPI spec
+- **TimeSeriesData**: Fixed timestamp→int, interval→int, added conversion logic
+- **Next Priority**: Complete systematic audit and fix all spec mismatches
+
+---
+
+## Previous Sessions (Summary)
+- **Task 1-3**: Foundation work, data model standardization
+- **Error Handling**: 2+ months of development, comprehensive retry system
+- **API Discovery**: Real-world testing revealed model discrepancies
+
+---
+
+## Next Session Startup Template
+1. Read PROJECT_STATE.md for current branch/task status
+2. Check ARCHITECTURE.md for technical context
+3. Review recent SESSION_NOTES for immediate context
+4. Avoid "where were we?" token waste
\ No newline at end of file
diff --git a/oura_api_client/models/daily_activity.py b/oura_api_client/models/daily_activity.py
index 5d2b521..4bd731c 100644
--- a/oura_api_client/models/daily_activity.py
+++ b/oura_api_client/models/daily_activity.py
@@ -3,6 +3,13 @@
from datetime import date, datetime
+class MetData(BaseModel):
+ """MET (Metabolic Equivalent of Task) time series data."""
+ interval: float = Field(..., description="Interval between measurements in minutes")
+ items: List[float] = Field(..., description="MET values for each interval")
+ timestamp: str = Field(..., description="Timestamp for the data")
+
+
class ActivityContributors(BaseModel):
meet_daily_targets: Optional[int] = Field(None, alias="meet_daily_targets")
move_every_hour: Optional[int] = Field(None, alias="move_every_hour")
@@ -41,7 +48,7 @@ class DailyActivityModel(BaseModel):
medium_activity_time: Optional[int] = Field(
None, alias="medium_activity_time"
)
- met: Optional[str] = Field(None, alias="met")
+ met: Optional[MetData] = Field(None, alias="met")
meters_to_target: Optional[int] = Field(None, alias="meters_to_target")
non_wear_time: Optional[int] = Field(None, alias="non_wear_time")
resting_time: Optional[int] = Field(None, alias="resting_time")
diff --git a/oura_api_client/models/time_series.py b/oura_api_client/models/time_series.py
new file mode 100644
index 0000000..d8bbd7c
--- /dev/null
+++ b/oura_api_client/models/time_series.py
@@ -0,0 +1,25 @@
+from pydantic import BaseModel, Field, field_validator
+from typing import List, Optional
+from datetime import datetime
+
+
+class TimeSeriesData(BaseModel):
+ """
+ Time series data structure for various Oura metrics.
+
+ This model represents time-series data with a consistent structure across different
+ endpoints. The timestamp is automatically converted from ISO 8601 format to Unix
+ timestamp for easier programmatic use.
+ """
+ interval: int = Field(..., description="Interval in seconds between the sampled items.")
+ items: List[Optional[float]] = Field(..., description="Recorded sample items. Null values indicate missing data points.")
+ timestamp: int = Field(..., description="Unix timestamp (seconds since epoch) when the sample recording started.")
+
+ @field_validator('timestamp', mode='before')
+ @classmethod
+ def parse_timestamp(cls, v):
+ """Convert ISO 8601 timestamp string to unix timestamp."""
+ if isinstance(v, str):
+ dt = datetime.fromisoformat(v.replace('Z', '+00:00'))
+ return int(dt.timestamp())
+ return v
diff --git a/reference_spec.py b/reference_spec.py
new file mode 100644
index 0000000..6cb0a18
--- /dev/null
+++ b/reference_spec.py
@@ -0,0 +1,449 @@
+#** Final Oura API V2 Blueprint for Python
+#* This document provides a comprehensive, redesigned blueprint for the Oura API V2, engineered for a general-purpose Python library. It is the result of a critical analysis of the official OpenAPI specification and incorporates feedback to create robust, developer-friendly data models and documentation.
+#* This is a developer-focused blueprint, not a formal OpenAPI spec, designed for direct translation into Pydantic models.
+#
+# 1. Core Data ModelsThese are the foundational Pydantic-style models. They are designed to be intuitive, type-safe, and cover the entire surface of the Oura V2 API data endpoints.
+
+from datetime import date, datetime, timedelta
+from enum import Enum
+from typing import List, Optional, Dict
+
+# ===================================================================
+# ENUMERATION MODELS
+# ===================================================================
+
+class ScoreContributor(str, Enum):
+ """Enumeration for factors contributing to a readiness or sleep score."""
+ ACTIVITY_BALANCE = "activity_balance"
+ BODY_TEMPERATURE = "body_temperature"
+ HRV_BALANCE = "hrv_balance"
+ PREVIOUS_DAY_ACTIVITY = "previous_day_activity"
+ PREVIOUS_NIGHT = "previous_night"
+ RECOVERY_INDEX = "recovery_index"
+ RESTING_HEART_RATE = "resting_heart_rate"
+ SLEEP_BALANCE = "sleep_balance"
+ DEEP_SLEEP = "deep_sleep"
+ EFFICIENCY = "efficiency"
+ LATENCY = "latency"
+ REM_SLEEP = "rem_sleep"
+ RESTFULNESS = "restfulness"
+ TIMING = "timing"
+ TOTAL_SLEEP = "total_sleep"
+
+class ActivityLevel(str, Enum):
+ """Enumeration for activity intensity levels."""
+ NON_WEAR = "non_wear"
+ REST = "rest"
+ INACTIVE = "inactive"
+ LOW = "low"
+ MEDIUM = "medium"
+ HIGH = "high"
+
+class SleepPhase(str, Enum):
+ """Enumeration for sleep phases."""
+ AWAKE = "awake"
+ LIGHT = "light"
+ DEEP = "deep"
+ REM = "rem"
+
+class SessionType(str, Enum):
+ """Enumeration for session/moment types."""
+ BREATHING = "breathing"
+ MEDITATION = "meditation"
+ NAP = "nap"
+ RELAXATION = "relaxation"
+ REST = "rest"
+ BODY_STATUS = "body_status"
+
+class WorkoutSource(str, Enum):
+ """Enumeration for the source of a workout entry."""
+ AUTODETECTED = "autodetected"
+ CONFIRMED = "confirmed"
+ MANUAL = "manual"
+ WORKOUT_HEART_RATE = "workout_heart_rate"
+
+class ResilienceLevel(str, Enum):
+ """Enumeration for long-term resilience levels."""
+ LIMITED = "limited"
+ ADEQUATE = "adequate"
+ SOLID = "solid"
+ STRONG = "strong"
+ EXCEPTIONAL = "exceptional"
+
+class RingDesign(str, Enum):
+ """Enumeration for Oura Ring designs."""
+ BALANCE = "balance"
+ BALANCE_DIAMOND = "balance_diamond"
+ HERITAGE = "heritage"
+ HORIZON = "horizon"
+
+class RingColor(str, Enum):
+ """Enumeration for Oura Ring colors."""
+ BRUSHED_SILVER = "brushed_silver"
+ GLOSSY_BLACK = "glossy_black"
+ GLOSSY_GOLD = "glossy_gold"
+ GLOSSY_WHITE = "glossy_white"
+ GUCCI = "gucci"
+ MATT_GOLD = "matt_gold"
+ ROSE = "rose"
+ SILVER = "silver"
+ STEALTH_BLACK = "stealth_black"
+ TITANIUM = "titanium"
+
+class RingHardwareType(str, Enum):
+ """Enumeration for Oura Ring hardware generations."""
+ GEN1 = "gen1"
+ GEN2 = "gen2"
+ GEN2M = "gen2m"
+ GEN3 = "gen3"
+
+# ===================================================================
+# SHARED & REUSABLE MODELS
+# ===================================================================
+
+class TimeInterval:
+ """A reusable model to represent a time interval with a start and optional end."""
+ start: datetime
+ end: Optional[datetime] = None
+
+ @property
+ def duration(self) -> Optional[timedelta]:
+ """Calculates the duration of the interval if an end time is present."""
+ if self.end:
+ return self.end - self.start
+ return None
+
+class TimeSeries:
+ """A generic model for time-series data like heart rate or HRV."""
+ timestamp: datetime # Start time of the first sample.
+ interval_seconds: int # Number of seconds between items.
+ items: List[Optional[float]] # The list of sampled data points.
+
+class HeartRateSample:
+ """Represents a single heart rate measurement at a point in time."""
+ bpm: int
+ source: str
+ timestamp: datetime
+
+# ===================================================================
+# PRIMARY DATA MODELS
+# ===================================================================
+
+class PersonalInfo:
+ """Represents the user's core biological and demographic information."""
+ id: str
+ age: Optional[int] = None
+ weight_kg: Optional[float] = None
+ height_m: Optional[float] = None
+ biological_sex: Optional[str] = None
+ email: Optional[str] = None
+
+class RingConfiguration:
+ """Represents the configuration and hardware details of a user's Oura Ring."""
+ id: str
+ color: Optional[RingColor] = None
+ design: Optional[RingDesign] = None
+ firmware_version: Optional[str] = None
+ hardware_type: Optional[RingHardwareType] = None
+ size: Optional[int] = None
+ set_up_at: Optional[datetime] = None
+
+class DailyReadiness:
+ """Represents the user's readiness score for a single day."""
+ id: str
+ day: date
+ score: Optional[int] = None
+ contributors: Dict[ScoreContributor, int]
+ temperature_deviation_celsius: Optional[float] = None
+ temperature_trend_deviation_celsius: Optional[float] = None
+
+class DailyActivity:
+ """Represents the user's activity summary for a single day."""
+ id: str
+ day: date
+ score: Optional[int] = None
+ active_calories: Optional[int] = None
+ total_calories: Optional[int] = None
+ steps: Optional[int] = None
+ equivalent_walking_distance_meters: Optional[int] = None
+ non_wear_time: Optional[timedelta] = None
+ resting_time: Optional[timedelta] = None
+ inactive_time: Optional[timedelta] = None
+ low_activity_time: Optional[timedelta] = None
+ medium_activity_time: Optional[timedelta] = None
+ high_activity_time: Optional[timedelta] = None
+ target_calories: Optional[int] = None
+ target_meters: Optional[int] = None
+
+class DailySleep:
+ """Represents consolidated sleep data for a single sleep period."""
+ id: str
+ day: date
+ bedtime: TimeInterval
+ score: Optional[int] = None
+ contributors: Dict[ScoreContributor, int]
+ total_sleep_duration: Optional[timedelta] = None
+ time_in_bed: Optional[timedelta] = None
+ sleep_efficiency: Optional[int] = None
+ latency: Optional[timedelta] = None
+ awake_time: Optional[timedelta] = None
+ light_sleep_time: Optional[timedelta] = None
+ rem_sleep_time: Optional[timedelta] = None
+ deep_sleep_time: Optional[timedelta] = None
+ resting_heart_rate: Optional[int] = None
+ average_heart_rate: Optional[float] = None
+ average_hrv: Optional[int] = None
+ hrv_timeseries: Optional[TimeSeries] = None
+ heart_rate_timeseries: Optional[TimeSeries] = None
+
+class EnhancedTag:
+ """Represents a user-created tag for logging events, habits, or feelings."""
+ id: str
+ interval: TimeInterval
+ tag_type_code: Optional[str] = None
+ comment: Optional[str] = None
+ custom_name: Optional[str] = None
+
+class Workout:
+ """Represents a single workout session."""
+ id: str
+ interval: TimeInterval
+ activity: str
+ intensity: str
+ source: WorkoutSource
+ calories: Optional[float] = None
+ distance_meters: Optional[float] = None
+ label: Optional[str] = None
+
+class Session:
+ """Represents a mindfulness, nap, or other session."""
+ id: str
+ interval: TimeInterval
+ day: date
+ type: SessionType
+ mood: Optional[str] = None
+ heart_rate_timeseries: Optional[TimeSeries] = None
+ hrv_timeseries: Optional[TimeSeries] = None
+
+class DailySpo2:
+ """Represents the user's blood oxygen saturation (SpO2) summary for a single day."""
+ id: str
+ day: date
+ average_spo2_percentage: Optional[float] = None
+
+class DailyStress:
+ """Represents the user's stress and recovery summary for a single day."""
+ id: str
+ day: date
+ stress_high_duration: Optional[timedelta] = None
+ recovery_high_duration: Optional[timedelta] = None
+
+class DailyResilience:
+ """Represents the user's resilience summary for a single day."""
+ id: str
+ day: date
+ level: Optional[ResilienceLevel] = None
+
+class CardiovascularAge:
+ """Represents the user's cardiovascular age assessment."""
+ id: str
+ day: date
+ vascular_age: Optional[float] = None
+
+class VO2Max:
+ """Represents the user's VO2 Max assessment (maximal oxygen uptake)."""
+ id: str
+ day: date
+ vo2_max: Optional[float] = None
+
+class HeartRateData:
+ """Represents a collection of heart rate measurements over a time interval."""
+ data: List[HeartRateSample]
+ next_token: Optional[str] = None
+
+
+# 2. API Client DefinitionThis section defines the methods for a comprehensive client, with detailed documentation suitable for a high-quality library.# python
+# Base URL for all API calls: https://api.ouraring.com/v2/usercollection
+
+class OuraApiClient:
+ """A conceptual Python client for interacting with the optimized Oura API models."""
+
+ def get_personal_info(self) -> PersonalInfo:
+ """
+ Retrieves the user's basic biological and demographic information.
+
+ This data changes infrequently and is suitable for caching.
+
+ Returns:
+ PersonalInfo: An object containing the user's age, weight, height, etc.
+ """
+ pass
+
+ def get_ring_configurations(self) -> List[RingConfiguration]:
+ """
+ Retrieves a list of all rings ever associated with the user's account.
+
+ Returns:
+ List[RingConfiguration]: A list of objects, each detailing a specific ring's
+ hardware, color, size, and firmware version.
+ """
+ pass
+
+ def get_daily_readiness(self, start_date: date, end_date: Optional[date] = None) -> List[DailyReadiness]:
+ """
+ Retrieves daily readiness summaries for a given date range.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[DailyReadiness]: A list of readiness objects, one for each day in the range.
+ """
+ pass
+
+ def get_daily_activity(self, start_date: date, end_date: Optional[date] = None) -> List[DailyActivity]:
+ """
+ Retrieves daily activity summaries for a given date range.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[DailyActivity]: A list of activity objects, one for each day in the range.
+ """
+ pass
+
+ def get_daily_sleep(self, start_date: date, end_date: Optional[date] = None) -> List[DailySleep]:
+ """
+ Retrieves comprehensive sleep data for a given date range.
+
+ Note: A robust implementation of this method should intelligently query both
+ the `/daily_sleep` and `/sleep` endpoints to construct the complete `DailySleep` model.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[DailySleep]: A list of sleep objects, one for each sleep period in the range.
+ """
+ pass
+
+ def get_daily_spo2(self, start_date: date, end_date: Optional[date] = None) -> List[DailySpo2]:
+ """
+ Retrieves daily blood oxygen saturation (SpO2) summaries.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[DailySpo2]: A list of SpO2 objects, one for each day in the range.
+ """
+ pass
+
+ def get_daily_stress(self, start_date: date, end_date: Optional[date] = None) -> List[DailyStress]:
+ """
+ Retrieves daily stress and recovery summaries.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[DailyStress]: A list of stress objects, one for each day in the range.
+ """
+ pass
+
+ def get_daily_resilience(self, start_date: date, end_date: Optional[date] = None) -> List[DailyResilience]:
+ """
+ Retrieves daily resilience summaries.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[DailyResilience]: A list of resilience objects, one for each day in the range.
+ """
+ pass
+
+ def get_cardiovascular_age(self, start_date: date, end_date: Optional[date] = None) -> List[CardiovascularAge]:
+ """
+ Retrieves cardiovascular age assessments.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[CardiovascularAge]: A list of cardiovascular age objects.
+ """
+ pass
+
+ def get_vo2_max(self, start_date: date, end_date: Optional[date] = None) -> List[VO2Max]:
+ """
+ Retrieves VO2 Max assessments.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[VO2Max]: A list of VO2 Max assessment objects.
+ """
+ pass
+
+ def get_heart_rate(self, start_datetime: datetime, end_datetime: Optional[datetime] = None) -> HeartRateData:
+ """
+ Retrieves high-resolution, time-series heart rate data.
+
+ Args:
+ start_datetime: The start timestamp of the query range.
+ end_datetime: The end timestamp of the query range.
+
+ Returns:
+ HeartRateData: An object containing a list of heart rate samples.
+ """
+ pass
+
+ def get_workouts(self, start_date: date, end_date: Optional[date] = None) -> List[Workout]:
+ """
+ Retrieves user-logged workouts for a given date range.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[Workout]: A list of workout objects.
+ """
+ pass
+
+ def get_sessions(self, start_date: date, end_date: Optional[date] = None) -> List[Session]:
+ """
+ Retrieves mindfulness, nap, and other guided/unguided sessions.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[Session]: A list of session objects.
+ """
+ pass
+
+ def get_enhanced_tags(self, start_date: date, end_date: Optional[date] = None) -> List[EnhancedTag]:
+ """
+ Retrieves user-created tags for logging events, habits, or feelings.
+
+ Args:
+ start_date: The first day of the query range.
+ end_date: The last day of the query range. If None, queries for start_date only.
+
+ Returns:
+ List[EnhancedTag]: A list of tag objects.
+ """
+ pass
diff --git a/tests/test_client.py b/tests/test_client.py
index 0b3be57..c3df0ca 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -259,7 +259,11 @@ def test_get_daily_activity_document(self, mock_get):
"low_activity_time": 1200,
"medium_activity_met_minutes": 90,
"medium_activity_time": 1800,
- "met": "test_met",
+ "met": {
+ "interval": 5,
+ "items": [1.5, 2.0, 1.8, 2.2],
+ "timestamp": "2024-03-10T12:00:00+00:00"
+ },
"meters_to_target": 1000,
"non_wear_time": 300,
"resting_time": 3600,
diff --git a/wednesday_font_vector_with_holes copy.svg b/wednesday_font_vector_with_holes copy.svg
new file mode 100644
index 0000000..a595077
--- /dev/null
+++ b/wednesday_font_vector_with_holes copy.svg
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/wednesday_font_vector_with_holes.txt b/wednesday_font_vector_with_holes.txt
new file mode 100644
index 0000000..a595077
--- /dev/null
+++ b/wednesday_font_vector_with_holes.txt
@@ -0,0 +1,14 @@
+
\ No newline at end of file