fix(forward): pair BW ACH ASCII reports using the _ASCII.TXT convention (v1.5.4)
Blastware's official Auto Call Home server writes per-event ASCII
reports as <stem>_<ext>_ASCII.TXT (e.g. N844L20G_630H_ASCII.TXT),
not <binary>.TXT (e.g. N844L20G.630H.TXT). Versions v1.5.0–v1.5.3
only looked for the latter and silently shipped every binary alone,
so the SFM database lost the per-event Peak Acceleration / Peak
Displacement / ZC Freq / Time of Peak / Peak Vector Sum + time /
sensor self-check fields on every forwarded event.
Fix: pair-finding logic now tries the ACH-convention filename first
and falls back to <binary>.TXT for compatibility with operator-saved
manual exports and existing test fixtures.
ach_report_name("M529LK44.AB0") → "M529LK44_AB0_ASCII.TXT"
legacy_report_name("M529LK44.AB0") → "M529LK44.AB0.TXT"
When both files exist (operator manually saved + ACH auto-exported),
ACH wins because that's the canonical name on modern BW deployments.
Both candidates checked case-insensitively against the cached
directory listing — no extra stat() calls.
6 new unit tests cover the new pairing logic, helper-function
correctness, and the precedence rule. Total now 31 tests, all green.
Field-deploy note: re-running v1.5.4 on a folder where v1.5.0–v1.5.3
already ran will NOT re-forward historical events — the
sfm_forwarded.json state file remembers them by sha256. To re-forward
historical events to populate SFM with the now-correctly-paired
reports, delete the state file before starting v1.5.4.
This commit is contained in:
+63
-11
@@ -96,9 +96,41 @@ def is_event_binary(path: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def ach_report_name(binary_name: str) -> str:
|
||||
"""BW ACH report-naming convention.
|
||||
|
||||
Blastware's official Auto Call Home server writes per-event ASCII
|
||||
reports as ``<stem>_<ext>_ASCII.TXT`` — the ``.`` between stem and
|
||||
ext is replaced with ``_`` and ``_ASCII.TXT`` is appended.
|
||||
|
||||
Examples:
|
||||
``M529LK44.AB0`` → ``M529LK44_AB0_ASCII.TXT``
|
||||
``N844L20G.630H`` → ``N844L20G_630H_ASCII.TXT``
|
||||
``H907L1R7.PG0H`` → ``H907L1R7_PG0H_ASCII.TXT``
|
||||
|
||||
For a filename without a dot (defensive — shouldn't happen for real
|
||||
BW events) we still append ``_ASCII.TXT``.
|
||||
"""
|
||||
stem, dot, ext = binary_name.rpartition(".")
|
||||
if not dot:
|
||||
return binary_name + "_ASCII.TXT"
|
||||
return stem + "_" + ext + "_ASCII.TXT"
|
||||
|
||||
|
||||
def legacy_report_name(binary_name: str) -> str:
|
||||
"""Manual-export convention: ``<binary>.TXT`` (e.g. when an operator
|
||||
saves an event report to text directly from BW's UI rather than
|
||||
letting ACH auto-export it). Kept as a fallback so the codec-agent
|
||||
test fixtures (``decode-re/5-8-26/event-c/M529LK44.AB0.TXT``) still
|
||||
pair correctly."""
|
||||
return binary_name + ".TXT"
|
||||
|
||||
|
||||
def report_path_for(binary_path: str) -> str:
|
||||
"""Return the conventional `<binary>.TXT` partner path."""
|
||||
return binary_path + ".TXT"
|
||||
"""Legacy entry point — returns the manual-export path. Prefer
|
||||
:func:`ach_report_name` for new BW deployments. Retained for
|
||||
backward compatibility with any caller still on the old convention."""
|
||||
return legacy_report_name(binary_path)
|
||||
|
||||
|
||||
def is_histogram_event(filename: str) -> bool:
|
||||
@@ -109,9 +141,12 @@ def is_histogram_event(filename: str) -> bool:
|
||||
return False (the conservative answer — we don't want to suppress
|
||||
"no report" warnings on potentially-waveform old-firmware events).
|
||||
|
||||
Used purely for log clarity — histograms don't get auto-exported BW
|
||||
ASCII reports, so "no report" on a histogram is not a problem to
|
||||
flag. Forwarding logic itself doesn't depend on this check.
|
||||
Used purely for log clarity — when a forward goes through without a
|
||||
paired TXT, the log distinguishes "histogram, no report expected"
|
||||
(acceptable: BW may not have written one even though it normally
|
||||
does for ACH-routed histograms) from "no report ⚠" on a waveform
|
||||
(more suspicious: BW almost always writes the TXT for waveform events).
|
||||
Forwarding logic itself doesn't depend on this check.
|
||||
"""
|
||||
name = os.path.basename(filename)
|
||||
ext = os.path.splitext(name)[1].lstrip(".").upper()
|
||||
@@ -314,12 +349,29 @@ def find_pending_events(
|
||||
skipped_already_forwarded += 1
|
||||
continue
|
||||
|
||||
# TXT pairing
|
||||
txt_name = e.name + ".TXT"
|
||||
# Case-insensitive match on the .TXT suffix
|
||||
if txt_name not in names:
|
||||
txt_name_lc = txt_name.lower()
|
||||
txt_name = next((n for n in names if n.lower() == txt_name_lc), None)
|
||||
# TXT pairing — try BW ACH convention first
|
||||
# (<stem>_<ext>_ASCII.TXT) and fall back to the manual-export
|
||||
# convention (<binary>.TXT). Both checked case-insensitively
|
||||
# against the cached directory listing. ACH wins when both
|
||||
# exist — that's the format BW's official ACH server writes.
|
||||
candidates = [ach_report_name(e.name), legacy_report_name(e.name)]
|
||||
|
||||
# Case-insensitive name lookup against the cached set.
|
||||
names_lc_to_actual = None
|
||||
txt_name: Optional[str] = None
|
||||
for cand in candidates:
|
||||
if cand in names:
|
||||
txt_name = cand
|
||||
break
|
||||
# Build lower-case index lazily — most folders have very few
|
||||
# TXT files relative to binaries, so the linear scan only
|
||||
# fires when neither exact-case candidate matches.
|
||||
if names_lc_to_actual is None:
|
||||
names_lc_to_actual = {n.lower(): n for n in names}
|
||||
actual = names_lc_to_actual.get(cand.lower())
|
||||
if actual:
|
||||
txt_name = actual
|
||||
break
|
||||
|
||||
txt_path: Optional[str] = None
|
||||
if txt_name:
|
||||
|
||||
Reference in New Issue
Block a user