348 files
This commit is contained in:
@@ -140,6 +140,7 @@ class LearningManager:
|
||||
self._sample_interval_model = StatisticalModel(max_samples=200)
|
||||
self._last_suggestion_update: datetime | None = None
|
||||
self._last_batch_simulation_count: int = 0 # track when to re-run batch
|
||||
self._last_suggestions_labeled_count: int = 0 # gate model/detection passes
|
||||
|
||||
def _apply_suggestions_and_notify(self, suggestions: dict[str, Any]) -> None:
|
||||
"""Apply suggestions that pass quality gates."""
|
||||
@@ -232,9 +233,17 @@ class LearningManager:
|
||||
predicted_duration: Expected duration in seconds
|
||||
match_result: MatchResult from profile_store.async_match_profile() (optional)
|
||||
"""
|
||||
# 1. Trigger background simulation to find optimal parameters for this cycle
|
||||
if cycle_data.get("power_data"):
|
||||
# Offload to executor since simulation can be heavy
|
||||
# 1. Trigger single-cycle simulation — only for cleanly-completed, labeled,
|
||||
# non-noise cycles. Skipping force_stopped/unlabeled/noise avoids deriving
|
||||
# start/stop thresholds from mis-detected or truncated cycles.
|
||||
_profile = detected_profile or cycle_data.get("profile_name")
|
||||
_is_clean = (
|
||||
cycle_data.get("power_data")
|
||||
and _profile
|
||||
and _profile != "noise"
|
||||
and cycle_data.get("status") == "completed"
|
||||
)
|
||||
if _is_clean:
|
||||
self.hass.async_create_task(self._async_run_simulation(cycle_data))
|
||||
|
||||
# 2. Check if we should request feedback
|
||||
@@ -242,11 +251,20 @@ class LearningManager:
|
||||
cycle_data, detected_profile, confidence, predicted_duration, match_result
|
||||
)
|
||||
|
||||
# 3. Update model-based suggestions (durations etc)
|
||||
self._update_model_suggestions()
|
||||
|
||||
# 3b. Update statistical detection suggestions (thresholds, gates, etc.)
|
||||
self._update_detection_suggestions()
|
||||
# 3+3b. Heavy per-profile suggestion passes — only run when the labeled
|
||||
# cycle count has grown since the last update (skips passes for unlabeled /
|
||||
# noise / duplicate ends with no new data).
|
||||
labeled_count = sum(
|
||||
1 for c in self.profile_store.get_past_cycles()
|
||||
if isinstance(c, dict)
|
||||
and c.get("profile_name")
|
||||
and c.get("profile_name") != "noise"
|
||||
and c.get("status") in ("completed", "force_stopped")
|
||||
)
|
||||
if labeled_count > self._last_suggestions_labeled_count:
|
||||
self._last_suggestions_labeled_count = labeled_count
|
||||
self._update_model_suggestions()
|
||||
self._update_detection_suggestions()
|
||||
|
||||
# 4. Run multi-cycle batch simulation when enough new labeled cycles have accumulated
|
||||
self._maybe_run_batch_simulation()
|
||||
@@ -517,18 +535,27 @@ class LearningManager:
|
||||
# even if the matcher was confident. Downgrade to a feedback request so
|
||||
# the user can verify the match; this catches confident but wrong labels.
|
||||
ml_quality = cycle_data.get("ml_quality_score")
|
||||
ml_suspicious = (
|
||||
isinstance(ml_quality, float)
|
||||
and ml_quality >= ML_QUALITY_SUSPICIOUS_THRESHOLD
|
||||
)
|
||||
# Use float() so numpy scalars (float32/float64) returned by resolve_scorer
|
||||
# are accepted — isinstance(numpy_float, float) is False in NumPy ≥ 2.0.
|
||||
# Wrap in try/except so non-numeric sentinel values are silently ignored.
|
||||
try:
|
||||
ml_suspicious = (
|
||||
ml_quality is not None
|
||||
and float(ml_quality) >= ML_QUALITY_SUSPICIOUS_THRESHOLD
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
ml_suspicious = False
|
||||
# Also downgrade when the cycle's power trace is mostly outside the
|
||||
# profile envelope band (low conformance = the shape matched but the
|
||||
# actual power levels are inconsistent with the profile).
|
||||
_conformance = cycle_data.get("envelope_conformance")
|
||||
envelope_suspicious = (
|
||||
isinstance(_conformance, float)
|
||||
and _conformance < 0.40
|
||||
)
|
||||
try:
|
||||
envelope_suspicious = (
|
||||
_conformance is not None
|
||||
and float(_conformance) < 0.40
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
envelope_suspicious = False
|
||||
if route_conf >= auto_label_conf:
|
||||
if ml_suspicious or envelope_suspicious:
|
||||
if ml_suspicious:
|
||||
|
||||
Reference in New Issue
Block a user