Skip to content
aityx
Esc
↑↓navigate↵open⌘Jpreview
On this page

Model format

The compact JSON a model is written in. Types, inputs, decisions, tables, functions and the expression language.

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 explains the envelope; Questions defines its answer contract. The shape below describes content, not a complete execution request.

{
  "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:

{
  "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.

[
  ["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:

{ "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

{ "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.

Was this page helpful?