feat(offset): DC-offset detector productionized into the shape pipeline
Productionizes the validated scratch/offset_scan3.py: a DC offset (baseline shifted off zero — sensor bumped/settled/drifted) is |median(pre-trigger)| >= 5 counts (0.025 in/s) AND flat across pre/mid/end thirds (spread <= 0.02); a transient moves one third and is rejected by the spread test. - shape_metrics: offset_from_samples / offset_from_h5 (reads .h5 samples + pretrig_samples attr; range-aware via the .h5's in/s float samples) - events schema: shape_offset / _axis / _pre / _spread (via _SCHEMA + the _migrate ADD COLUMN loop only; NOT the Migration-1 rebuild), threaded through insert + upsert mirroring shape_* - ingest: computed at all three waveform_store save paths alongside shape - backfill_event_shape: also computes + stores (and stale-clears) offset - exposed via /db/events automatically (SELECT *) Gating to waveforms is done downstream in terra-view ft_suspicion (mirrors how shape is ignored for histograms), not at the SFM call sites. 13 new tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDXjZCr4RqT2U3QvMDhgzf
This commit is contained in:
+25
-1
@@ -41,7 +41,7 @@ from minimateplus.blastware_file import blastware_filename, write_blastware_file
|
||||
from minimateplus.framing import S3Frame
|
||||
from minimateplus.models import Event
|
||||
from sfm import event_hdf5
|
||||
from sfm.shape_metrics import shape_from_h5
|
||||
from sfm.shape_metrics import shape_from_h5, offset_from_h5
|
||||
|
||||
log = logging.getLogger("sfm.waveform_store")
|
||||
|
||||
@@ -270,6 +270,13 @@ class WaveformStore:
|
||||
"shape_sample_count": _shape["sample_count"],
|
||||
"shape_axis": _shape["axis"],
|
||||
} if _shape else {}
|
||||
_offset = offset_from_h5(hdf5_path) if hdf5_filename else None
|
||||
_offset_rec = {
|
||||
"shape_offset": 1 if _offset["offset"] else 0,
|
||||
"shape_offset_axis": _offset["axis"],
|
||||
"shape_offset_pre": _offset["pre"],
|
||||
"shape_offset_spread": _offset["spread"],
|
||||
} if _offset else {}
|
||||
return {
|
||||
"filename": filename,
|
||||
"filesize": filesize,
|
||||
@@ -278,6 +285,7 @@ class WaveformStore:
|
||||
"hdf5_filename": hdf5_filename,
|
||||
"sidecar_filename": sidecar_path.name,
|
||||
**_shape_rec,
|
||||
**_offset_rec,
|
||||
}
|
||||
|
||||
def save_imported_bw(
|
||||
@@ -461,6 +469,13 @@ class WaveformStore:
|
||||
"shape_sample_count": _shape["sample_count"],
|
||||
"shape_axis": _shape["axis"],
|
||||
} if _shape else {}
|
||||
_offset = offset_from_h5(hdf5_path) if hdf5_filename else None
|
||||
_offset_rec = {
|
||||
"shape_offset": 1 if _offset["offset"] else 0,
|
||||
"shape_offset_axis": _offset["axis"],
|
||||
"shape_offset_pre": _offset["pre"],
|
||||
"shape_offset_spread": _offset["spread"],
|
||||
} if _offset else {}
|
||||
return ev, {
|
||||
"filename": filename,
|
||||
"filesize": filesize,
|
||||
@@ -470,6 +485,7 @@ class WaveformStore:
|
||||
"sidecar_filename": sidecar_path.name,
|
||||
"serial": serial,
|
||||
**_shape_rec,
|
||||
**_offset_rec,
|
||||
}
|
||||
|
||||
def save_imported_idf(
|
||||
@@ -751,6 +767,13 @@ class WaveformStore:
|
||||
"shape_sample_count": _shape["sample_count"],
|
||||
"shape_axis": _shape["axis"],
|
||||
} if _shape else {}
|
||||
_offset = offset_from_h5(hdf5_path) if hdf5_filename else None
|
||||
_offset_rec = {
|
||||
"shape_offset": 1 if _offset["offset"] else 0,
|
||||
"shape_offset_axis": _offset["axis"],
|
||||
"shape_offset_pre": _offset["pre"],
|
||||
"shape_offset_spread": _offset["spread"],
|
||||
} if _offset else {}
|
||||
return ev, {
|
||||
"filename": filename,
|
||||
"filesize": filesize,
|
||||
@@ -760,6 +783,7 @@ class WaveformStore:
|
||||
"sidecar_filename": sidecar_path.name,
|
||||
"serial": serial,
|
||||
**_shape_rec,
|
||||
**_offset_rec,
|
||||
}
|
||||
|
||||
def load_a5(self, serial: str, filename: str) -> Optional[list[S3Frame]]:
|
||||
|
||||
Reference in New Issue
Block a user