Skip to content
Closed
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
36 changes: 36 additions & 0 deletions custom_components/pricehawk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Schedule periodic state persistence
coordinator.schedule_persist()

# Phase 3.1 — schedule daily multi-plan ranking job at 00:30 local.
# First run also fires immediately so the alternatives sensor isn't
# empty until midnight on a fresh install.
coordinator.schedule_daily_ranking()
hass.async_create_task(coordinator.async_run_ranking_job())

# Copy www assets (icon + HTML) and register sidebar panel
await copy_www_assets(hass)
await setup_panel_iframe(hass, entry)
Expand Down Expand Up @@ -179,6 +185,34 @@ async def handle_backfill(call: object) -> None:

hass.services.async_register(DOMAIN, "backfill_history", handle_backfill)

# Phase 3.1 commit 5 — manual ranking trigger. Lets users force-run
# the ranking pipeline from Developer Tools → Services without
# waiting for the next 00:30 schedule fire. Most useful right after
# switching plans (so the alternatives ranking reflects the new
# distributor / postcode immediately).
async def handle_rank_alternatives(call: object) -> None:
# CR-fix: malformed service payload (e.g. ``top_k: "abc"`` from
# a typo in a YAML automation) would raise ValueError/TypeError
# and fail the call. Coerce defensively + fall back to default.
raw = call.data.get("top_k", 20) # type: ignore[attr-defined]
try:
top_k = int(raw)
except (TypeError, ValueError):
_LOGGER.warning(
"rank_alternatives: invalid top_k=%r, using default 20", raw
)
top_k = 20
top_k = max(1, min(top_k, 100))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
result = await coordinator.async_run_ranking_job(top_k=top_k)
_LOGGER.info(
"rank_alternatives service: ran successfully, %d result(s)",
len(result),
)

hass.services.async_register(
DOMAIN, "rank_alternatives", handle_rank_alternatives
)

_LOGGER.info("PriceHawk integration setup complete")
return True

Expand All @@ -191,6 +225,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
if coordinator:
coordinator.cancel_persist()
coordinator.cancel_ranking()
await coordinator.async_persist_state()

await remove_panel(hass)
Expand All @@ -199,5 +234,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if not hass.data.get(DOMAIN):
hass.services.async_remove(DOMAIN, "analyze_csv")
hass.services.async_remove(DOMAIN, "backfill_history")
hass.services.async_remove(DOMAIN, "rank_alternatives")

return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
21 changes: 21 additions & 0 deletions custom_components/pricehawk/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,24 @@ backfill_history:
min: 1
max: 90
mode: slider

rank_alternatives:
name: Rank Alternative Plans
description: >-
Run the cheap-rank pipeline against your current retailer + competitor
retailers (AGL, Origin, EnergyAustralia, Red Energy). Results stored on
the coordinator and exposed via the alternatives sensor (Phase 3.1
commit 6). Cheap-rank only for now; deep-rank by HA consumption replay
arrives in Phase 3.2.
fields:
top_k:
name: Top K
description: Number of cheapest alternatives to keep (default 20)
required: false
default: 20
example: 20
selector:
number:
min: 1
max: 100
mode: slider
Loading