Skip to content

feat(router): add local-Powerwall key-ownership merge contract - #145

Merged
Bre77 merged 1 commit into
mainfrom
fm/tfa-local-merge-contract
Sep 5, 2026
Merged

feat(router): add local-Powerwall key-ownership merge contract#145
Bre77 merged 1 commit into
mainfrom
fm/tfa-local-merge-contract

Conversation

@Bre77

@Bre77 Bre77 commented Sep 5, 2026

Copy link
Copy Markdown
Member

Intent

Add the local-Powerwall key-ownership contract to tesla-fleet-api, in tesla_fleet_api/router/energysite.py (energy-specific; router/base.py stays entity-agnostic). Define LOCAL_LIVE_STATUS_KEYS (solar_power, energy_left, total_pack_energy, percentage_charged, battery_power, load_power, grid_power, generator_power, grid_status, island_status) and LOCAL_SITE_INFO_KEYS (backup_reserve_percent, default_real_mode) as frozensets, plus merge_local_into_cloud(cloud, local, owned_keys) -> dict that overlays owned_keys present in local onto a copy of cloud; every other key keeps its cloud value. Exact semantics: returns a NEW dict (never mutates inputs); when local is None the result equals cloud; only keys in owned_keys AND present in local are overlaid; a key in owned_keys absent from local keeps the cloud value; a falsy-but-present local value (e.g. grid_power: 0) IS overlaid; keys in local outside owned_keys are ignored. Export all three from the package's public surface the same way EnergySiteRouter is exported (router/init.py and tesla/init.py).

Added a short WHY note in the function docstring: PowerwallEnergySite.live_status() returns all 16 cloud keys with None for the 6 it cannot serve, so presence-in-response cannot be the ownership test - a fixed owned-key set lets a consumer overlay local readings without clobbering cloud values, and lets a local outage fall back to cloud values rather than unavailable.

Added a 'Merge contract' subsection to docs/energy_local_control.md next to the EnergySiteRouter walkthrough (section 5), covering the two key sets, the one function, the fallback-to-cloud rule, and that per-command router dispatch is NOT the merge (calling router.live_status() returns one whole source, not a blend).

Added unit tests (tests/test_energysite_merge_contract.py) covering the five semantics above (local=None -> cloud values, owned+present overlaid, owned+absent keeps cloud, falsy-but-present overlaid, unowned local keys ignored) plus no-mutation and new-dict checks.

Deliberately did NOT touch Router dispatch, did NOT add health logic (stays with the consumer), did NOT add new classes. Deliberately did NOT bump the package version: this repo's release history shows version bumps happen as their own dedicated commits/PRs, not bundled into feature PRs, so pyproject.toml/init.py/uv.lock are left unchanged and the PR body should say so explicitly.

What Changed

  • Added LOCAL_LIVE_STATUS_KEYS and LOCAL_SITE_INFO_KEYS frozensets and a merge_local_into_cloud(cloud, local, owned_keys) -> dict function in tesla_fleet_api/router/energysite.py, which overlays only the owned keys present in local onto a copy of cloud (returns a new dict, never mutates inputs, falls back to cloud for absent/owned-missing keys and when local is None, and ignores unowned local keys).
  • Exported LOCAL_LIVE_STATUS_KEYS, LOCAL_SITE_INFO_KEYS, and merge_local_into_cloud from tesla_fleet_api/router/__init__.py and tesla_fleet_api/tesla/__init__.py, following the same public-surface pattern as EnergySiteRouter.
  • Documented a "Merge contract" subsection in docs/energy_local_control.md alongside the EnergySiteRouter walkthrough, covering the two key sets, the merge function's fallback-to-cloud behavior, and the distinction from Router per-command dispatch (which returns one whole source, not a blend).
  • Added tests/test_energysite_merge_contract.py covering: local=None yields cloud values, owned+present keys are overlaid, owned+absent keys keep cloud values, falsy-but-present local values are overlaid, unowned local keys are ignored, and no mutation of inputs occurs with a new dict returned.

No package version bump is included; per this repo's release process, version bumps ship as their own dedicated commits/PRs, so pyproject.toml, tesla_fleet_api/__init__.py, and uv.lock are unchanged.

Risk Assessment

✅ Low: Small, additive, well-isolated change (new pure function + two frozensets + exports + docs + tests) matching the stated intent exactly, with no modification to existing Router/dispatch logic or call sites.

Testing

Ran the new focused unit test suite (8/8 pass) and manually exercised the exported public API end-to-end (import from both tesla_fleet_api.tesla and tesla_fleet_api.router, merge behavior with falsy overlay, unowned-key exclusion, None-fallback, and non-mutation all verified live); confirmed no version-bump files were touched. All acceptance criteria in the user intent are satisfied with no findings.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • uv run pytest tests/test_energysite_merge_contract.py -v — all 8 tests pass, covering local=None fallback, owned+present overlay, owned+absent keeps cloud, falsy-but-present overlay, unowned-key ignore, no-mutation, new-dict identity, and site_info keys
  • Manual end-to-end script importing merge_local_into_cloud/LOCAL_LIVE_STATUS_KEYS from both tesla_fleet_api.tesla and tesla_fleet_api.router public surfaces, confirming identical function object, correct merge output, cloud-fallback on local=None, and cloud dict left unmutated
  • git diff f4c773c..24f73c7 --stat -- pyproject.toml uv.lock tesla_fleet_api/__init__.py — confirmed empty, verifying no version-bump files were touched as the intent required
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Add merge_local_into_cloud plus LOCAL_LIVE_STATUS_KEYS/LOCAL_SITE_INFO_KEYS
to router/energysite.py so a consumer can overlay local Powerwall readings
onto a cloud response without clobbering fields the local gateway can't
serve. PowerwallEnergySite.live_status() returns all cloud keys with None
for unservable ones, so presence-in-response can't be the ownership test -
a fixed owned-key set is what makes the overlay safe and lets a local
outage fall back to the cloud value.

Router dispatch is unchanged; this is a plain function next to
EnergySiteRouter, not new Router/health logic.

Claude-Session: https://claude.ai/code/session_01B18NQ2RAaJAX2rsiU31Qn6
@Bre77 Bre77 added the fm Opened by a Firstmate crewmate label Sep 5, 2026
@Bre77
Bre77 merged commit afb47e0 into main Sep 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fm Opened by a Firstmate crewmate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant