From aac1c8e06df73c2d4fdfaa7881504c904c9818b5 Mon Sep 17 00:00:00 2001 From: serversdown Date: Thu, 14 May 2026 21:09:21 +0000 Subject: [PATCH] fix(import): derive record_type from filename suffix instead of hardcoding "Waveform" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BW ACH ingest path was inserting every event with record_type="Waveform" regardless of the actual type because read_blastware_file() had `ev.record_type = "Waveform"` hardcoded, and the live watcher-forward path parses files from a tmp path (suffix ".bw") that doesn't carry the original extension. V10.72+ MiniMate Plus firmware encodes the event type as the last character of the AB0T extension scheme (H=Histogram, W=Waveform, M=Manual, E=Event, C=Combo). This change: 1. Adds derive_record_type_from_filename() public helper in minimateplus/event_file_io.py 2. Uses it inside read_blastware_file() so direct callers (the --dry-run path of scripts/import_bw.py, tests, ad-hoc scripts) get correct types automatically 3. Overrides ev.record_type in WaveformStore.save_imported_bw() using the ORIGINAL filename (source_path.name) — required because the parser sees only the tmp file Old S338 firmware (3-char extensions ending in `0`) and any unrecognized suffix fall back to "Waveform". Existing DB rows ingested before this fix are stuck with record_type="Waveform" — a one-off SQL backfill would fix them retroactively if desired. Terra-view's event modal also derives client-side from the filename, so the UI already shows the correct type for old events even without the backfill. Version bumped to 0.16.1 in pyproject.toml, event_file_io.py TOOL_VERSION, sfm/server.py FastAPI version, and CHANGELOG.md. Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 14 +++++++++ minimateplus/event_file_io.py | 53 +++++++++++++++++++++++++++++++++-- pyproject.toml | 2 +- sfm/server.py | 2 +- sfm/waveform_store.py | 10 +++++++ 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9f718..0bdfdf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to seismo-relay are documented here. --- +## v0.16.1 — 2026-05-14 + +### Fixed + +- **`record_type` always "Waveform" for forwarded events.** `read_blastware_file()` hardcoded `ev.record_type = "Waveform"` regardless of the file's actual type. The watcher-forward pipeline (the main BW ACH ingest path) compounds this by parsing files from a tmp path with a `.bw` suffix, so even a filename-based fallback inside the parser still wouldn't see the original extension. Now: + + 1. New `derive_record_type_from_filename(filename)` helper in `minimateplus/event_file_io.py` derives the type from the LAST character of the filename's extension (V10.72+ AB0T scheme: `H`=Histogram, `W`=Waveform, `M`=Manual, `E`=Event, `C`=Combo). Falls back to `"Waveform"` for old S338 firmware (3-char extensions ending in `0`) and any unrecognized suffix. + 2. `read_blastware_file()` now calls the helper with its `path.name` so direct callers (the `--dry-run` path in `scripts/import_bw.py`, tests, ad-hoc scripts) get the right value automatically. + 3. `WaveformStore.save_imported_bw()` overrides `ev.record_type` with the **original** filename's derived type after parsing (the tmp file inside the parser doesn't carry the original extension). This is the path the live watcher-forwarder hits, so the DB column now reflects the actual event type going forward. + + Events ingested before this fix are stuck with `record_type="Waveform"` in the DB; a one-off backfill (`UPDATE events SET record_type = ... WHERE blastware_filename LIKE '%H'`) would fix them retroactively if desired. Terra-view's event modal also derives client-side from the filename, so the UI already shows the correct type for old events even without the backfill. + +--- + ## v0.16.0 — 2026-05-11 The "BW ACH ingestion" release. When paired with **series3-watcher v1.5.0**, every Blastware ACH event (binary + `_ASCII.TXT` report) lands in SeismoDb with device-authoritative peaks, project metadata, sensor self-check, and ZC/Time-of-Peak data — without depending on the still-undecoded waveform body codec. This is the end-to-end product win discussed in v0.15.0's "out of scope" notes: sortable / filterable monthly-summary review of historical events, populated from the BW ASCII export rather than re-decoded samples. diff --git a/minimateplus/event_file_io.py b/minimateplus/event_file_io.py index a415599..12ad2db 100644 --- a/minimateplus/event_file_io.py +++ b/minimateplus/event_file_io.py @@ -47,7 +47,7 @@ SIDECAR_KIND = "sfm.event" # bumped without a `pip install` re-run — leading to confusing stale # version stamps in sidecars. Bump this constant and CHANGELOG.md # together at release time. -TOOL_VERSION = "0.16.0" +TOOL_VERSION = "0.16.1" try: # Best-effort: prefer the installed metadata when it's NEWER than the @@ -646,6 +646,50 @@ def _peaks_from_samples(samples: dict[str, list[int]]) -> PeakValues: ) +_RECORD_TYPE_BY_EXT_SUFFIX = { + 'H': 'Histogram', + 'W': 'Waveform', + 'M': 'Manual', + 'E': 'Event', + 'C': 'Combo', +} + + +def derive_record_type_from_filename(filename, default: str = "Waveform") -> str: + """Derive a BW Event's record_type from its filename's extension suffix. + + V10.72+ MiniMate Plus firmware encodes the event type as the LAST + character of the extension (the `T` in BW's `AB0T` scheme): + + ``M529LKIQ.G10H`` → H → ``"Histogram"`` + ``T350L385.VY0W`` → W → ``"Waveform"`` + ``...M`` → M → ``"Manual"`` + ``...E`` → E → ``"Event"`` + ``...C`` → C → ``"Combo"`` + + Old S338 firmware uses 3-char extensions ending in ``0`` whose + encoding is not yet known — those fall through to ``default``. + Micromate Series 4 uses a different scheme entirely (observed: + ``IDFH``, ``IDFW``) but the LAST-char convention (H / W) still holds + for the type code, so it works for both families. + + Returns ``default`` if filename is empty, has no extension, or the + suffix char isn't a recognized type code. + """ + if not filename: + return default + try: + name = Path(filename).name + except (TypeError, ValueError): + return default + if '.' not in name: + return default + ext = name.rsplit('.', 1)[1] + if not ext: + return default + return _RECORD_TYPE_BY_EXT_SUFFIX.get(ext[-1].upper(), default) + + def read_blastware_file(path: Union[str, Path]) -> Event: """ Parse a Blastware waveform file into an Event. @@ -727,7 +771,12 @@ def read_blastware_file(path: Union[str, Path]) -> Event: ev = Event(index=-1) if strt_fields.get("waveform_key"): ev._waveform_key = bytes.fromhex(strt_fields["waveform_key"]) - ev.record_type = "Waveform" + # Derive record_type from the filename's extension suffix (H/W/M/E/C). + # When called from save_imported_bw the path here is a tmp file with a + # ".bw" suffix, so the derivation falls back to "Waveform" and the + # caller overrides ev.record_type using the original filename — see + # waveform_store.save_imported_bw. + ev.record_type = derive_record_type_from_filename(path.name) ev.rectime_seconds = strt_fields.get("rectime_seconds") ev.total_samples = strt_fields.get("total_samples") ev.pretrig_samples = strt_fields.get("pretrig_samples") diff --git a/pyproject.toml b/pyproject.toml index 70530de..c8abd3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "seismo-relay" -version = "0.16.0" +version = "0.16.1" description = "Python client and REST server for MiniMate Plus seismographs" requires-python = ">=3.10" dependencies = [ diff --git a/sfm/server.py b/sfm/server.py index 772f922..9005652 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -86,7 +86,7 @@ app = FastAPI( "Implements the minimateplus RS-232 protocol library.\n" "Proxied by terra-view at /api/sfm/*." ), - version="0.16.0", + version="0.16.1", ) # Allow requests from the waveform viewer opened as a local file (file://) diff --git a/sfm/waveform_store.py b/sfm/waveform_store.py index 93cd970..0d04460 100644 --- a/sfm/waveform_store.py +++ b/sfm/waveform_store.py @@ -300,6 +300,16 @@ class WaveformStore: except FileNotFoundError: pass + # read_blastware_file derives record_type from its path arg, but + # that arg is the tmp file (suffix ".bw") — so override with the + # original filename's encoded type (H/W/M/E/C in the BW AB0T + # scheme). Without this override every BW-imported event lands + # in the DB with record_type="Waveform" regardless of the actual + # type (Histogram, Manual, etc.). + ev.record_type = event_file_io.derive_record_type_from_filename( + source_path.name + ) + # Parse the BW ASCII report if one was supplied. Failures here # are non-fatal: we still write the binary + sidecar without the # rich derived fields.