Receive llm_usage_recorded events from arcadia-llm-gateway

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>
This commit is contained in:
2026-05-21 08:29:36 +10:00
parent 29f4ad97d6
commit e1f0aedcf7
6 changed files with 269 additions and 1 deletions

View File

@@ -0,0 +1,69 @@
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

View File

@@ -42,5 +42,7 @@ defmodule ArcadiaCloudWeb.Router do
get "/dashboard/margin", DashboardController, :margin
get "/dashboard/accrual", DashboardController, :accrual
post "/integrations/llm-usage", IntegrationsController, :llm_usage
end
end