fix(decode): recover the exact waveform trigger from the binary (no .TXT)
Follow-up to the ts1→ts2 fix: get the trigger to the second from the binary alone, instead of falling back to the stop time (~record-duration late) for no-report events. The configured post-trigger record time is a big-endian float32 in the recording-setup config block, exactly 30 bytes before the "Standard Recording Setup" marker. _parse_record_time_seconds reads it; the waveform branch now stamps trigger = ts2 - record_time. Verified: the field reads 1.0 / 2.0 / 3.0 s across different setups in the corpus, and all 7 BE12844 oracle events now decode to their exact Blastware trigger (N844LQHB 10:33:29) from the binary, no paired .TXT needed. Falls back to ts2 (the stop) if the config block is absent. A paired report's event_datetime stays authoritative (clock drift). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDXjZCr4RqT2U3QvMDhgzf
This commit is contained in:
@@ -818,6 +818,30 @@ def derive_record_type_from_filename(filename, default: str = "Waveform") -> str
|
||||
return _RECORD_TYPE_BY_EXT_SUFFIX.get(ext[-1].upper(), default)
|
||||
|
||||
|
||||
# Marker for the recording-setup config block, and the offset of the record-time
|
||||
# float32 within it. The configured post-trigger record time (seconds) is a
|
||||
# big-endian float32 exactly 30 bytes before the "Standard Recording Setup"
|
||||
# label. Verified across the corpus reading 1.0 / 2.0 / 3.0 s on different
|
||||
# setups — and ts2 - record_time reproduces Blastware's trigger to the second
|
||||
# (N844LQHB: stop 10:33:32 - 3.0 = 10:33:29).
|
||||
_RECSETUP_MARKER = b"Standard Recording Setup"
|
||||
_RECTIME_OFFSET_BEFORE_MARKER = 30
|
||||
|
||||
|
||||
def _parse_record_time_seconds(raw: bytes) -> Optional[float]:
|
||||
"""The configured post-trigger record time in seconds, from the recording-
|
||||
setup config block, or None when absent / implausible."""
|
||||
a = raw.find(_RECSETUP_MARKER)
|
||||
if a < _RECTIME_OFFSET_BEFORE_MARKER:
|
||||
return None
|
||||
off = a - _RECTIME_OFFSET_BEFORE_MARKER
|
||||
try:
|
||||
rt = struct.unpack(">f", raw[off:off + 4])[0]
|
||||
except struct.error:
|
||||
return None
|
||||
return rt if 0.05 <= rt <= 600.0 else None
|
||||
|
||||
|
||||
def read_blastware_file(path: Union[str, Path]) -> Event:
|
||||
"""
|
||||
Parse a Blastware waveform file into an Event.
|
||||
@@ -966,22 +990,25 @@ def read_blastware_file(path: Union[str, Path]) -> Event:
|
||||
# record type:
|
||||
# * Waveform: ts1 = the monitoring-SESSION start (shared across every
|
||||
# event that day — a unit arming at 06:00 stamps 06:00 on all of them),
|
||||
# ts2 = THIS event's recording STOP. ts2 is the correct binary-only
|
||||
# estimate of the event time; BW's displayed Date/Time is the trigger =
|
||||
# ts2 - record duration (~3 s), but the record-duration byte in the STRT
|
||||
# record is a misparsed record-type marker here (see the strt-build
|
||||
# note), so the exact trigger comes from the paired BW report's
|
||||
# event_datetime — apply_report_to_event() overrides with it when a
|
||||
# report is present. (Stamping ts1 showed the session start, hours off.)
|
||||
# ts2 = THIS event's recording STOP. Blastware's Date/Time is the
|
||||
# TRIGGER = ts2 - record time, and the record time is a float32 in the
|
||||
# recording-setup config block (see _parse_record_time_seconds), so the
|
||||
# exact trigger is recoverable from the binary alone. Falls back to ts2
|
||||
# (the stop, within the record duration) if the config block is absent.
|
||||
# (Stamping ts1 showed the session start, hours off.)
|
||||
# * Histogram / undecodable: ts1 = the window start, which IS the event
|
||||
# time — keep it.
|
||||
# Discriminate by ``is_waveform_body`` (the codec), not the filename.
|
||||
if is_waveform_body and ts2 is not None:
|
||||
_stop = datetime.datetime(ts2.year, ts2.month, ts2.day,
|
||||
ts2.hour, ts2.minute, ts2.second)
|
||||
_rt = _parse_record_time_seconds(raw)
|
||||
_trig = _stop - datetime.timedelta(seconds=_rt) if _rt is not None else _stop
|
||||
ev.timestamp = Timestamp(
|
||||
raw=footer[10:18],
|
||||
flag=0x10,
|
||||
year=ts2.year, unknown_byte=0, month=ts2.month, day=ts2.day,
|
||||
hour=ts2.hour, minute=ts2.minute, second=ts2.second,
|
||||
year=_trig.year, unknown_byte=0, month=_trig.month, day=_trig.day,
|
||||
hour=_trig.hour, minute=_trig.minute, second=_trig.second,
|
||||
)
|
||||
elif ts1 is not None:
|
||||
ev.timestamp = Timestamp(
|
||||
|
||||
Reference in New Issue
Block a user