v0.20.0 -- Full s3 event parse and PDF creation. #28

Merged
serversdown merged 46 commits from dev into main 2026-05-28 17:54:34 -04:00
2 changed files with 44 additions and 15 deletions
Showing only changes of commit 6a73523e4d - Show all commits
+26 -11
View File
@@ -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
View File
@@ -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 || '—';