167 lines
5.2 KiB
Python
167 lines
5.2 KiB
Python
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
|