feat(forward): SFM event forwarder (v1.5.0)

When SFM_FORWARD_ENABLED=true and SFM_URL is set, every Blastware
event binary in the ACH watch folder is forwarded to an SFM server's
/db/import/blastware_file endpoint as a multipart POST.  The paired
<binary>.TXT ASCII report (which Blastware's ACH writes alongside
each event) is shipped in the same request, letting the SFM server
index the full per-channel stats — PPV, ZC Freq, Time of Peak, Peak
Acceleration / Displacement, Peak Vector Sum + time, sensor
self-check Pass/Fail per channel, and monitor-log timestamps —
without depending on the still-undecoded BW waveform body codec.

New module event_forwarder.py:
  - is_event_binary() filename matcher (BW's <P><serial3><stem>.<ext>
    scheme; rejects .MLG, .TXT, .log, .ini, .h5, etc.)
  - ForwardState (.json file keyed by sha256 — idempotent across
    restarts and auto-updates)
  - find_pending_events() with quiescence + grace-period guards
  - Hand-rolled multipart encoder (stdlib-only)
  - forward_event_pair() / forward_pending() — POST loop with
    structured per-event outcomes

Wired into series3_watcher.run_watcher() on its own cadence
(SFM_FORWARD_INTERVAL_SECONDS, default 60s) so it doesn't slow the
existing 5-min heartbeat scan.

Default-off: existing 1.4.x deployments keep their old behaviour
after auto-updating until an operator sets SFM_URL +
SFM_FORWARD_ENABLED=true and restarts.

17 unit tests in test_event_forwarder.py cover filename matching,
state idempotency, scan logic (quiescence, grace, max age,
already-forwarded, .TXT pairing), multipart byte shape, and an
end-to-end POST against a tiny stdlib http.server fake.

Bumps version 1.4.4 → 1.5.0 (minor — additive feature, no API break).
Requires SFM server v0.16+ for the paired-.TXT import endpoint.
This commit is contained in:
2026-05-09 00:03:31 +00:00
parent 010016d515
commit f4ec6ef945
8 changed files with 1052 additions and 5 deletions
+23
View File
@@ -6,6 +6,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
## [1.5.0] - 2026-05-09
### Added
- **SFM event forwarder.** When `SFM_FORWARD_ENABLED=true` and `SFM_URL` is set, every Blastware event binary is forwarded to an SFM server's `/db/import/blastware_file` endpoint as a multipart POST. The corresponding `<binary>.TXT` ASCII report (which Blastware's ACH writes alongside each event) is paired by filename and shipped in the same request, letting the SFM server index the full per-channel stats (PPV, ZC Freq, Time of Peak, Peak Acceleration / Displacement, Peak Vector Sum + time, sensor self-check Pass/Fail, monitor-log timestamps) without depending on the still-undecoded Blastware waveform body codec.
- **Idempotent forwarding.** Forwarded files are tracked by sha256 in a JSON state file (default `<log dir>/sfm_forwarded.json`, override via `SFM_STATE_FILE`). Re-scans don't re-POST and the state survives restarts / auto-updates.
- **Quiescence + grace-period guards.** Files modified within `SFM_QUIESCENCE_SECONDS` (default 5s) are skipped to avoid forwarding mid-write. If a binary's `.TXT` partner hasn't appeared after `SFM_MISSING_REPORT_GRACE_SECONDS` (default 60s), the binary is forwarded alone rather than blocking forever.
- New `event_forwarder.py` module + 17 unit tests in `test_event_forwarder.py` covering filename matching, state idempotency, scan logic, multipart encoding, and a fake-server end-to-end POST.
### Configuration
New `[agent]` keys (all default-off — existing 1.4.x deployments don't change behaviour on auto-update):
- `SFM_FORWARD_ENABLED` (default `false`)
- `SFM_URL` (e.g. `http://10.0.0.44:8200`)
- `SFM_FORWARD_INTERVAL_SECONDS` (default `60`)
- `SFM_QUIESCENCE_SECONDS` (default `5`)
- `SFM_MISSING_REPORT_GRACE_SECONDS` (default `60`)
- `SFM_HTTP_TIMEOUT` (default `60`)
- `SFM_STATE_FILE` (default: `<log dir>/sfm_forwarded.json`)
### Compatibility
- Requires SFM server v0.16+ (the `/db/import/blastware_file` endpoint that accepts paired `.TXT` reports — released alongside this watcher version on the seismo-relay side).
## [1.4.4] - 2026-03-17
### Removed