ui: surface per-channel ZC Freq (and ">100") in event modals
The PDF report shows per-channel ZC Freq alongside PPV in the stats block, but neither modal exposed it. Now that the sidecar projection carries zc_freq_hz + zc_freq_above_range, plumb them through: - sfm_webapp.html: inline suffix on existing Peaks cells, e.g. "Tran 0.04500 in/s · >100 Hz". Empty suffix when no ZC is available (legacy events without a preserved .TXT). - event_browser.html: new ZC Freq column on the per-channel stats table. Required adding a parallel sidecar fetch in loadEvent() (waveform.json alone doesn't carry bw_report). Fetch failure is non-fatal — falls back to "—" in the new column. Above-range ZC peaks (BW ">100 Hz") render with a literal ">" prefix mirroring the PDF, so operators don't have to generate the PDF to see when a channel hit the zero-crossing ceiling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+26
-11
@@ -499,6 +499,14 @@ async function loadEvent(eventId) {
|
|||||||
renderEventList();
|
renderEventList();
|
||||||
setStatus('Loading waveform…');
|
setStatus('Loading waveform…');
|
||||||
try {
|
try {
|
||||||
|
// Sidecar fetch runs in parallel — its bw_report block carries ZC
|
||||||
|
// Freq + above-range flags + sensor-check results that the per-
|
||||||
|
// channel stats table surfaces. Failures are non-fatal (legacy
|
||||||
|
// events without a preserved .TXT have no sidecar bw_report).
|
||||||
|
const sidecarP = fetch(`${apiBase}/db/events/${eventId}/sidecar`)
|
||||||
|
.then(r => r.ok ? r.json() : null)
|
||||||
|
.catch(() => null);
|
||||||
|
|
||||||
const r = await fetch(`${apiBase}/db/events/${eventId}/waveform.json`);
|
const r = await fetch(`${apiBase}/db/events/${eventId}/waveform.json`);
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
if (r.status === 404) {
|
if (r.status === 404) {
|
||||||
@@ -511,7 +519,8 @@ async function loadEvent(eventId) {
|
|||||||
renderWaveform(data);
|
renderWaveform(data);
|
||||||
// Also fetch metadata from the events list for richer header
|
// Also fetch metadata from the events list for richer header
|
||||||
const ev = allEvents.find(e => e.id === eventId);
|
const ev = allEvents.find(e => e.id === eventId);
|
||||||
renderMeta(data, ev);
|
const sidecar = await sidecarP;
|
||||||
|
renderMeta(data, ev, sidecar);
|
||||||
setStatus(`Event loaded.`, 'ok');
|
setStatus(`Event loaded.`, 'ok');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setStatus(`Failed to load event: ${e.message}`, 'error');
|
setStatus(`Failed to load event: ${e.message}`, 'error');
|
||||||
@@ -528,7 +537,7 @@ function showEmpty(msg) {
|
|||||||
charts = {};
|
charts = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMeta(data, ev) {
|
function renderMeta(data, ev, sidecar) {
|
||||||
const metaDiv = document.getElementById('event-meta');
|
const metaDiv = document.getElementById('event-meta');
|
||||||
const fields = [
|
const fields = [
|
||||||
['Serial', data.serial || ev?.serial || '—'],
|
['Serial', data.serial || ev?.serial || '—'],
|
||||||
@@ -543,14 +552,20 @@ function renderMeta(data, ev) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Per-channel stats table mirroring the printout's middle block.
|
// Per-channel stats table mirroring the printout's middle block.
|
||||||
// Pulls per-channel PPV from the events row (DB columns) and additional
|
// PPV from the events DB row; ZC Freq + saturation flags from the
|
||||||
// details (peak time, peak accel, peak displacement, sensor check) from
|
// sidecar's bw_report block (when a .TXT was preserved on ingest).
|
||||||
// bw_report when present.
|
const bwrPeaks = (sidecar?.bw_report || {}).peaks || {};
|
||||||
|
const bwrMic = (sidecar?.bw_report || {}).mic || {};
|
||||||
const fmt = v => (v == null ? '—' : (typeof v === 'number' ? v.toFixed(3) : v));
|
const fmt = v => (v == null ? '—' : (typeof v === 'number' ? v.toFixed(3) : v));
|
||||||
|
const fmtZc = bwr => {
|
||||||
|
if (!bwr || bwr.zc_freq_hz == null) return '—';
|
||||||
|
const prefix = bwr.zc_freq_above_range ? '>' : '';
|
||||||
|
return `${prefix}${Math.round(bwr.zc_freq_hz)} Hz`;
|
||||||
|
};
|
||||||
const rows = [
|
const rows = [
|
||||||
['Tran', ev?.tran_ppv],
|
['Tran', ev?.tran_ppv, fmtZc(bwrPeaks.tran)],
|
||||||
['Vert', ev?.vert_ppv],
|
['Vert', ev?.vert_ppv, fmtZc(bwrPeaks.vert)],
|
||||||
['Long', ev?.long_ppv],
|
['Long', ev?.long_ppv, fmtZc(bwrPeaks.long)],
|
||||||
];
|
];
|
||||||
// Mic display honors the current user preference (dBL default).
|
// Mic display honors the current user preference (dBL default).
|
||||||
// mic_ppv is stored as raw psi on series3 events; convert when needed.
|
// mic_ppv is stored as raw psi on series3 events; convert when needed.
|
||||||
@@ -568,11 +583,11 @@ function renderMeta(data, ev) {
|
|||||||
const statsHtml = `
|
const statsHtml = `
|
||||||
<table class="stats-table">
|
<table class="stats-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th>Channel</th><th>PPV (in/s)</th></tr>
|
<tr><th>Channel</th><th>PPV (in/s)</th><th>ZC Freq</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
${rows.map(([ch, ppv]) => `<tr><td>${ch}</td><td>${fmt(ppv)}</td></tr>`).join('')}
|
${rows.map(([ch, ppv, zc]) => `<tr><td>${ch}</td><td>${fmt(ppv)}</td><td>${zc}</td></tr>`).join('')}
|
||||||
<tr><td>MicL</td><td>${micStr}</td></tr>
|
<tr><td>MicL</td><td>${micStr}</td><td>${fmtZc(bwrMic)}</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
`;
|
`;
|
||||||
|
|||||||
+18
-4
@@ -2886,6 +2886,12 @@ function _renderSidecar(data) {
|
|||||||
const bw = data.blastware || {};
|
const bw = data.blastware || {};
|
||||||
const src = data.source || {};
|
const src = data.source || {};
|
||||||
const rev = data.review || {};
|
const rev = data.review || {};
|
||||||
|
// bw_report carries the per-channel ASCII-derived stats (ZC Freq,
|
||||||
|
// saturation flags, peak time, etc.). Only present on events
|
||||||
|
// ingested with a preserved .TXT (post-2026-05-27); falls back to
|
||||||
|
// empty for legacy events.
|
||||||
|
const bwrPeaks = (data.bw_report || {}).peaks || {};
|
||||||
|
const bwrMic = (data.bw_report || {}).mic || {};
|
||||||
|
|
||||||
document.getElementById('sc-title').textContent = `Event — ${bw.filename || ev.waveform_key || 'unknown'}`;
|
document.getElementById('sc-title').textContent = `Event — ${bw.filename || ev.waveform_key || 'unknown'}`;
|
||||||
|
|
||||||
@@ -2918,11 +2924,19 @@ function _renderSidecar(data) {
|
|||||||
document.getElementById('sc-f-sr').textContent = (ev.sample_rate ?? '—') + (ev.sample_rate ? ' sps' : '');
|
document.getElementById('sc-f-sr').textContent = (ev.sample_rate ?? '—') + (ev.sample_rate ? ' sps' : '');
|
||||||
document.getElementById('sc-f-key').textContent = ev.waveform_key || '—';
|
document.getElementById('sc-f-key').textContent = ev.waveform_key || '—';
|
||||||
|
|
||||||
document.getElementById('sc-f-tran').textContent = fmtPpv(pv.transverse);
|
// Suffix with " · {prefix}{N} Hz" when bw_report has a ZC Freq.
|
||||||
document.getElementById('sc-f-vert').textContent = fmtPpv(pv.vertical);
|
// Above-range ZC peaks (BW ">100 Hz") get a literal ">" prefix so
|
||||||
document.getElementById('sc-f-long').textContent = fmtPpv(pv.longitudinal);
|
// operators see the same indicator the PDF shows.
|
||||||
|
const fmtZc = bwr => {
|
||||||
|
if (!bwr || bwr.zc_freq_hz == null) return '';
|
||||||
|
const prefix = bwr.zc_freq_above_range ? '>' : '';
|
||||||
|
return ` · ${prefix}${Math.round(bwr.zc_freq_hz)} Hz`;
|
||||||
|
};
|
||||||
|
document.getElementById('sc-f-tran').textContent = fmtPpv(pv.transverse) + fmtZc(bwrPeaks.tran);
|
||||||
|
document.getElementById('sc-f-vert').textContent = fmtPpv(pv.vertical) + fmtZc(bwrPeaks.vert);
|
||||||
|
document.getElementById('sc-f-long').textContent = fmtPpv(pv.longitudinal) + fmtZc(bwrPeaks.long);
|
||||||
document.getElementById('sc-f-pvs').textContent = fmtPpv(pv.vector_sum);
|
document.getElementById('sc-f-pvs').textContent = fmtPpv(pv.vector_sum);
|
||||||
document.getElementById('sc-f-mic').textContent = fmtMic(pv.mic_psi);
|
document.getElementById('sc-f-mic').textContent = fmtMic(pv.mic_psi) + fmtZc(bwrMic);
|
||||||
|
|
||||||
document.getElementById('sc-f-project').textContent = pi.project || '—';
|
document.getElementById('sc-f-project').textContent = pi.project || '—';
|
||||||
document.getElementById('sc-f-client').textContent = pi.client || '—';
|
document.getElementById('sc-f-client').textContent = pi.client || '—';
|
||||||
|
|||||||
Reference in New Issue
Block a user