fix(review): quick FT path enforces 3-state exclusivity + twin propagation; changelog + twin caveat
set_false_trigger now clears reviewed_real when flagging false_trigger=True
(mirrors update_event_review's exclusivity), and the quick
PATCH /db/events/{id}/false_trigger endpoint now calls
propagate_review_to_twins after the flag write, matching the sidecar PATCH
path's try/except-with-log.warning pattern. Previously the quick path could
leave both flags set and never touched twins.
Also corrects the v0.25.0 CHANGELOG bullet (exclusivity was not actually
enforced on the quick path until this commit) and adds a caveat comment on
find_twins about rare clamped/saturated-PVS false-positive twin matches.
This commit is contained in:
+7
-4
@@ -24,13 +24,16 @@ window).
|
||||
`SeismoDb()` construction, no manual migration). `query_events` /
|
||||
`get_event` (and thus `/db/events`) return it automatically (`SELECT *`).
|
||||
- **Mutual exclusivity with `false_trigger`** — setting `reviewed_real=1`
|
||||
clears `false_trigger`, and vice versa, both via `set_false_trigger` /
|
||||
the sidecar review PATCH path.
|
||||
clears `false_trigger`, and vice versa, enforced on both review paths:
|
||||
the sidecar review PATCH (`update_event_review`) and the quick
|
||||
`PATCH /db/events/{id}/false_trigger` endpoint (`set_false_trigger`).
|
||||
- **`find_twins`** — matches an event's histogram/waveform twins by serial +
|
||||
identical peak-vector-sum + a timestamp window.
|
||||
- **`propagate_review_to_twins`** — copies an event's `false_trigger`/
|
||||
`reviewed_real` state onto its twins, wired into the
|
||||
`PATCH /db/events/{id}/sidecar` review path so flagging one flags both.
|
||||
`reviewed_real` state onto its twins, wired into both the
|
||||
`PATCH /db/events/{id}/sidecar` review path and the quick
|
||||
`PATCH /db/events/{id}/false_trigger` path, so flagging one flags both
|
||||
regardless of which endpoint made the change.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+28
-5
@@ -610,6 +610,16 @@ class SeismoDb:
|
||||
within ``window_seconds`` of this event's timestamp. Excludes the
|
||||
event itself. Returns [] if the event or any required field
|
||||
(serial / peak_vector_sum / timestamp) is missing.
|
||||
|
||||
Caveat: identical-PVS matching is a proxy for "same physical event
|
||||
recorded twice," not a guarantee. In the rare case where the device
|
||||
clamps/saturates PVS (clamped to sqrt(3) * geo_range), two distinct
|
||||
saturated events on the same serial within the window can share the
|
||||
same clamped PVS value and be matched as twins even though they are
|
||||
different events. This is harmless in practice — false_trigger/
|
||||
reviewed_real are derived/index columns re-derivable from the
|
||||
sidecar source of truth — but worth knowing if twin counts look
|
||||
surprising on a saturated/clamped run.
|
||||
"""
|
||||
row = self.get_event(event_id)
|
||||
if not row:
|
||||
@@ -652,12 +662,25 @@ class SeismoDb:
|
||||
return moved
|
||||
|
||||
def set_false_trigger(self, event_id: str, value: bool) -> bool:
|
||||
"""Set or clear the false_trigger flag on an event. Returns True if found."""
|
||||
"""
|
||||
Set or clear the false_trigger flag on an event. Returns True if found.
|
||||
|
||||
Mirrors the 3-state exclusivity enforced by `update_event_review`:
|
||||
setting false_trigger true also clears `reviewed_real` (a false
|
||||
trigger can't also be a confirmed-real event). Clearing false_trigger
|
||||
leaves `reviewed_real` untouched.
|
||||
"""
|
||||
with self._connect() as conn:
|
||||
cur = conn.execute(
|
||||
"UPDATE events SET false_trigger=? WHERE id=?",
|
||||
(1 if value else 0, event_id),
|
||||
)
|
||||
if value:
|
||||
cur = conn.execute(
|
||||
"UPDATE events SET false_trigger=1, reviewed_real=0 WHERE id=?",
|
||||
(event_id,),
|
||||
)
|
||||
else:
|
||||
cur = conn.execute(
|
||||
"UPDATE events SET false_trigger=0 WHERE id=?",
|
||||
(event_id,),
|
||||
)
|
||||
return cur.rowcount > 0
|
||||
|
||||
def delete_event(self, event_id: str) -> Optional[dict]:
|
||||
|
||||
+10
-1
@@ -1957,7 +1957,10 @@ def db_set_false_trigger(
|
||||
value: bool = Query(..., description="True to flag as false trigger, False to clear"),
|
||||
) -> dict:
|
||||
"""
|
||||
Set or clear the false_trigger flag on a single event.
|
||||
Set or clear the false_trigger flag on a single event. Enforces the
|
||||
same 3-state exclusivity as the sidecar review PATCH (flagging false_trigger
|
||||
clears reviewed_real) and propagates the resulting flags to the event's
|
||||
histogram/waveform twin(s), same as the sidecar path.
|
||||
|
||||
Used by the terra-view event review UI.
|
||||
Returns 404 if the event_id is not found.
|
||||
@@ -1965,6 +1968,12 @@ def db_set_false_trigger(
|
||||
found = _get_db().set_false_trigger(event_id, value)
|
||||
if not found:
|
||||
raise HTTPException(status_code=404, detail=f"Event {event_id} not found")
|
||||
|
||||
try:
|
||||
_get_db().propagate_review_to_twins(event_id)
|
||||
except Exception as exc:
|
||||
log.warning("twin review-propagation failed for %s: %s", event_id, exc)
|
||||
|
||||
return {"status": "ok", "event_id": event_id, "false_trigger": value}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from sfm.database import SeismoDb
|
||||
from minimateplus.models import Event
|
||||
|
||||
|
||||
def _ins(db, eid_key="01110000", serial="BE1"):
|
||||
ev = Event(index=0); ev._waveform_key = bytes.fromhex(eid_key)
|
||||
db.insert_events([ev], serial=serial)
|
||||
return db.query_events(serial=serial)[0]["id"]
|
||||
|
||||
|
||||
def test_set_false_trigger_clears_reviewed_real(tmp_path):
|
||||
db = SeismoDb(tmp_path / "s.db"); eid = _ins(db)
|
||||
db.update_event_review(eid, {"reviewed_real": True})
|
||||
assert db.get_event(eid)["reviewed_real"] == 1
|
||||
|
||||
db.set_false_trigger(eid, True)
|
||||
row = db.get_event(eid)
|
||||
assert row["false_trigger"] == 1 and row["reviewed_real"] == 0
|
||||
|
||||
|
||||
def test_clearing_false_trigger_leaves_reviewed_real_untouched(tmp_path):
|
||||
db = SeismoDb(tmp_path / "s.db"); eid = _ins(db)
|
||||
db.update_event_review(eid, {"false_trigger": True})
|
||||
db.set_false_trigger(eid, False)
|
||||
row = db.get_event(eid)
|
||||
assert row["false_trigger"] == 0
|
||||
# reviewed_real was never set true, so it stays 0 either way; the point
|
||||
# is set_false_trigger(False) does not touch the column at all.
|
||||
assert row["reviewed_real"] == 0
|
||||
Reference in New Issue
Block a user