From f5e93d56129a144a0e85de0fd5d20fcd335e8b59 Mon Sep 17 00:00:00 2001 From: serversdown Date: Tue, 9 Jun 2026 20:00:41 +0000 Subject: [PATCH] feat(slm): backfill the live chart from the DOD trail on open On opening the live view, fetch GET /api/slmm/{unit}/history?hours=2 and seed the chart with the recent trend BEFORE connecting the live socket, so it opens with context instead of blank. Live frames then append in order. - backfillChart() populates all four series (Lp/Leq/L1/L10) from the trail. - initLiveDataStream is async and awaits the backfill before opening the WS. - Chart rolling window raised 60 -> 600 points so the ~2h backfill (1/min) isn't immediately shifted out. - Trail timestamps are naive UTC -> append 'Z' so they localize consistently with the live frames. Co-Authored-By: Claude Opus 4.8 (1M context) --- templates/partials/slm_live_view.html | 41 +++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/templates/partials/slm_live_view.html b/templates/partials/slm_live_view.html index e9d8a0f..70ceefb 100644 --- a/templates/partials/slm_live_view.html +++ b/templates/partials/slm_live_view.html @@ -513,7 +513,37 @@ if (typeof window.currentWebSocket === 'undefined') { window.currentWebSocket = null; } -function initLiveDataStream(unitId) { +// Backfill the chart with the recent DOD trail so it opens with context. +async function backfillChart(unitId) { + try { + const r = await fetch(`/api/slmm/${encodeURIComponent(unitId)}/history?hours=2`); + if (!r.ok) return; + const d = await r.json(); + const readings = d.readings || []; + if (!window.chartData) return; + for (const row of readings) { + // Trail timestamps are naive UTC; append 'Z' so they convert to local + // consistently with the live frames (which use local Date.now()). + window.chartData.timestamps.push(row.timestamp ? new Date(row.timestamp + 'Z').toLocaleTimeString() : ''); + window.chartData.lp.push(parseFloat(row.lp || 0)); + window.chartData.leq.push(parseFloat(row.leq || 0)); + window.chartData.ln1.push(parseFloat(row.ln1 || 0)); + window.chartData.ln2.push(parseFloat(row.ln2 || 0)); + } + if (window.liveChart) { + window.liveChart.data.labels = window.chartData.timestamps; + window.liveChart.data.datasets[0].data = window.chartData.lp; + window.liveChart.data.datasets[1].data = window.chartData.leq; + window.liveChart.data.datasets[2].data = window.chartData.ln1; + window.liveChart.data.datasets[3].data = window.chartData.ln2; + window.liveChart.update('none'); + } + } catch (e) { + console.warn('Chart backfill failed:', e); + } +} + +async function initLiveDataStream(unitId) { // Close existing connection if any if (window.currentWebSocket) { window.currentWebSocket.close(); @@ -533,6 +563,10 @@ function initLiveDataStream(unitId) { window.liveChart.update(); } + // Seed the chart with recent history BEFORE opening the live socket, so live + // frames append after the backfill (right order) and the chart isn't blank. + await backfillChart(unitId); + // WebSocket URL for SLMM backend via proxy. // /monitor = the shared fan-out DOD feed (many viewers, one device connection, // and it carries L1/L10 which the DRD /stream cannot). @@ -649,8 +683,9 @@ function updateLiveChart(data) { window.chartData.ln1.push(parseFloat(data.ln1 || 0)); window.chartData.ln2.push(parseFloat(data.ln2 || 0)); - // Keep only last 60 data points - if (window.chartData.timestamps.length > 60) { + // Keep a rolling window large enough to hold the ~2h backfill (one point/min) + // plus a good run of live points before the oldest scroll off. + if (window.chartData.timestamps.length > 600) { window.chartData.timestamps.shift(); window.chartData.lp.shift(); window.chartData.leq.shift();