A real-time browser dashboard that monitors space weather, global seismic activity, and visualizes the scientifically-debated 27–28 day correlation lag between geomagnetic storms and earthquake probability.
No build step. No API key. No database. Pure ES modules + public APIs.
Operational earthquake, space-weather, and environmental feeds are fetched live from NOAA, USGS, and Open-Meteo at runtime. The tectonic map now uses cited local PB2002 GeoJSON artifacts for both present-day plate regions and boundary geometry, generated from Bird's public source files.
Client-side IndexedDB provides a 90-day rolling event window for correlation analysis.
A Node.js Express server proxies external APIs to eliminate CORS issues in deployment and enforce query validation/security headers. An optional local Python research stack (Flask, numpy, pandas, statsmodels, scikit-learn) is reserved for heavier null calibration, interpretable modeling, and scorecard-style evaluation without changing the public runtime.
Recommended local launch:
npm run launch
See docs/planning/ROADMAP.md for the full development plan.
See .github/copilot-instructions.md for contributor/AI coding context.
| Data | Source | Feed |
|---|---|---|
| Earthquakes M4.5+ | USGS Earthquake Hazards | GeoJSON real-time (1-min lag) |
| Solar Wind speed/density | NOAA DSCOVR/ACE Plasma | 1-min JSON feed |
| Solar Wind Bt/Bz | NOAA DSCOVR/ACE Mag | 1-min JSON feed |
| Kp Geomagnetic Index | NOAA SWPC | Real-time + 3-day history |
| X-ray Flux / Solar Flares | NOAA GOES-Primary | 7-day JSON |
| Dst Index | NOAA / Kyoto WDC | Live via /api/noaa/dst |
| Proton Flux | NOAA GOES-Primary | 6-hour JSON |
| Historical earthquake search | USGS ComCat | Validated proxy via /api/usgs/comcat |
| Historical geomagnetic indices | NOAA/NCEI SWPC dayind archive |
Validated proxy via /api/noaa/dayind?date=YYYY-MM-DD |
| Weather | Open-Meteo | Free API, no key |
| Air Quality (PM2.5, AQI) | Open-Meteo Air Quality | Free API, no key |
| Global tectonic plate regions + boundaries | Bird PB2002 (2003) | Local GeoJSON artifacts at public/data/tectonics/pb2002-plates.geojson and public/data/tectonics/pb2002-boundaries.geojson |
| Map Tiles | OpenStreetMap / Esri / CARTO | CDN |
The tectonic map layers are intentionally not live third-party browser dependencies. They are reproducible local artifacts generated from Peter Bird's public PB2002 source files via npm run build:tectonics, so the default plate-study view remains cited, stable, and cacheable.
| Tab | What it does |
|---|---|
| Map | Interactive Leaflet map — the primary 2D research view for live USGS earthquakes, cited PB2002 plate regions, subtype-styled tectonic boundary families (including explicit subduction symbology), a partial local motion-vector artifact keyed to real plate codes, a magnitude filter slider, clearer plate-study controls, and multiple basemaps; any future 3D globe should remain optional and isolated from the default map path |
| Space Weather | Live NOAA solar wind (Chart.js animated line chart), Kp index (colour-coded bar chart), 3-day history with storm threshold, GOES X-ray flare log |
| Seismic | Dynamic USGS earthquake list (newest first, time-ago), statistics (M5+/M6+ counts, largest), magnitude distribution chart (Chart.js horizontal bars with magnitude color-coding) |
| Environment | Real-time weather (temp, feels-like, humidity, pressure, wind, condition) and air quality (PM2.5, PM10, CO, NO₂, European AQI) via Open-Meteo free API, AQI gauge doughnut chart |
| Correlation | 0–60 day lag scan, conditional `P(M5+ |
| Settings | Configurable alert thresholds, dark mode toggle (☀️/🌙), notifications, localStorage persistence |
Visual Polish 🎨
- ✅ 8-point spacing grid + elevation shadow tokens
- ✅ Glass-morphism surfaces (footer, cards)
- ✅ Fluid responsive grids (320px → 1440px+)
- ✅ Chart.js upgrade: All 5 charts now interactive, dark-mode aware, animated
- ✅ Dark mode toggle: Class-based CSS, localStorage persistence, smooth 300ms transitions
Robustness & Error Handling 🛡️
- ✅ Fetch timeout + exponential backoff retry (10s timeout, 2s/4s/8s delays)
- ✅ Applied to all APIs: NOAA, USGS, Open-Meteo
- ✅ Connection status indicator (online/degraded/offline)
- ✅ Graceful fallback to demo data on API failure
Data Persistence 💾
- ✅ IndexedDB storage: 90-day rolling window for storms and earthquakes
- ✅ Automatic data pruning: older records cleaned up daily
- ✅ Data persists across page reloads and browser sessions
- ✅ Reactive store with pub/sub pattern for UI updates
Statistical Enhancements 📊
- ✅ Pearson correlation coefficient (r) calculation
- ✅ Fisher transform p-value estimation (two-tailed)
- ✅ Correlation strength labels: None/Weak/Moderate/Strong
- ✅ Configurable lag window analysis (default 21–35 days, mid-point 27–28)
Installable on mobile, tablet, and desktop. Works offline with cached data.
- Manifest: Standalone app mode (no address bar), custom icons, shortcuts
- Service Worker: Cache-first strategy for assets (CSS, JS, Leaflet, Chart.js), network-first for APIs with stale-while-revalidate
- Offline Support: Cached earthquake/storm data available without network
- Background Sync: Refreshes data automatically when reconnected
- Add to Home Screen: Pinnable on iOS/Android with custom icon
Install: Click the "Install" button when visiting the site (in supported browsers), or use browser menu "Add to Home Screen".
Requires Node ≥ 18 for the recommended launch flow. ES modules cannot run on file://, so use a local server.
npm install
npm run launchWhat npm run launch does:
- reuses an already-running local app server if one exists
- otherwise starts
server.js - opens your default browser to
http://localhost:3000
Optional headless launch:
npm run launch:headlessnpm start
# then open http://localhost:3000The Node proxy is the primary runtime because it:
- serves
public/as the isolated web root - proxies NOAA / USGS / Open-Meteo to avoid browser CORS issues
- applies rate limiting, CSP,
X-Frame-Options,nosniff, and query validation
If you change the curated tectonic-source workflow or want to rebuild the local PB2002 plate-model artifacts from Bird's source files:
npm run build:tectonicsThis regenerates both public/data/tectonics/pb2002-plates.geojson and public/data/tectonics/pb2002-boundaries.geojson for the browser map. The runtime uses those cited local artifacts first and only falls back to the old built-in sketch lines if the boundary file fails to load.
Port is configurable. PowerShell example:
$env:PORT = "3001"
npm run launch# Activate the workspace venv first
solar-env\Scripts\Activate.ps1
python -m http.server 8000 --directory public
# open http://localhost:8000Use this mode only for quick static rendering checks. It is not the recommended way to run the full app because some live data streams will be blocked or degraded without the Node proxy.
The browser app can now call an optional local Python sidecar for deterministic bootstrap / permutation-style null calibration.
solar-env\Scripts\Activate.ps1
python scripts/research_sidecar.pyWhat it does:
- binds to
127.0.0.1:5051only - stays local-only and is never exposed directly to the browser
- is reached through the Node proxy at
/api/research/statusand/api/research/bootstrap - powers the Correlation tab's Run Bootstrap Null Test workflow
- uses the workspace research stack for heavier analysis (
numpy,pandas,statsmodels,scikit-learn) when browser JavaScript would become awkward or opaque
This is optional research compute, not normal app startup. If the sidecar is not running, the rest of the dashboard still works.
The venv at solar-env/ is reserved for future research/compute work such as:
- bootstrap or permutation testing for the null distribution
- regional stratification and long-window archive analysis
- Gutenberg–Richter / b-value calculations
- interpretable GLMs / count models via
statsmodels - calibration and holdout scorecards via
scikit-learn - optional sidecar analytics services
An optional local Python research sidecar now exists at scripts/research_sidecar.py, but the venv is still not required for normal dashboard usage.
When Python is used in this repo, the rule is strict:
- always activate
solar-env\Scripts\Activate.ps1first - never use system Python
- keep Node/Express as the main user-facing server
- proxy any Python sidecar through
server.jsrather than exposing it directly to the browser
# To set up the venv from scratch:
python -m venv solar-env
solar-env\Scripts\Activate.ps1
pip install -r requirements.txtContainerization note: Docker is a future optional reproducibility/deployment path, not the default local workflow. If adopted later, Node should remain the public entry point and the Python research sidecar should stay optional rather than becoming a mandatory second runtime for every contributor.
tectonic-solar/
├── server.js # Node proxy server + security headers + research feed validation
├── package.json # Runtime scripts (`launch`, `start`, `test:tabs`)
├── requirements.txt # Python research environment dependencies
├── public/ # Browser-served web root
│ ├── index.html
│ ├── manifest.json
│ ├── sw.js
│ ├── data/
│ │ └── tectonics/
│ │ ├── pb2002-plates.geojson
│ │ └── pb2002-boundaries.geojson
│ └── src/
│ ├── css/
│ └── js/
├── scripts/
│ ├── launch.js # Friendly launcher: start/reuse server + open browser
│ ├── build-pb2002-boundaries.mjs # Rebuild cited PB2002 plate/boundary GeoJSON artifacts
│ ├── research_sidecar.py # Local-only Flask sidecar for bootstrap null calibration
│ ├── research_stats.py # Pure NumPy research helpers used by the sidecar
│ ├── hypothesis-sim.mjs # Deterministic lag-analysis sanity harness
│ ├── tab-smoke-test.mjs # 6-tab Playwright smoke test
│ ├── verify-visuals.js
│ ├── lighthouse-automation.js
│ ├── restart-server.js
│ └── test-automation.js
├── tests/
│ └── test_research_stats.py # Pytest checks for bootstrap/null helper behavior
├── docs/
│ ├── planning/
│ ├── research/
│ ├── operations/
│ ├── testing/
│ ├── development/
│ └── handoff/
└── solar-env/ # Local Python venv for future research compute tasks
The app is built to test a contested 27–28 day lag hypothesis: the idea that geomagnetic storms may be associated with elevated earthquake probability roughly one solar rotation later. This is an exploratory research target, not an established mechanism.
Key references:
- Odintsov et al. (2006) — Geomagnetic activity and seismicity
- Han et al. (2004) — Statistical correlation study
- Varga & Grafarend (2018) — Earth rotation and seismicity
Current hypothesis workflow in the app:
- live storm and earthquake monitoring
- 0–60 day lag scan with explicit null-result framing
- empirical conditional probability of M5+ activity in the 25–30 day post-storm window
- optional bootstrap null calibration through the local Python sidecar
- optional historical USGS ComCat load through a validated proxy route
| Concern | Primary file(s) | Role |
|---|---|---|
| Research narrative / falsification criteria | docs/research/RESEARCH.md |
Explains what is being tested, what would count as support, and what would falsify the idea |
| Shared lag-analysis logic | public/src/js/hypothesis-core.mjs |
Normalization, lag scan, conditional probability, and conservative interpretation states |
| Historical loading + orchestration | public/src/js/prediction.js |
Storm seed/archive loading, full analysis runner, and UI-facing prediction outputs |
| Legacy/basic correlation UI | public/src/js/correlation.js |
Older 27–28 day window indicator, timeline refresh, and Pearson/Fisher helper path |
| Optional Python null calibration bridge | public/src/js/researchCompute.js |
Browser-side adapter for the local Python sidecar behind the Node proxy |
| Deterministic validation harness | scripts/hypothesis-sim.mjs |
Null, positive-control, and off-target checks for the same core lag-analysis logic |
This split is intentional. Keep method docs, live UI behavior, heavy compute, and validation notes separated by concern rather than pretending the whole hypothesis workflow lives in one file.
The near-term plan is intentionally conservative and research-first:
- Make the app easy to run so testing and exploration happen routinely (
npm run launch) - Reproduce the null first on short live windows and in deterministic simulation before making any signal claims
- Expand historical depth securely via public, keyless, validated proxy feeds
- Use Python only for heavy statistical work once the browser/Node path is saturated
Use the same lag-analysis core in two modes:
- Live / historical mode inside the app for real NOAA + USGS data and validated ComCat backfill
- Official storm-history mode inside the app for NOAA/NCEI
dayinddaily geomagnetic indices (3-hour planetary Kp archive) - Deterministic simulation mode to verify that the engine stays near-null under independence, detects an implanted 27-day signal, and does not mislabel an off-target lag
The live app now distinguishes between insufficient data, null-consistent, off-target peak, weak 25–30d bump, and candidate 25–30d signal so a high percentage alone is not mistaken for evidence.
Legacy Pearson/Fisher outputs still exist for the older/basic browser correlation path, but they are no longer the sole evidence surface for the hypothesis workflow.
When moving from setup to real-data analysis, the preferred path is now:
- run
npm run test:hypothesis-sim - launch the app
- click Load Full Research Foundation in the Correlation tab
- optionally start the Python research sidecar and click Run Bootstrap Null Test
- rerun the lag scan on the combined NOAA + USGS historical corpus
npm run test:hypothesis-simThis simulation script is a sanity check for the analysis engine, not evidence that the real-world hypothesis is true.
For the fuller execution plan, see docs/planning/ROADMAP.md.
This project intentionally keeps the research surface area wide without loosening the security model:
public/is the only served web root- no API keys or authenticated feeds
- no server-side database or caching layer
- Node proxy applies rate limiting to the API surface and security headers to the whole app
- historical research queries are validated before proxying upstream
- historical NOAA storm archive requests are date-validated before proxying upstream
- new external feeds should be added through
server.js+public/src/js/config.js - live-data UI should prefer safe DOM APIs over
innerHTML
Minimum: Chrome ≥89 · Firefox ≥78 · Safari ≥14 · Edge ≥89
Features: ES2020, Fetch API, IndexedDB, Service Workers, Notifications, CSS custom properties
Tested:
- ✅ Chrome 120+ (desktop, mobile)
- ✅ Firefox 115+ (desktop)
- ✅ Safari 16+ (macOS, iOS 16+)
- ✅ Edge 120+ (desktop)
| Metric | Target | Notes |
|---|---|---|
| First Paint | <1s | CSS in <head>, no render-blocking JS |
| Largest Contentful Paint | <2.5s | Chart.js takes 1.5–2s for complex data |
| Interaction to Paint | <100ms | Chart hover tooltips, tab switches |
| Service Worker Cache | <500KB | Gzipped: 45KB JS + 8KB CSS + 160KB Leaflet + 180KB Chart.js |
| IndexedDB Query (100K records) | <50ms | Date-indexed range queries |
| Lighthouse PWA Score | ≥90 | Manifest, SW, HTTPS (if deployed) |
Testing Checklist:
- Launch via
npm run launch - Confirm the Map info/footer source labels resolve to
Bird PB2002 (2003)rather than the fallback sample state - Run
npm run test:hypothesis-simand confirm null / positive-control / off-target scenarios behave as expected - Load the 2-year NOAA storm archive and confirm the storm catalog moves beyond seed/live-only mode
- Responsive layout at 375px, 768px, 1440px
- Dark mode toggle persists across reload
- Offline mode: disable network in DevTools, verify cached data loads
- All charts render and respond to hover
- IndexedDB persists storms/earthquakes after page reload
- Service Worker cache inspected in DevTools Application tab
- Correlation surfaces render correctly, including any legacy Pearson r / p-value outputs still shown in the browser path
- Fetch retry: simulate API timeout, verify exponential backoff
- Service Worker: Static asset list is manual (update
sw.jsif new CDN libs added) - Tectonic layer: PB2002 is a cited present-day plate model (regions + boundaries), not a live plate-motion service or a time-varying tectonic reconstruction
- Motion vectors: the current arrow layer is now loaded from a local partial artifact keyed to PB2002 plate codes, but it still only covers 6 plates and remains illustrative rather than authoritative
- Dark Mode & Leaflet: Leaflet map tiles don't respond to dark mode toggle (Leaflet layer limitation)
- IndexedDB Data Volume: Pruning uses synchronous cursor traversal (acceptable up to ~1M records)
- Correlation: Default lag window 21–35 days (27–28 day mid-point) — adjustable via JavaScript but no UI slider yet
- Chart.js: No native line drawing between correlation pairs (workaround: scatter points only)
- .github/copilot-instructions.md — Contributor/AI coding context (server setup, venv, data sources, conventions)
- docs/planning/SPRINT-1-DELIVERY.md — Complete list of Sprint 1-4 changes
- docs/handoff/HANDOFF.md — Handoff index / lineage root; see
docs/handoff/for dated session handoffs - docs/development/DEV-QUICK-REFERENCE.md — Developer guide for extensions and maintenance
- docs/planning/ROADMAP.md — Future features and the current research execution plan
- docs/research/RESEARCH.md — Hypothesis analysis and falsification criteria