Skip to content

Commit 9ad165e

Browse files
authored
feat: add GitHub templates (CODEOWNERS, PR template, issue templates) (#10)
* feat: add GitHub templates (CODEOWNERS, PR template, issue templates) - CODEOWNERS for automatic reviewer assignment - Pull request template with checklist - Issue templates for bugs and feature requests Stu Mason + AI <me@stumason.dev> * style: fix ruff formatting on 4 Python files Ran ruff format to fix formatting issues that were causing CI failure. Stu Mason + AI <me@stumason.dev> * fix: prefix unused user_id argument with underscore Fixes ruff ARG002 (unused method argument) lint error while keeping backwards compatibility. Stu Mason + AI <me@stumason.dev> * fix: use noqa comment instead of underscore prefix The underscore prefix broke backwards compatibility for callers using user_id as a keyword argument. Using noqa: ARG002 to suppress the lint error while keeping the public API stable. Stu Mason + AI <me@stumason.dev>
1 parent baf9840 commit 9ad165e

File tree

10 files changed

+110
-55
lines changed

10 files changed

+110
-55
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @StuMason
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Bug Report
2+
description: Report something that's broken
3+
labels: ["bug"]
4+
body:
5+
- type: textarea
6+
id: description
7+
attributes:
8+
label: What happened?
9+
validations:
10+
required: true
11+
- type: textarea
12+
id: steps
13+
attributes:
14+
label: Steps to reproduce
15+
validations:
16+
required: true
17+
- type: textarea
18+
id: expected
19+
attributes:
20+
label: Expected behaviour
21+
validations:
22+
required: true
23+
- type: textarea
24+
id: context
25+
attributes:
26+
label: Additional context

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
blank_issues_enabled: true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Feature Request
2+
description: Suggest a new feature or enhancement
3+
labels: ["enhancement"]
4+
body:
5+
- type: textarea
6+
id: problem
7+
attributes:
8+
label: Problem or motivation
9+
validations:
10+
required: true
11+
- type: textarea
12+
id: solution
13+
attributes:
14+
label: Proposed solution
15+
validations:
16+
required: true
17+
- type: textarea
18+
id: alternatives
19+
attributes:
20+
label: Alternatives considered

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## Summary
2+
3+
4+
## Changes
5+
6+
-
7+
8+
## Test plan
9+
10+
- [ ] Tests added/updated
11+
- [ ] Manually tested locally
12+
13+
## Checklist
14+
15+
- [ ] Code follows project conventions
16+
- [ ] Documentation updated if needed

src/polar_flow/endpoints/activity_samples.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,15 @@ async def list(
5050
```
5151
"""
5252
# Determine date range
53-
end = date.today() if to_date is None else (
54-
date.fromisoformat(to_date) if isinstance(to_date, str) else to_date
53+
end = (
54+
date.today()
55+
if to_date is None
56+
else (date.fromisoformat(to_date) if isinstance(to_date, str) else to_date)
5557
)
56-
start = end - timedelta(days=days - 1) if from_date is None else (
57-
date.fromisoformat(from_date) if isinstance(from_date, str) else from_date
58+
start = (
59+
end - timedelta(days=days - 1)
60+
if from_date is None
61+
else (date.fromisoformat(from_date) if isinstance(from_date, str) else from_date)
5862
)
5963

6064
path = f"/v3/users/activities/samples?from={start}&to={end}"

src/polar_flow/endpoints/sleep.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ async def get(self, user_id: str, date: str | date) -> SleepData:
6363

6464
async def list(
6565
self,
66-
user_id: str | None = None, # Keep for backwards compat but not used
66+
user_id: str | None = None, # noqa: ARG002 - Keep for backwards compat but not used
6767
*,
6868
days: int | None = None,
6969
since: str | None = None,

src/polar_flow/models/biosensing.py

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,11 @@ class SpO2Result(BaseModel):
2121
test_status: str = Field(description="Test completion status")
2222
blood_oxygen_percent: int = Field(ge=0, le=100, description="SpO2 percentage")
2323
spo2_class: str = Field(description="Classification (NORMAL, LOW, etc.)")
24-
spo2_value_deviation_from_baseline: str = Field(
25-
description="Deviation from user's baseline"
26-
)
27-
spo2_quality_average_percent: float = Field(
28-
description="Signal quality percentage"
29-
)
24+
spo2_value_deviation_from_baseline: str = Field(description="Deviation from user's baseline")
25+
spo2_quality_average_percent: float = Field(description="Signal quality percentage")
3026
average_heart_rate_bpm: int = Field(description="Average HR during test")
3127
heart_rate_variability_ms: float = Field(description="HRV in milliseconds")
32-
spo2_hrv_deviation_from_baseline: str = Field(
33-
description="HRV deviation from baseline"
34-
)
28+
spo2_hrv_deviation_from_baseline: str = Field(description="HRV deviation from baseline")
3529
altitude_meters: float | None = Field(default=None, description="Altitude if available")
3630

3731
@property
@@ -65,16 +59,10 @@ class ECGResult(BaseModel):
6559
time_zone_offset: int = Field(description="Timezone offset in minutes")
6660
average_heart_rate_bpm: int = Field(description="Average HR during test")
6761
heart_rate_variability_ms: float = Field(description="HRV in milliseconds (RMSSD)")
68-
heart_rate_variability_level: str = Field(
69-
description="HRV classification (LOW, NORMAL, HIGH)"
70-
)
62+
heart_rate_variability_level: str = Field(description="HRV classification (LOW, NORMAL, HIGH)")
7163
rri_ms: float = Field(description="R-R interval in milliseconds")
72-
pulse_transit_time_systolic_ms: float | None = Field(
73-
default=None, description="PTT systolic"
74-
)
75-
pulse_transit_time_diastolic_ms: float | None = Field(
76-
default=None, description="PTT diastolic"
77-
)
64+
pulse_transit_time_systolic_ms: float | None = Field(default=None, description="PTT systolic")
65+
pulse_transit_time_diastolic_ms: float | None = Field(default=None, description="PTT diastolic")
7866
pulse_transit_time_quality_index: float | None = Field(
7967
default=None, description="PTT quality index"
8068
)
@@ -103,9 +91,7 @@ class TemperatureSample(BaseModel):
10391
"""Single temperature measurement sample."""
10492

10593
temperature_celsius: float = Field(description="Temperature in Celsius")
106-
recording_time_delta_milliseconds: int = Field(
107-
description="Time offset from period start (ms)"
108-
)
94+
recording_time_delta_milliseconds: int = Field(description="Time offset from period start (ms)")
10995

11096

11197
class BodyTemperaturePeriod(BaseModel):
@@ -156,12 +142,8 @@ def max_temperature(self) -> float | None:
156142
class SkinTemperature(BaseModel):
157143
"""Sleep skin temperature with baseline deviation."""
158144

159-
sleep_time_skin_temperature_celsius: float = Field(
160-
description="Skin temperature during sleep"
161-
)
162-
deviation_from_baseline_celsius: float = Field(
163-
description="Deviation from user's baseline"
164-
)
145+
sleep_time_skin_temperature_celsius: float = Field(description="Skin temperature during sleep")
146+
deviation_from_baseline_celsius: float = Field(description="Deviation from user's baseline")
165147
sleep_date: str = Field(description="Date of sleep (YYYY-MM-DD)")
166148

167149
@property

src/polar_flow/models/sleep.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,37 +19,52 @@ class SleepData(BaseModel):
1919
sleep_start_time: dt.datetime = Field(description="When sleep started")
2020
sleep_end_time: dt.datetime = Field(description="When sleep ended")
2121
device_id: str = Field(description="Device ID that recorded the sleep")
22-
continuity: float = Field(description="Sleep continuity score (0.0-5.0, 0=no data)", ge=0.0, le=5.0)
23-
continuity_class: int = Field(description="Sleep continuity classification (0-5, 0=no data)", ge=0, le=5)
22+
continuity: float = Field(
23+
description="Sleep continuity score (0.0-5.0, 0=no data)", ge=0.0, le=5.0
24+
)
25+
continuity_class: int = Field(
26+
description="Sleep continuity classification (0-5, 0=no data)", ge=0, le=5
27+
)
2428
light_sleep: int = Field(description="Light sleep duration in seconds", ge=0)
2529
deep_sleep: int = Field(description="Deep sleep duration in seconds", ge=0)
2630
rem_sleep: int = Field(description="REM sleep duration in seconds", ge=0)
2731
unrecognized_sleep_stage: int = Field(
2832
description="Unrecognized sleep stage duration in seconds", ge=0
2933
)
30-
sleep_score: int = Field(description="Overall sleep quality score (0-100, 0=no data)", ge=0, le=100)
34+
sleep_score: int = Field(
35+
description="Overall sleep quality score (0-100, 0=no data)", ge=0, le=100
36+
)
3137
total_interruption_duration: int = Field(
3238
description="Total interruption duration in seconds", ge=0
3339
)
34-
sleep_charge: int | None = Field(default=None, description="Sleep charge score (-1=unavailable, 1-100)", ge=-1, le=100)
40+
sleep_charge: int | None = Field(
41+
default=None, description="Sleep charge score (-1=unavailable, 1-100)", ge=-1, le=100
42+
)
3543
sleep_goal: int | None = Field(default=None, description="Sleep goal in seconds", ge=0)
36-
sleep_rating: int | None = Field(default=None, description="User's subjective sleep rating (0-5, 0=not rated)", ge=0, le=5)
44+
sleep_rating: int | None = Field(
45+
default=None, description="User's subjective sleep rating (0-5, 0=not rated)", ge=0, le=5
46+
)
3747
short_interruption_duration: int | None = Field(
3848
default=None, description="Short interruption duration in seconds", ge=0
3949
)
4050
long_interruption_duration: int | None = Field(
4151
default=None, description="Long interruption duration in seconds", ge=0
4252
)
4353
sleep_cycles: int | None = Field(default=None, description="Number of sleep cycles", ge=0)
44-
group_duration_score: float | None = Field(default=None, description="Group duration score", ge=0.0, le=100.0)
45-
group_solidity_score: float | None = Field(default=None, description="Group solidity score", ge=0.0, le=100.0)
54+
group_duration_score: float | None = Field(
55+
default=None, description="Group duration score", ge=0.0, le=100.0
56+
)
57+
group_solidity_score: float | None = Field(
58+
default=None, description="Group solidity score", ge=0.0, le=100.0
59+
)
4660
group_regeneration_score: float | None = Field(
4761
default=None, description="Group regeneration score", ge=0.0, le=100.0
4862
)
4963

5064
# Sample data (time → value mappings)
5165
hypnogram: dict[str, int] | None = Field(
52-
default=None, description="Hypnogram data (time → sleep stage mapping: 0=awake, 1=light, 3=deep, 4=REM)"
66+
default=None,
67+
description="Hypnogram data (time → sleep stage mapping: 0=awake, 1=light, 3=deep, 4=REM)",
5368
)
5469
heart_rate_samples: dict[str, int] | None = Field(
5570
default=None, description="Heart rate samples (time → BPM mapping, ~5 min intervals)"

tests/test_biosensing_endpoint.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,7 @@ class TestBiosensingSpO2:
102102
"""Tests for SpO2 endpoint."""
103103

104104
@pytest.mark.asyncio
105-
async def test_get_spo2_success(
106-
self, httpx_mock: HTTPXMock, spo2_response: list[dict]
107-
) -> None:
105+
async def test_get_spo2_success(self, httpx_mock: HTTPXMock, spo2_response: list[dict]) -> None:
108106
"""Test successful SpO2 data fetch."""
109107
httpx_mock.add_response(json=spo2_response)
110108

@@ -123,9 +121,7 @@ async def test_get_spo2_with_date_range(self, httpx_mock: HTTPXMock) -> None:
123121
httpx_mock.add_response(json=[])
124122

125123
async with PolarFlow(access_token="test_token") as client:
126-
await client.biosensing.get_spo2(
127-
from_date="2024-01-01", to_date="2024-01-07"
128-
)
124+
await client.biosensing.get_spo2(from_date="2024-01-01", to_date="2024-01-07")
129125

130126
request = httpx_mock.get_request()
131127
assert "from=2024-01-01" in str(request.url)
@@ -155,9 +151,7 @@ class TestBiosensingECG:
155151
"""Tests for ECG endpoint."""
156152

157153
@pytest.mark.asyncio
158-
async def test_get_ecg_success(
159-
self, httpx_mock: HTTPXMock, ecg_response: list[dict]
160-
) -> None:
154+
async def test_get_ecg_success(self, httpx_mock: HTTPXMock, ecg_response: list[dict]) -> None:
161155
"""Test successful ECG data fetch."""
162156
httpx_mock.add_response(json=ecg_response)
163157

@@ -199,9 +193,7 @@ async def test_get_body_temperature(
199193
assert len(results[0].samples) == 3
200194

201195
@pytest.mark.asyncio
202-
async def test_body_temp_computed_properties(
203-
self, body_temp_response: list[dict]
204-
) -> None:
196+
async def test_body_temp_computed_properties(self, body_temp_response: list[dict]) -> None:
205197
"""Test body temperature computed properties."""
206198
result = BodyTemperaturePeriod.model_validate(body_temp_response[0])
207199

@@ -227,9 +219,7 @@ async def test_get_skin_temperature(
227219
assert results[0].sleep_time_skin_temperature_celsius == 35.8
228220

229221
@pytest.mark.asyncio
230-
async def test_skin_temp_elevation_detection(
231-
self, skin_temp_response: list[dict]
232-
) -> None:
222+
async def test_skin_temp_elevation_detection(self, skin_temp_response: list[dict]) -> None:
233223
"""Test skin temperature elevation detection."""
234224
normal = SkinTemperature.model_validate(skin_temp_response[0])
235225
elevated = SkinTemperature.model_validate(skin_temp_response[1])

0 commit comments

Comments
 (0)