Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Improv Model

Improv models a problem as a multidimensional cube rather than a flat grid. The vocabulary is small and maps directly onto the types in improv_core_model.

Categories

A category is a dimension of the model — for example Time, Product, or Region. Each category has a stable numeric id (CategoryId) and a human-readable name.

Items

An item is one member of a category: 2025 and 2026 are items of Time; Widget A and Widget B are items of Product. Each item has an id (ItemId), the id of the category it belongs to, and a name.

Measures

A measure is a named variable indexed by a subset of the categories — its tensor dimensions. Price might be defined over [Product], while Quantity and Revenue are defined over [Time, Product].

A measure is one of two kinds:

  • Input — raw data you enter.
  • Derived — computed from a formula.

Every measure declares a value type: Number, Boolean, Text, DateTime, or Enum. The engine computes with all of them: dates are written as #2025-01-31# literals in formulas, come from date/timestamp columns in Postgres, and display as RFC 3339; an Enum computes as its number. The GUI edits DateTime cells; the TUI and the CLI's add-measure and set take number, boolean or text (CSV and SQL imports bring in numbers).

Coordinates

A coordinate names one cell of a measure's tensor by mapping categories to items — for example {Time=2025, Product=Widget A}. Internally a coordinate is an ordered map (BTreeMap<CategoryId, ItemId>), so it is insertion-order-independent: {Time=2025, Product=WidgetA} and {Product=WidgetA, Time=2025} are the same coordinate. That ordering is what lets the engine derive a stable dataflow key (see Architecture).

Input data lives in the model as a map from (measure, coordinate) to a value. Only input measures have entries; derived measures are computed by the engine.

Structure, logic, and data

The payoff of naming everything is that the three concerns move independently:

  • Rename freely. Names are cosmetic; formulas bind to ids, not names, so renaming a category or measure never breaks a formula.
  • Add items without touching formulas. Adding 2027 to Time extends every measure defined over Time automatically — a formula like Revenue = Price * Quantity needs no edit.
  • Edit logic in one place. A formula is defined once per measure, over dimensions, instead of being copied down a column of cells.

The canonical example

Throughout these docs the running example is a Time × Product revenue model:

Revenue[Time, Product] = Price[Product] * Quantity[Time, Product]

Price is defined over Product only, so it is broadcast over Time: every year reuses the same per-product price. With

ProductPrice
Widget A10
Widget B20

and

TimeProductQuantity
2025Widget A100
2025Widget B50
2026Widget A120
2026Widget B80

the engine computes:

TimeProductRevenue
2025Widget A1000
2025Widget B1000
2026Widget A1200
2026Widget B1600

These are the values the engine's test suite checks against.