> ## Documentation Index
> Fetch the complete documentation index at: https://data.ornn.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Effective Utilization Index

> How much of the tracked GPU fleet is actually working — the demand side of the compute market, and the price per useful GPU-hour.

The price indices answer *what does compute cost?* The Effective Utilization Index
answers *how much of the compute that exists is actually working?*

These are different questions, and the second one is not visible in price. A price
can sit flat while the market tightens or slackens underneath it — the same
\$/GPU-hour means something very different at 30% occupancy than at 85%. Utilization
is the demand-side series that price alone cannot show.

## The headline: price per useful GPU-hour

A rented GPU that sits idle still bills. Dividing the market price by the share of
capacity actually working gives the **effective price** — what a buyer pays for
compute that runs:

```
effectivePrice = price / EUR
```

To read the scale: a $2.80 rate at 37% effective utilization works out to $7.57
per useful GPU-hour — 2.7x the sticker. That ratio, not the sticker, is the number
that governs the economics of a training run or an inference fleet. (Illustrative
arithmetic; the live values come from the endpoints below.)

## Construction

The index composes three layers.

<Steps>
  <Step title="Occupancy — is the capacity rented at all?">
    Capacity-weighted share of tracked GPU-hours under an active rental:

    ```
    O = SUM(rented GPU-hours) / SUM(capacity GPU-hours)
    ```

    where `capacity = rented + available` on each hourly reading, drawn from
    tracked marketplace listings across the providers Ornn observes.

    The numerator and denominator are summed **separately** before dividing. This
    is not the same as averaging the per-reading ratios: a mean of ratios lets a
    4-GPU listing count as much as a 4,000-GPU one, so one tiny provider going
    idle moves "market" occupancy as hard as a hyperscaler. Weighting by the
    capacity each reading represents is what makes the series a market rate
    rather than a average of unrelated fractions.
  </Step>

  <Step title="Intensity — how hard does it run once rented?">
    Mean GPU load during rented hours, from fleet heartbeat telemetry.

    Intensity is live from the fleet's per-node 1-hour heartbeat rollups:
    the GPU-hour-weighted mean of hourly GPU load across node-hours that were
    under an active reservation inside the window, reported as `intensitySource:
            "fleet_rollup_1h"`. It is one **fleet-wide** factor per response — the
    rollups do not carry a GPU class, so `gpuType` and `region` filters slice
    occupancy but not intensity.

    <Warning>
      If the telemetry feed is unreachable — or the window holds fewer than 24
      reserved GPU-hours — the index degrades rather than guessing: the response
      carries `basis: "occupancy_only"`, `intensity: null`, and `intensitySource:
                "unavailable"`, and `eur` equals `occupancy`. Check `basis` before comparing
      series across time — it is the field that tells you which index you are
      holding.
    </Warning>
  </Step>

  <Step title="Effective Utilization Rate">
    ```
    EUR = O x I     when intensity is available  (basis: occupancy_x_intensity)
    EUR = O         when it is not               (basis: occupancy_only)
    ```
  </Step>
</Steps>

### Derived series

| Field            | Formula                | Reads as                                                                       |
| ---------------- | ---------------------- | ------------------------------------------------------------------------------ |
| `effectivePrice` | `price / EUR`          | \$ per **useful** GPU-hour — the buyer's real unit cost                        |
| `revPag`         | `price x O`            | Revenue per **available** GPU-hour — the operator's yield, the RevPAR analogue |
| `index`          | `100 x EUR / EUR_base` | Index level, base 100 at `baseDate`                                            |

`revPag` stays on occupancy rather than EUR on purpose: an operator is paid for the
hour the GPU is rented, whether or not the tenant saturates it.

## Why it moved: demand and supply effects

A utilization rate that only goes up and down is nearly useless for a trading
decision, because two opposite market events produce the same move. Occupancy
rising because buyers took more GPUs is a tightening market. Occupancy rising
because a provider withdrew capacity is a *shrinking* one. The headline rate cannot
tell them apart, so the index decomposes every step:

```
demandEffect = (R1 - R0) / C0        capacity held at its old level
supplyEffect = R1 x (1/C1 - 1/C0)    rented held at its new level
```

The two sum to the observed change exactly, with no residual:

```
demandEffect + supplyEffect = occupancyChange
```

Worked example — rented flat at 100 GPUs, capacity halved from 400 to 200:
occupancy doubles from 25% to 50%, and the decomposition attributes **all** of it
to supply (`demandEffect: 0`, `supplyEffect: 0.25`). Nobody bought a single extra
GPU. That is the distinction the index exists to publish.

On the snapshot endpoint the same decomposition runs across the whole window, so
`summary.demandEffect` and `summary.supplyEffect` explain where the market ended up
versus where it started.

### Attribution only happens like-for-like

The effects carry a causal claim, so they are only published when the two days
being compared contain the **same set of GPU types**. If the basket changed —
typically a partial scrape outage — the effects are `null` and
`decompositionBasis` says `composition_changed` instead of `like_for_like`.

This matters more than it sounds. With six types requested, a day where five stop
reporting would otherwise decompose into a **−49.8 point "demand collapse"** on a
day when the one surviving type actually rose a point. The arithmetic balances
perfectly; it is attributing a reporting gap to buyer behaviour. Refusing to
attribute is the only honest answer.

The rate itself still publishes on such a day, alongside `reportingTypes` and
`coverage` so the gap is visible on the same row. `summary.compositionBreaks`
counts how many steps in the window could not be attributed.

`decompositionBasis` values: `like_for_like` (attributed), `composition_changed`
(suppressed), `no_prior_day` (first row of the series).

## Reading the market: the other fields

| Field                               | What it tells you                                                                                                                               |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `capacityMean`                      | Mean tracked GPUs. The denominator is a supply series in its own right — capacity growth is the build-out, visible before it shows up in price. |
| `headroomMean`                      | Mean unrented GPUs. How much slack the market is carrying.                                                                                      |
| `priceCoverage`                     | Share of capacity that had a settled price to divide into. Below 1, `effectivePrice` describes a subset of the fleet.                           |
| `peakOccupancy` / `troughOccupancy` | The window's range. A high mean built from a flat series is a different market from the same mean built from spikes.                            |
| `volatility`                        | Standard deviation of daily occupancy, in rate points. Dispersion of demand, not of price.                                                      |
| `coverage`                          | Share of expected hourly readings actually present (see below).                                                                                 |
| `readings`                          | Raw observation count behind the number.                                                                                                        |

## Data quality

**Coverage is published next to every rate.** Missing scrape hours are missing
information, not idle GPUs, and an index that lets a feed outage read as a demand
collapse is worse than no index. Coverage is measured against the *requested*
slice set — asking for a GPU type a region does not carry correctly lowers
coverage rather than quietly shrinking the denominator.

**Thin days are dropped, not published.** A day with fewer than 24 readings is
excluded from the series *and* skipped as a decomposition baseline, so the next
real day compares against the last real day rather than against a gap. Publishing
a confident-looking rate computed from three hours of scrape is how a data product
loses its reputation.

**Impossible readings are clamped.** `capacity = rented + available`, so occupancy
above 1 means the feed disagrees with itself; it clamps to 1 rather than
publishing an impossible rate. Zero-capacity readings carry no information and are
excluded entirely.

**Zero never becomes infinity.** `effectivePrice` is `null` when EUR is zero, not
`Infinity`.

**Levels are fleet sizes, not GPU-hours.** The underlying readings are hourly, so
a naive sum would scale with how often the scraper ran — a slow scrape day would
read as a smaller fleet, and the demand/supply decomposition would move even on a
market where nothing happened. Each slice is normalised to a mean over its own
readings before anything is summed, so `capacity`, `rented`, `capacityMean` and
`headroomMean` are GPU counts and the effects are immune to scrape cadence.

**`effectivePrice` divides matched baskets.** If part of the fleet has no settled
price that day, the price and the occupancy it divides into are both computed over
the priced slice only, and `priceCoverage` reports how much of the fleet that was.

## Index level and rebasing

`index` is `100 x EUR / EUR_base`, with `baseDate` defaulting to `2026-01-01`.

The base is a fixed constant rather than "the first day of whatever you asked for",
because a level rebased to the window start silently changes meaning between two
requests for different windows. If the base date has no usable data the series
rebases on its own first day and reports `baseAvailable: false` — always check that
flag before comparing levels across responses.

## Endpoints

<CardGroup cols={2}>
  <Card title="Snapshot" icon="gauge" href="/docs/api-reference/analytics/get-the-effective-utilization-index">
    `GET /api/utilization-index` — window rate, quality signals, decomposition.
  </Card>

  <Card title="History" icon="chart-line" href="/docs/api-reference/analytics/get-the-effective-utilization-index-history">
    `GET /api/utilization-index/history` — daily series with per-day effects.
  </Card>
</CardGroup>

Both accept `gpuType`, `region`, `startDate`, `endDate` and `baseDate`. The window
defaults to the trailing 30 days and is capped at 400 days per request.

## Known limitations

Stated plainly, because an index is only as good as its disclosed weaknesses.

* **Intensity is one fleet-wide factor.** It comes from Ornn-fleet heartbeat
  rollups and is not sliced by GPU class or region, and internal/test
  reservations are not yet excluded — a per-class intensity needs the node
  inventory join. It also measures the Ornn fleet while occupancy measures
  tracked marketplace listings; the two populations overlap but are not
  identical. The `basis` field is authoritative for what any response holds.
* **Region pairs a regional occupancy with a market-wide price.** The occupancy
  side is region-filtered; `gpu_daily_metrics` is averaged across the regions it
  carries for a type, matching `/api/daily-index`. For `region=global` these agree;
  for a narrower region the effective price is approximate.
* **Occupancy is derived from tracked marketplace listings**, so it measures the
  market Ornn observes, not every GPU on earth. Capacity coverage widens as more
  providers are tracked, which can move `capacityMean` for reasons that are not
  market events.
* **Daily granularity only.** The price index settles daily, so a finer
  utilization series would have no price to divide into.
