POST /api/v1/integrations/llm-usage stores priced LLM usage events (idempotent on gateway_request_id) in llm_usage_events. The gateway is the LLM-pricing authority — arcadia-cloud trusts the charge it sends rather than re-pricing. The monthly invoice rollup now appends an llm_usage line per deployment alongside the infra quote lines; the exact decimal charges are summed and rounded to cents once. Closes the gateway→cloud billing loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
2.2 KiB
Elixir
70 lines
2.2 KiB
Elixir
defmodule ArcadiaCloudWeb.IntegrationsController do
|
|
@moduledoc """
|
|
Inbound integration hooks from sibling Sky AI services.
|
|
|
|
`llm_usage` receives `llm_usage_recorded` events from
|
|
arcadia-llm-gateway — one per billable LLM request — and stores them
|
|
for the monthly invoice rollup. platform-admin only (the gateway
|
|
authenticates with a service JWT carrying that role).
|
|
"""
|
|
|
|
use ArcadiaCloudWeb, :controller
|
|
|
|
alias ArcadiaCloud.LlmUsage
|
|
|
|
def llm_usage(conn, params) do
|
|
with :ok <- require_platform_admin(conn) do
|
|
case LlmUsage.record_event(translate(params)) do
|
|
{:ok, :recorded} ->
|
|
conn |> put_status(:created) |> json(%{status: "recorded"})
|
|
|
|
{:ok, :duplicate} ->
|
|
json(conn, %{status: "duplicate"})
|
|
|
|
{:error, changeset} ->
|
|
conn |> put_status(:unprocessable_entity) |> json(%{error: errors(changeset)})
|
|
end
|
|
end
|
|
end
|
|
|
|
# Maps the gateway's wire payload onto the LlmUsageEvent schema.
|
|
defp translate(params) do
|
|
%{
|
|
gateway_request_id: params["gateway_request_id"],
|
|
tenant_id: params["tenant_id"],
|
|
deployment_id: params["deployment_id"],
|
|
provider: params["provider"],
|
|
model: params["model"],
|
|
request_kind: params["request_kind"],
|
|
input_tokens: params["input_tokens"],
|
|
output_tokens: params["output_tokens"],
|
|
cached_input_tokens: params["cached_input_tokens"],
|
|
total_tokens: params["total_tokens"],
|
|
upstream_cost: params["upstream_cost_usd"],
|
|
customer_charge: params["customer_charge_usd"],
|
|
customer_charge_cents: params["customer_charge_cents"],
|
|
markup_mode: params["markup_mode"],
|
|
occurred_at: params["occurred_at"]
|
|
}
|
|
end
|
|
|
|
defp require_platform_admin(conn) do
|
|
identity = conn.assigns.current_identity
|
|
|
|
if is_list(identity.roles) and "platform-admin" in identity.roles do
|
|
:ok
|
|
else
|
|
conn
|
|
|> put_status(:forbidden)
|
|
|> json(%{error: "platform_admin_required"})
|
|
|> halt()
|
|
end
|
|
end
|
|
|
|
defp errors(changeset) do
|
|
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
|
|
Enum.reduce(opts, msg, fn {k, v}, acc -> String.replace(acc, "%{#{k}}", to_string(v)) end)
|
|
end)
|
|
end
|
|
end
|