fix: add thor specific ascii parser.
This commit is contained in:
+47
-23
@@ -65,9 +65,17 @@ def _normalize_key(raw: str) -> str:
|
||||
|
||||
|
||||
def _strip_unit_suffix(value: str) -> str:
|
||||
"""Return the numeric part of values like "0.2119 in/s" → "0.2119"."""
|
||||
"""Return the numeric part of values like "0.2119 in/s" → "0.2119".
|
||||
|
||||
Also strips Thor's below/above-threshold prefixes:
|
||||
"<0.005 in/s" → "0.005" (below-noise-floor reading)
|
||||
">100 Hz" → "100" (above-measurement-range reading)
|
||||
"""
|
||||
parts = value.strip().split()
|
||||
return parts[0] if parts else value.strip()
|
||||
token = parts[0] if parts else value.strip()
|
||||
if token.startswith("<") or token.startswith(">"):
|
||||
token = token[1:]
|
||||
return token
|
||||
|
||||
|
||||
def _parse_float(value: str) -> Optional[float]:
|
||||
@@ -178,38 +186,54 @@ def parse_idf_report(text: Union[str, bytes]) -> Dict[str, Any]:
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Numeric scalars
|
||||
for key in ("sample_rate",):
|
||||
# Numeric scalars. For every field we typify here, we MUST drop the
|
||||
# raw string copy from `out` when parsing fails — Thor writes things
|
||||
# like "<0.005 in/s" (below threshold) and "N/A" (not measured) that
|
||||
# would otherwise linger in `out` as strings, sneak into SQLite REAL
|
||||
# columns via permissive type affinity, and then crash the JS
|
||||
# frontend on `.toFixed(...)`.
|
||||
int_fields = ("sample_rate",)
|
||||
for key in int_fields:
|
||||
v = raw.get(key)
|
||||
if v is not None:
|
||||
iv = _parse_int(v)
|
||||
if iv is not None:
|
||||
out[key] = iv
|
||||
if v is None:
|
||||
continue
|
||||
iv = _parse_int(v)
|
||||
if iv is not None:
|
||||
out[key] = iv
|
||||
else:
|
||||
out.pop(key, None)
|
||||
|
||||
for key in ("tran_ppv", "vert_ppv", "long_ppv", "peak_vector_sum",
|
||||
"tran_zc_freq", "vert_zc_freq", "long_zc_freq",
|
||||
"tran_peak_acceleration", "vert_peak_acceleration",
|
||||
"long_peak_acceleration",
|
||||
"tran_peak_displacement", "vert_peak_displacement",
|
||||
"long_peak_displacement",
|
||||
"tran_time_of_peak", "vert_time_of_peak", "long_time_of_peak",
|
||||
"mic_time_of_peak", "mic_zc_freq"):
|
||||
float_fields = (
|
||||
"tran_ppv", "vert_ppv", "long_ppv", "peak_vector_sum",
|
||||
"tran_zc_freq", "vert_zc_freq", "long_zc_freq",
|
||||
"tran_peak_acceleration", "vert_peak_acceleration",
|
||||
"long_peak_acceleration",
|
||||
"tran_peak_displacement", "vert_peak_displacement",
|
||||
"long_peak_displacement",
|
||||
"tran_time_of_peak", "vert_time_of_peak", "long_time_of_peak",
|
||||
"mic_time_of_peak", "mic_zc_freq",
|
||||
)
|
||||
for key in float_fields:
|
||||
v = raw.get(key)
|
||||
if v is not None:
|
||||
fv = _parse_float(v)
|
||||
if fv is not None:
|
||||
out[key] = fv
|
||||
if v is None:
|
||||
continue
|
||||
fv = _parse_float(v)
|
||||
if fv is not None:
|
||||
out[key] = fv
|
||||
else:
|
||||
out.pop(key, None)
|
||||
|
||||
# Microphone — Thor reports MicPSPL (dB(L)) which is the closest
|
||||
# analogue to BW's mic_ppv. Stored as a float; units are in the
|
||||
# original raw field (`mic_pspl` raw entry preserves "99.4 dB(L)").
|
||||
# analogue to BW's mic_ppv. The raw "99.4 dB(L)" string stays in
|
||||
# `out` under the original `mic_pspl` key for display; the parsed
|
||||
# float goes in `mic_ppv`.
|
||||
mic = raw.get("mic_pspl")
|
||||
if mic is not None:
|
||||
fv = _parse_float(mic)
|
||||
if fv is not None:
|
||||
out["mic_ppv"] = fv
|
||||
|
||||
# Record / pre-trigger duration
|
||||
# Record / pre-trigger duration — same drop-on-failure discipline.
|
||||
rt = raw.get("record_time")
|
||||
if rt is not None:
|
||||
fv = _parse_float(rt)
|
||||
|
||||
+12
-5
@@ -2285,13 +2285,16 @@ let sessLoaded = false;
|
||||
const _unitSerials = new Set();
|
||||
|
||||
function _ppvClass(v) {
|
||||
if (v == null) return '';
|
||||
if (v >= 2.0) return 'ppv-high';
|
||||
if (v >= 0.5) return 'ppv-warn';
|
||||
const n = (v == null) ? null : Number(v);
|
||||
if (n == null || !isFinite(n)) return '';
|
||||
if (n >= 2.0) return 'ppv-high';
|
||||
if (n >= 0.5) return 'ppv-warn';
|
||||
return 'ppv-ok';
|
||||
}
|
||||
function _ppvFmt(v) {
|
||||
return v != null ? v.toFixed(5) : '—';
|
||||
if (v == null) return '—';
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
return isFinite(n) ? n.toFixed(5) : String(v);
|
||||
}
|
||||
function _fmtTs(ts) {
|
||||
if (!ts) return '—';
|
||||
@@ -2386,7 +2389,11 @@ async function loadHistory() {
|
||||
<td class="${_ppvClass(ev.vert_ppv)}">${_ppvFmt(ev.vert_ppv)}</td>
|
||||
<td class="${_ppvClass(ev.long_ppv)}">${_ppvFmt(ev.long_ppv)}</td>
|
||||
<td class="${_ppvClass(pvs)}">${_ppvFmt(pvs)}</td>
|
||||
<td class="td-dim">${ev.mic_ppv != null && ev.mic_ppv > 0 ? (20 * Math.log10(ev.mic_ppv / DBL_REF)).toFixed(1) + ' dBL' : '—'}</td>
|
||||
<td class="td-dim">${(() => {
|
||||
const m = ev.mic_ppv == null ? null : Number(ev.mic_ppv);
|
||||
if (m == null || !isFinite(m) || m <= 0) return '—';
|
||||
return (20 * Math.log10(m / DBL_REF)).toFixed(1) + ' dBL';
|
||||
})()}</td>
|
||||
<td class="td-text">${ev.project ?? '—'}</td>
|
||||
<td class="td-text">${ev.client ?? '—'}</td>
|
||||
<td class="td-dim">${ev.record_type ?? '—'}</td>
|
||||
|
||||
Reference in New Issue
Block a user