diff --git a/src/schemas.py b/src/schemas.py new file mode 100644 index 0000000..2d9e449 --- /dev/null +++ b/src/schemas.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""Pydantic request/response schemas for the water monitoring web API.""" + +from datetime import datetime +from typing import Any, Dict, Optional + +from pydantic import BaseModel, Field + + +class StationResponse(BaseModel): + station_id: int + station_code: str + thai_name: str + english_name: str + latitude: Optional[float] = None + longitude: Optional[float] = None + geohash: Optional[str] = None + status: str = "active" + + +class StationCreateModel(BaseModel): + station_code: str = Field(..., description="Station code (e.g., P.1, P.20)") + thai_name: str = Field(..., description="Thai name of the station") + english_name: str = Field(..., description="English name of the station") + latitude: Optional[float] = Field(None, ge=-90, le=90, description="Latitude coordinate") + longitude: Optional[float] = Field(None, ge=-180, le=180, description="Longitude coordinate") + geohash: Optional[str] = Field(None, description="Geohash for the location") + status: str = Field("active", description="Station status") + + +class StationUpdateModel(BaseModel): + thai_name: Optional[str] = Field(None, description="Thai name of the station") + english_name: Optional[str] = Field(None, description="English name of the station") + latitude: Optional[float] = Field(None, ge=-90, le=90, description="Latitude coordinate") + longitude: Optional[float] = Field(None, ge=-180, le=180, description="Longitude coordinate") + geohash: Optional[str] = Field(None, description="Geohash for the location") + status: Optional[str] = Field(None, description="Station status") + + +class MeasurementResponse(BaseModel): + timestamp: datetime + station_code: str + station_name_en: str + station_name_th: str + water_level: float + discharge: Optional[float] = None + discharge_percent: Optional[float] = None + status: str = "active" + + +class HealthResponse(BaseModel): + overall_status: str + timestamp: str + checks: Dict[str, Dict[str, Any]] + + +class MetricsResponse(BaseModel): + counters: Dict[str, float] + gauges: Dict[str, float] + histograms: Dict[str, Dict[str, float]] + + +class ScrapingStatusResponse(BaseModel): + is_running: bool + last_run: Optional[datetime] = None + next_run: Optional[datetime] = None + total_runs: int = 0 + successful_runs: int = 0 + failed_runs: int = 0 diff --git a/src/web_api.py b/src/web_api.py index 19e2391..7e22128 100644 --- a/src/web_api.py +++ b/src/web_api.py @@ -6,85 +6,30 @@ FastAPI web interface for water monitoring system import asyncio from contextlib import asynccontextmanager from datetime import datetime, timedelta -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List from fastapi import BackgroundTasks, FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse -from pydantic import BaseModel, Field 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 .schemas import ( + HealthResponse, + MeasurementResponse, + MetricsResponse, + ScrapingStatusResponse, + StationCreateModel, + StationResponse, + StationUpdateModel, +) from .water_scraper_v3 import EnhancedWaterMonitorScraper logger = get_logger(__name__) -# Pydantic models for API responses -class StationResponse(BaseModel): - station_id: int - station_code: str - thai_name: str - english_name: str - latitude: Optional[float] = None - longitude: Optional[float] = None - geohash: Optional[str] = None - status: str = "active" - - -class StationCreateModel(BaseModel): - station_code: str = Field(..., description="Station code (e.g., P.1, P.20)") - thai_name: str = Field(..., description="Thai name of the station") - english_name: str = Field(..., description="English name of the station") - latitude: Optional[float] = Field(None, ge=-90, le=90, description="Latitude coordinate") - longitude: Optional[float] = Field(None, ge=-180, le=180, description="Longitude coordinate") - geohash: Optional[str] = Field(None, description="Geohash for the location") - status: str = Field("active", description="Station status") - - -class StationUpdateModel(BaseModel): - thai_name: Optional[str] = Field(None, description="Thai name of the station") - english_name: Optional[str] = Field(None, description="English name of the station") - latitude: Optional[float] = Field(None, ge=-90, le=90, description="Latitude coordinate") - longitude: Optional[float] = Field(None, ge=-180, le=180, description="Longitude coordinate") - geohash: Optional[str] = Field(None, description="Geohash for the location") - status: Optional[str] = Field(None, description="Station status") - - -class MeasurementResponse(BaseModel): - timestamp: datetime - station_code: str - station_name_en: str - station_name_th: str - water_level: float - discharge: Optional[float] = None - discharge_percent: Optional[float] = None - status: str = "active" - - -class HealthResponse(BaseModel): - overall_status: str - timestamp: str - checks: Dict[str, Dict[str, Any]] - - -class MetricsResponse(BaseModel): - counters: Dict[str, float] - gauges: Dict[str, float] - histograms: Dict[str, Dict[str, float]] - - -class ScrapingStatusResponse(BaseModel): - is_running: bool - last_run: Optional[datetime] = None - next_run: Optional[datetime] = None - total_runs: int = 0 - successful_runs: int = 0 - failed_runs: int = 0 - - # Global application state app_state = { "scraper": None,