Skip to content

fix: address six audit findings in MPC controller and integration setup - #11

Merged
Gamso merged 6 commits into
mainfrom
fix/several_improve
Jul 1, 2026
Merged

Gamso merged 6 commits into
mainfrom
fix/several_improve

Conversation

@Gamso

@Gamso Gamso commented Jun 21, 2026

Copy link
Copy Markdown
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

Gamso and others added 6 commits June 21, 2026 09:18
- 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>
@Gamso
Gamso merged commit 846625a into main Jul 1, 2026
2 checks passed
@Gamso
Gamso deleted the fix/several_improve branch July 4, 2026 16:44
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