"""Authenticated SUPER authoring endpoints; construction never starts a simulation.""" from fastapi import APIRouter, Depends, HTTPException, Query, Request from pydantic import ValidationError from sqlalchemy import func, select from ..models import SuperSelectionSnapshot from ..research.assets import Assets from ..security import require_auth from .contracts import BuildCandidates, ExperimentPreview, PlanSave, SelectionPreview, SelectionReference from .evidence import read_selection, snapshot_output from .metadata import metadata from .service import SuperResearch router = APIRouter(prefix="/api/v1/superalpha", tags=["superalpha"], dependencies=[Depends(require_auth)]) @router.get("/metadata") async def get_metadata(request: Request): async with request.app.state.sessions() as db: return await metadata(db) @router.get("/plans") async def plans(request: Request, q: str = "", limit: int = Query(25, ge=1, le=100), offset: int = Query(0, ge=0)): async with request.app.state.sessions() as db: return await Assets(db).list("superalpha_plan", q, limit, offset) @router.post("/plans") async def save_plan(body: PlanSave, request: Request): async with request.app.state.sessions.begin() as db: return await SuperResearch(db).save(body) @router.get("/plans/{plan_id}") async def plan(plan_id: str, request: Request, version: int | None = Query(None, ge=1)): async with request.app.state.sessions() as db: return await Assets(db).get(plan_id, version, "superalpha_plan") @router.get("/plans/{plan_id}/versions") async def versions(plan_id: str, request: Request): async with request.app.state.sessions() as db: await Assets(db).get(plan_id, expected_kind="superalpha_plan") return await Assets(db).versions(plan_id) @router.delete("/plans/{plan_id}") async def archive(plan_id: str, request: Request, version: int = Query(..., ge=1)): async with request.app.state.sessions.begin() as db: await Assets(db).get(plan_id, expected_kind="superalpha_plan") return await Assets(db).archive(plan_id, version) @router.post("/selections", status_code=202) async def preview_selection(body: SelectionPreview, request: Request): async with request.app.state.sessions.begin() as db: result = await SuperResearch(db).selection_job(body) request.app.state.runner.wake.set() return result @router.get("/selections") async def selection(request: Request, snapshot_id: str | None = None, job_id: str | None = None, q: str = "", limit: int = Query(25, ge=1, le=100), offset: int = Query(0, ge=0)): try: args = SelectionReference(snapshot_id=snapshot_id, job_id=job_id, q=q, limit=limit, offset=offset) except ValidationError as exc: raise HTTPException(422, str(exc)) from None async with request.app.state.sessions() as db: return await read_selection(db, args) @router.post("/candidates", status_code=201) async def build(body: BuildCandidates, request: Request): async with request.app.state.sessions.begin() as db: return await SuperResearch(db).build(body) @router.get("/experiments") async def experiments(request: Request, plan_id: str | None = None, limit: int = Query(25, ge=1, le=100), offset: int = Query(0, ge=0)): async with request.app.state.sessions() as db: return await SuperResearch(db).experiments(plan_id, limit, offset) @router.get("/experiments/{experiment_id}") async def experiment(experiment_id: str, request: Request, limit: int = Query(100, ge=1, le=100), offset: int = Query(0, ge=0)): async with request.app.state.sessions() as db: return await SuperResearch(db).experiment(experiment_id, limit, offset) @router.post("/experiments/{experiment_id}/preview") async def preview(experiment_id: str, body: ExperimentPreview, request: Request): async with request.app.state.sessions.begin() as db: return await SuperResearch(db).preview(experiment_id, body.candidate_ids) @router.get("/alphas/{alpha_id}") async def alpha(alpha_id: str, request: Request): async with request.app.state.sessions() as db: return await SuperResearch(db).alpha(alpha_id) @router.get("/selection-history") async def selection_history(request: Request, plan_id: str, limit: int = Query(25, ge=1, le=100), offset: int = Query(0, ge=0)): async with request.app.state.sessions() as db: query = select(SuperSelectionSnapshot).where(SuperSelectionSnapshot.source == "preview", SuperSelectionSnapshot.request["plan_id"].as_string() == plan_id) total = await db.scalar(select(func.count()).select_from(query.subquery())) rows = await db.scalars(query.order_by(SuperSelectionSnapshot.observed_at.desc(), SuperSelectionSnapshot.id).limit(limit).offset(offset)) return {"items": [snapshot_output(row, 0) for row in rows], "total": total, "limit": limit, "offset": offset} @router.get("/experiments/{experiment_id}/results") async def experiment_results(experiment_id: str, request: Request, limit: int = Query(25, ge=1, le=100), offset: int = Query(0, ge=0)): from ..models import Pnl from ..research.serialization import encode_snapshot from ..research_access.contracts import History from ..research_access.queries import EvidenceQueries from .evidence import actual_components async with request.app.state.sessions() as db: await SuperResearch(db).experiment(experiment_id, 1) result = await EvidenceQueries(db).history(History(research_id=experiment_id, alpha_type="SUPER", limit=limit, offset=offset)) for item in result["items"]: item["components"] = await actual_components(db, item["id"], 0) pnl = await db.get(Pnl, item["alpha_id"]) if item["alpha_id"] else None item["pnl_fetched_at"] = pnl.fetched_at if pnl else None return encode_snapshot(result) @router.get("/alphas/{alpha_id}/components") async def alpha_components(alpha_id: str, request: Request, limit: int = Query(25, ge=1, le=100), offset: int = Query(0, ge=0)): async with request.app.state.sessions() as db: return (await SuperResearch(db).alpha(alpha_id, limit, offset))["components"]