Skip to content

Phase 3.0 UAT fixes — metrics_won + duplicate entities + Flow Power default#53

Merged
Artic0din merged 1 commit into
phase-3-multi-planfrom
phase-3-uat-fixes
May 16, 2026
Merged

Phase 3.0 UAT fixes — metrics_won + duplicate entities + Flow Power default#53
Artic0din merged 1 commit into
phase-3-multi-planfrom
phase-3-uat-fixes

Conversation

@Artic0din
Copy link
Copy Markdown
Owner

@Artic0din Artic0din commented May 16, 2026

Summary

Three runtime bugs surfaced during the post-Phase-3.0d UAT walkthrough
on a fresh-wiped HA install. None caught by the test suite — they're
sensor-rendering-vs-coordinator-data shape mismatches that only
manifest at runtime against a real entry.

Stacked on PR #28 (phase-3-multi-plan). Will auto-rebase to dev
once #28 merges.

Fixes

🟢 metrics_won returned fake 0/3 when Amber not configured

  • MetricsWonSensor.native_value inline fallback returned literal
    string "0/3" when amber_import or current_plan_import was
    None. Now returns None — sensor renders unavailable honestly
    instead of fake-comparing against a phantom zero-cost provider.

🟢 Duplicate sensor entity sets for the user's current plan

  • Generic per-provider sensors (cost / import_rate / export_rate)
    registered for the user's CURRENT plan AND comparators. The
    current plan already has hardcoded current_plan_* sensors, so
    the generic ones produced duplicates like
    sensor.pricehawk_globird_zerohero_residential_flexible_rate_united_energy_cost_today.
  • Now skips the current plan in the providers loop. Comparators
    (Amber, FlowPower, LocalVolts) keep their per-provider entities.

🟢 Flow Power default-OFF on new installs

  • Wizard hardcoded flow_power_enabled = True regardless of user
    choice. Every install got sensor.pricehawk_flow_power_cost_today: $1.0 placeholder. Now opt-in: only enabled when user picks Flow
    Power at credentials, OR enables via comparators OptionsFlow step.

Stats

  • 2 files changed: sensor.py, config_flow.py
  • +28 / -9 LOC
  • 623/623 non-pydantic tests pass
  • ruff clean

Test plan

  • Local pytest 623/623
  • Local ruff clean
  • CodeRabbit review
  • Sourcery review
  • Live HA UAT verify entities clean post-deploy

🤖 Generated with Claude Code

Summary by Sourcery

Fix UAT issues around metrics comparison reporting, duplicate provider sensors, and Flow Power comparator defaults.

Bug Fixes:

  • Ensure the metrics_won sensor reports unavailable when Amber comparison data is missing instead of returning a fake 0/3 value.
  • Avoid creating duplicate generic provider sensors for the user’s current plan by skipping its provider in the generic per-provider registration loop.
  • Make Flow Power a default-off comparator so its sensors are only created when explicitly selected by the user or via options flow.

Enhancements:

  • Maintain backward compatibility for metrics_won by computing a fallback value only for older coordinator data shapes while respecting missing comparator data.

Summary of Changes

What Changed:

  • metrics_won Sensor Unavailability: Modified MetricsWonSensor.native_value to return None (rendering as unavailable) instead of the literal string "0/3" when comparison data (amber_import or current_plan_import) is unavailable. Backward-compatible fallback preserved for older coordinator data shapes.

  • Duplicate Provider Sensors Eliminated: Updated async_setup_entry in sensor.py to skip registering generic per-provider sensors for the user's current plan. This prevents duplicate entities between hardcoded current_plan_* sensors and per-provider sensor.pricehawk_<brand>_<planid>_* sensors. Comparator providers (Amber, FlowPower, LocalVolts) retain their per-provider entities.

  • Flow Power Opt-In Default: Changed Flow Power from always-enabled to opt-in behavior:

    • In config_flow.py final step: CONF_FLOW_POWER_ENABLED now becomes True only when the primary provider selected is PROVIDER_FLOW_POWER (previously unconditionally True)
    • In options-flow comparators step: Default for CONF_FLOW_POWER_ENABLED toggle changed from True to False

Why:

These fixes address runtime issues discovered during Phase 3.0 UAT on a fresh Home Assistant installation. The changes prevent phantom sensor comparisons, eliminate duplicate entities, and make Flow Power configuration more intuitive by defaulting to opt-in.

Breaking Changes:

None. Backward compatibility is maintained; older coordinator shapes continue to work with fallback logic.

Files Changed

File Lines Added Lines Removed
custom_components/pricehawk/config_flow.py 7 5
custom_components/pricehawk/sensor.py 21 4
Total 28 9

Review Change Stack

Three bugs surfaced during the post-Phase-3.0d UAT walkthrough on a
fresh-wiped HA install. None caught by the test suite — they're
sensor-rendering-vs-coordinator-data shape mismatches that only
manifest at runtime.

🟢 metrics_won returned fake "0/3" when Amber not configured
- coordinator's `metrics_won = None` round-2 fix was correct, but
  `MetricsWonSensor.native_value`'s inline-compute fallback returned
  the literal string "0/3" when amber_import or current_plan_import
  was None. Now returns None — sensor renders "unavailable" honestly
  instead of fake-comparing against a phantom zero-cost provider.

🟢 Duplicate sensor entity sets for the user's current plan
- Generic per-provider sensors (cost / import_rate / export_rate)
  were registered for the user's CURRENT plan AND comparators. The
  current plan already has hardcoded `current_plan_*` sensors, so
  the generic ones produced duplicates like
  `sensor.pricehawk_globird_zerohero_residential_flexible_rate_united_energy_cost_today`.
  sensor.py now skips the current plan in the providers loop;
  comparators (Amber, FlowPower, LocalVolts) keep their per-provider
  entities.

🟢 Flow Power default-OFF on new installs
- Wizard defaulted `flow_power_enabled = True` regardless of user
  choice. Every install got a placeholder
  `sensor.pricehawk_flow_power_cost_today: $1.0` whether the user
  cared or not. Now opt-in: enabled only when user picks Flow Power
  as the primary at credentials, OR enables it via the comparators
  OptionsFlow step. Same default flipped in the comparators step
  schema.

623/623 non-pydantic tests pass; ruff clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • main
  • develop

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c57ef68-89e4-4cf0-af64-06d6c94b7698

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-3-uat-fixes

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 16, 2026

Reviewer's Guide

Adjusts metrics_won sensor behavior to correctly represent unavailable Amber comparisons, avoids registering duplicate provider sensors for the current plan, and changes Flow Power to be opt-in instead of always enabled in the config flow.

File-Level Changes

Change Details Files
Align metrics_won sensor value with coordinator data and avoid synthetic values when Amber comparison is unavailable.
  • Trust coordinator.data['metrics_won'] when present and return it directly.
  • Retain inline fallback computation only for older coordinator data shapes when metrics_won is missing.
  • Return None instead of a hardcoded "0/3" when Amber import or current plan import rate is missing so the sensor shows as unavailable.
custom_components/pricehawk/sensor.py
Prevent duplicate generic provider sensors for the user's current plan.
  • Derive the current plan provider id from the coordinator when available.
  • Skip the current plan provider when iterating providers to register generic per-provider sensors so only comparator providers get generic entities.
custom_components/pricehawk/sensor.py
Make Flow Power comparator opt-in in the onboarding and options flows.
  • Set flow_power_enabled at initial setup based on whether the user selected Flow Power as the primary provider instead of forcing it on.
  • Change the comparators options schema so Flow Power defaults to disabled unless explicitly enabled by the user.
custom_components/pricehawk/config_flow.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • Accessing coordinator._current_plan_provider.id relies on a private attribute and assumes the provider IDs in providers_block are the same type; consider using a public accessor (or storing the current provider ID in the coordinator data) and defensively handling missing/mismatched IDs before comparing.
  • In MetricsWonSensor.native_value, the inline fallback now returns None when Amber is not configured but still performs numeric comparisons on other fields; it may be safer to short-circuit and return None if any of the required rate/daily fields are missing or not numeric to avoid subtle runtime type issues.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Accessing `coordinator._current_plan_provider.id` relies on a private attribute and assumes the provider IDs in `providers_block` are the same type; consider using a public accessor (or storing the current provider ID in the coordinator data) and defensively handling missing/mismatched IDs before comparing.
- In `MetricsWonSensor.native_value`, the inline fallback now returns `None` when Amber is not configured but still performs numeric comparisons on other fields; it may be safer to short-circuit and return `None` if any of the required rate/daily fields are missing or not numeric to avoid subtle runtime type issues.

## Individual Comments

### Comment 1
<location path="custom_components/pricehawk/sensor.py" line_range="556-562" />
<code_context>
+    # `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())
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against `_current_plan_provider` being present but `None`, and double-check ID type consistency.

`hasattr(coordinator, "_current_plan_provider")` only checks for existence, so if the attribute exists but is `None`, `coordinator._current_plan_provider.id` will raise an `AttributeError`. Consider:

```python
current_plan_provider = getattr(coordinator, "_current_plan_provider", None)
current_plan_id = getattr(current_plan_provider, "id", None)
```

Also, `provider_id == current_plan_id` may fail silently if their types differ (e.g., dict key vs model ID). If their types aren’t guaranteed to match, normalize before comparing (e.g., `str(provider_id) == str(current_plan_id)`).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +556 to +562
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:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Guard against _current_plan_provider being present but None, and double-check ID type consistency.

hasattr(coordinator, "_current_plan_provider") only checks for existence, so if the attribute exists but is None, coordinator._current_plan_provider.id will raise an AttributeError. Consider:

current_plan_provider = getattr(coordinator, "_current_plan_provider", None)
current_plan_id = getattr(current_plan_provider, "id", None)

Also, provider_id == current_plan_id may fail silently if their types differ (e.g., dict key vs model ID). If their types aren’t guaranteed to match, normalize before comparing (e.g., str(provider_id) == str(current_plan_id)).

@Artic0din Artic0din merged commit 33670fd into phase-3-multi-plan May 16, 2026
12 of 13 checks passed
@Artic0din Artic0din deleted the phase-3-uat-fixes branch May 16, 2026 09:28
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