# `Macaw.Data.Quantity`
[🔗](https://github.com/dcoai/macaw_data/blob/v0.5.3/lib/macaw/data/quantity.ex#L1)

A fixed list of quantities, each with its facts: a base unit, a kind, bounds, additivity,
and display families by unit system (spec/03 §8–§13).

A quantity is what a unit measures. A unit (a UCUM code, `Macaw.Data.Unit`) and a
quantity together are a *measure*, `%{unit: code, quantity: quantity}`, the two facts a
field description carries. **They are facts about the data, not knobs**: a measure does
not change a value, it says what the value already is, so a display can choose how to
show it.

The list is fixed and grows by proposal: `duration`, `information`, `data_rate`,
`frequency`, `ratio`, `count`, `temperature`, `length`, `speed`, `power`, `energy`,
`pressure`, `area`, `volume`, `mass`, `pace`, `angle`, `currency`,
`electric_potential`, `electric_current`, `electric_charge`, `illuminance`,
`sound_level`, `mass_concentration` and `amount_concentration`. spec/03 §8–§13 tables each
one's facts and families.

- **Kind** is `:linear`, `:affine` (a temperature), `:logarithmic` (a sound level) or
  `:cyclic` (an angle, whose mean is circular).
- **Bounds** are a display hint, never validation: a difference is legitimately
  negative, so a value outside them is never refused.
- **Additivity** depends on the aggregation: `additivity/2` refines the quantity's
  defaults across series and over time by the field's metric kind (`:gauge`,
  `:counter`, `:rate`).
- **Unit systems** are `:metric`, `:us`, `:uk` and `:as_posted`; a system picks the
  family a value is shown in and never touches the data.

Information is **decimal by default**: UCUM's `kBy` and `Telemetry.Metrics`'
`:kilobyte` are 1,000 bytes, and a unit written with a binary prefix (`MiBy`) or in
`bit` belongs to its own family. The data picks it; no setting does.

How a value is drawn in its family (the display unit, tick formats, a value's label in
a locale) is the drawing library's: visualize's `Visualize.Quantity` keeps it, over this
vocabulary.

The vocabulary half of visualize's `Visualize.Quantity`.

## Examples

    iex> Macaw.Data.Quantity.family(:mass, "kg", :us)
    ["[oz_av]", "[lb_av]"]

    iex> Macaw.Data.Quantity.additivity(:data_rate)
    %{across: true, over_time: false}

    iex> Macaw.Data.Quantity.label(%{unit: "{request}"})
    "request"

# `kind`

```elixir
@type kind() :: :linear | :affine | :logarithmic | :cyclic
```

How a quantity's values combine (spec/03 §9).

# `measure`

```elixir
@type measure() :: %{
  optional(:unit) =&gt; Macaw.Data.Unit.code() | nil,
  optional(:quantity) =&gt; t() | nil
}
```

A unit and a quantity, either or both; the two facts a field description carries.

# `metric_kind`

```elixir
@type metric_kind() :: :gauge | :counter | :rate
```

How a field's values were measured, which refines its quantity's additivity.

# `reason`

```elixir
@type reason() ::
  {:unit, Macaw.Data.Unit.code(), Macaw.Data.Unit.reason()}
  | {:quantity, t(), Macaw.Data.Unit.code()}
  | {:unknown_quantity, term()}
```

Why a measure does not resolve.

# `system`

```elixir
@type system() :: :metric | :us | :uk | :as_posted
```

A unit system: a display preference, never applied to the data.

# `t`

```elixir
@type t() ::
  :duration
  | :information
  | :data_rate
  | :frequency
  | :ratio
  | :count
  | :temperature
  | :length
  | :speed
  | :power
  | :energy
  | :pressure
  | :area
  | :volume
  | :mass
  | :pace
  | :angle
  | :currency
  | :electric_potential
  | :electric_current
  | :electric_charge
  | :illuminance
  | :sound_level
  | :mass_concentration
  | :amount_concentration
```

One of the fixed list of quantities.

# `additivity`

```elixir
@spec additivity(t() | nil, metric_kind() | nil) :: %{
  across: boolean(),
  over_time: boolean()
}
```

Whether a sum of the quantity's values means anything (spec/03 §10, D-155):
`%{across: boolean, over_time: boolean}` — summing several series at one instant, and
summing one series over time. The quantity's physical defaults are refined by the
field's metric kind: a `:gauge` and a `:rate` never add over time, and a `:counter`'s
increments add over time when the quantity adds across. No quantity (a plain number)
defaults to both.

## Examples

    iex> Macaw.Data.Quantity.additivity(:count, :gauge)
    %{across: true, over_time: false}

    iex> Macaw.Data.Quantity.additivity(:temperature, :counter)
    %{across: false, over_time: false}

# `all`

```elixir
@spec all() :: [t(), ...]
```

The fixed list of quantities (spec/03 §8).

# `base_unit`

```elixir
@spec base_unit(t()) :: Macaw.Data.Unit.code() | nil
```

A quantity's base unit, the unit its values are in when no unit is given; `nil` for a
`:currency`, whose code the data must say.

# `bounds`

```elixir
@spec bounds(t()) :: {number() | nil, number() | nil}
```

Where a quantity's values physically lie, in its base unit, `{min | nil, max | nil}`
(spec/03 §9): a **display hint** an axis may anchor at, never validation — a value
outside them is never refused.

## Examples

    iex> Macaw.Data.Quantity.bounds(:duration)
    {0, nil}

# `bounds`

```elixir
@spec bounds(t(), Macaw.Data.Unit.code()) :: {number() | nil, number() | nil}
```

A quantity's bounds in a unit of it: the base's, converted. A unit without a factor
(`Cel`) takes the base's bounds as they are.

## Examples

    iex> Macaw.Data.Quantity.bounds(:ratio, "%")
    {0.0, 100.0}

# `family`

```elixir
@spec family(t(), Macaw.Data.Unit.code() | nil) :: [Macaw.Data.Unit.code()]
```

The display family a unit of the quantity belongs to, smallest first; `[]` for a unit
of no family's shape (spec/03 §11). Within the unit's convention (`family/3`), a unit
belongs to the first family holding a member of its shape — its atoms and exponents,
prefixes aside and a binary prefix kept apart — so the unit picks among a quantity's
families: bits, binary prefixes, hertz, watt-hours, US lengths. No unit means the base
unit.

## Examples

    iex> Macaw.Data.Quantity.family(:information, "MiBy")
    ["By", "KiBy", "MiBy", "GiBy", "TiBy"]

    iex> Macaw.Data.Quantity.family(:length, "[ft_i]")
    ["[in_i]", "[ft_i]", "[mi_i]"]

# `family`

```elixir
@spec family(t(), Macaw.Data.Unit.code() | nil, system()) :: [Macaw.Data.Unit.code()]
```

The display family a system shows a unit of the quantity in (spec/03 §11, D-158): the
family the unit's convention names for `:metric`, `:us` or `:uk` when it names one,
else the unit's own (`family/2`), which is what `:as_posted` always is.

A quantity read in several conventions — a pressure is gauge, barometric or clinical —
picks the unit's by its exact code first, then by its shape, else the first, so a
system chooses within a convention and never across: a barometric `hPa` is shown in
inches of mercury under `:us`, and a blood pressure in `mm[Hg]` stays in it under every
system.

## Examples

    iex> Macaw.Data.Quantity.family(:mass, "kg", :uk)
    ["[oz_av]", "[lb_av]", "[stone_av]"]

    iex> Macaw.Data.Quantity.family(:duration, "ms", :us)
    ["ns", "us", "ms", "s", "min", "h", "d"]

    iex> Macaw.Data.Quantity.family(:pressure, "hPa", :us)
    ["[in_i'Hg]"]

# `kind`

```elixir
@spec kind(t()) :: kind()
```

How a quantity's values combine (spec/03 §9): `:linear`, `:affine` (a temperature),
`:logarithmic` (a sound level) or `:cyclic` (an angle).

## Examples

    iex> Macaw.Data.Quantity.kind(:angle)
    :cyclic

# `label`

```elixir
@spec label(measure()) :: String.t() | nil
```

The label rule (spec/03 §14): **the unit, else the quantity, else nothing** — the
unit's symbol with its annotations written when it is not empty, else the quantity's
name with spaces for underscores, else `nil`. Raises `ArgumentError` for a measure
`resolve/1` refuses.

## Examples

    iex> Macaw.Data.Quantity.label(%{unit: "ms"})
    "ms"

    iex> Macaw.Data.Quantity.label(%{quantity: :count})
    "count"

    iex> Macaw.Data.Quantity.label(%{})
    nil

# `of_unit`

```elixir
@spec of_unit(Macaw.Data.Unit.code()) :: t() | nil
```

The quantity a code's dimension implies, or `nil` when none of the list has it. A
dimensionless code holding `%` is a `:ratio`, any other a `:count`, and a code of one
currency alone (`{iso4217:USD}`) a `:currency`.

## Examples

    iex> Macaw.Data.Quantity.of_unit("kW.h")
    :energy

    iex> Macaw.Data.Quantity.of_unit("{request}")
    :count

# `resolve`

```elixir
@spec resolve(measure()) ::
  {:ok, %{unit: Macaw.Data.Unit.code() | nil, quantity: t() | nil}}
  | {:error, reason()}
```

A measure completed and checked (spec/03 §13): a unit alone implies its quantity, a
quantity alone means its base unit, and a unit whose dimension is not its quantity's is
refused. A `:ratio` or a `:count` takes any dimensionless unit, and a `:currency` a
code of one currency alone.

## Examples

    iex> Macaw.Data.Quantity.resolve(%{quantity: :duration})
    {:ok, %{unit: "s", quantity: :duration}}

    iex> Macaw.Data.Quantity.resolve(%{unit: "By", quantity: :duration})
    {:error, {:quantity, :duration, "By"}}

# `systems`

```elixir
@spec systems() :: [system(), ...]
```

The unit systems a display may prefer (spec/03 §12), `:as_posted` the default.

---

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