---
title: Model format
description: The compact JSON a model is written in. Types, inputs, decisions, tables, functions and the expression language.
icon: braces
---

This reference describes **`model.content`**, the executable definition. The complete model file is
`{content, questions}`: keep both parts and submit them together with `state` on every execution.
[Models](/docs/concepts/models) explains the envelope; [Questions](/docs/concepts/questions) defines its answer
contract. The shape below describes `content`, not a complete execution request.

```json
{
  "name": "<model name>",
  "types": { "<Name>": "<type>" },
  "inputs": { "<name>": "<input>" },
  "decisions": { "<name>": "<decision>" },
  "functions": { "<name>": "<function>" }
}
```

`types` and `functions` are optional. Inputs, decisions and functions share one namespace. Names may contain
spaces and underscores; in an expression, write a name exactly as declared.

## Types

Types may be strings, objects or one-element arrays, depending on their form.

| Form | Example | Meaning |
|---|---|---|
| built-in | `"number"`, `"string"`, `"boolean"`, `"date"`, `"time"`, `"date and time"` | |
| constrained | `"number [0..120]"`, `"number > 0"` | the type, a space, a condition |
| choice | `"billing\|bug\|other"` | values joined by `\|` |
| ordered levels | `"low<medium<high"` | values joined by `<`, lowest first |
| named | `"Applicant"` | a key of `types` |
| struct | `{ "amount": "number", "delivered_on": "date" }` | an object of fields |
| list | `["number"]` | a one-element array |

When a value itself contains `|` or `<`, use `{ "$enum": ["a", "b"] }` or `{ "$levels": ["low", "high"] }`.
`string` is a free text input, allowed only for a key of a JSON state; a reader cannot answer it from text.

## Inputs

The compact parser accepts bare types, but the public API applies strict validation: each input needs
a `type` and a `desc`. Use an object:

```json
{
  "type": "none|degraded|blocked",
  "desc": "How much of the customer's normal operation is affected?",
  "options": { "none": "no operational effect", "degraded": "slower or partially failing", "blocked": "a core operation cannot be completed" },
  "from": "what the customer says they cannot do",
  "threshold": 0.75,
  "blank": false
}
```

| Field | Meaning |
|---|---|
| `desc` | The question the reader must answer. Required. |
| `from` | Optional hint about where in the state the fact appears. |
| `options` | What each value of a choice or level means. |
| `threshold` | Minimum reader confidence; below it the input is missing. |
| `blank` | `true` when the input may be absent. |

Inputs are observations. The policy belongs in the decisions.

## Decisions

A decision is one of:

- a **string**: an expression, `"delay_hours * 60 + delay_minutes_part"`;
- an **array**: a decision table;
- an **object**: `{ "type", "desc", "let", "expr" | "table", "hit", "requires" }` with exactly one of `expr` and
  `table`. `let` names intermediate values, in order, visible to later entries and to `expr`.

Requirements between decisions and inputs are inferred from the names used in expressions. Decisions must not
form cycles.

### Decision tables

The first row is the header; every other row is a rule.

```json
[
  ["cause", "claim_age_days", "=> boolean", "# why"],
  ["strike", "-",    "false", "Strikes are excluded from this policy."],
  ["-",      "> 30", "false", "Claim submitted after the 30-day window."],
  ["-",      "-",    "true",  "Weather, technical and other delays are covered within 30 days."]
]
```

- An **input column** is an expression over inputs, decisions or `let` names, usually just a name.
- An **output column** starts with `=>`: `"=> name: type"` in general; `"=> boolean"`, `"=> number"`,
  `"=> low<medium<high"` for a single output; `"=>"` alone when the decision object carries the type.
- The **annotation column** `"# why"` holds the rationale of each row, one sentence a reviewer would accept.
- **Input cells** are conditions on the column value: `"< 18"`, `"[18..65]"`, `">= 700"`, `"true"`, `"-"` for any value. In a
  string-typed column, bare words are literals: `"gold"`, `"gold, silver"`, `"not(gold)"`. A cell starting with
  `=` is an expression.
- **Output cells** are expressions; in a string-typed column a bare word is a literal; `"-"` means null.
- Public API strict validation requires **FIRST** tables: rows are tried top to bottom and the first match wins. The last row is the else
  row, `"-"` in every input cell, so no state falls through to null.

Rules can also be objects, which read better for wide tables:

```json
{ "when": { "days_since_delivery": "> 30" }, "then": "store_credit", "why": "Outside the 30-day refund window, within the 60-day store-credit window." }
```

`when` lists only the columns the rule tests; `then` is the output cell, or an object naming every output.

## Functions

```json
{ "monthly payment": { "params": { "amount": "number", "rate": "number", "months": "number" }, "returns": "number", "body": "amount * rate / (1 - (1 + rate) ** -months)" } }
```

Functions use only their parameters and other functions. The body is an expression or a table. Call them by name:
`"monthly payment(amount, rate, 36)"`.

## The expression language

Expressions use a readable language for calculations, dates and rule conditions. Common forms include comparisons
`= != < <= > >=`, `and`, `or`, `not(…)`, arithmetic `+ - * / **`, `if … then … else …`, range tests,
and dotted access into objects (`applicant.income`).

The recorded invoice model uses these expressions:

| Expression | Purpose |
|---|---|
| `invoice_date + duration("P10D")` | Add ten calendar days to the invoice date. |
| `payment_on >= invoice_date and payment_on <= discount_by` | Test the inclusive discount window. |
| `decimal(amount, 2)` | Round the computed payable amount to cents. |

String literals use double quotes. Dates can be constructed with `date("2026-10-01")`; subtracting dates
produces a duration whose `.days` can be read. Functions declared in the model are called by name.
This is not an exhaustive function catalogue. Validate a definition through
`POST /v1/systemtwo/models` and test its computed values before relying on it.

## The question contract

For every question id there is exactly one decision with that name:

| Question | Decision type |
|---|---|
| `noul` | `boolean`. The answer is the probability the decision is true. |
| `choice` | the option names as a choice, spelled exactly: `"billing\|bug\|other"` |
| `score` | the levels as ordered levels, in order: `"low<medium<high"` |
| `number` | `number`, or `number <range>` when the question gives one |
| `date` | `date` |

Other decisions are intermediate values and must not be named like a question.

## Addresses

Everything is addressed like the JSON: `inputs.<name>`, `decisions.<name>`, `decisions.<name>.rule[3]`
(1-based, row 0 is the header), `decisions.<name>.let.<entry>`, `functions.<name>`. Diagnostics and
text spans use model paths. Receipts group entries under `decisions.<name>` and identify a table row
with its 1-based `rule` field; they do not embed the entire model or its annotations.
