# Metrics & analysis

> Metrics & analysis — specs for 12 React components: MetricDefinition, NonGaapReconciliation, MrrBridge, BudgetVsActual and 8 more.

Source: https://lopifi.com/docs/catalog/13-metrics-and-analysis
Part of: Lopifi — US GAAP books on autopilot, reviewed by a CPA (https://lopifi.com)

---

# Metrics & analysis

Definitions in [`domain/10-metrics.md`](../domain/10-metrics.md).

---

## 162. `MetricDefinition`

> **Every metric card requires one.** There is no variant without a definition.

Renders the formula with the actual input values substituted, plus cohort, period, source, and caveat:

```
  Net Revenue Retention                                    104.6%
  ────────────────────────────────────────────────────────────────
  (Starting MRR + Expansion − Contraction − Churn) ÷ Starting MRR
  (  412,000    +  61,400   −    18,200    − 24,100 ) ÷  412,000

  Cohort:  customers active on 1 Sep 2026
  Period:  1–30 Sep 2026
  Source:  subscription ledger · excludes one-time fees
  Note:    new customers excluded by definition
```

"ARR" means four different things at four companies. This component is why the number in this product is not one of them.

**Definitions are versioned.** When one changes, historical values computed under the old definition are marked — an unannounced definition change looks like a business change.

## 163. `NonGaapReconciliation`
EBITDA, adjusted EBITDA, free cash flow, adjusted net income. Appears **below** net income in a separated block, never inside the P&L, with a line-by-line reconciliation and **every adjustment named**. "Adjusted EBITDA" with unnamed adjustments is not a number.

## 164. `MrrBridge`
Opening MRR → new → expansion → contraction → churn → closing, as a waterfall. **A single MRR number with a percentage change explains nothing**; the bridge is the primary view.

Renders GRR and NRR together or neither — NRR alone hides churn behind expansion. States that MRR is an operating metric that will not tie to the P&L.

## 165. `BudgetVsActual`
Actual, budget, variance (absolute and relative), and forecast, by period and by dimension. Respects `polarity` — a favorable expense variance is *under* budget. Materiality thresholds applied, with the threshold displayed. Reforecast versions are tracked so you can see what was believed when.

The variance is a **view, not a column**. Actual, Δ and Δ% are the same three figures asked three questions, and putting all of them on screen at once is how a budget review becomes a wall of numbers nobody reads to the end. The comparison is declared rather than positional: the actual column names budget as its base, so hiding the budget column to fit the screen does not silently re-point the variance at whatever ended up to its left.

Two things make this more than a three-column table, and both are here because a budget review goes wrong without them.

**The cut.** A favourable total is routinely two departments cancelling out. Engineering 1.6M over and sales 1.7M under reads, at the entity line, as 0.1M under — favourable — and says nothing about either. A variance report that can only be read at the entity level is one that will be believed for a quarter longer than it should be. Members are not required to sum to the entity — unallocated corporate spend sits outside them — and a vintage that restates an entity budget says nothing about one department's share, so under a cut the member's own budget stands rather than the component inventing an allocation nobody approved.

**The vintage.** The original plan and each reforecast are versions of one object, not "budget" plus "reforecasts": model a reforecast as a different kind of thing and "what did we believe in February" stops having a single answer. A vintage states only what it changed. The comparison base is chosen out loud in the toolbar, and the walk between versions sits under the grid, because a reforecast that quietly replaces the plan makes every past month look better than it was called at the time. September beating the version in force while missing the one the board saw in November are both true readings; a component that picked one silently would be picking the flattering one.

## 166. `ForecastTable`
Driver-based projections with scenarios. **Projections render dashed** on every chart and carry a "forecast" label on every figure. Actuals-to-date and forecast-remaining are visually distinct within the same period.

The boundary between what happened and what is believed is drawn in three places, not one.

**Between periods.** Everything after the last closed period is shaded, ruled with a dashed edge, set in the secondary tone, and headed by a column that says `forecast` under its label — so every projected figure on screen sits beneath the word rather than merely near it.

**Within a period.** The month that has started and not finished is not one column; it is three — what has posted, what is expected for the rest, and the landing they sum to. A part-elapsed month reported as a single figure sits beside eight closed ones and inherits their authority, which is the difference between "we are at 41.2 with nine days to go" and "we did 58.2" — the second of which nobody said and everybody quotes. The landing is **computed from the other two**, never supplied: a caller who provides all three will eventually provide three figures that do not add up, and the grid would render the contradiction without noticing it.

**Between scenarios.** The scenario in force is named in the caption and in the toolbar. It will not rank them and will not style one as the good case — an unlabelled forecast is indistinguishable from a claim, and a ranked one is a recommendation wearing a forecast's clothes.

## 167. `CovenantMonitor`
Per-covenant: the **contractual** definition (lenders define EBITDA with specific add-backs), the computed value, the threshold, the headroom, and the next test date.

The number here may legitimately differ from the dashboard's EBITDA because the credit agreement defines it differently. That's not a bug, and the component says so on the card.

Headroom under 20% is `--warning`; a breach is `--negative` — with the numeric headroom always shown, never color alone.

## 168. `KpiHealthStrip`
The dashboard's top row: liquidity, profitability, and cash metrics with polarity, comparison basis, and definitions. **Metrics derived from unreconciled data are marked provisional** — a KPI computed mid-close, before the bank is reconciled, is an estimate, and labeling it is what stops people quoting it externally.

---

## 174. `RevenueTracker`
Revenue by stream, period by period, with the four questions an operator actually asks: how much
this period, versus what, from where, and how concentrated.

The summary tiles are computed from the same array the grid cells are drawn from — there is no
second data path, because a header KPI that disagrees with the table beneath it is the fastest
way to lose a reader. Total revenue is summed here rather than accepted as a prop for the same
reason.

Month-on-month **and** year-on-year, together: a month-on-month read of a seasonal business is
noise, and a year-on-year read of a fast-growing one is stale.

The run-rate tile is labelled a projection from one period. Multiplying a month by twelve is not
ARR — it takes no view of churn, seasonality or contract length — and the tile says so rather
than letting the figure travel into a deck as though it did.

One stream over a third of revenue is flagged in words. Concentration is a fact about the risk in
the business, not about the month.

## 175. `ExpenseTracker`
Spend by category on three bases — actual, budget, variance — because they are the same
categories asked three questions, and a nine-column grid of all of them is one nobody reads past
the third column.

**Every row carries a polarity, and the component derives it.** An increase in spend is not good
news; under budget is favourable and over budget is not. A grid that paints a rising cost green
has told the reader the opposite of the truth.

Polarity describes the figure *as stored*, not the concept behind it, and that is the easiest
thing here to get backwards: this system holds costs as negatives, so a heavier month is a figure
moving **down** and the row wants `standard`. A model holding the same costs as positive
magnitudes wants `inverted`. `ExpenseTracker` takes a `sign` and works it out itself rather than
asking its caller to reason about a storage convention to get the colours the right way round.

Committed-but-unbilled is shown beside the total, never folded into it. A purchase order is not
an expense — accruing it silently is a cutoff error, and showing it nowhere is how a category
with six figures of open POs looks under budget right up until the invoices land.

The movers list ranks by amount, not by percentage. A line that doubled from 400 to 800 is not
the story of a month in which another went from 180,000 to 240,000.

## 176. `DriverModel`
The inputs a forecast is built from and the outputs they produce, in one grid with the boundary
between them visible. Three separations carry it: actual from projection, input from output, and
assumption from arithmetic.

Inputs are typed; outputs carry the ƒ mark and their formula. A model where you cannot tell which
is which is a model where nobody can find the assumption that moved the answer.

Only **projected inputs** accept editing. An actual is what the ledger posted, and a model that
lets you type over it will eventually be used to explain a month that never happened.

The named assumptions sit under the grid in words — "new logos hold at 14 a month" — because that
is the sentence someone can disagree with, and disagreement is the point of a forecast review.
A growth rate buried three rows into a projected column is not.

## 177. `ScenarioCompare`
Scenarios in columns, measures down, every column measured against the base — the comparison is
declared rather than positional, so it survives the reader reordering or hiding columns.

**It will not rank the scenarios.** No best-case styling, no sorting by outcome. A scenario is a
set of assumptions, and presenting the largest number as the good one is how a forecast review
becomes a negotiation about which column to show the board.

**It will not hide a breach.** A measure carrying a limit — a covenant floor, a minimum cash
balance — names the scenarios that cross it in words under the grid. A downside that breaches a
covenant is the only thing on the page that matters, and it is not left as a cell someone has to
notice.

## 178. `PlByDimension`
The profit and loss cut by department, entity, product or region rather than by period. Same
lines, different question — and the second one is what the department head is in the room to ask.

Total is a column and it is computed from the members beside it plus whatever is unallocated,
never supplied separately. A total that does not equal its parts loses the room.

Unallocated is its own column, not a rounding and not an allocation. Corporate costs that belong
to no department exist, and spreading them on a rule nobody agreed to is how a department P&L
becomes something its owner cannot argue with.
