from __future__ import annotations from datetime import datetime from typing import Any, List, Optional from fastapi import APIRouter, HTTPException, Query from app.schemas.metrics import ( MetricCreate, MetricResultsWriteRequest, MetricRunTrigger, MetricScheduleCreate, MetricScheduleUpdate, MetricUpdate, ) from app.services import metric_store router = APIRouter(prefix="/api/v1", tags=["metrics"]) @router.post("/metrics") def create_metric(payload: MetricCreate) -> Any: """Create a metric definition.""" try: return metric_store.create_metric(payload) except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.post("/metrics/{metric_id}") def update_metric(metric_id: int, payload: MetricUpdate) -> Any: """Update fields of a metric definition.""" try: return metric_store.update_metric(metric_id, payload) except KeyError: raise HTTPException(status_code=404, detail="Metric not found") except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.get("/metrics/{metric_id}") def get_metric(metric_id: int) -> Any: """Fetch a metric definition by id.""" metric = metric_store.get_metric(metric_id) if not metric: raise HTTPException(status_code=404, detail="Metric not found") return metric @router.get("/metrics") def list_metrics( biz_domain: Optional[str] = None, is_active: Optional[bool] = None, keyword: Optional[str] = Query(None, description="Search by code/name"), limit: int = Query(100, ge=1, le=500), offset: int = Query(0, ge=0), ) -> List[Any]: """List metrics with optional filters.""" return metric_store.list_metrics( biz_domain=biz_domain, is_active=is_active, keyword=keyword, limit=limit, offset=offset, ) @router.post("/metric-schedules") def create_schedule(payload: MetricScheduleCreate) -> Any: """Create a metric schedule.""" try: return metric_store.create_metric_schedule(payload) except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.post("/metric-schedules/{schedule_id}") def update_schedule(schedule_id: int, payload: MetricScheduleUpdate) -> Any: """Update a metric schedule.""" try: return metric_store.update_metric_schedule(schedule_id, payload) except KeyError: raise HTTPException(status_code=404, detail="Schedule not found") except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.get("/metrics/{metric_id}/schedules") def list_schedules(metric_id: int) -> List[Any]: """List schedules for one metric.""" return metric_store.list_schedules_for_metric(metric_id=metric_id) @router.post("/metric-runs/trigger") def trigger_run(payload: MetricRunTrigger) -> Any: """Insert a run record (execution handled externally).""" try: return metric_store.trigger_metric_run(payload) except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.get("/metric-runs") def list_runs( metric_id: Optional[int] = None, status: Optional[str] = None, limit: int = Query(100, ge=1, le=500), offset: int = Query(0, ge=0), ) -> List[Any]: """List run records.""" return metric_store.list_metric_runs( metric_id=metric_id, status=status, limit=limit, offset=offset ) @router.get("/metric-runs/{run_id}") def get_run(run_id: int) -> Any: """Fetch run details.""" run = metric_store.get_metric_run(run_id) if not run: raise HTTPException(status_code=404, detail="Run not found") return run @router.post("/metric-results/{metric_id}") def write_results(metric_id: int, payload: MetricResultsWriteRequest) -> Any: # Align path metric_id with payload to avoid mismatch. if payload.metric_id != metric_id: raise HTTPException(status_code=400, detail="metric_id in path/body mismatch") try: inserted = metric_store.write_metric_results(payload) except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc return {"metric_id": metric_id, "inserted": inserted} @router.get("/metric-results") def query_results( metric_id: int, stat_from: Optional[datetime] = None, stat_to: Optional[datetime] = None, limit: int = Query(200, ge=1, le=1000), offset: int = Query(0, ge=0), ) -> List[Any]: """Query metric results by time range.""" return metric_store.query_metric_results( metric_id=metric_id, stat_from=stat_from, stat_to=stat_to, limit=limit, offset=offset, ) @router.get("/metric-results/latest") def latest_result(metric_id: int) -> Any: """Fetch the latest metric result.""" result = metric_store.latest_metric_result(metric_id) if not result: raise HTTPException(status_code=404, detail="Metric result not found") return result