rebuild(stage-11): deliver deterministic heaven workflows
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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
|
||||
Reference in New Issue
Block a user