"""/deployments (cursor page of `Deployment` rows) · /cost (per request / day / month / year for every current deployment) · /cost/context (cost of one fully populated context).""" from __future__ import annotations from typing import Any from fastapi import APIRouter, Query, Request from aiatlas.api.common import ( PRICE_COLS, PRICE_FROM, ApiError, cached, deployment_row, parse_ts, resolve_entity, resolve_id, ) from aiatlas.db import connection, fetch_all, fetch_val from aiatlas.services.cost import compute_cost, context_fill_cost router = APIRouter(prefix="/api/v1", tags=["deployments"]) TOTAL_CAP = 10_000 @router.get("/deployments") @cached(300) async def list_deployments(request: Request, model: str | None = None, provider: str | None = None, current: int = Query(1, ge=0, le=1), limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0, le=10000), before: str | None = Query(None, description="cursor: valid_from of the last item"), org: str | None = None, sort: str = Query("valid_from", pattern="^(valid_from|output|input|model|provider)$"), status: str | None = Query(None, pattern="^(active|delisted|all)$", description="active = current rows (default when current=1), delisted = closed rows only, all = both")) -> dict[str, Any]: where = ["m.entity_type in ('model','artifact')"] params: dict[str, Any] = {"lim": limit, "off": offset} status = status or ("active" if current else "all") if status == "active": where.append("p.valid_to is null") where.append("m.merged_into is null") elif status == "delisted": where.append("p.valid_to is not null") else: where.append("(p.valid_to is not null or m.merged_into is null)") # closed rows of since-merged models stay visible as history before_ts = parse_ts(before, "before") if before_ts is not None: where.append("p.valid_from < :before") params["before"] = before_ts async with connection() as conn: if model: where.append("p.model_id = :model") params["model"] = await resolve_id(conn, model) if provider: where.append("p.provider_id = :provider") params["provider"] = await resolve_id(conn, provider) if org: where.append("(mo.slug = :org or mo.id = :org or mo.canonical_name ilike :org)") params["org"] = org order = {"valid_from": ("p.valid_to desc nulls last, p.id desc" if status == "delisted" else "p.valid_from desc, p.id desc"), "output": "p.output_per_mtok asc nulls last, p.id", "input": "p.input_per_mtok asc nulls last, p.id", "model": "m.canonical_name asc, pv.canonical_name asc, p.id", "provider": "pv.canonical_name asc, m.canonical_name asc, p.id"}[sort] where_sql = " and ".join(where) rows = await fetch_all(conn, f"select {PRICE_COLS} from {PRICE_FROM} where {where_sql} order by {order} limit :lim offset :off", **params) total = await fetch_val(conn, f"select count(*) from (select 1 from {PRICE_FROM} where {where_sql} limit {TOTAL_CAP}) t", **{k: v for k, v in params.items() if k not in ('lim', 'off')}) items = [deployment_row(r) for r in rows] return {"items": items, "total": int(total or 0), "limit": limit, "offset": offset, "next_before": items[-1]["valid_from"] if len(items) == limit and sort == "valid_from" and status != "delisted" else None, "current": bool(current), "status": status} @router.get("/cost") @cached(300) async def cost(request: Request, model: str = Query(..., description="model slug or id"), provider: str | None = None, input_tokens: int = Query(1000, ge=0, le=100_000_000), output_tokens: int = Query(500, ge=0, le=100_000_000), requests_per_day: float = Query(1000, ge=0, le=1e9), cached_share: float = Query(0.0, ge=0, le=1), batch: int = Query(0, ge=0, le=1)) -> dict[str, Any]: async with connection() as conn: m = await resolve_entity(conn, model, ("model", "artifact")) where = ["p.model_id = :mid", "p.valid_to is null"] params: dict[str, Any] = {"mid": m["id"]} if provider: where.append("p.provider_id = :pid") params["pid"] = await resolve_id(conn, provider, ("provider",)) rows = await fetch_all(conn, f"select {PRICE_COLS} from {PRICE_FROM} where {' and '.join(where)} order by p.output_per_mtok asc nulls last, pv.canonical_name limit 200", **params) items = [] for r in rows: d = deployment_row(r) c = compute_cost(d["prices"], input_tokens=input_tokens, output_tokens=output_tokens, requests_per_day=requests_per_day, cached_share=cached_share, batch=bool(batch)) items.append({"deployment": d, "cost": c}) items.sort(key=lambda x: (x["cost"]["per_request"] is None, x["cost"]["per_request"] or 0)) return {"model": {"id": m["id"], "slug": m["slug"], "name": m["canonical_name"]}, "inputs": {"input_tokens": input_tokens, "output_tokens": output_tokens, "requests_per_day": requests_per_day, "cached_share": cached_share, "batch": bool(batch)}, "items": items, "total": len(items), "currency": "USD", "methodology": "per_request = input_tokens × effective input price / 1e6 + output_tokens × output price / 1e6 (+ per-request fee when published); " "effective input = (1 − cached_share) × input + cached_share × cached input (falls back to the standard price with a note); " "batch uses batch prices when published; daily = per_request × requests_per_day; monthly = daily × 30; annual = daily × 365. Only current offers.", "note": None if items else "no current deployment for this model"} @router.get("/cost/context") @cached(300) async def cost_context(request: Request, tokens: int = Query(1_000_000, ge=1, le=100_000_000), limit: int = Query(50, ge=1, le=500), model: str | None = None, org: str | None = None) -> dict[str, Any]: """Cost of ONE fully populated context of `tokens` input tokens per current deployment whose context window is ≥ tokens, cheapest first.""" where = ["p.valid_to is null", "p.input_per_mtok > 0", "m.entity_type in ('model','artifact')", "m.merged_into is null", "coalesce(p.context_length, case when m.attributes->>'context_length' ~ '^[0-9]+$' then (m.attributes->>'context_length')::bigint end) >= :tokens"] params: dict[str, Any] = {"tokens": tokens, "lim": limit} async with connection() as conn: if model: where.append("p.model_id = :mid") params["mid"] = await resolve_id(conn, model) if org: where.append("(mo.slug = :org or mo.id = :org or mo.canonical_name ilike :org)") params["org"] = org rows = await fetch_all(conn, f"""select {PRICE_COLS}, coalesce(p.context_length, case when m.attributes->>'context_length' ~ '^[0-9]+$' then (m.attributes->>'context_length')::bigint end) as ctx from {PRICE_FROM} where {' and '.join(where)} order by p.input_per_mtok asc, m.canonical_name limit :lim""", **params) items = [{"deployment": deployment_row(r), "context_length": r["ctx"], "context_source": "offer" if r.get("context_length") else "model attribute", "cost_usd": context_fill_cost(r["input_per_mtok"], tokens)} for r in rows] return {"tokens": tokens, "items": items, "total": len(items), "currency": "USD", "methodology": "cost = input price (USD per 1M tokens) × tokens / 1e6 for every current offer whose context window (offer's, else the model's attribute) is at least `tokens`. " "Long-context surcharges published as native units are not applied."} __all__ = ["ApiError", "router"]