# `PhoenixKitAI.Money`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ai/blob/v0.26.1/lib/phoenix_kit_ai/money.ex#L1)

Money as the module keeps it: an amount of US dollars as a `Decimal`, never a
float and never a scaled integer. A call's cost (`Request.cost`), the spend caps
(`PhoenixKitAI.Budget`), the task limits (`PhoenixKitAI.TaskLimits`) and the
prices per 1M tokens (`PhoenixKitAI.ManualPrice`) are all plain dollars.

* `to_decimal/1` reads a number from anywhere (a provider's JSON float, a stored
  string, a `Decimal`); a float goes through its shortest decimal form, so
  `3.0e-5` is exactly `0.00003`, never `Decimal.from_float/1`'s binary tail.
* `parse_dollars/1` reads what a person typed.
* `amount/1` is the strict reader for an amount set in code or stored as a
  setting: a `Decimal` or a dollar string. An integer is refused there, because
  the released versions stored those amounts as integer millionths of a dollar
  (`5_000_000` meant $5), and reading one as dollars would loosen a cap a
  million times over. `0` is the one integer accepted (no amount).
* `round_cost/1` is the only rounding: a call's total cost, to the 18 decimal
  places of the `cost` column (an attodollar — far below the cheapest token).
* `to_json/1` is how an amount is written into a JSON map: a string, so it reads
  back as the same decimal and never as a float.

# `amount`

```elixir
@spec amount(term()) :: {:ok, Decimal.t()} | {:error, :ambiguous_amount} | :error
```

An amount set in code or stored as a setting, as dollars: a `Decimal` or a
dollar string, from 0 to $1 000 000 000; the integer `0` is no amount.
`{:error, :ambiguous_amount}` for any other integer or a float (see the
moduledoc), `:error` for anything that is not an amount at all.

# `legacy_amount?`

```elixir
@spec legacy_amount?(term()) :: boolean()
```

Whether a stored amount is in the shape the released versions wrote: an
integer, or a string of digits only (`"5000000"`), which meant millionths of a
dollar. A dollar string always has a decimal point or is `"0"`.

# `parse_dollars`

```elixir
@spec parse_dollars(term()) :: {:ok, Decimal.t()} | :error
```

Dollars typed by a person: finite, from 0 to $1 000 000 000. `:error` for
anything else — not a number, NaN, an infinity, a negative amount.

# `ready?`

```elixir
@spec ready?() :: boolean()
```

Whether the database keeps costs in dollars: core's V223 has made
`phoenix_kit_ai_requests.cost` and no legacy cost column (`cost_cents`,
`cost_nanodollars`) is left beside it. Checked once per node and remembered
once true; until then every call is refused
(`{:error, :money_migration_required}`), because a sum of a column that is
not there — or of a new empty one beside the old costs — reads as no spend.

# `round_cost`

```elixir
@spec round_cost(Decimal.t() | nil) :: Decimal.t() | nil
```

A call's total cost rounded to the 18 decimal places of the `cost` column.

# `sum`

```elixir
@spec sum(Decimal.t() | integer() | nil) :: Decimal.t()
```

A sum read back from the database (a `Decimal`, or nil for no rows) as dollars.

# `to_decimal`

```elixir
@spec to_decimal(term()) :: {:ok, Decimal.t()} | :error
```

A number as a `Decimal`: a `Decimal`, an integer, a finite float (through its
shortest decimal form) or a decimal string (trimmed). `:error` for anything
else, NaN and infinities included.

# `to_json`

```elixir
@spec to_json(Decimal.t()) :: String.t()
```

An amount as it is written into a JSON map or a setting: a decimal string with
at least two places (`"5.00"`, `"0.10"`, `"0.0000004"`), so it reads back as the
same decimal, never as a float, and never looks like the released versions'
digits-only millionths (`legacy_amount?/1`).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
