"""Read historical station measurements from PostgreSQL.""" import datetime import os from typing import Dict, List, Optional from sqlalchemy import create_engine, text class PostgresHistory: def __init__(self, connection_string: Optional[str] = None, engine=None): connection_string = connection_string or os.getenv("POSTGRES_CONNECTION_STRING") if engine is None and not connection_string: raise RuntimeError("POSTGRES_CONNECTION_STRING is not configured") self.engine = engine or create_engine(connection_string, pool_pre_ping=True) def station_history( self, station_code: str, start: datetime.datetime, end: datetime.datetime, limit: int = 2000, ) -> List[Dict]: if not 1 <= limit <= 10000: raise ValueError("limit must be between 1 and 10000") if start >= end: raise ValueError("start must be before end") query = text( """ SELECT m.timestamp, s.station_code, m.water_level, m.discharge, m.discharge_percent FROM water_measurements m JOIN stations s ON m.station_id = s.id WHERE s.station_code = :station_code AND m.timestamp >= :start_time AND m.timestamp <= :end_time ORDER BY m.timestamp ASC LIMIT :limit """ ) with self.engine.connect() as connection: rows = connection.execute( query, { "station_code": station_code, "start_time": start, "end_time": end, "limit": limit, }, ) result = [] for row in rows: timestamp = row[0] if isinstance(timestamp, str): timestamp = datetime.datetime.fromisoformat(timestamp) result.append( { "timestamp": timestamp, "station_code": row[1], "water_level": float(row[2]) if row[2] is not None else None, "discharge": float(row[3]) if row[3] is not None else None, "discharge_percent": float(row[4]) if row[4] is not None else None, } ) return result