Files
xiaobaifupan/next/backend/features/heaven/routes.py
T

111 lines
3.8 KiB
Python

from __future__ import annotations
import json
from collections.abc import Iterator
from typing import Annotated, Literal
from fastapi import APIRouter, Query, Request
from fastapi.responses import StreamingResponse
from backend.data.gateway import MarketDataUnavailable
from backend.features.accounts.auth import SmartAccessPrincipal, SmartWritePrincipal
from backend.features.heaven.schemas import (
DeleteResponse,
FortuneInput,
HeartCompleteInput,
HeartLineInput,
InterpretInput,
TrendInput,
)
from backend.features.heaven.service import HeavenError
from backend.http.errors import AppError
from backend.llm.gateway import LLMGatewayError
router = APIRouter(prefix="/heaven", tags=["heaven"])
@router.get("/setup", response_model=dict)
def setup(
request: Request,
principal: SmartAccessPrincipal,
reading_date: Annotated[str, Query(alias="date", min_length=10, max_length=10)],
) -> dict:
return _call(request, "setup", principal, reading_date)
@router.post("/trend/load", response_model=dict)
def load_trend(payload: TrendInput, request: Request, principal: SmartWritePrincipal) -> dict:
return _call(request, "trend", principal, payload.query, payload.trade_date, payload.manual)
@router.post("/fortune", response_model=dict)
def create_fortune(payload: FortuneInput, request: Request, principal: SmartWritePrincipal) -> dict:
return _call(request, "fortune", principal, payload.trade_date)
@router.post("/heart/line", response_model=dict)
def heart_line(payload: HeartLineInput, request: Request, principal: SmartWritePrincipal) -> dict:
return _call(request, "heart_line", principal, payload.trade_date, payload.values)
@router.post("/heart/complete", response_model=dict)
def complete_heart(
payload: HeartCompleteInput, request: Request, principal: SmartWritePrincipal
) -> dict:
return _call(
request,
"complete_heart",
principal,
payload.trade_date,
payload.values,
payload.first_thought_confirmed,
)
@router.get("/readings", response_model=list[dict])
def readings(
request: Request,
principal: SmartAccessPrincipal,
mode: Annotated[Literal["trend", "fortune", "heart"] | None, Query()] = None,
reading_date: Annotated[str | None, Query(alias="date")] = None,
) -> list[dict]:
return _call(request, "readings", principal, mode, reading_date)
@router.delete("/readings/{reading_id}", response_model=DeleteResponse)
def delete_reading(
reading_id: int, request: Request, principal: SmartWritePrincipal
) -> DeleteResponse:
return DeleteResponse(deleted=_call(request, "delete", principal, reading_id))
@router.post("/interpret")
def interpret(
payload: InterpretInput, request: Request, principal: SmartWritePrincipal
) -> StreamingResponse:
prepared = _call(request, "prepare_interpret", principal, payload.reading_id)
def body() -> Iterator[bytes]:
for event in request.app.state.container.heaven.stream_interpret(prepared):
yield (json.dumps(event, ensure_ascii=False, separators=(",", ":")) + "\n").encode(
"utf-8"
)
return StreamingResponse(
body(),
media_type="application/x-ndjson",
headers={"Cache-Control": "no-cache, no-transform", "X-Accel-Buffering": "no"},
)
def _call(request: Request, method: str, *args):
try:
return getattr(request.app.state.container.heaven, method)(*args)
except HeavenError as exc:
raise AppError("heaven_unavailable", str(exc), 409) from exc
except MarketDataUnavailable as exc:
raise AppError("market_data_unavailable", str(exc), 503) from exc
except LLMGatewayError as exc:
status = 403 if exc.code in {"membership_required", "quota_exhausted"} else 503
raise AppError(exc.code, str(exc), status) from exc