mirror of
https://github.com/lynchaos/ashvale-station.git
synced 2026-09-12 12:47:49 +00:00
Outlook to Live, humidity calibration, Models tab rebuilt without scrollers
Seven day outlook moves from History to Live, which now runs four rows. Conditions ahead tightened so the Live column no longer needs a scroller. Adds HumidityCompensator: an additive RH offset estimated by one-step RLS from a trusted hygrometer, clamped to +/-35%, persisted, exposed at POST /api/calibrate/humidity and on the renamed Models and calibration tab. It also implements the psychrometric term (RH moved from element temperature onto air temperature via conserved vapour pressure) but leaves it OFF by default. The thermal argument predicts a hot element reads low; measured against a reference hygrometer this board read 75.4% where the truth was 50.4%, so it reads HIGH and that correction would push it the wrong way. When the flag is enabled, simulate.py applies the exact inverse, per the simulator/compensator trap in DESIGN.md section 2. Models pane rebuilt: the scorecard is one column per target so all 18 heads are visible, and no panel on the tab uses an internal scroller. Verified in Chromium at 1600x900: Live, History and Models all report zero scrollbars, zero clipping, no page scroll, zero console errors. Backtest is numerically identical to the previous commit, confirming the humidity work is a no-op while the flag is off.
This commit is contained in:
@@ -190,6 +190,8 @@ class Station:
|
||||
"cloud_index": cloud,
|
||||
"cpu_offset": (raw.get("cpu_temp") or float("nan")) - (raw.get("temp_raw") or float("nan")),
|
||||
"compensator_k": self.tracker.compensator.k,
|
||||
"hum_offset": self.tracker.hum_compensator.offset,
|
||||
"hum_psychrometric": float(est["hum_c"]) - float(raw.get("hum") or float("nan")),
|
||||
"health": anomaly["health_overall"],
|
||||
"novelty_d2": anomaly["novelty"].get("d2", 0.0),
|
||||
}
|
||||
@@ -265,6 +267,32 @@ class Station:
|
||||
f"k -> {result['k']:.3f} (residual {result['residual']:+.2f} C)")
|
||||
return result
|
||||
|
||||
def calibrate_humidity(self, reference_pct: float) -> Dict:
|
||||
raw_h = self.live.get("hum")
|
||||
raw_t = self.live.get("temp_raw")
|
||||
temp_c = self.live.get("temp_c")
|
||||
if raw_h is None or raw_t is None or temp_c is None:
|
||||
return {"error": "no live reading yet"}
|
||||
result = self.tracker.hum_compensator.calibrate(
|
||||
float(raw_h), float(raw_t), float(temp_c), float(reference_pct))
|
||||
self.save_state()
|
||||
self.store.log_event("calibration", "info",
|
||||
f"rh offset -> {result['offset']:+.2f}% "
|
||||
f"(residual {result['residual']:+.2f}%)")
|
||||
return result
|
||||
|
||||
def reset_humidity_calibration(self) -> Dict:
|
||||
from .estimation import HumidityCompensator
|
||||
self.tracker.hum_compensator = HumidityCompensator(
|
||||
self.cfg.sensor.hum_offset, self.cfg.sensor.hum_offset_min,
|
||||
self.cfg.sensor.hum_offset_max,
|
||||
psychrometric=self.cfg.sensor.hum_psychrometric,
|
||||
)
|
||||
self.save_state()
|
||||
self.store.log_event("calibration", "info",
|
||||
f"rh offset reset to prior {self.cfg.sensor.hum_offset}")
|
||||
return {"offset": self.tracker.hum_compensator.offset, "reset": True, "n": 0}
|
||||
|
||||
def reset_calibration(self) -> Dict:
|
||||
"""Return the self-heating coefficient to its configured prior.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user