Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Fixed (retro-review of #107)

Two follow-ups from a Claude retro-review of the live-UAT bug-fix PR:

- **Stale inline comment in `_pick_latest_dispatch_file`.** Comment still claimed `_LEGACY` was part of every filename — the very assumption #107 corrected. Updated to describe both shapes plus why lexical sort still works. (`aemo_api.py:119-122`)
- **`provider_id` not lowercased in `external_statistic_id`.** All current provider IDs (`amber`, `globird`, `dwt_aemo_direct`) are lowercase so the gap was latent, but a future provider with mixed/upper case would re-trigger the same silent `Invalid statistic_id` failure #107 fixed for `entry_id`. Belt-and-suspenders `.lower()` on the `provider_id` segment + regression test (`DWT_AEMO_Direct` form). (`statistics.py:46`)

### CI

- Codecov upload: bump `codecov/codecov-action` v4 → v5 (per Context7 sweep), rename `file:` → `files:` for the v5 input contract, add explicit `token: ${{ secrets.CODECOV_TOKEN }}` reference, and set `fail_ci_if_error: false` so a codecov flake never breaks CI. Token itself is configured via GitHub repo secret `CODECOV_TOKEN`, never in the codebase.
Expand Down
6 changes: 4 additions & 2 deletions custom_components/pricehawk/aemo_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,10 @@ def _pick_latest_dispatch_file(html: str) -> str | None:
matches = _FILE_RE.findall(html)
if not matches:
return None
# Filenames are PUBLIC_DISPATCHIS_YYYYMMDDHHMM_..._LEGACY.zip.
# Lexical sort correctly puts the most recent timestamp last.
# Filenames are PUBLIC_DISPATCHIS_YYYYMMDDHHMM_NNN[_LEGACY].zip.
# The `_LEGACY` suffix was dropped from the NEMWeb directory listing in May 2026;
# the regex accepts both shapes. Lexical sort still puts the most recent timestamp
# last because the YYYYMMDDHHMM prefix sits at a fixed position regardless of shape.
return sorted(matches)[-1]


Expand Down
2 changes: 1 addition & 1 deletion custom_components/pricehawk/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def external_statistic_id(entry_id: str, provider_id: str) -> str:
backfill with "Invalid statistic_id". Lowercase the entry-id slice.
Live UAT 2026-05-23.
"""
return f"{DOMAIN}:cost_{entry_id[:8].lower()}_{provider_id}"
return f"{DOMAIN}:cost_{entry_id[:8].lower()}_{provider_id.lower()}"


def _metadata_for(entry_id: str, provider_id: str) -> StatisticMetaData:
Expand Down
12 changes: 12 additions & 0 deletions tests/test_external_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ def test_distinct_per_entry(self):
b = external_statistic_id("entry-BBB", "amber")
assert a != b

def test_provider_id_lowercased_for_ha_recorder_contract(self):
"""Belt-and-suspenders: a future provider whose id is not all-lowercase
(e.g. ``DWT_AEMO_Direct``) must still produce a valid recorder object_id.
Regression guard from #107 retro-review.
"""
sid = external_statistic_id("01KS83AKB2TN6G0BT9TAC1EMN9", "DWT_AEMO_Direct")
_, object_id = sid.split(":", 1)
assert object_id == object_id.lower(), (
f"object_id {object_id!r} must be lowercase per HA recorder contract"
)
assert sid == "pricehawk:cost_01ks83ak_dwt_aemo_direct"

def test_id_is_lowercase_for_ha_recorder_contract(self):
"""HA's recorder validates statistic_id as ``<domain>:<object_id>``
where ``object_id`` must match ``[a-z0-9_]+``. HA's ULID entry_ids
Expand Down
Loading