v0.27.0 Decoder fixes, offset exploration and testing. #34
@@ -8,6 +8,84 @@ All notable changes to seismo-relay are documented here.
|
||||
|
||||
---
|
||||
|
||||
## v0.27.0 — 2026-08-28
|
||||
|
||||
**Per-sample decoder verification at scale, plus the offset investigation.**
|
||||
The series-3 codec is now verified sample-by-sample against **14,338** preserved
|
||||
Blastware ASCII exports — 1,249 waveform and 13,089 histogram, spanning 45 units
|
||||
and files back to 2018. That is 11x the ground truth the production store
|
||||
carried, and it found one real codec bug (below).
|
||||
|
||||
### Fixed
|
||||
- **Sub-minute histograms with a partial final block decoded to nothing**
|
||||
(`histogram_codec.detect_multi_interval_stride`). The stride search confirmed
|
||||
itself on a third block header whenever the body was long enough to hold one —
|
||||
but a body can exceed two strides and still contain only two real blocks, because
|
||||
a *partial* final block leaves trailing padding. BE18193 `T193L0XM.CI0H` (51
|
||||
intervals at 2 s = one full 30-interval block plus a 21-interval remainder, in a
|
||||
2787-byte body) therefore had its correct stride of 612 discarded and produced an
|
||||
empty decode. A missing third header now means end-of-stream rather than
|
||||
disqualification; the block-counter check, which is what actually prevents the
|
||||
false positives that once mis-dispatched 9,082 files, is unchanged.
|
||||
|
||||
Found by decoding the full DL2 archive against its preserved Blastware ASCII
|
||||
exports. Across **63,535 unique** histogram binaries the fix recovers **4 files** —
|
||||
`K440HJCN.3C0H` and `K557IF1U.8K0H` (stride 252), `T191HVNP.0S0H` (92) and
|
||||
`T193L0XM.CI0H` (612) — with **zero** files regressed. Verification over all
|
||||
14,340 archive pairs goes 14,337 → 14,338 exact, the only remainder being two
|
||||
series-4 IDF files that belong to a different codec.
|
||||
|
||||
(The DL2 export keeps a byte-identical `Sent/` mirror of its root, so a naive
|
||||
walk double-counts every binary — 127,035 paths are 63,535 distinct files. The
|
||||
ASCII exports are *not* mirrored, so the 14,340 pair count is already distinct.)
|
||||
|
||||
⚠ Prod stores hold `.h5` files generated before this fix. Those 4 events stay
|
||||
empty until `backfill_sidecars.py` is re-run — not worth a two-hour prod backfill
|
||||
on its own; fold it into the next one.
|
||||
|
||||
- **Histogram/waveform twin matching is now interval-based** (`find_twins`). A real
|
||||
trigger is recorded twice — as a triggered waveform (stamped at the trigger instant)
|
||||
and inside the scheduled histogram whose interval contains it (stamped at the 7am/7pm
|
||||
interval start) — so the two twins can be **hours apart**. The old ±5-minute window
|
||||
silently missed them, which broke review propagation (flagging one twin didn't flag its
|
||||
twin). Twins are now matched by same serial + identical `peak_vector_sum` + opposite
|
||||
record type + the waveform falling within the histogram's interval (bounded by the next
|
||||
same-serial histogram). `window_seconds` is retained but ignored. Fixes terra-view #102
|
||||
sub-task 2.
|
||||
|
||||
- **`/health` reported a hard-coded `0.1.0`** instead of the real service version.
|
||||
`sfm/server.py` now derives its version from `minimateplus.event_file_io.TOOL_VERSION`,
|
||||
making that constant the single source of truth for the service version and the
|
||||
sidecar stamp alike — one place to bump at release.
|
||||
|
||||
- **`CLAUDE.md` had 793 NUL bytes appended** after its last line, which made `grep`
|
||||
treat the file as binary and silently skip it. Present since at least v0.21.0.
|
||||
Stripped.
|
||||
|
||||
### Added
|
||||
- **`docs/offset_investigation.md`** — a dated journal of the "offset" hardware
|
||||
fault: base rate, detector design, per-unit case files, ruled-out hypotheses
|
||||
(each kept with the evidence that killed it), and Instantel's own autozero
|
||||
procedure with its 2027–2069 acceptance window.
|
||||
- **`scratch/verify_against_ascii.py`** — decodes a corpus of BW binaries and
|
||||
diffs every sample against the paired `_ASCII.TXT`. Includes a saturation
|
||||
carve-out: BW clamps clipped events to the range maximum and writes `OORANGE`,
|
||||
while the decoder faithfully reports counts past nominal full scale.
|
||||
- **`scratch/offset_scan3.py`** — offset detector. Measures the resting floor in
|
||||
the *pre-trigger* window (definitionally quiet) and requires it to hold across
|
||||
pre / middle / end. Result: **5 of 45 units (11%)**, stable across a 2x
|
||||
threshold range. Supersedes `offset_scan.py` and `offset_scan2.py`, both kept
|
||||
as the reasoning trail.
|
||||
|
||||
### Verified
|
||||
- **19,244 healthy channel-events sit at a pre-trigger floor of exactly 0.000
|
||||
(62.7%), 94.5% within ±1 quantisation unit, median +0.0000.** No systematic
|
||||
zero-point bias in the decoder — an independent confirmation of the
|
||||
32000-count geo full scale, arrived at from a different direction than the
|
||||
ASCII sample comparisons.
|
||||
|
||||
---
|
||||
|
||||
## v0.26.0 — 2026-08-27
|
||||
|
||||
**Series-3 decode correctness.** Two body-model rewrites, a systematic
|
||||
|
||||
@@ -2,21 +2,24 @@
|
||||
|
||||
Ground-up Python replacement for **Blastware**, Instantel's Windows-only software for
|
||||
managing MiniMate Plus seismographs. Connects over direct RS-232 or cellular modem
|
||||
(Sierra Wireless RV50 / RV55). Current version: **v0.26.0**.
|
||||
(Sierra Wireless RV50 / RV55). Current version: **v0.27.0**.
|
||||
|
||||
---
|
||||
|
||||
## Where things stand (updated 2026-08-27)
|
||||
## Where things stand (updated 2026-08-28)
|
||||
|
||||
Read this first when picking the project back up.
|
||||
|
||||
- **Series-3 decode is correct and verified.** All 11,603 series-3 binaries in
|
||||
the prod snapshot pass every check (channel lengths, peaks vs the device's
|
||||
own reported PPV, nothing above full scale, length vs declared record time).
|
||||
Ground truth: 1,211/1,211 histograms exact per-interval and 75/75 waveform
|
||||
sample counts exact against preserved Blastware ASCII exports.
|
||||
⚠ That is per-sample proof on 11% of files and peak-only consistency on the
|
||||
other 89% — see `docs/instantel_protocol_reference.md` §7.6.1.
|
||||
- **Series-3 decode is verified per-sample at scale (v0.27.0).** The full DL2
|
||||
archive decodes **14,338 / 14,338** paired files exactly against their
|
||||
preserved Blastware ASCII exports — 1,249 waveform + 13,089 histogram, 45
|
||||
units, files back to 2018. That is 11x the ground truth the prod store
|
||||
carried, and it supersedes the old "per-sample on 11%, peak-only on 89%"
|
||||
caveat. Harness: `scratch/verify_against_ascii.py` (note its saturation
|
||||
carve-out — BW clamps clipped events, the decoder reports true counts).
|
||||
Independent corroboration of the 32000-count scale: 19,244 healthy
|
||||
channel-events sit at a pre-trigger floor of exactly 0.000 (62.7%), 94.5%
|
||||
within ±1 quantisation unit, median +0.0000 — no zero-point bias.
|
||||
- **Series-4 (Thor / Micromate) is NOT verified.** UM-series sits at ~48%
|
||||
against device peaks with a ~1.7% systematic bias and a near-zero tail.
|
||||
Thor IDFW is pinned to `decode_waveform_legacy` deliberately.
|
||||
@@ -24,11 +27,23 @@ Read this first when picking the project back up.
|
||||
(= 10.0/1.25) units discrepancy; `scripts/backfill_sidecars.py --force` also
|
||||
inserts DB rows for store files that have none (one-time per store) and the
|
||||
dry-run does not report that count.
|
||||
- **After any codec change, regenerate the store** — `backfill_sidecars.py
|
||||
--force` then `backfill_event_shape.py`, DB backup first. Stored `.h5` files
|
||||
do not update themselves.
|
||||
- **Parked:** the "offset" hardware-fault investigation (Appendix E of the
|
||||
protocol reference) pending the multi-year BW archive.
|
||||
- **After any codec change, regenerate the store** — `backfill_sidecars.py`
|
||||
then `backfill_event_shape.py`, DB backup first. Stored `.h5` files do not
|
||||
update themselves. No `--force` needed as long as `TOOL_VERSION` was bumped
|
||||
(it gates regeneration). ⚠ On the office NAS this takes **~2 hours**
|
||||
(~1.5 files/sec vs 85/sec on the dev box — gzip-4 in `sfm/event_hdf5.py`
|
||||
against a Synology CPU). Budget it up front.
|
||||
**v0.27.0 owes prod a backfill:** the partial-final-block fix recovers 4
|
||||
histograms that are still empty in the store.
|
||||
- **The "offset" hardware fault has its own journal** --
|
||||
`docs/offset_investigation.md`. **5 of 45 units (11%)**, and the fault is
|
||||
**persistent** — it stays until the geophone is serviced. Detect it with
|
||||
`scratch/offset_scan3.py`: the resting floor in the **pre-trigger** window,
|
||||
required to hold across pre/middle/end. Never score only the dominant-peak
|
||||
axis and never use the mean — both produce false recoveries (see the
|
||||
retraction banner in the journal). Instantel's autozero procedure and its
|
||||
2027-2069 acceptance window are recorded there too. Best open lead is
|
||||
`SUB 0x0E` (unimplemented), which may carry those very numbers.
|
||||
|
||||
|
||||
When new information about the protocol is discovered, please update the instantel_protocol_reference.md with the findings in addition to this document
|
||||
@@ -1803,4 +1818,4 @@ body) because writing a dial string may require DLE escaping for embedded contro
|
||||
|
||||
To parse BW TX captures: use `bridges/captures/` scripts or adapt the `find_write_frames()` pattern
|
||||
in `/tmp/analyze_write_payload.py` — it correctly handles `0x10 0x03` DLE-escaped ETX bytes
|
||||
inside write frame data (the naive parser terminates early at the escaped `0x03`). | ||||