From 8c17af4849fa8ec4231ab55fd3bc7a2f5ded040e Mon Sep 17 00:00:00 2001 From: serversdown Date: Mon, 8 Jun 2026 22:50:18 +0000 Subject: [PATCH] fix: ignore garbled measurement-state reads (phantom STOPPED/STARTED) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A buffer desync on the shared persistent connection (commonly right after a DRD/DOD test) can make a Measure? read return a stray value. The state classifier treated anything not in {"Start","Measure"} as "not measuring", so a garbled read logged a phantom STOPPED, the next clean read logged STARTED, and that reset measurement_start_time — producing constant STOPPED/STARTED device-log pairs and a drifting elapsed timer. Now only recognized states drive transitions: {"Start","Measure"} = measuring, {"Stop"} = stopped, anything else = no change. Garbled reads are also not persisted as the cached state, so they can't poison the next transition check. Builds on the earlier Start<->Measure normalization. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/services.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/app/services.py b/app/services.py index 5e15a06..b5c68ea 100644 --- a/app/services.py +++ b/app/services.py @@ -75,13 +75,24 @@ def persist_snapshot(s: NL43Snapshot, db: Session): # but the DRD stream path tags snapshots "Measure" (and the DOD fallback also # uses "Measure"). Treat ALL of these as "measuring" — otherwise opening and # closing the live stream flips state "Start"->"Measure"->"Start", which the - # old equality check misread as stop-then-start and RESET measurement_start_time - # every single time (the "elapsed time keeps resetting / shows wrong value on - # another computer" bug — and each extra viewer made it worse). + # old equality check misread as stop-then-start and reset measurement_start_time. + # + # Also: only act on RECOGNIZED states. A buffer desync on the shared connection + # (e.g. right after a DRD/DOD test) can make a Measure? read return a stray, + # garbled value; treating that as "not measuring" produced constant phantom + # "STOPPED -> STARTED" log pairs and reset the timer. Ignore unknown reads. MEASURING_STATES = {"Start", "Measure"} - is_measuring = new_state in MEASURING_STATES + STOPPED_STATES = {"Stop"} was_measuring = previous_state in MEASURING_STATES + if new_state in MEASURING_STATES: + is_measuring = True + elif new_state in STOPPED_STATES: + is_measuring = False + else: + logger.warning(f"Ignoring unrecognized measurement state for {s.unit_id}: {new_state!r}") + is_measuring = was_measuring # garbled/unknown read — no transition + if not was_measuring and is_measuring: # Measurement just started - record the start time row.measurement_start_time = datetime.utcnow() @@ -103,7 +114,10 @@ def persist_snapshot(s: NL43Snapshot, db: Session): except Exception: pass - row.measurement_state = new_state + # Only persist a recognized state so one garbled read can't poison the next + # transition check (which would manufacture the phantom STOPPED/STARTED pair). + if new_state in MEASURING_STATES or new_state in STOPPED_STATES: + row.measurement_state = new_state row.counter = s.counter row.lp = s.lp row.leq = s.leq