diff --git a/custom_components/pricehawk/config_flow.py b/custom_components/pricehawk/config_flow.py index f98ae80..92238d0 100644 --- a/custom_components/pricehawk/config_flow.py +++ b/custom_components/pricehawk/config_flow.py @@ -1860,10 +1860,12 @@ async def async_step_dashboard_token( # Provider enables based on the primary choice amber_enabled = current_provider == PROVIDER_AMBER localvolts_enabled = current_provider == PROVIDER_LOCALVOLTS - # Flow Power is always on as a comparator. If the primary IS - # Flow Power, the region/base/supply were set at the - # credentials step; otherwise default to NSW1 / 34c / 100c. - flow_power_enabled = True + # Phase 3.0g (UAT): Flow Power default-OFF. Was forced ON + # under Phase 2 wizard (every install got a placeholder + # `flow_power_cost_today: $1.0` sensor whether the user + # cared or not). Comparators are now opt-in via the + # OptionsFlow comparators step. + flow_power_enabled = current_provider == PROVIDER_FLOW_POWER options: dict[str, Any] = { CONF_PLAN_TYPE: self._data.get(CONF_PLAN_TYPE, PLAN_ZEROHERO), @@ -2032,7 +2034,7 @@ async def async_step_comparators( ): bool, vol.Optional( CONF_FLOW_POWER_ENABLED, - default=current_opts.get(CONF_FLOW_POWER_ENABLED, True), + default=current_opts.get(CONF_FLOW_POWER_ENABLED, False), ): bool, vol.Optional( CONF_LOCALVOLTS_ENABLED, diff --git a/custom_components/pricehawk/sensor.py b/custom_components/pricehawk/sensor.py index 30db303..3aeab39 100644 --- a/custom_components/pricehawk/sensor.py +++ b/custom_components/pricehawk/sensor.py @@ -208,10 +208,15 @@ def __init__(self, coordinator: Any, entry: ConfigEntry) -> None: @property def native_value(self) -> str | None: + # Phase 3.0g (UAT): trust coordinator's None as "no comparison + # available" (e.g., Amber not configured). Don't synthesize a + # fake "0/3" — sensor renders "unavailable" instead, which + # honestly reflects the missing comparator. val = self.coordinator.data.get("metrics_won") if val is not None: return val - # Compute inline if coordinator doesn't provide it + # Inline-compute fallback for older coordinator data shapes + # (back-compat). Returns None when Amber isn't available. data = self.coordinator.data amber_import = data.get("amber_import_rate") current_plan_import = data.get("current_plan_import_rate") @@ -220,7 +225,7 @@ def native_value(self) -> str | None: amber_daily = data.get("amber_daily_cost") current_plan_daily = data.get("current_plan_daily_cost") if amber_import is None or current_plan_import is None: - return "0/3" + return None metrics = [ amber_import < current_plan_import, (amber_export or 0) > (current_plan_export or 0), @@ -540,10 +545,22 @@ async def async_setup_entry( entities.append(ZeroHeroStatusSensor(coordinator, entry)) # Generic per-provider sensors (pricehawk__*) — registered for - # every provider currently active in the coordinator. Reads the canonical - # data["providers"][] block. + # every comparator provider currently active in the coordinator. + # Phase 3.0g (UAT): SKIP the user's CURRENT plan provider — its + # rate/cost/kwh metrics already have hardcoded `current_plan_*` + # sensors registered above. Registering both produces duplicate + # entities (`sensor.pricehawk___*` vs + # `sensor.pricehawk_current_plan_*`). Comparators (Amber, Flow + # Power, LocalVolts) keep their per-provider entities. providers_block = coordinator.data.get("providers", {}) if coordinator.data else {} + current_plan_id = ( + coordinator._current_plan_provider.id + if hasattr(coordinator, "_current_plan_provider") + else None + ) for provider_id, snap in providers_block.items(): + if provider_id == current_plan_id: + continue provider_name = snap.get("name", provider_id.title()) entities.append( GenericProviderRateSensor(