import sqlite3 from sfm.database import SeismoDb from minimateplus.models import Event, Timestamp def _ins(db, key, serial, pvs, ts, record_type="Waveform"): ev = Event(index=0) ev._waveform_key = bytes.fromhex(key) ev.timestamp = ts db.insert_events([ev], serial=serial) row = [r for r in db.query_events(serial=serial) if r["waveform_key"] == key][0] with sqlite3.connect(db.db_path) as c: c.execute("UPDATE events SET peak_vector_sum=?, record_type=? WHERE id=?", (pvs, record_type, row["id"])) return row["id"] def _ts(hour, minute, second=0, day=25): return Timestamp(raw=b"", flag=0x10, year=2026, unknown_byte=0, month=2, day=day, hour=hour, minute=minute, second=second) def test_histogram_and_waveform_twin_across_hours(tmp_path): # The real UM12947 case: histogram stamped at its 7pm interval start, the # triggered waveform 75 min later — same serial + identical PVS. The old # ±5-min window missed this; interval matching catches it, both directions. db = SeismoDb(tmp_path / "s.db") hist_pm = _ins(db, "01110001", "BE1", 0.4763, _ts(19, 31, 17), "Histogram") wave = _ins(db, "01110002", "BE1", 0.4763, _ts(20, 46, 44), "Waveform") _ins(db, "01110003", "BE1", 0.0100, _ts(7, 0, 0, day=26), "Histogram") # bounds the interval assert {r["id"] for r in db.find_twins(hist_pm)} == {wave} assert {r["id"] for r in db.find_twins(wave)} == {hist_pm} def test_same_type_not_twinned(tmp_path): # Two waveforms, same serial + PVS, seconds apart → NOT twins (cross-type only). db = SeismoDb(tmp_path / "s.db") a = _ins(db, "01110001", "BE1", 0.4763, _ts(20, 19, 5), "Waveform") _ins(db, "01110002", "BE1", 0.4763, _ts(20, 19, 45), "Waveform") assert db.find_twins(a) == [] def test_waveform_matches_only_the_containing_interval(tmp_path): # Two overnight intervals with the same PVS; a waveform in the SECOND interval # must twin with that histogram, never the first — even though PVS matches both. db = SeismoDb(tmp_path / "s.db") h1 = _ins(db, "01110001", "BE1", 0.4763, _ts(19, 0, 0, day=25), "Histogram") h2 = _ins(db, "01110002", "BE1", 0.4763, _ts(7, 0, 0, day=26), "Histogram") w = _ins(db, "01110003", "BE1", 0.4763, _ts(8, 0, 0, day=26), "Waveform") assert {r["id"] for r in db.find_twins(w)} == {h2} assert w not in {r["id"] for r in db.find_twins(h1)} def test_different_pvs_not_twinned(tmp_path): db = SeismoDb(tmp_path / "s.db") h = _ins(db, "01110001", "BE1", 0.4763, _ts(19, 0, 0), "Histogram") _ins(db, "01110002", "BE1", 0.9999, _ts(20, 0, 0), "Waveform") # different PVS assert db.find_twins(h) == [] def test_open_ended_latest_interval(tmp_path): # A waveform after the latest histogram (nothing bounds the interval) still twins. db = SeismoDb(tmp_path / "s.db") h = _ins(db, "01110001", "BE1", 0.4763, _ts(19, 0, 0), "Histogram") w = _ins(db, "01110002", "BE1", 0.4763, _ts(23, 30, 0), "Waveform") assert {r["id"] for r in db.find_twins(h)} == {w} def test_missing_fields_returns_empty(tmp_path): db = SeismoDb(tmp_path / "s.db") assert db.find_twins("nonexistent-id") == []