update the codebase poc ver1
This commit is contained in:
233
workspace/sprint_1_2/CODEBASE/backend/api/analysis_api.py
Normal file
233
workspace/sprint_1_2/CODEBASE/backend/api/analysis_api.py
Normal file
@@ -0,0 +1,233 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from fastapi.responses import StreamingResponse
|
||||
from datetime import datetime
|
||||
from data.spec.schemas import (
|
||||
AnalysisJobSubmit, JobStatus, PipelineStep, StepEvent,
|
||||
ModelCatalog, ModelRegistrationResult, HealthStatus,
|
||||
AnalysisJobSyncSubmit, JobResult, ErrorResponse,
|
||||
)
|
||||
from backend.implementation.analysis_jobs import service as analysis_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["analysis"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
_event_queues: dict[str, asyncio.Queue] = {}
|
||||
_queue_lock = asyncio.Lock()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _get_queue(job_id: str):
|
||||
async with _queue_lock:
|
||||
if job_id not in _event_queues:
|
||||
_event_queues[job_id] = asyncio.Queue()
|
||||
yield _event_queues[job_id]
|
||||
|
||||
|
||||
def _get_queue_sync(job_id: str) -> asyncio.Queue:
|
||||
if job_id not in _event_queues:
|
||||
_event_queues[job_id] = asyncio.Queue()
|
||||
return _event_queues[job_id]
|
||||
|
||||
|
||||
async def _verify_jwt_token(token: str = Depends(oauth2_scheme)) -> str:
|
||||
try:
|
||||
from backend.api.auth_api import verify_jwt_token as _verify
|
||||
return await _verify(token)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
def _sse_format(event: StepEvent) -> str:
|
||||
lines = [f"event: {event.event_type}"]
|
||||
payload = event.model_dump(mode="json")
|
||||
lines.append(f"data: {payload}")
|
||||
lines.append("")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/analysis-jobs",
|
||||
response_model=dict,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={401: {"model": ErrorResponse}, 422: {"model": ErrorResponse}},
|
||||
)
|
||||
async def submit_analysis_job(
|
||||
payload: AnalysisJobSubmit,
|
||||
user_id: str = Depends(_verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
job_id = await analysis_service.submit_job(
|
||||
session_id=payload.session_id,
|
||||
params=payload.params or {},
|
||||
model_versions=payload.model_versions,
|
||||
)
|
||||
return {"job_id": job_id}
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/analysis-jobs/{job_id}",
|
||||
response_model=JobStatus,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def get_job_status(job_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await analysis_service.job_status(job_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/analysis-jobs/{job_id}/steps",
|
||||
response_model=list[PipelineStep],
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def get_job_steps(job_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await analysis_service.job_steps(job_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/analysis",
|
||||
response_model=JobResult,
|
||||
responses={401: {"model": ErrorResponse}, 422: {"model": ErrorResponse}},
|
||||
)
|
||||
async def submit_sync_analysis(
|
||||
payload: AnalysisJobSyncSubmit,
|
||||
user_id: str = Depends(_verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await analysis_service.submit_sync(
|
||||
session_id=payload.session_id,
|
||||
params=payload.params or {},
|
||||
model_versions=payload.model_versions,
|
||||
)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/analysis-jobs/{job_id}/stream",
|
||||
responses={
|
||||
401: {"model": ErrorResponse},
|
||||
404: {"model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
async def stream_job_events(job_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
queue = _get_queue_sync(job_id)
|
||||
|
||||
async def event_generator():
|
||||
try:
|
||||
while True:
|
||||
event = await queue.get()
|
||||
yield _sse_format(event)
|
||||
if event.event_type == "completed" or event.event_type == "failed":
|
||||
break
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"SSE stream cancelled for job_id={job_id}")
|
||||
finally:
|
||||
async with _queue_lock:
|
||||
_event_queues.pop(job_id, None)
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"X-Accel-Buffering": "no",
|
||||
"Connection": "keep-alive",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/internal/analysis-jobs/{job_id}/events",
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
include_in_schema=False,
|
||||
)
|
||||
async def internal_push_event(job_id: str, event: dict):
|
||||
queue = _get_queue_sync(job_id)
|
||||
try:
|
||||
step_event = StepEvent(
|
||||
step_id=event.get("step_id", ""),
|
||||
job_id=job_id,
|
||||
event_type=event.get("event_type", "progress"),
|
||||
task_type=event.get("task_type", ""),
|
||||
status=event.get("status", "running"),
|
||||
data=event.get("data"),
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
await queue.put(step_event)
|
||||
except Exception as exc:
|
||||
logger.error(f"Failed to push event for job {job_id}: {exc}")
|
||||
return {"queued": True}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/health",
|
||||
response_model=HealthStatus,
|
||||
include_in_schema=False,
|
||||
)
|
||||
async def health_check():
|
||||
try:
|
||||
return await analysis_service.health()
|
||||
except NotImplementedError:
|
||||
return HealthStatus(
|
||||
status="ok",
|
||||
version="0.1.0",
|
||||
dependencies={},
|
||||
uptime_seconds=0.0,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/model-registry",
|
||||
response_model=ModelCatalog,
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def list_models(user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await analysis_service.list_registered_models()
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/models/register",
|
||||
response_model=ModelRegistrationResult,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def register_model(
|
||||
model_id: str,
|
||||
file: UploadFile | None = None,
|
||||
user_id: str = Depends(_verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await analysis_service.register_model(model_id, file)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
82
workspace/sprint_1_2/CODEBASE/backend/api/auth_api.py
Normal file
82
workspace/sprint_1_2/CODEBASE/backend/api/auth_api.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from data.spec.schemas import LoginRequest, Token, UserProfile, UserUpdateRequest, RefreshRequest, ErrorResponse
|
||||
from backend.implementation.auth import service as auth_service
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["auth"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def verify_jwt_token(token: str = Depends(oauth2_scheme)) -> str:
|
||||
try:
|
||||
profile = await auth_service.me(token)
|
||||
return profile.user_id
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/auth/login",
|
||||
response_model=Token,
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def login(payload: LoginRequest):
|
||||
try:
|
||||
return await auth_service.login(payload.username, payload.password)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/auth/logout",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def logout(token: str = Depends(oauth2_scheme)):
|
||||
try:
|
||||
await auth_service.logout(token)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/auth/refresh",
|
||||
response_model=Token,
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def refresh(payload: RefreshRequest):
|
||||
try:
|
||||
return await auth_service.refresh(payload.refresh_token)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/users/me",
|
||||
response_model=UserProfile,
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def get_me(user_id: str = Depends(verify_jwt_token)):
|
||||
try:
|
||||
return await auth_service.me(user_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/users/me",
|
||||
response_model=UserProfile,
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def update_me(payload: UserUpdateRequest, user_id: str = Depends(verify_jwt_token)):
|
||||
try:
|
||||
return await auth_service.update_me(user_id, payload.model_dump(exclude_none=True))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
47
workspace/sprint_1_2/CODEBASE/backend/api/ingestion_api.py
Normal file
47
workspace/sprint_1_2/CODEBASE/backend/api/ingestion_api.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from data.spec.schemas import IngestionRecord, RecordDetail, ErrorResponse
|
||||
from backend.implementation.ingestion_history import service as ingestion_service
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["ingestion-history"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def _verify_jwt_token(token: str = Depends(oauth2_scheme)) -> str:
|
||||
try:
|
||||
from backend.api.auth_api import verify_jwt_token as _verify
|
||||
return await _verify(token)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/ingestion-history",
|
||||
response_model=list[IngestionRecord],
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def list_ingestion_records(user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await ingestion_service.list_records(user_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/ingestion-history/{record_id}",
|
||||
response_model=RecordDetail,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def get_ingestion_record(record_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await ingestion_service.get_record(record_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
@@ -0,0 +1,59 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from data.spec.schemas import NotificationItem, NotificationPreferences, ErrorResponse
|
||||
from backend.implementation.notification import service as notification_service
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["notification"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def _verify_jwt_token(token: str = Depends(oauth2_scheme)) -> str:
|
||||
try:
|
||||
from backend.api.auth_api import verify_jwt_token as _verify
|
||||
return await _verify(token)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/notifications",
|
||||
response_model=list[NotificationItem],
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def list_notifications(user_id: str = Depends(_verify_jwt_token), filters: dict | None = None):
|
||||
try:
|
||||
return await notification_service.list_notifications(user_id, filters)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/notifications/{notification_id}/read",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def mark_read(notification_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
await notification_service.mark_read(notification_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/notifications/preferences",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def set_preferences(payload: NotificationPreferences, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
await notification_service.set_preferences(user_id, payload.model_dump(exclude_none=True))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
91
workspace/sprint_1_2/CODEBASE/backend/api/patient_api.py
Normal file
91
workspace/sprint_1_2/CODEBASE/backend/api/patient_api.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from data.spec.schemas import Patient, PatientCreate, PatientListResponse, ErrorResponse
|
||||
from backend.implementation.patient import service as patient_service
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["patient"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def _verify_jwt_token(token: str = Depends(oauth2_scheme)) -> str:
|
||||
try:
|
||||
from backend.api.auth_api import verify_jwt_token as _verify
|
||||
return await _verify(token)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/patients",
|
||||
response_model=PatientListResponse,
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def list_patients(user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
items = await patient_service.list_patients(user_id)
|
||||
return PatientListResponse(items=items, total=len(items))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/patients",
|
||||
response_model=Patient,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={401: {"model": ErrorResponse}, 422: {"model": ErrorResponse}},
|
||||
)
|
||||
async def create_patient(payload: PatientCreate, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await patient_service.create_patient(payload.model_dump(exclude_none=True))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/patients/{patient_id}",
|
||||
response_model=Patient,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def get_patient(patient_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await patient_service.get_patient(patient_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/patients/{patient_id}/sessions",
|
||||
response_model=list[dict],
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def list_patient_sessions(patient_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await patient_service.list_sessions(patient_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/patients/{patient_id}/history",
|
||||
response_model=list[dict],
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def patient_ingestion_history(patient_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await patient_service.ingestion_history(patient_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
320
workspace/sprint_1_2/CODEBASE/backend/api/safety_api.py
Normal file
320
workspace/sprint_1_2/CODEBASE/backend/api/safety_api.py
Normal file
@@ -0,0 +1,320 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
import httpx
|
||||
import logging
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from fastapi.responses import StreamingResponse
|
||||
from PILOT_PROJECT.workspace.sprint_1_2.CODEBASE.data.spec.schemas.safety_schemas import ChatResponse
|
||||
from data.spec.schemas import (
|
||||
HeatmapResult, RationaleResult, ChatEvent, DriftCheckResult,
|
||||
EvidenceList, ActivationMeta, AnnotationArtifact, EscalationTicket,
|
||||
GuardrailResult, ErrorResponse, CorrectionSubmit, CorrectionRecord,
|
||||
)
|
||||
|
||||
from backend.implementation.safety import service as safety_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["safety"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def _verify_jwt_token(token: str = Depends(oauth2_scheme)) -> str:
|
||||
try:
|
||||
from backend.api.auth_api import verify_jwt_token as _verify
|
||||
return await _verify(token)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
def _sse_chat_format(event: ChatEvent) -> str:
|
||||
lines = [f"event: {event.event_type}"]
|
||||
payload = event.model_dump(mode="json")
|
||||
lines.append(f"data: {payload}")
|
||||
lines.append("")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/explanations/gradcam",
|
||||
response_model=HeatmapResult,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def gradcam(session_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await safety_service.gradcam(session_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/explanations/rationale",
|
||||
response_model=RationaleResult,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def rationale(
|
||||
session_id: str,
|
||||
redaction_hash: str | None = None,
|
||||
user_id: str = Depends(_verify_jwt_token)
|
||||
):
|
||||
try:
|
||||
return await safety_service.rationale(session_id, redaction_hash)
|
||||
except HTTPException:
|
||||
raise
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/safety/circuit-breaker",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def circuit_breaker(session_id: str, payload: dict, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
await safety_service.circuit_break(session_id, payload.get("flag", False))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/chat/socratic",
|
||||
response_model=ChatResponse,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model: ErrorResponse}},
|
||||
)
|
||||
async def socratic_chat(
|
||||
session_id: str,
|
||||
payload: dict,
|
||||
redaction_hash: str | None = None,
|
||||
user_id: str = Depends(_verify_jwt_token)
|
||||
):
|
||||
try:
|
||||
return await safety_service.socratic_chat(
|
||||
session_id,
|
||||
payload.get("prompt", ""),
|
||||
redaction_hash=redaction_hash
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/drift/check",
|
||||
response_model=DriftCheckResult,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def drift_check(session_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await safety_service.drift_check(session_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/rag/evidence",
|
||||
response_model=EvidenceList,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def rag_evidence(session_id: str, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await safety_service.rag_evidence(session_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/activations",
|
||||
response_model=ActivationMeta,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def activations(session_id: str, params: dict, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await safety_service.activations(session_id, params)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/annotations/artifacts",
|
||||
response_model=AnnotationArtifact,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def upload_artifact(
|
||||
session_id: str,
|
||||
file: UploadFile = File(...),
|
||||
user_id: str = Depends(_verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await safety_service.upload_artifact(session_id, file)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/ground-truth",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def ground_truth(session_id: str, payload: dict, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
await safety_service.ground_truth(session_id, payload.get("label", {}))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/escalation",
|
||||
response_model=EscalationTicket,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def escalate(session_id: str, payload: dict, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await safety_service.escalate(session_id, payload.get("reason", ""))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/annotations/morphology",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def morphology(session_id: str, payload: dict, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
await safety_service.morphology(session_id, payload.get("annotation", {}))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/safety/guardrail-check",
|
||||
response_model=GuardrailResult,
|
||||
responses={401: {"model": ErrorResponse}, 422: {"model": ErrorResponse}},
|
||||
)
|
||||
async def guardrail_check(payload: dict, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
session_id = payload.get("session_id", "")
|
||||
return await safety_service.guardrail_check(
|
||||
session_id=session_id,
|
||||
prompt=payload.get("prompt", ""),
|
||||
score=float(payload.get("score", 0.0)),
|
||||
)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except (ValueError, TypeError) as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/feedback",
|
||||
response_model=CorrectionRecord,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={
|
||||
401: {"model": ErrorResponse},
|
||||
404: {"model": ErrorResponse},
|
||||
422: {"model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
async def submit_correction(
|
||||
session_id: str,
|
||||
payload: CorrectionSubmit,
|
||||
user_id: str = Depends(_verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await safety_service.submit_correction(session_id, payload.dict())
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/sessions/{session_id}/chat/stream",
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def chat_stream(
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
redaction_hash: str | None = None,
|
||||
user_id: str = Depends(_verify_jwt_token),
|
||||
):
|
||||
async def generate():
|
||||
try:
|
||||
async for chunk in safety_service.chat_stream(session_id, prompt, redaction_hash):
|
||||
event = ChatEvent(
|
||||
session_id=session_id,
|
||||
event_type="chunk",
|
||||
content=chunk,
|
||||
is_final=False,
|
||||
)
|
||||
yield _sse_chat_format(event)
|
||||
final_event = ChatEvent(
|
||||
session_id=session_id,
|
||||
event_type="completed",
|
||||
content="",
|
||||
is_final=True,
|
||||
)
|
||||
yield _sse_chat_format(final_event)
|
||||
except HTTPException as exc:
|
||||
error_event = ChatEvent(
|
||||
session_id=session_id,
|
||||
event_type="error",
|
||||
content=exc.detail,
|
||||
is_final=True,
|
||||
)
|
||||
yield _sse_chat_format(error_event)
|
||||
except NotImplementedError:
|
||||
fallback_event = ChatEvent(
|
||||
session_id=session_id,
|
||||
event_type="error",
|
||||
content="Chat streaming service not yet implemented",
|
||||
is_final=True,
|
||||
)
|
||||
yield _sse_chat_format(fallback_event)
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"Chat stream cancelled for session_id={session_id}")
|
||||
|
||||
return StreamingResponse(
|
||||
generate(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"X-Accel-Buffering": "no",
|
||||
"Connection": "keep-alive",
|
||||
},
|
||||
)
|
||||
214
workspace/sprint_1_2/CODEBASE/backend/api/session_api.py
Normal file
214
workspace/sprint_1_2/CODEBASE/backend/api/session_api.py
Normal file
@@ -0,0 +1,214 @@
|
||||
from typing import Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from data.spec.schemas import (
|
||||
Session, SessionDetail, SessionCreate, SessionPatchReview,
|
||||
FrameMetadata, PersistResult, ExportResult, ScrubResult,
|
||||
ReportCreate, ReportSignRequest, ReportSyncEMRRequest, SyncResult,
|
||||
ErrorResponse,
|
||||
)
|
||||
from backend.implementation.session import service as session_service
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["session"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def verify_jwt_token(token: str = Depends(oauth2_scheme)) -> str:
|
||||
try:
|
||||
from backend.api.auth_api import verify_jwt_token as _verify
|
||||
return await _verify(token)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions",
|
||||
response_model=Session,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={401: {"model": ErrorResponse}, 422: {"model": ErrorResponse}},
|
||||
)
|
||||
async def create_session(payload: SessionCreate, user_id: str = Depends(verify_jwt_token)):
|
||||
try:
|
||||
return await session_service.create_session(
|
||||
user_id=user_id,
|
||||
patient_id=payload.patient_id,
|
||||
case_id=getattr(payload, "case_id", None),
|
||||
)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/sessions/{session_id}",
|
||||
response_model=SessionDetail,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def get_session(session_id: str, user_id: str = Depends(verify_jwt_token)):
|
||||
try:
|
||||
return await session_service.get_session(session_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/frames",
|
||||
response_model=FrameMetadata,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def add_frame(
|
||||
session_id: str,
|
||||
file: UploadFile = File(...),
|
||||
frame_number: int | None = None,
|
||||
user_id: str = Depends(verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await session_service.add_frame(session_id, file, frame_number)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/sessions/{session_id}/review",
|
||||
response_model=Session,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def patch_review(
|
||||
session_id: str,
|
||||
payload: SessionPatchReview,
|
||||
user_id: str = Depends(verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await session_service.patch_review(session_id, payload.model_dump(exclude_none=True))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/reports",
|
||||
response_model=dict,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def create_report(payload: ReportCreate, user_id: str = Depends(verify_jwt_token)):
|
||||
try:
|
||||
result = await session_service.persist(payload.session_id, payload.payload)
|
||||
return {"report_id": result.session_id, "status": result.status, "updated_at": result.updated_at.isoformat()}
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/reports/{report_id}/sign",
|
||||
response_model=dict,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def sign_report(
|
||||
report_id: str,
|
||||
payload: ReportSignRequest,
|
||||
user_id: str = Depends(verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
result = await session_service.persist(report_id, {"signed": True, "signature": payload.signature})
|
||||
return {"report_id": report_id, "signed": True, "updated_at": result.updated_at.isoformat()}
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/reports/{report_id}/emr-sync",
|
||||
response_model=SyncResult,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def sync_emr(
|
||||
report_id: str,
|
||||
payload: ReportSyncEMRRequest,
|
||||
user_id: str = Depends(verify_jwt_token),
|
||||
):
|
||||
from datetime import datetime
|
||||
try:
|
||||
result = await session_service.persist(report_id, {"emr_sync": True})
|
||||
return SyncResult(
|
||||
report_id=report_id,
|
||||
emr_status="pending",
|
||||
emr_reference=None,
|
||||
synced_at=datetime.utcnow(),
|
||||
)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/persist",
|
||||
response_model=PersistResult,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def persist_session(
|
||||
session_id: str,
|
||||
review: dict,
|
||||
user_id: str = Depends(verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await session_service.persist(session_id, review)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/export-pdf",
|
||||
response_model=ExportResult,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def export_pdf(
|
||||
session_id: str,
|
||||
params: dict,
|
||||
user_id: str = Depends(verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await session_service.export_pdf(session_id, params)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/scrub-validate",
|
||||
response_model=ScrubResult,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def scrub_validate(
|
||||
session_id: str,
|
||||
metadata: dict,
|
||||
user_id: str = Depends(verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await session_service.scrub_validate(session_id, metadata)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
47
workspace/sprint_1_2/CODEBASE/backend/api/settings_api.py
Normal file
47
workspace/sprint_1_2/CODEBASE/backend/api/settings_api.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from data.spec.schemas import UserSettings, SettingsUpdate, ErrorResponse
|
||||
from backend.implementation.settings import service as settings_service
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["settings"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def _verify_jwt_token(token: str = Depends(oauth2_scheme)) -> str:
|
||||
try:
|
||||
from backend.api.auth_api import verify_jwt_token as _verify
|
||||
return await _verify(token)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/settings",
|
||||
response_model=UserSettings,
|
||||
responses={401: {"model": ErrorResponse}},
|
||||
)
|
||||
async def get_settings(user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await settings_service.get_settings(user_id)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/settings",
|
||||
response_model=UserSettings,
|
||||
responses={401: {"model": ErrorResponse}, 422: {"model": ErrorResponse}},
|
||||
)
|
||||
async def update_settings(payload: SettingsUpdate, user_id: str = Depends(_verify_jwt_token)):
|
||||
try:
|
||||
return await settings_service.update_settings(user_id, payload.model_dump(exclude_none=True))
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))
|
||||
40
workspace/sprint_1_2/CODEBASE/backend/api/telemetry_api.py
Normal file
40
workspace/sprint_1_2/CODEBASE/backend/api/telemetry_api.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from data.spec.schemas import AnomalyReport, AnomalyRecord, ErrorResponse
|
||||
from backend.implementation.telemetry import service as telemetry_service
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["telemetry"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def verify_jwt_token(token: str = Depends(oauth2_scheme)) -> str:
|
||||
try:
|
||||
from backend.api.auth_api import verify_jwt_token as _verify
|
||||
return await _verify(token)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/analysis-jobs/{job_id}/anomalies",
|
||||
response_model=AnomalyRecord,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={401: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
)
|
||||
async def report_anomaly(
|
||||
job_id: str,
|
||||
payload: AnomalyReport,
|
||||
user_id: str = Depends(verify_jwt_token),
|
||||
):
|
||||
try:
|
||||
return await telemetry_service.report_anomaly(job_id, payload.data)
|
||||
except NotImplementedError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=str(exc))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
Reference in New Issue
Block a user