[verified] feat: add live river dashboard
Add mapped river and ThaiWater sensor layers, PostgreSQL history charts, API endpoints, and dashboard tests.
This commit is contained in:
+50
-1
@@ -9,14 +9,17 @@ from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from fastapi import BackgroundTasks, FastAPI, HTTPException
|
||||
import requests
|
||||
from fastapi import BackgroundTasks, FastAPI, HTTPException, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from .config import Config
|
||||
from .health_check import APIHealthCheck, DatabaseHealthCheck, HealthCheckManager, MemoryHealthCheck
|
||||
from .logging_config import get_logger, setup_logging
|
||||
from .metrics import get_metrics_collector, increment_counter, set_gauge
|
||||
from .postgres_history import PostgresHistory
|
||||
from .schemas import (
|
||||
HealthResponse,
|
||||
MeasurementResponse,
|
||||
@@ -26,6 +29,7 @@ from .schemas import (
|
||||
StationResponse,
|
||||
StationUpdateModel,
|
||||
)
|
||||
from .thaiwater import ThaiWaterClient
|
||||
from .water_scraper_v3 import EnhancedWaterMonitorScraper
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -108,6 +112,7 @@ app = FastAPI(
|
||||
version="3.1.3",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
app.mount("/static", StaticFiles(directory=os.path.dirname(_DASHBOARD_HTML_PATH)), name="static")
|
||||
|
||||
# Add CORS middleware.
|
||||
# Origins come from CORS_ALLOW_ORIGINS (comma-separated). When none are configured
|
||||
@@ -419,6 +424,50 @@ def _to_measurement_response(measurement: Dict[str, Any]) -> MeasurementResponse
|
||||
)
|
||||
|
||||
|
||||
@app.get("/sensors/thaiwater")
|
||||
async def get_thaiwater_sensors():
|
||||
"""Get current ThaiWater water-level sensors in the Ping basin."""
|
||||
increment_counter("api_requests", labels={"endpoint": "thaiwater_sensors"})
|
||||
try:
|
||||
client = ThaiWaterClient(
|
||||
api_key=Config.THAIWATER_API_KEY,
|
||||
timeout=Config.REQUEST_TIMEOUT,
|
||||
)
|
||||
return await asyncio.to_thread(client.fetch_ping_sensors)
|
||||
except RuntimeError as error:
|
||||
raise HTTPException(status_code=503, detail=str(error))
|
||||
except requests.RequestException as error:
|
||||
logger.error(f"Error fetching ThaiWater sensors: {error}")
|
||||
raise HTTPException(status_code=502, detail="ThaiWater API unavailable")
|
||||
|
||||
|
||||
@app.get("/measurements/history/{station_code}")
|
||||
async def get_postgres_history(
|
||||
station_code: str,
|
||||
hours: int = Query(168, ge=1, le=24 * 365),
|
||||
limit: int = Query(2000, ge=1, le=5000),
|
||||
):
|
||||
"""Get historical measurements for a station from PostgreSQL."""
|
||||
try:
|
||||
db_config = Config.get_database_config()
|
||||
if db_config["type"] != "postgresql":
|
||||
raise RuntimeError("PostgreSQL is not configured")
|
||||
history = PostgresHistory(db_config["connection_string"])
|
||||
end_time = datetime.now()
|
||||
return await asyncio.to_thread(
|
||||
history.station_history,
|
||||
station_code,
|
||||
end_time - timedelta(hours=hours),
|
||||
end_time,
|
||||
limit,
|
||||
)
|
||||
except RuntimeError as error:
|
||||
raise HTTPException(status_code=503, detail=str(error))
|
||||
except Exception as error:
|
||||
logger.error(f"Error fetching PostgreSQL history: {error}")
|
||||
raise HTTPException(status_code=502, detail="PostgreSQL history unavailable")
|
||||
|
||||
|
||||
@app.get("/measurements/latest", response_model=List[MeasurementResponse])
|
||||
async def get_latest_measurements(limit: int = 100):
|
||||
"""Get latest measurements from all stations"""
|
||||
|
||||
Reference in New Issue
Block a user