# WashData - Home Assistant integration for appliance cycle monitoring via smart plugs. # Copyright (C) 2026 Lukas Bandura # SPDX-License-Identifier: AGPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . """Auto-generated by ml_washdata/wash_ml/promotion.py. Do not edit by hand. Embedded WashData model: 'live_match_commit' (target: 'match_top1_correct'). Kind: standardized logistic regression. Runtime dependency: NumPy only. Regenerate with ``./ml.sh experiment`` in the ml_washdata lab and copy the new file. Determinism check at generation time: max_abs_score_diff=1.6467e-08 over 2392 rows. Usage in the integration:: from .live_match_commit_model import score, predict, FEATURE_COLUMNS features = build_runtime_features(...) # must populate FEATURE_COLUMNS is_positive = predict(features) """ from __future__ import annotations import base64 import gzip import json from typing import Mapping import numpy as np MODEL_NAME = 'live_match_commit' MODEL_TARGET = 'match_top1_correct' MODEL_KIND = 'standardized_logistic' TARGET_UNITS = '' THRESHOLD = 0.371786 FEATURE_COLUMNS = [ 'match_progress_top1', 'top1_distance', 'margin', 'distance_ratio', 'candidate_count_log', 'prefix_active_fraction', 'duration_ratio_top1', 'elapsed_log', ] # Provenance (metrics at training time): MODEL_METRICS = json.loads("""{ "owner_holdout": { "accuracy": 0.771789, "balanced_accuracy": 0.741539, "f1": 0.836751, "fn": 120, "fp": 79, "positive_rate": 0.675459, "precision": 0.865874, "problem_recall": 0.809524, "rows": 872, "specificity": 0.673554, "tn": 163, "tp": 510 } }""") _MODEL_BLOB = ( 'H4sIAAAAAAACA1VU246kNhD9FYunRKGJDb7BKpGi0UaKtJONktmn1Qq5wXRbAxgZdzqb1fx7jmG6NXmiqMupOnXxt+zozJo1tNCM' 'aVELnWednaMNWfOZFoJyJcqS57Rglaoha4gVo5UQsoQoGOe6ZiJnRc2kkoolX64oE7pi+RsEVWheUa7FF2TwdtjwS0UlFzo5Ms2o' 'oila8lJQIWV+YEXJeF1zKfIDUJlAjUwmWai6qsSW7I6R1JwBRaqUI1gTbd+amDVZSUt5oPJQ1k8lbXjdUPEDpQ2lWZ4N8LsE23Z+' 'vEwzevE5m0zszu0S/CnYdW2jXxgc06ft3RrN3Fn8Tyac3AzhpmuDic5D0Zm5dz3SA/Qyx3b0J2iXYAf3T2u66P627RCS4Lf4yxY4' '7/G3dHY0ywoCKRh8Ti4CbZpc4iOpMFqW8Hp2cw9FKqA3oXf/7hGoyHWpRhuD60DqW+avsw3t2Y+9v8SkMF2HxN3XNH2lmNJ1nh3N' 'mJigbW+N6HwF48C2RamkEgx/c9awkkJYskbBvPjVbdRAwyZP+HFRb8Q7tyauKVwKrXhS+uNopxY2M46bhdYCi5IFf0XBWpV5ti6I' 'HFzn4tcdEHsHj5hSywoCUgtGX17ybDYTkmZjKmCf32u3YPLRbnP9AONhM5LdeOj8PLjegnJD/vgOnT8wcp8ecSuJZwvfgCoj2RbC' 'TN8XwHzY4kn0xJCU82YkO/z1bGeyItCSn38CCPYodf4d8QAMV7da8mztsnu7+USQk6xXd481pHfDYANu8U1BR4u67UpuZcdUylMw' 'brY98TO5WvNMzCX6w2iOdlzfkYD9c5Ml02WNifRyAczDx8fH357aX9//8vTpz/ftw8cPnx5//4sMwU8b4Y3QTiSYGSt2KtIKYm8Q' '3t7eB1rQu27FENF+llSvcjpvXWnNucCVUq1Lzfj2JJSqqvFspJeE0ppj99KbwSvFZbVdNB4UhcD8fwh4B7A5FfuSMpztZDDtq1nP' 'aIwp0P3Jp3OffG/HH7dzxXnadCz7NmzX+zrJu7W9zC5iNbKkuQ0pMavSPciX/wDk5KXIHwUAAA==' ) _MODEL_CACHE: dict | None = None def _load() -> dict: global _MODEL_CACHE if _MODEL_CACHE is None: payload = gzip.decompress(base64.b64decode(_MODEL_BLOB.encode("ascii"))) spec = json.loads(payload.decode("utf-8")) _MODEL_CACHE = { "center": np.asarray(spec["center"], dtype=float), "scale": np.asarray(spec["scale"], dtype=float), "coef": np.asarray(spec["coef"], dtype=float), "bias": float(spec["bias"]), "threshold": float(spec["threshold"]), "output_center": float(spec.get("output_center") or 0.0), "output_scale": float(spec.get("output_scale") if spec.get("output_scale") is not None else 1.0), "feature_columns": list(spec["feature_columns"]), } return _MODEL_CACHE def score(features: Mapping[str, float]) -> float: """Return the model probability in [0, 1] for one feature mapping.""" model = _load() vector = np.array( [float(features.get(column) or 0.0) for column in model["feature_columns"]], dtype=float, ) scaled = (vector - model["center"]) / model["scale"] logit = float(scaled @ model["coef"] + model["bias"]) logit = max(-60.0, min(60.0, logit)) return 1.0 / (1.0 + np.exp(-logit)) def predict(features: Mapping[str, float]) -> bool: """True when the example crosses the embedded decision threshold.""" return score(features) >= _load()["threshold"]