LIBRE / src /interface /api /routes /prediction_routes.py
RyZ
feat: adding predicted ecg
75b7786
Raw
History Blame Contribute Delete
5.7 kB
"""
interface/api/routes/prediction_routes.py
───────────────────────────────────────────
REST endpoints for querying BP prediction history.
Endpoints:
GET /api/v1/predictions/{user_id} β€” Latest N predictions for a user
GET /api/v1/predictions/{user_id}/signal/{signal_id} β€” Prediction for a specific signal
Error handling:
Domain exceptions bubble up to the global handlers in error_handlers.py.
EntityNotFoundError is raised explicitly when a lookup returns None.
"""
from __future__ import annotations
from datetime import datetime
from typing import Annotated, Optional
from fastapi import APIRouter, Depends, Query, status
from src.application.dto.prediction_dto import PredictionHistoryResponse, PredictionResponse
from src.application.use_cases.get_prediction_history import GetPredictionHistoryUseCase
from src.domain.exceptions.domain_exceptions import EntityNotFoundError
from src.domain.interfaces.repositories.prediction_repository import PredictionRepository
from src.interface.api.dependencies import (
get_prediction_history_use_case,
get_prediction_repository,
)
from src.shared.constants import DEFAULT_PAGINATION_LIMIT
from src.shared.logger import get_logger
logger = get_logger(__name__)
router = APIRouter(
prefix="/predictions",
tags=["Blood Pressure Predictions"],
)
# ── Shared error response schemas for Swagger ─────────────────────────────────
_NOT_FOUND_RESPONSE = {
"description": "Resource not found.",
"content": {
"application/json": {
"example": {
"error": "not_found",
"message": "BPPrediction with id='...' not found.",
"context": {"entity_type": "BPPrediction", "entity_id": "..."},
"timestamp": "2026-05-31T09:00:00+00:00",
}
}
},
}
_DB_ERROR_RESPONSE = {
"description": "Database temporarily unavailable.",
"content": {
"application/json": {
"example": {
"error": "database_unavailable",
"message": "The database is temporarily unavailable. Please try again later.",
"context": {},
"timestamp": "2026-05-31T09:00:00+00:00",
}
}
},
}
@router.get(
"/{user_id}",
response_model=PredictionHistoryResponse,
status_code=status.HTTP_200_OK,
summary="Get Prediction History for a User",
description=(
"Retrieve paginated BP prediction history for a patient. "
"Optionally filter by date range (ISO 8601 UTC timestamps)."
),
responses={
200: {"description": "Prediction history retrieved successfully."},
503: _DB_ERROR_RESPONSE,
},
)
async def get_prediction_history(
user_id: str,
use_case: Annotated[GetPredictionHistoryUseCase, Depends(get_prediction_history_use_case)],
limit: int = Query(default=DEFAULT_PAGINATION_LIMIT, ge=1, le=500),
start: Optional[datetime] = Query(default=None, description="Start of date range (UTC ISO8601)"),
end: Optional[datetime] = Query(default=None, description="End of date range (UTC ISO8601)"),
) -> PredictionHistoryResponse:
"""
Retrieve BP prediction history for a given user.
- Without ``start``/``end``: returns the latest N predictions (``limit``).
- With ``start`` and ``end``: returns predictions within the date range.
**Possible errors:**
- `503 database_unavailable` β€” Supabase is temporarily unreachable.
"""
logger.info(
"GET /predictions/%s β€” limit=%d, start=%s, end=%s",
user_id, limit, start, end,
)
return await use_case.execute(
user_id=user_id,
limit=limit,
start=start,
end=end,
)
@router.get(
"/{user_id}/signal/{signal_id}",
response_model=PredictionResponse,
status_code=status.HTTP_200_OK,
summary="Get Prediction for a Specific PPG Signal",
description=(
"Retrieve the AI-generated blood pressure prediction associated "
"with a specific PPG signal ID."
),
responses={
200: {"description": "Prediction retrieved successfully."},
404: _NOT_FOUND_RESPONSE,
503: _DB_ERROR_RESPONSE,
},
)
async def get_prediction_by_signal(
user_id: str,
signal_id: str,
prediction_repo: Annotated[PredictionRepository, Depends(get_prediction_repository)],
) -> PredictionResponse:
"""
Retrieve the BP prediction associated with a specific PPG signal.
**Possible errors:**
- `404 not_found` β€” No prediction exists for the given ``signal_id``.
- `503 database_unavailable` β€” Supabase is temporarily unreachable.
"""
logger.info("GET /predictions/%s/signal/%s", user_id, signal_id)
prediction = await prediction_repo.get_by_signal_id(signal_id)
if prediction is None:
raise EntityNotFoundError(entity_type="BPPrediction", entity_id=signal_id)
return PredictionResponse(
id=prediction.id,
ppg_signal_id=prediction.ppg_signal_id,
predicted_sbp=prediction.predicted_sbp,
predicted_dbp=prediction.predicted_dbp,
predicted_ecg=prediction.predicted_ecg,
mean_arterial_pressure=prediction.mean_arterial_pressure,
pulse_pressure=prediction.pulse_pressure,
hypertension_stage=prediction.hypertension_stage,
model_version=prediction.model_version,
inference_time_ms=prediction.inference_time_ms,
sa_log=prediction.sa_log,
created_at=prediction.created_at,
)