Dashboard coverage: count 5-minute slots with data, not readings vs the recent interval
Publish Container / publish (push) Successful in 2m29s

Coverage divided each day's reading count by 1440/median-recent-interval, so days logged at a
slower pace (100 s, 120 s) showed as 50-60% complete once uploads sped up to 60 s. Each day now
records which of its 288 five-minute slots have a reading (backfilled from raw files on startup),
so coverage is independent of upload interval. Older days without slot data fall back to the old maths.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Nate EmmertandClaude Sonnet 5.5 committed 2026-10-20 10:46:44 -04:00
1 parent b8ce8ce149
commit 9515d1af00
2 files changed
+39 -4

No files matched your search

+11 -4
View File
@@ -301,7 +301,13 @@ function medianIntervalMin() {
d.sort((a, b) => a - b);
return d[d.length >> 1] / 60000;
}
function coverageFor(row, perDay) { return row && perDay ? Math.min(100, (row.n / perDay) * 100) : null; }
// Share of the day's 288 five-minute slots that have at least one reading, so it doesn't matter whether the
// station reports every 60, 100 or 120 s. Days saved before slots were tracked fall back to reading count.
function coverageFor(row, perDay) {
if (!row) return null;
if (row.slots != null) return Math.min(100, (row.slots / 288) * 100);
return perDay ? Math.min(100, (row.n / perDay) * 100) : null;
}
function renderHealthCard() {
const host = $('healthCard');
@@ -311,7 +317,8 @@ function renderHealthCard() {
const interval = medianIntervalMin();
const perDay = interval ? 1440 / interval : null;
const past = DAILY.filter((d) => d.d !== todayStr()).slice(-30);
const cov = perDay && past.length ? mean(past.map((d) => coverageFor(d, perDay))) : null;
const covVals = past.map((d) => coverageFor(d, perDay)).filter((v) => v != null);
const cov = covVals.length ? mean(covVals) : null;
const t = todayRow();
host.innerHTML = `<div class="ui-card"><h3>Station Health</h3><div class="kv">
<div><b><span class="pill ${ok ? 'ok' : 'warn'}">${ok ? 'Online' : 'Delayed'}</span></b><span>Last report</span><small>${ageMin <= 0 ? 'just now' : ageMin + ' min ago'}</small></div>
@@ -530,8 +537,8 @@ function renderInsights() {
const interval = medianIntervalMin();
const perDay = interval ? 1440 / interval : null;
const cov = past.slice(-45);
if (perDay) {
const vals = cov.map((d) => +coverageFor(d, perDay).toFixed(0));
if (cov.length) {
const vals = cov.map((d) => +(coverageFor(d, perDay) ?? 0).toFixed(0));
const ds = barDataset('Coverage %', vals, c.cool);
ds.backgroundColor = vals.map((v) => (v < 90 ? c.accent + 'bb' : c.cool + 'bb'));
makeChart('inCoverage', 'bar', [ds], cov.map((d) => prettyDay(d.d)), '%', { scales: { y: { min: 0, max: 100 } } });