Skip to content

Refactor EMA smoothing into composable powermeter wrappers#331

Merged
tomquist merged 3 commits intodevelopfrom
claude/refactor-powermeter-wrapper-Go7y9
Apr 19, 2026
Merged

Refactor EMA smoothing into composable powermeter wrappers#331
tomquist merged 3 commits intodevelopfrom
claude/refactor-powermeter-wrapper-Go7y9

Conversation

@tomquist
Copy link
Copy Markdown
Owner

Summary

Extracted EMA smoothing and deadband gating logic from the monolithic TargetSmoother class into composable, reusable powermeter wrapper classes. This enables per-powermeter configuration of smoothing parameters while maintaining the same control-loop semantics.

Key Changes

  • New wrapper architecture: Created PowermeterWrapper base class in src/astrameter/powermeter/wrappers/base.py to standardize decorator pattern for powermeter composition

  • Extracted smoothing logic: Split TargetSmoother into two focused wrappers:

    • SmoothedPowermeter: Applies exponential moving average (EMA) to total power with sign-change catchup and max-step limiting
    • DeadbandPowermeter: Stateless gate that returns zeros when total power is below threshold
  • Configuration flexibility: Updated config_loader.py to support per-powermeter SMOOTH_TARGET_ALPHA, MAX_SMOOTH_STEP, and DEADBAND settings with [GENERAL] section fallbacks

  • Simplified balancer interface: Changed LoadBalancer to accept optional reset_fn callback instead of direct TargetSmoother reference, decoupling the balancer from smoothing implementation

  • Updated CT002 integration: Removed embedded smoother from CT002 class; smoothing is now applied at the powermeter layer via config-driven wrapper composition

  • Comprehensive test coverage: Added 243-line test suite (smoothing_test.py) covering EMA convergence, dedup logic, sign-change catchup, proportional phase distribution, and lifecycle delegation

  • Migrated existing wrappers: Refactored PidPowermeter, ThrottledPowermeter, and TransformedPowermeter to inherit from PowermeterWrapper base class

Notable Implementation Details

  • Dedup semantics preserved: EMA update skips only when both sample identity AND raw total are unchanged, preventing stale keys from masking fresh meter readings
  • Proportional distribution: Smoothed total is distributed across phases maintaining original per-phase ratios
  • Sign-change catchup: When raw value crosses zero, EMA uses accelerated alpha (min(0.5, alpha*4)) for faster response
  • Backward compatibility: Removed smooth_target_alpha, max_smooth_step, and deadband parameters from CT002.__init__() in favor of config-driven wrapper composition

https://claude.ai/code/session_01LrkYqzhz4EMqBAKG4YAdUR

claude added 2 commits April 19, 2026 20:04
Extract TargetSmoother into SmoothedPowermeter and DeadbandPowermeter
wrappers, following the existing decorator pattern. Introduce a
PowermeterWrapper base class in a new wrappers/ subfolder to eliminate
delegation boilerplate across all five wrappers (transform, throttle,
smoother, deadband, pid).

The balancer no longer knows about smoothing—it receives pre-smoothed
values through the opaque wrapper chain and uses a reset_fn callback
for probe commit/reject. Smoothing params (SMOOTH_TARGET_ALPHA,
DEADBAND, MAX_SMOOTH_STEP) are now per-powermeter config with
[GENERAL] fallback.

https://claude.ai/code/session_01LrkYqzhz4EMqBAKG4YAdUR
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

Warning

Rate limit exceeded

@tomquist has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 31 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 22 minutes and 31 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d5bb8f2-398b-4ad7-926e-28b92a59b2b4

📥 Commits

Reviewing files that changed from the base of the PR and between dc20442 and 23c44b5.

📒 Files selected for processing (24)
  • config.ini.example
  • src/astrameter/config/config_loader.py
  • src/astrameter/ct002/balancer.py
  • src/astrameter/ct002/ct002.py
  • src/astrameter/ct002/smoother.py
  • src/astrameter/main.py
  • src/astrameter/powermeter/__init__.py
  • src/astrameter/powermeter/base.py
  • src/astrameter/powermeter/wrappers/__init__.py
  • src/astrameter/powermeter/wrappers/base.py
  • src/astrameter/powermeter/wrappers/pid.py
  • src/astrameter/powermeter/wrappers/pid_test.py
  • src/astrameter/powermeter/wrappers/smoothing.py
  • src/astrameter/powermeter/wrappers/smoothing_test.py
  • src/astrameter/powermeter/wrappers/throttling.py
  • src/astrameter/powermeter/wrappers/throttling_test.py
  • src/astrameter/powermeter/wrappers/transform.py
  • src/astrameter/powermeter/wrappers/transform_test.py
  • tests/smoke_efficiency_saturation.py
  • tests/test_balancer_probe_lockup.py
  • tests/test_ct002_active_control.py
  • tests/test_e2e_probe_lockup.py
  • tests/test_efficiency_e2e.py
  • tests/test_smoother.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/refactor-powermeter-wrapper-Go7y9

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@tomquist tomquist marked this pull request as ready for review April 19, 2026 20:16
Remove smooth_target_alpha from CT002 constructor in
smoke_efficiency_saturation.py (missed during refactor).
Update test_balancer_probe_lockup.py docstring to remove reference
to deleted test_smoother.py.

https://claude.ai/code/session_01LrkYqzhz4EMqBAKG4YAdUR
@tomquist tomquist merged commit 1f8445a into develop Apr 19, 2026
13 checks passed
@tomquist tomquist deleted the claude/refactor-powermeter-wrapper-Go7y9 branch April 19, 2026 20:28
tomquist pushed a commit that referenced this pull request Apr 19, 2026
Regrouped the flat bullet list into Breaking / Added / Changed / Fixed
subsections so readers can scan by change type. Each bullet is now a
standalone diff to main (no cross-references) and cites every PR that
contributed.

Added Breaking entries that were missing:
- CT002/CT003 ACTIVE_CONTROL default (smoothing + 15 W BALANCE_DEADBAND
  + saturation detection on by default)
- WAIT_FOR_NEXT_MESSAGE default True (affects Shelly emulation too, not
  just CT002/CT003)
- Async Powermeter base (out-of-tree subclasses must implement
  async get_powermeter_watts())

Added missing feature bullets: per-powermeter EMA smoothing/deadband
wrappers (#331), Hampel outlier filter (#334), MQTT BROKER_URI (#309),
exc_info on warnings (#307). Filled in previously-missing PR refs on
the rebrand, CT002/CT003, MQTT Insights, web config editor, PID
controller, and GIT_COMMIT_SHA bullets.

https://claude.ai/code/session_01BCVmemteVXNfoTQE4De2CU
tomquist added a commit that referenced this pull request Apr 19, 2026
* Restructure CHANGELOG Next section as Keep-a-Changelog diff to main

Regrouped the flat bullet list into Breaking / Added / Changed / Fixed
subsections so readers can scan by change type. Each bullet is now a
standalone diff to main (no cross-references) and cites every PR that
contributed.

Added Breaking entries that were missing:
- CT002/CT003 ACTIVE_CONTROL default (smoothing + 15 W BALANCE_DEADBAND
  + saturation detection on by default)
- WAIT_FOR_NEXT_MESSAGE default True (affects Shelly emulation too, not
  just CT002/CT003)
- Async Powermeter base (out-of-tree subclasses must implement
  async get_powermeter_watts())

Added missing feature bullets: per-powermeter EMA smoothing/deadband
wrappers (#331), Hampel outlier filter (#334), MQTT BROKER_URI (#309),
exc_info on warnings (#307). Filled in previously-missing PR refs on
the rebrand, CT002/CT003, MQTT Insights, web config editor, PID
controller, and GIT_COMMIT_SHA bullets.

https://claude.ai/code/session_01BCVmemteVXNfoTQE4De2CU

* Fold same-cycle fixes into their parent Added bullets

The Fixed / Changed bullets for CT002/CT003 saturation, efficiency-
rotation lockup, and the MQTT_INSIGHTS empty-config crash / HA mosquitto
availability check referenced features introduced in this same release
cycle, so they weren't a standalone diff against main. Merged those PR
refs into the CT002/CT003 and MQTT Insights Added bullets (the end
state, which is what a main-viewer cares about). Modbus UNIT_ID fix
stays in Fixed — Modbus existed on main.

https://claude.ai/code/session_01BCVmemteVXNfoTQE4De2CU

* Fold CT002/CT003 active-control defaults into Added bullet

The CT002/CT003 ACTIVE_CONTROL default is not a 'changed default' vs
main — CT002/CT003 don't exist on main, so the default is just part of
the new feature description. Moved the default-on behavior and
BALANCE_DEADBAND details into the CT002/CT003 Added bullet.

Also narrowed the WAIT_FOR_NEXT_MESSAGE Breaking bullet to just the
Shelly emulator (the real diff against main); the CT002/CT003 aspect
is implicit in the CT002/CT003 Added bullet.

Fixed a minor verb mismatch in Changed: 'Added battery activity
info logs' → 'Expanded Shelly emulation logs'.

https://claude.ai/code/session_01BCVmemteVXNfoTQE4De2CU

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants