Repository navigation
Refactor app into modular Flask project + probabilistic forecast, dashboard, map, and tests #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
vrajpatell
merged 1 commit into
master
from
codex/upgrade-earthquake-monitoring-dashboard
Jun 26, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| USGS_FEED_URL=https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson | ||
| CACHE_TTL_SECONDS=300 | ||
| SQLITE_DB_PATH=data/earthquakes.db | ||
| DEFAULT_FEED_WINDOW=day | ||
| MODEL_PATH=models/earthquake_risk.joblib | ||
| ENABLE_BOOTSTRAP_DATA=true | ||
| ENABLE_MODEL_TRAINING_ON_STARTUP=false | ||
| MAX_API_LIMIT=2000 | ||
| ADMIN_TOKEN=change-me-for-admin-training |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| name: tests | ||
| on: [push, pull_request] | ||
| jobs: | ||
| pytest: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-python@v5 | ||
| with: {python-version: '3.11'} | ||
| - run: pip install -r requirements.txt | ||
| - run: pytest | ||
| - run: python -m compileall . |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,10 @@ | ||
| __pycache__/ | ||
| *.pyc | ||
| data/ | ||
| *.py[cod] | ||
| .pytest_cache/ | ||
| .env | ||
| .venv/ | ||
| venv/ | ||
| data/*.db* | ||
| models/*.joblib | ||
| models/*.json | ||
| .DS_Store |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 3.11.15 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # Changelog | ||
|
|
||
| ## 2026-06-26 | ||
|
|
||
| - Refactored the Flask app into an application factory and service modules. | ||
| - Added robust SQLite cache schema, indexes, upserts, refresh metadata, and fallback handling. | ||
| - Added summary, regions, timeseries, forecast, model status/train, and deep health APIs. | ||
| - Added probabilistic risk scoring with heuristic fallback and optional scikit-learn training. | ||
| - Rebuilt dashboard, added Leaflet map, forecast page, and about page. | ||
| - Updated Render deployment, dependencies, environment documentation, tests, and CI. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,149 +1,99 @@ | ||
| # Live Earthquake Dashboard (Flask + JavaScript) | ||
| # QuakeRisk: Earthquake Monitoring & Probabilistic Forecasting Dashboard | ||
|
|
||
| Deployable Flask app for **live earthquake visualization** on Render, with resilient caching and two UI views: | ||
| - Chart dashboard (`/dashboard`) | ||
| - Interactive 3D globe (`/globe`) powered by `globe.gl` | ||
| A production-ready Flask application for monitoring USGS GeoJSON earthquake feeds, caching records in SQLite, visualizing activity with Chart.js/Leaflet/globe.gl, and providing **experimental probabilistic earthquake forecasting**. | ||
|
|
||
| ## Features | ||
| > Scientific disclaimer: earthquakes cannot be predicted deterministically with reliable exact time, location, and magnitude. This app only provides regional risk scoring, magnitude likelihood estimation, aftershock/seismic activity trend analysis, and educational uncertainty warnings. | ||
|
|
||
| - Uses the **USGS live GeoJSON feed** (`all_day`) as primary data source. | ||
| - Caches latest feed data in SQLite (`data/earthquakes.db`) for reliability when the external API is temporarily unavailable. | ||
| - Exposes `GET /api/earthquakes` with magnitude/geo filters and optional force refresh. | ||
| - Includes: | ||
| - chart-based dashboard for quick analytics | ||
| - 3D globe visualization with tooltip details, magnitude sizing/coloring, and auto-refresh controls | ||
| - Ready for Render deployment with Gunicorn via `Procfile`. | ||
| ## Screenshots | ||
|
|
||
| Add screenshots after deployment for the dashboard, 2D map, 3D globe, and forecast page. | ||
|
|
||
| ## Architecture | ||
|
|
||
| - **Backend:** Flask (`main.py`) | ||
| - **Data Source:** USGS Earthquake Feed | ||
| - **Cache:** SQLite local cache (auto-created) | ||
| - **Frontend:** | ||
| - `templates/dashboard.html` (Chart.js dashboard) | ||
| - `templates/globe.html` + `static/js/globe.js` + `static/css/globe.css` (`globe.gl` view) | ||
| - `main.py` keeps `gunicorn main:app` compatibility. | ||
| - `app/__init__.py` creates the Flask app. | ||
| - `app/routes.py` exposes HTML pages and JSON APIs. | ||
| - `app/services/usgs_client.py` fetches/parses USGS GeoJSON. | ||
| - `app/services/cache.py` manages SQLite tables, indexes, upserts, and refresh metadata. | ||
| - `app/services/analytics.py` powers dashboard summary aggregations. | ||
| - `app/services/model.py` implements a heuristic baseline plus optional scikit-learn classifier. | ||
| - `templates/` and `static/` contain responsive frontend pages. | ||
| - `tests/` contains pytest coverage for parsing, cache, APIs, and forecast fallback. | ||
|
|
||
| ## Routes | ||
|
|
||
| - `/` and `/dashboard`: existing dashboard view | ||
| - `/globe`: new 3D globe visualization page | ||
| - `/api/earthquakes`: JSON data endpoint used by both views | ||
| - `/health`: health check endpoint | ||
|
|
||
| ## API | ||
|
|
||
| ### `GET /api/earthquakes` | ||
|
|
||
| Query params: | ||
| - `mag_min`, `mag_max` | ||
| - `lat1`, `lat2` | ||
| - `lon1`, `lon2` | ||
| - `refresh=1` (optional force live refresh) | ||
|
|
||
| Response shape: | ||
|
|
||
| ```json | ||
| { | ||
| "meta": { | ||
| "source": "live|cache|fallback_cache|bootstrap_cache", | ||
| "refresh_result": "live|cache|fallback_cache|bootstrap_cache", | ||
| "last_refresh_epoch": 0, | ||
| "count": 0, | ||
| "filters": {} | ||
| }, | ||
| "data": [ | ||
| { | ||
| "id": "...", | ||
| "time_ms": 0, | ||
| "place": "...", | ||
| "magnitude": 0, | ||
| "latitude": 0, | ||
| "longitude": 0, | ||
| "depth": 0 | ||
| } | ||
| ] | ||
| } | ||
| HTML: `/dashboard`, `/map`, `/globe`, `/forecast`, `/about`. | ||
|
|
||
| APIs: `/api/earthquakes`, `/api/summary`, `/api/regions`, `/api/timeseries`, `/api/forecast`, `/api/model/status`, `POST /api/model/train`, `/health`, `/api/health/deep`. | ||
|
|
||
| ## API examples | ||
|
|
||
| ```bash | ||
| curl 'http://localhost:5000/api/earthquakes?feed=day&mag_min=2.5&limit=100' | ||
| curl 'http://localhost:5000/api/summary?feed=7day' | ||
| curl 'http://localhost:5000/api/forecast?lat=37.7749&lon=-122.4194&radius_km=300&hours=72&min_magnitude=2.5' | ||
| ``` | ||
|
|
||
| ## Local run | ||
| ## ML forecast explanation | ||
|
|
||
| The forecast endpoint returns a 0-100 risk score, bucket, expected activity count, model confidence, top contributing features, nearby events, and a warning. It always works through a baseline heuristic; if enough cached data exists, `POST /api/model/train` can train a scikit-learn classifier saved with joblib. | ||
|
|
||
| Training is time-aware and reports accuracy, precision, recall, F1, confusion matrix, and ROC AUC when valid. It avoids deterministic claims and should not be used for emergency decisions. | ||
|
|
||
| ## Local setup | ||
|
|
||
| ```bash | ||
| python3 -m venv .venv | ||
| python -m venv .venv | ||
| source .venv/bin/activate | ||
| pip install -r requirements.txt | ||
| python3 main.py | ||
| python main.py | ||
| ``` | ||
|
|
||
| Open <http://localhost:5000/dashboard>. | ||
|
|
||
| ## Render deployment | ||
|
|
||
| The included `Procfile` and `render.yaml` use: | ||
|
|
||
| ```bash | ||
| gunicorn main:app --bind 0.0.0.0:$PORT | ||
| ``` | ||
|
|
||
| Open: | ||
| - Dashboard: `http://localhost:5000/dashboard` | ||
| - 3D globe: `http://localhost:5000/globe` | ||
|
|
||
| ## Globe view behavior | ||
|
|
||
| The globe view calls `/api/earthquakes` directly and maps data as follows: | ||
| - `lat` ← `latitude` | ||
| - `lng` ← `longitude` | ||
| - point radius/altitude scale with `magnitude` | ||
| - point color reflects magnitude severity bands | ||
| - tooltip includes place, magnitude, depth, and time | ||
|
|
||
| UI controls include: | ||
| - minimum magnitude filter | ||
| - recent time window filter (last 24h/12h/6h/3h or all cached points) | ||
| - color mode toggle (magnitude or depth) | ||
| - maximum rendered points cap | ||
| - point height scaling slider | ||
| - auto-refresh toggle (2-minute interval) | ||
| - auto-rotate toggle | ||
| - manual refresh button | ||
| - magnitude color legend | ||
| - status, count, source, and last updated timestamp | ||
|
|
||
| Interaction: | ||
| - hover a point for details | ||
| - click a point to focus the camera on that earthquake | ||
|
|
||
| If data is empty or unavailable, the page shows friendly status messaging instead of crashing. | ||
|
|
||
| ## Deploy to Render | ||
|
|
||
| This repository includes both: | ||
| - `Procfile` (classic process command) | ||
| - `render.yaml` (Render Blueprint / IaC) | ||
|
|
||
| ### Option A: Deploy via `render.yaml` (recommended) | ||
|
|
||
| 1. Push the repo to GitHub. | ||
| 2. In Render, choose **New +** → **Blueprint**. | ||
| 3. Select this repository and apply the blueprint. | ||
|
|
||
| ### Option B: Manual Web Service setup | ||
|
|
||
| 1. Push the repo to GitHub. | ||
| 2. In Render, create a **Web Service** from the repo. | ||
| 3. Runtime: Python 3. | ||
| 4. Build command: | ||
| ```bash | ||
| pip install -r requirements.txt | ||
| ``` | ||
| 5. Start command: | ||
| ```bash | ||
| gunicorn main:app --bind 0.0.0.0:$PORT | ||
| ``` | ||
| 6. Optional environment variables: | ||
| - `CACHE_TTL_SECONDS=300` | ||
| - `USGS_FEED_URL=https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson` | ||
|
|
||
| ## Health check | ||
|
|
||
| `GET /health` returns: | ||
|
|
||
| ```json | ||
| {"status":"ok"} | ||
| Health check path: `/health`. | ||
|
|
||
| SQLite works out of the box for Render free-tier compatibility, but local disk can be ephemeral. For durable history, attach a Render persistent disk and set `SQLITE_DB_PATH` to that mount, or migrate the cache layer to Postgres later. | ||
|
|
||
| ## Environment variables | ||
|
|
||
| See `.env.example` for all supported settings: `USGS_FEED_URL`, `CACHE_TTL_SECONDS`, `SQLITE_DB_PATH`, `DEFAULT_FEED_WINDOW`, `MODEL_PATH`, `ENABLE_BOOTSTRAP_DATA`, `ENABLE_MODEL_TRAINING_ON_STARTUP`, `MAX_API_LIMIT`, and `ADMIN_TOKEN`. | ||
|
|
||
| ## Model training | ||
|
|
||
| Set `ADMIN_TOKEN`, then call: | ||
|
|
||
| ```bash | ||
| curl -X POST -H "X-Admin-Token: $ADMIN_TOKEN" http://localhost:5000/api/model/train | ||
| ``` | ||
|
|
||
| ## Notes | ||
| If no token is configured, the training endpoint is disabled. | ||
|
|
||
| ## Tests | ||
|
|
||
| ```bash | ||
| pytest | ||
| python -m compileall . | ||
| ``` | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| - If USGS is unavailable, APIs fall back to cached or clearly labeled bootstrap/demo data. | ||
| - If CDN assets fail, the 2D map and dashboard still provide monitoring views. | ||
| - If model training reports insufficient data, continue using the heuristic forecast until more cache history exists. | ||
|
|
||
| ## Future improvements | ||
|
|
||
| - On startup, the app creates cache tables and attempts refresh. | ||
| - If USGS is unreachable, the app serves last cached records. | ||
| - This behavior is suitable for Render free/limited instances with occasional transient network failures. | ||
| - Persistent Postgres backend. | ||
| - Region-specific model calibration. | ||
| - Background scheduled ingestion. | ||
| - Authentication for admin operations. | ||
| - Screenshot automation for README assets. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| from __future__ import annotations | ||
| import logging | ||
| from flask import Flask | ||
| from app.config import Config | ||
| from app.routes import bp | ||
| from app.services.cache import init_db, seed_bootstrap_if_empty, all_records | ||
| from app.services.model import train | ||
|
|
||
| def create_app(config_class=Config): | ||
| app=Flask(__name__, template_folder='../templates', static_folder='../static') | ||
| app.config.from_object(config_class) | ||
| logging.basicConfig(level=logging.INFO) | ||
| init_db(app.config['SQLITE_DB_PATH']) | ||
| seed_bootstrap_if_empty(app.config['SQLITE_DB_PATH'], app.config['ENABLE_BOOTSTRAP_DATA']) | ||
| if app.config['ENABLE_MODEL_TRAINING_ON_STARTUP']: | ||
| try: train(all_records(app.config['SQLITE_DB_PATH']), app.config['MODEL_PATH']) | ||
| except Exception: app.logger.exception('Startup model training failed') | ||
| app.register_blueprint(bp) | ||
| return app | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| BASE_DIR = Path(__file__).resolve().parent.parent | ||
|
|
||
| class Config: | ||
| SECRET_KEY = os.getenv('SECRET_KEY', 'dev-not-secret') | ||
| USGS_FEED_URL = os.getenv('USGS_FEED_URL', 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson') | ||
| CACHE_TTL_SECONDS = int(os.getenv('CACHE_TTL_SECONDS', '300')) | ||
| SQLITE_DB_PATH = os.getenv('SQLITE_DB_PATH', str(BASE_DIR / 'data' / 'earthquakes.db')) | ||
| DEFAULT_FEED_WINDOW = os.getenv('DEFAULT_FEED_WINDOW', 'day') | ||
| MODEL_PATH = os.getenv('MODEL_PATH', str(BASE_DIR / 'models' / 'earthquake_risk.joblib')) | ||
| ENABLE_BOOTSTRAP_DATA = os.getenv('ENABLE_BOOTSTRAP_DATA', 'true').lower() in {'1','true','yes','on'} | ||
| ENABLE_MODEL_TRAINING_ON_STARTUP = os.getenv('ENABLE_MODEL_TRAINING_ON_STARTUP', 'false').lower() in {'1','true','yes','on'} | ||
| MAX_API_LIMIT = int(os.getenv('MAX_API_LIMIT', '2000')) | ||
| ADMIN_TOKEN = os.getenv('ADMIN_TOKEN', '') | ||
| FLASK_ENV = os.getenv('FLASK_ENV', os.getenv('ENV', 'production')) | ||
| REQUEST_TIMEOUT_SECONDS = int(os.getenv('REQUEST_TIMEOUT_SECONDS', '15')) | ||
|
|
||
| FEEDS = { | ||
| 'hour': 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson', | ||
| 'day': 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson', | ||
| '7day': 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson', | ||
| '30day': 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.geojson', | ||
| 'significant': 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/significant_month.geojson', | ||
| 'm1': 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_month.geojson', | ||
| 'm2.5': 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson', | ||
| 'm4.5': 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_month.geojson', | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the default
ENABLE_BOOTSTRAP_DATA=trueand an empty DB, this inserts bootstrap rows during app creation;upsert_earthquakesrecordslast_successful_refresh_epochas now, so the first/api/earthquakescall hits the TTL check inrefresh_if_neededand returnscacheinstead of trying USGS. New deployments can showbootstrap_demoearthquakes for the fullCACHE_TTL_SECONDSunless a user forces refresh.Useful? React with 👍 / 👎.