WashData ML subsystem (experimental, gated)
Compact, NumPy-only models plus the runtime that trains and consumes them.
No new dependencies (NumPy is already in manifest.json). Everything here is
gated by flags in const.py and is inert until enabled, so the proven
detection/matching/ETA code paths are unchanged by default.
Feature flags (const.py)
SHOW_ML_LAB- show the ML Lab panel tab (shadow-mode comparison + review).ENABLE_ML_SUGGESTIONS- surface ML-calibrated setting suggestions alongside the classic ones (MLSuggestionEngine).ENABLE_ML_TRAINING- allow the scheduled/manual on-device training loop.CONF_ENABLE_ML_MODELS(per-device option) - opt-in gate (viaml_models_enabled(options)) for feeding ML signals into runtime decisions; default off so callers keep existing behavior. (No runtime consumer wires this yet - the live ML paths below run under their own flags.)
What ships here
promoted_manifest.json+<name>_model.py- the embedded baseline models (the broad-corpus models trained offline in/root/ml_washdata). Each module is self-contained and exposesscore(),predict(),FEATURE_COLUMNS,THRESHOLD,MODEL_METRICS.<name>_feature_contract.jsondocuments the live data each feature comes from;<name>_parity.jsonare golden feature→score cases the tests assert against.feature_extraction.py- NumPy-only runtime feature extractors (latest_end_event_features,live_match_features,quality_features,profile_expectation, energy integration) matching the models'FEATURE_COLUMNS.engine.py-resolve_scorer(capability, store), the single bridge that returns a classifier scoring callable preferring an on-device trained spec over the embedded baseline ("on_device"vs"baseline");resolve_regressor( capability, store)is its regression twin forstandardized_linearheads that have no shipped baseline (returns(None, None)until one is promoted), plusml_models_enabled(opt-in gate) andavailable_models(manifest provenance).trainer.py- NumPy-only training for two spec kinds: logistic classifiers (fit_logistic,select_threshold,binary_metrics,auc,build_spec/score_spec- byte-compatible with the embeddedscore()math) and ridge regressors (fit_ridge,regression_metrics,build_regression_spec/predict_value_spec- standardized features + standardized target).training_task.py- on-device orchestration: derives labels from the device's own cycles (end events from trace geometry; quality from status + ML-Lab review labels; live_match from match-ranking-history snapshots), synthesises completion-fraction examples for the regression capabilities, trains, and promotes a classifier only when its held-out AUC is within margin of the baseline (a regressor only when its held-out MAE beats the naive elapsed/ expected projection).matching_tuner.py-tune_matching_config(cycles): NumPy-only, executor-safe leave-one-out tuning of the matcher's bounded scoring weights (corr_weight,duration_weight,energy_weight,dtw_ensemble_w) over the device's own labelled cycles. Same promotion discipline as the models (gate on a held-out split by a margin); it only ever changes the emphasis between shape/level/energy, never structural matching behaviour.
Models (all standardized-logistic; only models that beat their baseline are shipped):
hybrid_curve_quality_model- P(finished cycle is a problem).live_match_commit_model- P(top-1 live program match is correct).cycle_end_detector_model- P(a low-power event is the true end vs a pause).
(No regression baseline is shipped: the remaining_time and total_energy
completion-fraction regressors did not beat the expected_duration - elapsed
heuristic on the broad corpus, so they stay inert until on-device training
promotes a per-device spec that beats that naive projection.)
How trained models reach inference
resolve_scorer(capability, store) is used by the ML Lab shadow comparison
(ws_api._compute_ml_comparison) and by MLSuggestionEngine. If the profile
store holds an on-device spec for that capability (trained by training_task and
persisted under ml_model_versions), it is used; otherwise the embedded baseline
module is used. The shipped baseline is a broad-corpus model - per-user accuracy
gains come from on-device training, not from replacing the baseline.
Regenerating the embedded baseline (offline lab only)
cd /root/ml_washdata
./ml.sh experiment # retrain + verify the determinism gate
python promote_to_integration.py --target <this directory> # reads output/promoted/
promote_to_integration.py refuses to copy any model whose encode/decode round
trip is not deterministic. On-device training never touches these baseline files;
it writes trained specs into the profile store instead.