Repository navigation
fix: address six audit findings in MPC controller and integration setup - #11
Merged
Merged
Conversation
Gamso
commented
Jun 21, 2026
Owner
- Remove dead constant MAX_PROJECTION_DELTA (never referenced in source)
- Fix mpc_confidence stuck at "Low" in heat mode: replace global is_ready() gate with per-mode profile coverage as the primary confidence driver; global readiness becomes a small +0.10 bonus only
- Replace hardcoded power_draw=[1.0,1.5,3.0,6.0] (4-slot, clamped) with geometric model MODE_POWER_RATIO**rank so all five fan modes get distinct energy costs and the scale generalises to any mode count
- Fix options changes having no effect until HA restart: register async_reload_entry as an update listener via entry.add_update_listener
- Fix "Manual Override" being logged for every controller-initiated fan change: guard _handle_manual_change with a controller_commanded_fan sentinel that the controller sets before each self-command
- Add saturation annotation in mpc_reason when already at max fan with error above deadband (makes capacity-bound COOL days observable)
- Add regression test covering confidence with full per-mode coverage but global is_ready() still False
- Remove dead constant MAX_PROJECTION_DELTA (never referenced in source) - Fix mpc_confidence stuck at "Low" in heat mode: replace global is_ready() gate with per-mode profile coverage as the primary confidence driver; global readiness becomes a small +0.10 bonus only - Replace hardcoded power_draw=[1.0,1.5,3.0,6.0] (4-slot, clamped) with geometric model MODE_POWER_RATIO**rank so all five fan modes get distinct energy costs and the scale generalises to any mode count - Fix options changes having no effect until HA restart: register async_reload_entry as an update listener via entry.add_update_listener - Fix "Manual Override" being logged for every controller-initiated fan change: guard _handle_manual_change with a controller_commanded_fan sentinel that the controller sets before each self-command - Add saturation annotation in mpc_reason when already at max fan with error above deadband (makes capacity-bound COOL days observable) - Add regression test covering confidence with full per-mode coverage but global is_ready() still False
Effective cooling/heating power is not constant: per Newton's law of cooling it scales with the distance to the setpoint. The previous single median estimator was structurally diluted by the many samples collected near equilibrium, so it under-stated the fan's real working power. Each profile now learns a linear model effective_slope(error) = a + b*error by ordinary least squares over its (comfort_error, effective_slope) samples (gain b clamped >= 0). Slope samples therefore store the comfort error (5-tuple, with backward-compatible migration of 3/4-tuples). - get_mode_effective_slope now reports the representative working slope (model at REFERENCE_SLOPE_ERROR); for constant/legacy profiles (b == 0) this is exactly the previous median, so behaviour is unchanged there. - The MPC simulator recomputes the slope at each step from the simulated error, so projections decelerate realistically near the setpoint (no more phantom overshoot) and use the higher real power when the room is far from target (faster, more accurate catch-up). - The disturbance bias now compares the observed slope against the model at the current error, so it captures only genuine external disturbances. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build on the gap-dependent slope model to surface two diagnostics that reflect how reliable and how thermally responsive each fan profile is: - model_r_squared: coefficient of determination of the slope/error regression (0..1). High values mean the cooling/heating rate is well explained by the distance to the setpoint, i.e. a trustworthy profile. - thermal_time_constant_h: tau ~= 1/gain (hours). In a first-order RC model the approach rate is error/tau, so the learned gain approximates 1/tau. Larger tau means more thermal inertia / resistance. Both are returned as None for constant/legacy profiles (no regression), exposed as attributes on each profile effective-slope sensor and in the MPC profiles summary (get_mode_profiles). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New smart_fan_controller.force_fan service forces a specific fan mode for a fixed number of minutes, overriding the MPC. A duration of 0 cancels an active override and hands control back to the MPC. - The override is stored per entry and resolved each control cycle (_resolve_active_force), which clears expired or now-invalid overrides. - It is applied immediately via a trigger_cycle callback instead of waiting for the next 2-minute loop. - While active, mpc_status reports "Forced", mpc_reason shows the time remaining, and the data-collection "force" flag is set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audit follow-ups:
- M1: add a re-entrancy guard around the control loop. The cycle body is
split into _execute_control_cycle(), and run_control_loop() skips when a
cycle is already in progress. This prevents the periodic timer, the
startup run, and the force_fan immediate trigger from executing
concurrently and racing on shared state or issuing duplicate fan
commands. A skipped force trigger is still applied by the next cycle
since the override is stored.
- M3: guard the numeric parsing of VTherm state in _read_climate_cycle_data.
A transient non-numeric value ("unknown"/"unavailable") during restarts
now skips the cycle instead of raising and aborting the control loop.
- L2: drop SensorDeviceClass.ENUM from the Fan Mode sensor. ENUM requires a
static options list, but fan modes are discovered at runtime and vary per
climate, which emitted Home Assistant validation warnings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audit follow-ups: - L1: declare after_dependencies on versatile_thermostat so VTherm loads first, and add issue_tracker and loggers to the manifest. - L4: gate slope-sample collection on hvac_mode in (heat, cool). off/dry/fan_only cycles no longer pollute the learned per-fan profiles. - L7: add unit tests for the previously untested control-loop helpers: _should_collect_slope_sample (all gating branches incl. the new heat/cool gate), _detect_disturbances (defrost activation/expiry, operating idle), and _async_apply_fan_change (confirms on success and still confirms on failure to avoid a retry storm). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.