feat: database stats on the dashboard via GET /api/stats
CI/CD Pipeline - Northern Thailand Ping River Monitor / Test Suite (3.11) (push) Failing after 23s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Build Docker Image (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Integration Test with Services (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Staging (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Production (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Performance Test (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Code Quality (push) Successful in 14s
Documentation / Validate Documentation (push) Failing after 8s
Documentation / Generate API Documentation (push) Successful in 9s
Documentation / Build Sphinx Documentation (push) Successful in 17s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Cleanup (push) Successful in 1s
Documentation / Documentation Summary (push) Successful in 3s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Test Suite (3.11) (push) Failing after 23s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Build Docker Image (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Integration Test with Services (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Staging (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Production (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Performance Test (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Code Quality (push) Successful in 14s
Documentation / Validate Documentation (push) Failing after 8s
Documentation / Generate API Documentation (push) Successful in 9s
Documentation / Build Sphinx Documentation (push) Successful in 17s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Cleanup (push) Successful in 1s
Documentation / Documentation Summary (push) Successful in 3s
New SQLAdapter.get_database_stats() aggregates totals, station count, date range, and hourly-slot coverage in one query per dialect. The endpoint follows the existing 503-guard/to_thread/TTL-cache pattern; the dashboard gains a five-tile stats strip on the existing refresh cadence. Coverage denominator is hour-truncated so off-hour endpoints cannot push it past 100%; MySQL slot expression avoids % characters that would break under pyformat bind interpolation.
This commit is contained in:
@@ -255,6 +255,14 @@
|
||||
</div>
|
||||
<div style="height:260px;margin-top:14px;overflow:hidden;position:relative"><canvas id="history-chart" aria-label="Historical water level and discharge chart" style="display:block"></canvas></div>
|
||||
</section>
|
||||
|
||||
<section class="stats" id="db-stats" aria-label="Database statistics" style="margin-top:14px;display:none;grid-template-columns:repeat(auto-fit,minmax(170px,1fr))">
|
||||
<article class="stat"><div class="stat-label">Total datapoints</div><div class="stat-value" id="db-total">—</div><div class="stat-note">measurements stored</div></article>
|
||||
<article class="stat"><div class="stat-label">Date range</div><div class="stat-value" id="db-range" style="font-size:1.05rem;line-height:1.3;white-space:normal">—</div><div class="stat-note">first to last record</div></article>
|
||||
<article class="stat"><div class="stat-label">Days spanned</div><div class="stat-value" id="db-days">—</div><div class="stat-note">between first and last</div></article>
|
||||
<article class="stat"><div class="stat-label">Stations</div><div class="stat-value" id="db-stations">—</div><div class="stat-note">distinct in database</div></article>
|
||||
<article class="stat"><div class="stat-label">Coverage</div><div class="stat-value" id="db-coverage">—</div><div class="stat-note">of hourly slots recorded</div></article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH" crossorigin="anonymous"></script>
|
||||
@@ -656,6 +664,7 @@
|
||||
renderSummary(stations, readings);
|
||||
$('loading').style.display = 'none';
|
||||
loadForecasts(); // non-blocking; the card stays hidden until models are deployed
|
||||
loadDbStats(); // non-blocking; the strip stays hidden if /api/stats is unavailable
|
||||
} catch (error) {
|
||||
$('loading').style.display = 'none';
|
||||
$('error').style.display = 'grid';
|
||||
@@ -1017,6 +1026,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDbStats() {
|
||||
const strip = $('db-stats');
|
||||
try {
|
||||
const response = await fetch('/api/stats');
|
||||
if (!response.ok) { strip.style.display = 'none'; return; }
|
||||
const stats = await response.json();
|
||||
const fmtDate = (value) => new Date(value).toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' });
|
||||
$('db-total').textContent = Number(stats.total_measurements).toLocaleString();
|
||||
$('db-range').textContent = `${fmtDate(stats.first_timestamp)} – ${fmtDate(stats.last_timestamp)}`;
|
||||
$('db-days').textContent = Number(stats.days_spanned).toLocaleString();
|
||||
$('db-stations').textContent = String(stats.station_count);
|
||||
$('db-coverage').textContent = stats.coverage_percent == null ? '—' : `${Number(stats.coverage_percent).toFixed(1)}%`;
|
||||
strip.style.display = 'grid';
|
||||
} catch (error) {
|
||||
strip.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
$('refresh-button').addEventListener('click', loadDashboard);
|
||||
$('zones-toggle').addEventListener('click', toggleFloodZones);
|
||||
$('replay-2024').addEventListener('click', replayFlood2024);
|
||||
|
||||
Reference in New Issue
Block a user