python-mytnb 0.5.1__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.
Files changed (26) hide show
  1. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/PKG-INFO +1 -1
  2. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/pyproject.toml +1 -1
  3. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/models.py +23 -4
  4. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/tests/test_models.py +36 -5
  5. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/uv.lock +1 -1
  6. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/.github/workflows/ci.yml +0 -0
  7. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/.github/workflows/publish.yml +0 -0
  8. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/.gitignore +0 -0
  9. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/LICENSE +0 -0
  10. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/README.md +0 -0
  11. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/__init__.py +0 -0
  12. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/__main__.py +0 -0
  13. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/auth.py +0 -0
  14. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/cli.py +0 -0
  15. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/client/__init__.py +0 -0
  16. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/client/auth.py +0 -0
  17. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/client/client.py +0 -0
  18. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/client/config.py +0 -0
  19. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/client/legacy.py +0 -0
  20. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/client/rest.py +0 -0
  21. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/crypto.py +0 -0
  22. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/src/mytnb/exceptions.py +0 -0
  23. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/tests/test_auth.py +0 -0
  24. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/tests/test_cli.py +0 -0
  25. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/tests/test_client.py +0 -0
  26. {python_mytnb-0.5.1 → python_mytnb-0.5.2}/tests/test_crypto.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-mytnb
3
- Version: 0.5.1
3
+ Version: 0.5.2
4
4
  Summary: Python library to interface with the myTNB API (Tenaga Nasional Berhad)
5
5
  Project-URL: Repository, https://github.com/danieyal/python-mytnb
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-mytnb"
7
- version = "0.5.1"
7
+ version = "0.5.2"
8
8
  description = "Python library to interface with the myTNB API (Tenaga Nasional Berhad)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -269,10 +269,29 @@ class AccountUsage(BaseModel):
269
269
 
270
270
  @property
271
271
  def average_usage_kwh(self) -> Optional[float]:
272
- for m in self.usage_metrics:
273
- if m.key == "AVGUSAGE":
274
- return m.numeric_value
275
- return None
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)
276
295
 
277
296
  @property
278
297
  def current_cost_rm(self) -> Optional[float]:
@@ -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": "AVGUSAGE",
95
+ "Key": "AVERAGEUSAGE",
93
96
  "Title": "Avg Usage",
94
- "SubTitle": "daily",
95
- "Value": "15.6",
96
- "ValueUnit": "kWh",
97
+ "SubTitle": "More than the last bill period",
98
+ "Value": "20%",
99
+ "ValueUnit": "",
97
100
  },
98
101
  ],
99
102
  "Cost": [
@@ -245,8 +248,36 @@ class TestAccountUsage:
245
248
  assert usage.current_usage_kwh == 234.5
246
249
 
247
250
  def test_average_usage_kwh(self):
251
+ # Computed from by_day (mean of daily consumption), not a usage metric.
248
252
  usage = AccountUsage.from_api_response(FULL_API_RESPONSE)
249
- assert usage.average_usage_kwh == 15.6
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)
250
281
 
251
282
  def test_current_cost_rm(self):
252
283
  usage = AccountUsage.from_api_response(FULL_API_RESPONSE)
@@ -600,7 +600,7 @@ wheels = [
600
600
 
601
601
  [[package]]
602
602
  name = "python-mytnb"
603
- version = "0.4.0"
603
+ version = "0.5.2"
604
604
  source = { editable = "." }
605
605
  dependencies = [
606
606
  { name = "cryptography" },
File without changes
File without changes
File without changes