Skip to content

fix(state,pricing): bound entity-id replace() to first occurrence (#900)#900

Open
jackmcintyre wants to merge 1 commit into
mainfrom
issue/900
Open

fix(state,pricing): bound entity-id replace() to first occurrence (#900)#900
jackmcintyre wants to merge 1 commit into
mainfrom
issue/900

Conversation

@jackmcintyre

Copy link
Copy Markdown
Owner

Summary

Entity-id derivation used unbounded str.replace() in 7 places, swapping EVERY occurrence of the substring. For ids containing the substring more than once (rare but possible with custom renames like sensor.amber_price_price), the result was malformed — e.g. sensor.amber_price_detailed_price_detailed instead of sensor.amber_price_detailed_price.

This matches the existing correct pattern at reader.py:198/203 which already used count=1 for the amber_express_ prefix transform. The inconsistency was the bug.

Changes — added count=1 to all 7 sites

  • state/reader.py:
    • _read_shadow_prices (2 sites: _price_forecast)
    • _read_legacy_forecasts (2 sites: _price_price_detailed)
    • read_all_external_state demand_window derivation (1 site: price_spikedemand_window)
  • pricing/provider.py:
    • AmberProvider.read_forecasts (1 site)
    • AmberExpressProvider.read_forecasts (1 site)

Validation

  • TDD: failing test TestEntityIdDerivationCount with double _price substring asserting only the first occurrence is replaced. Confirmed the unbounded replace produced sensor.amber_price_detailed_price_detailed (both replaced); after fix produces sensor.amber_price_detailed_price.
  • Full suite: 3216 passed, 1 xfailed. Coverage: 95%.
  • uv run ruff check clean.

Entity-id derivation used unbounded str.replace() in 7 places, swapping EVERY
occurrence of the substring. For ids containing the substring more than once
(rare but possible with custom renames like 'sensor.amber_price_price'), the
result was malformed — e.g. 'sensor.amber_price_detailed_price_detailed'
instead of 'sensor.amber_price_detailed_price'.

Added count=1 to all 7 sites:
- state/reader.py: _read_shadow_prices (2), _read_legacy_forecasts (2),
  read_all_external_state demand_window derivation (1)
- pricing/provider.py: AmberProvider.read_forecasts (1),
  AmberExpressProvider.read_forecasts (1)

This matches the existing correct pattern at reader.py:198/203 which already
used count=1.

TDD: failing test with double '_price' substring asserting only the first
occurrence is replaced. 3216 pass, coverage 95%.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant