feat(db): find_twins (serial + identical PVS + time window)
This commit is contained in:
@@ -603,6 +603,34 @@ class SeismoDb:
|
|||||||
).fetchall()
|
).fetchall()
|
||||||
return [dict(r) for r in rows]
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
|
def find_twins(self, event_id: str, *, window_seconds: int = 300) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Find this event's histogram/waveform twin(s): rows sharing the same
|
||||||
|
serial and an identical peak_vector_sum, whose timestamp falls
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
row = self.get_event(event_id)
|
||||||
|
if not row:
|
||||||
|
return []
|
||||||
|
serial = row.get("serial"); pvs = row.get("peak_vector_sum"); ts = row.get("timestamp")
|
||||||
|
if serial is None or pvs is None or not ts:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
t = datetime.datetime.fromisoformat(ts.replace(" ", "T"))
|
||||||
|
except ValueError:
|
||||||
|
return []
|
||||||
|
lo = (t - datetime.timedelta(seconds=window_seconds)).isoformat()
|
||||||
|
hi = (t + datetime.timedelta(seconds=window_seconds)).isoformat()
|
||||||
|
with self._connect() as conn:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT * FROM events WHERE serial=? AND id!=? AND peak_vector_sum=? "
|
||||||
|
"AND timestamp BETWEEN ? AND ?",
|
||||||
|
(serial, event_id, pvs, lo, hi),
|
||||||
|
).fetchall()
|
||||||
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
def set_false_trigger(self, event_id: str, value: bool) -> bool:
|
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."""
|
||||||
with self._connect() as conn:
|
with self._connect() as conn:
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import datetime
|
||||||
|
from sfm.database import SeismoDb
|
||||||
|
from minimateplus.models import Event, Timestamp
|
||||||
|
|
||||||
|
|
||||||
|
def _ins(db, key, serial, pvs, ts):
|
||||||
|
ev = Event(index=0)
|
||||||
|
ev._waveform_key = bytes.fromhex(key)
|
||||||
|
ev.timestamp = ts
|
||||||
|
# peak_vector_sum comes from peak_values; simplest: insert then UPDATE pvs directly
|
||||||
|
db.insert_events([ev], serial=serial)
|
||||||
|
row = [r for r in db.query_events(serial=serial) if r["waveform_key"] == key][0]
|
||||||
|
import sqlite3
|
||||||
|
with sqlite3.connect(db.db_path) as c:
|
||||||
|
c.execute("UPDATE events SET peak_vector_sum=? WHERE id=?", (pvs, row["id"]))
|
||||||
|
return row["id"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_twins_matches_same_serial_pvs_near_time(tmp_path):
|
||||||
|
db = SeismoDb(tmp_path / "s.db")
|
||||||
|
base = Timestamp(raw=b"", flag=0x10, year=2026, unknown_byte=0, month=2, day=25, hour=20, minute=19, second=5)
|
||||||
|
twin = Timestamp(raw=b"", flag=0x10, year=2026, unknown_byte=0, month=2, day=25, hour=20, minute=19, second=45)
|
||||||
|
far = Timestamp(raw=b"", flag=0x10, year=2026, unknown_byte=0, month=2, day=25, hour=21, minute=0, second=0)
|
||||||
|
# d needs a timestamp distinct from `twin` (UNIQUE(serial, timestamp) would
|
||||||
|
# otherwise collide with b and UPSERT onto its row instead of inserting a
|
||||||
|
# new one) while staying near `base` in time.
|
||||||
|
near = Timestamp(raw=b"", flag=0x10, year=2026, unknown_byte=0, month=2, day=25, hour=20, minute=19, second=44)
|
||||||
|
a = _ins(db, "01110001", "BE1", 0.4763, base)
|
||||||
|
b = _ins(db, "01110002", "BE1", 0.4763, twin) # twin: same serial+pvs, 40s apart
|
||||||
|
c = _ins(db, "01110003", "BE1", 0.4763, far) # same pvs but >window away
|
||||||
|
d = _ins(db, "01110004", "BE1", 0.9999, near) # near time but different pvs
|
||||||
|
ids = {r["id"] for r in db.find_twins(a, window_seconds=300)}
|
||||||
|
assert ids == {b}
|
||||||
Reference in New Issue
Block a user