python-mytnb 0.5.0__tar.gz → 0.5.2__tar.gz
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.
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/PKG-INFO +1 -1
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/pyproject.toml +1 -1
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/models.py +26 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/tests/test_models.py +41 -4
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/uv.lock +1 -1
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/.github/workflows/ci.yml +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/.github/workflows/publish.yml +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/.gitignore +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/LICENSE +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/README.md +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/__init__.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/__main__.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/auth.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/cli.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/client/__init__.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/client/auth.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/client/client.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/client/config.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/client/legacy.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/client/rest.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/crypto.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/src/mytnb/exceptions.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/tests/test_auth.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/tests/test_cli.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/tests/test_client.py +0 -0
- {python_mytnb-0.5.0 → python_mytnb-0.5.2}/tests/test_crypto.py +0 -0
|
@@ -267,6 +267,32 @@ class AccountUsage(BaseModel):
|
|
|
267
267
|
return m.numeric_value
|
|
268
268
|
return None
|
|
269
269
|
|
|
270
|
+
@property
|
|
271
|
+
def average_usage_kwh(self) -> Optional[float]:
|
|
272
|
+
"""Average daily consumption (kWh) across available daily readings.
|
|
273
|
+
|
|
274
|
+
Computed from ``by_day`` rather than a usage metric: the API's
|
|
275
|
+
``AVERAGEUSAGE`` metric is a percentage comparison against the previous
|
|
276
|
+
bill period (e.g. "20% — More than the last bill period"), not a kWh
|
|
277
|
+
value. Days with no recorded consumption are excluded: both missing
|
|
278
|
+
readings and zero-usage days (such as the current, still-partial day).
|
|
279
|
+
Returns None when there is no usable daily data.
|
|
280
|
+
"""
|
|
281
|
+
values: list[float] = []
|
|
282
|
+
for week in self.by_day:
|
|
283
|
+
for day in week.days:
|
|
284
|
+
if day.is_missing_reading:
|
|
285
|
+
continue
|
|
286
|
+
try:
|
|
287
|
+
consumption = day.consumption_kwh
|
|
288
|
+
except (ValueError, TypeError):
|
|
289
|
+
continue
|
|
290
|
+
if consumption > 0:
|
|
291
|
+
values.append(consumption)
|
|
292
|
+
if not values:
|
|
293
|
+
return None
|
|
294
|
+
return round(sum(values) / len(values), 2)
|
|
295
|
+
|
|
270
296
|
@property
|
|
271
297
|
def current_cost_rm(self) -> Optional[float]:
|
|
272
298
|
for m in self.cost_metrics:
|
|
@@ -88,12 +88,15 @@ FULL_API_RESPONSE = {
|
|
|
88
88
|
"OtherUsageMetrics": {
|
|
89
89
|
"Usage": [
|
|
90
90
|
USAGE_METRIC_DATA,
|
|
91
|
+
# Real API shape: AVERAGEUSAGE is a percentage comparison vs the last
|
|
92
|
+
# bill period, not a kWh average (that's why average_usage_kwh is
|
|
93
|
+
# computed from by_day instead of read from this metric).
|
|
91
94
|
{
|
|
92
|
-
"Key": "
|
|
95
|
+
"Key": "AVERAGEUSAGE",
|
|
93
96
|
"Title": "Avg Usage",
|
|
94
|
-
"SubTitle": "
|
|
95
|
-
"Value": "
|
|
96
|
-
"ValueUnit": "
|
|
97
|
+
"SubTitle": "More than the last bill period",
|
|
98
|
+
"Value": "20%",
|
|
99
|
+
"ValueUnit": "",
|
|
97
100
|
},
|
|
98
101
|
],
|
|
99
102
|
"Cost": [
|
|
@@ -244,6 +247,38 @@ class TestAccountUsage:
|
|
|
244
247
|
usage = AccountUsage.from_api_response(FULL_API_RESPONSE)
|
|
245
248
|
assert usage.current_usage_kwh == 234.5
|
|
246
249
|
|
|
250
|
+
def test_average_usage_kwh(self):
|
|
251
|
+
# Computed from by_day (mean of daily consumption), not a usage metric.
|
|
252
|
+
usage = AccountUsage.from_api_response(FULL_API_RESPONSE)
|
|
253
|
+
assert usage.average_usage_kwh == 8.5
|
|
254
|
+
|
|
255
|
+
def test_average_usage_kwh_multiple_days(self):
|
|
256
|
+
data = {
|
|
257
|
+
"ByDay": [
|
|
258
|
+
{
|
|
259
|
+
"Range": "Week 1",
|
|
260
|
+
"Days": [
|
|
261
|
+
# Two valid days that count toward the average.
|
|
262
|
+
{"Date": "2026-06-01", "Year": "2026", "Month": "06",
|
|
263
|
+
"Day": "01", "Consumption": "10", "Amount": "2.7"},
|
|
264
|
+
{"Date": "2026-06-02", "Year": "2026", "Month": "06",
|
|
265
|
+
"Day": "02", "Consumption": "20", "Amount": "5.4"},
|
|
266
|
+
# Excluded via the missing-reading flag (high value would
|
|
267
|
+
# skew the mean if the filter regressed).
|
|
268
|
+
{"Date": "2026-06-03", "Year": "2026", "Month": "06",
|
|
269
|
+
"Day": "03", "Consumption": "99", "Amount": "26.7",
|
|
270
|
+
"IsMissingReading": True},
|
|
271
|
+
# Excluded via zero consumption (the current partial day),
|
|
272
|
+
# independently of the missing-reading flag.
|
|
273
|
+
{"Date": "2026-06-04", "Year": "2026", "Month": "06",
|
|
274
|
+
"Day": "04", "Consumption": "0", "Amount": "0"},
|
|
275
|
+
],
|
|
276
|
+
}
|
|
277
|
+
],
|
|
278
|
+
}
|
|
279
|
+
usage = AccountUsage.from_api_response(data)
|
|
280
|
+
assert usage.average_usage_kwh == 15.0 # mean(10, 20)
|
|
281
|
+
|
|
247
282
|
def test_current_cost_rm(self):
|
|
248
283
|
usage = AccountUsage.from_api_response(FULL_API_RESPONSE)
|
|
249
284
|
assert usage.current_cost_rm == 87.60
|
|
@@ -280,6 +315,7 @@ class TestAccountUsage:
|
|
|
280
315
|
def test_empty_response(self):
|
|
281
316
|
usage = AccountUsage.from_api_response({})
|
|
282
317
|
assert usage.current_usage_kwh is None
|
|
318
|
+
assert usage.average_usage_kwh is None
|
|
283
319
|
assert usage.current_cost_rm is None
|
|
284
320
|
assert usage.projected_cost_rm is None
|
|
285
321
|
assert usage.by_month is None
|
|
@@ -291,6 +327,7 @@ class TestAccountUsage:
|
|
|
291
327
|
}
|
|
292
328
|
usage = AccountUsage.from_api_response(data)
|
|
293
329
|
assert usage.current_usage_kwh is None
|
|
330
|
+
assert usage.average_usage_kwh is None
|
|
294
331
|
assert usage.current_cost_rm is None
|
|
295
332
|
|
|
296
333
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|