Layerz
Server Details
A structured financial modeling layer for AI agents. Build, version, and audit financial models without drift, then export to Excel, from Claude or any MCP client. Learn more: https://layerz.cc/for-agents
The last two health checks of this connector failed. If this server requires authentication, missing or expired test credentials may be the cause. A test profile lets Glama authenticate for health checks and discover tools; it is separate from your personal connections.
If you are the author, claim ownership, then add or update a test profile under Admin → Test Profile.
- Status
- Unhealthy
- Uptime
- 37.5% over 73 days
- OAuth
- Works in Glama
- Last Tested
- Transport
- Streamable HTTP
- URL
TDQS
Scored across 30 tools
Most tools have a distinct resource+action purpose, but several clusters overlap: `get_model` vs `read` both retrieve model data, `patch`/`import_branch`/`update_branch` can all create or write branch data, and `create_model`/`build_from_blueprint` both apply templates. The very detailed descriptions help disambiguate, but boundaries are not always crisp, especially for branch writes.
All tools use the `layerz_` prefix and snake_case, with mostly predictable verb_noun names such as `list_models`, `create_model`, and `delete_branch`. Minor deviations are noun-only tools (`dependencies`, `history`, `diff`, `external_ref`) and lone verbs (`patch`, `read`), but the convention remains readable and largely consistent.
30 tools exceeds the 3–15 sweet spot and the 25+ threshold that generally indicates an over-large surface. Although the financial modeling domain is broad and several tools bundle operations via `op`, the set is heavy enough to increase cognitive load and misselection risk.
The set covers most lifecycle operations: model creation/reading/mutation, branch management, integrations, mappings, versions, templates, sharing, file upload/parse, export, validation, dependencies, diff, and external refs. Notable gaps include no delete-model tool and no explicit template demotion/unpromote path, but agents can work around most workflows.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
- Changed
layerz_patch1 field changed- changed
Input schema / properties / ops / items / oneOfPrevious value: -[ - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "by_branch": { - "description": "Chart only: one styled series per branch; false reverts to the checked-out view.", - "type": "boolean" - }, - "cagr": { - "description": "mini_table (axis=time): show the trailing CAGR column. Default true", - "type": "boolean" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "anyOf": [ - { - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Flags the line as a control judged on its own value: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. `null` removes the check. Failing periods surface in `layerz_read` under `health`, non-blocking." - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (null clears it)." - }, - "compare_to": { - "anyOf": [ - { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "KPI native comparison; null clears it." - }, - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "anyOf": [ - { - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." - }, - "name": { - "description": "New display label (also the formula identifier)", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "update", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "New parent UID (null to move to root)" - }, - "period_mode": { - "description": "kpi / mini_table period mode. 'ytd' shows the year to date: the cumulative from January through the target period (each column for a time-axis mini_table). Flows are summed, an `average` line averaged, a `stock` line read at the period, and a ratio line (formula `a / b`) recomputed on the cumulated operands. Branch columns, écarts and the KPI comparison follow (prev_year = year to date of the same month a year earlier). Default 'period'.", - "enum": [ - "period", - "ytd" - ], - "type": "string" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Change the item role. Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "description": "Chart series UIDs", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch; supersedes `series`.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "description": "Existing item UID to update", - "minLength": 1, - "type": "string" - }, - "unit": { - "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", - "enum": [ - "none", - "k", - "m", - "auto" - ], - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "by_branch": { - "description": "Chart only: plot one styled series per branch instead of the checked-out view.", - "type": "boolean" - }, - "cagr": { - "description": "mini_table (axis=time): show the trailing CAGR column. Default true", - "type": "boolean" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "create", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", - "minLength": 1, - "type": "string" - }, - "period_mode": { - "description": "kpi / mini_table period mode. 'ytd' shows the year to date: the cumulative from January through the target period (each column for a time-axis mini_table). Flows are summed, an `average` line averaged, a `stock` line read at the period, and a ratio line (formula `a / b`) recomputed on the cumulated operands. Branch columns, écarts and the KPI comparison follow (prev_year = year to date of the same month a year earlier). Default 'period'.", - "enum": [ - "period", - "ytd" - ], - "type": "string" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "unit": { - "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", - "enum": [ - "none", - "k", - "m", - "auto" - ], - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name", - "op" - ], - "type": "object" - }, - { - "properties": { - "from_uid": { - "description": "Existing item UID to replace atomically (section/dashboard unsupported)", - "minLength": 1, - "type": "string" - }, - "new_item": { - "description": "Inline payload describing the replacement item", - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "by_branch": { - "description": "Chart only: plot one styled series per branch instead of the checked-out view.", - "type": "boolean" - }, - "cagr": { - "description": "mini_table (axis=time): show the trailing CAGR column. Default true", - "type": "boolean" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "period_mode": { - "description": "kpi / mini_table period mode. 'ytd' shows the year to date: the cumulative from January through the target period (each column for a time-axis mini_table). Flows are summed, an `average` line averaged, a `stock` line read at the period, and a ratio line (formula `a / b`) recomputed on the cumulated operands. Branch columns, écarts and the KPI comparison follow (prev_year = year to date of the same month a year earlier). Default 'period'.", - "enum": [ - "period", - "ytd" - ], - "type": "string" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "unit": { - "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", - "enum": [ - "none", - "k", - "m", - "auto" - ], - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name" - ], - "type": "object" - }, - "op": { - "const": "replace", - "type": "string" - }, - "preserve_order": { - "type": "boolean" - }, - "preserve_parent": { - "type": "boolean" - }, - "preserve_presentation": { - "description": "Default true: every presentation / annotation field `new_item` omits (item_style, format, comment, check, span, position, period_window, compare_to, sparkline, series_defs, …) is carried over from the replaced item when the new role can hold it; `false` rebuilds the item from `new_item` alone.", - "type": "boolean" - }, - "preserve_style": { - "type": "boolean" - } - }, - "required": [ - "op", - "from_uid", - "new_item" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "delete", - "type": "string" - }, - "uid": { - "description": "UID of item to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "after": { - "description": "UID of item to place after (for ordering)", - "minLength": 1, - "type": "string" - }, - "op": { - "const": "move", - "type": "string" - }, - "parent": { - "description": "New parent UID", - "minLength": 1, - "type": "string" - }, - "uid": { - "description": "UID of item to move", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_create", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "properties": { - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_update", - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "list_delete", - "type": "string" - }, - "uid": { - "description": "UID of list to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "finance_md": { - "anyOf": [ - { - "maxLength": 5000, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "formats": { - "additionalProperties": { - "properties": { - "display_name": { - "maxLength": 80, - "type": "string" - }, - "excel_format": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "name": { - "maxLength": 50, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "name", - "excel_format" - ], - "type": "object" - }, - "propertyNames": { - "maxLength": 50, - "minLength": 1, - "type": "string" - }, - "type": "object" - }, - "glyph": { - "anyOf": [ - { - "maxLength": 60, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "number_locale": { - "anyOf": [ - { - "enum": [ - "en", - "fr", - "de" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "op": { - "const": "meta", - "type": "string" - }, - "subtitle": { - "anyOf": [ - { - "maxLength": 160, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "timelines": { - "properties": { - "monthly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "quarterly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "yearly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - } - }, - "type": "object" - } - }, - "required": [ - "op" - ], - "type": "object" - } -]New value: +[ + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: one styled series per branch; false reverts to the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "anyOf": [ + { + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Flags the line as a control judged on its own value: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. `null` removes the check. Failing periods surface in `layerz_read` under `health`, non-blocking." + }, + "collapsed": { + "description": "fold the group by default: a section with children, a Σ aggregator or a list-mode line opens collapsed in the web grid and its rows start hidden under the Excel outline (+/−). false unfolds. View state, outside the blueprint and the diffs", + "type": "boolean" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (null clears it)." + }, + "compare_to": { + "anyOf": [ + { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "KPI native comparison; null clears it." + }, + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "anyOf": [ + { + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." + }, + "name": { + "description": "New display label (also the formula identifier)", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "update", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "New parent UID (null to move to root)" + }, + "period_mode": { + "description": "kpi / mini_table period mode. 'ytd' shows the year to date: the cumulative from January through the target period (each column for a time-axis mini_table). Flows are summed, an `average` line averaged, a `stock` line read at the period, and a ratio line (formula `a / b`) recomputed on the cumulated operands. Branch columns, écarts and the KPI comparison follow (prev_year = year to date of the same month a year earlier). Default 'period'.", + "enum": [ + "period", + "ytd" + ], + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Change the item role. Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "description": "Chart series UIDs", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch; supersedes `series`.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "description": "Existing item UID to update", + "minLength": 1, + "type": "string" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: plot one styled series per branch instead of the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "collapsed": { + "description": "fold the group by default: a section with children, a Σ aggregator or a list-mode line opens collapsed in the web grid and its rows start hidden under the Excel outline (+/−). false unfolds. View state, outside the blueprint and the diffs", + "type": "boolean" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "create", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", + "minLength": 1, + "type": "string" + }, + "period_mode": { + "description": "kpi / mini_table period mode. 'ytd' shows the year to date: the cumulative from January through the target period (each column for a time-axis mini_table). Flows are summed, an `average` line averaged, a `stock` line read at the period, and a ratio line (formula `a / b`) recomputed on the cumulated operands. Branch columns, écarts and the KPI comparison follow (prev_year = year to date of the same month a year earlier). Default 'period'.", + "enum": [ + "period", + "ytd" + ], + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name", + "op" + ], + "type": "object" + }, + { + "properties": { + "from_uid": { + "description": "Existing item UID to replace atomically (section/dashboard unsupported)", + "minLength": 1, + "type": "string" + }, + "new_item": { + "description": "Inline payload describing the replacement item", + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: plot one styled series per branch instead of the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "collapsed": { + "description": "fold the group by default: a section with children, a Σ aggregator or a list-mode line opens collapsed in the web grid and its rows start hidden under the Excel outline (+/−). false unfolds. View state, outside the blueprint and the diffs", + "type": "boolean" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "period_mode": { + "description": "kpi / mini_table period mode. 'ytd' shows the year to date: the cumulative from January through the target period (each column for a time-axis mini_table). Flows are summed, an `average` line averaged, a `stock` line read at the period, and a ratio line (formula `a / b`) recomputed on the cumulated operands. Branch columns, écarts and the KPI comparison follow (prev_year = year to date of the same month a year earlier). Default 'period'.", + "enum": [ + "period", + "ytd" + ], + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name" + ], + "type": "object" + }, + "op": { + "const": "replace", + "type": "string" + }, + "preserve_order": { + "type": "boolean" + }, + "preserve_parent": { + "type": "boolean" + }, + "preserve_presentation": { + "description": "Default true: every presentation / annotation field `new_item` omits (item_style, format, comment, check, span, position, period_window, compare_to, sparkline, series_defs, …) is carried over from the replaced item when the new role can hold it; `false` rebuilds the item from `new_item` alone.", + "type": "boolean" + }, + "preserve_style": { + "type": "boolean" + } + }, + "required": [ + "op", + "from_uid", + "new_item" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "delete", + "type": "string" + }, + "uid": { + "description": "UID of item to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "after": { + "description": "UID of item to place after (for ordering)", + "minLength": 1, + "type": "string" + }, + "op": { + "const": "move", + "type": "string" + }, + "parent": { + "description": "New parent UID", + "minLength": 1, + "type": "string" + }, + "uid": { + "description": "UID of item to move", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_create", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "properties": { + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_update", + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "list_delete", + "type": "string" + }, + "uid": { + "description": "UID of list to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "finance_md": { + "anyOf": [ + { + "maxLength": 5000, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "formats": { + "additionalProperties": { + "properties": { + "display_name": { + "maxLength": 80, + "type": "string" + }, + "excel_format": { + "maxLength": 120, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 50, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "excel_format" + ], + "type": "object" + }, + "propertyNames": { + "maxLength": 50, + "minLength": 1, + "type": "string" + }, + "type": "object" + }, + "glyph": { + "anyOf": [ + { + "maxLength": 60, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." + }, + "name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "number_locale": { + "anyOf": [ + { + "enum": [ + "en", + "fr", + "de" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "op": { + "const": "meta", + "type": "string" + }, + "subtitle": { + "anyOf": [ + { + "maxLength": 160, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "timelines": { + "properties": { + "monthly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "quarterly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "yearly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + } + }, + "required": [ + "op" + ], + "type": "object" + } +]
2 tool updates
- Changed
layerz_diff5 fields changed- added
Input schema / properties / branch_idAdded value: +{ + "description": "With `values`: the branch whose `[base, branch]` checkout both revisions are computed under; absent = the base plan.", + "minLength": 1, + "type": "string" +} - added
Input schema / properties / granularityAdded value: +{ + "description": "With `values`: the dated grain of the comparison; `native` (default) = the finest active grain.", + "enum": [ + "monthly", + "quarterly", + "yearly", + "native" + ], + "type": "string" +} - added
Input schema / properties / toleranceAdded value: +{ + "description": "With `values`: absolute difference above which a cell counts as changed (default 0.01).", + "minimum": 0, + "type": "number" +} - added
Input schema / properties / uidsAdded value: +{ + "description": "With `values`: the lines compared (default: checks and totals).", + "items": { + "maxLength": 64, + "minLength": 1, + "type": "string" + }, + "maxItems": 200, + "type": "array" +} - added
Input schema / properties / valuesAdded value: +{ + "description": "Adds the computed-values diff of the two revisions under `values` (the numbers, not the formulas).", + "type": "boolean" +}
- Changed
layerz_patch1 field changed- added
Input schema / properties / compare_valuesAdded value: +{ + "description": "Adds `value_diff`: the computed-values diff between the model before and after the batch — the `compute` UIDs, else the key lines (checks and totals) — at the finest grain under `compute_branch`, with before / after / delta per changed period and a summary. With `dry_run`, a preview of what the batch changes in the numbers.", + "type": "boolean" +}
1 tool update
- Changed
layerz_list_models1 field changed- changed
Input schema / properties / q / descriptionPrevious value: -"Name filter: case-insensitive substring (\"swoop\" matches \"SWOOP - BP 2026\"); `*` is a wildcard, `%`, `_` and `&` are literal."New value: +"Name filter: case- and accent-insensitive substring (\"swoop\" matches \"SWOOP - BP 2026\", \"homelior\" matches \"Homélior\"); `*` is a wildcard, `%`, `_` and `&` are literal."
2 tool updates
- Changed
layerz_import_branch1 field changed- changed
Input schema / properties / replace_source_id / descriptionPrevious value: -"file_id path only: refresh this existing file source in place (id from layerz_list_integrations) instead of creating a second one."New value: +"file_id path only: refresh this existing file source in place (id from layerz_list_integrations) instead of creating a second one. The rows are classified with that source's own rules plus the model-wide ones, as its sync does."
- Changed
layerz_manage_integration1 field changed- added
Input schema / properties / config / properties / fallbackAdded value: +{ + "description": "What a key no mapping rule covers becomes. `auto_create: true` creates a line named after the key; false (the default once the model has rules) leaves the row `unmapped`. A change re-projects from the staged rows.", + "properties": { + "auto_create": { + "type": "boolean" + } + }, + "type": "object" +}
2 tool updates
- Changed
layerz_manage_integration2 fields changed- changed
Input schema / properties / config / properties / field_map / descriptionPrevious value: -"Tabular sources: source column → canonical field (txn_date, amount, category, description, account_number, period). A value is a column name, a 0-based index, or an array of columns joined with \" / \" as a composite key (category: [\"Sens\", \"Catégorie\"] routes \"Charge / Hébergement\" and \"Produit / Hébergement\" separately). `amount` and `txn_date` are single-column (`measures` covers several value columns). A change re-fetches and re-stages; rejected on a csv/fec file source, whose columns are fixed at import."New value: +"Tabular sources: source column → canonical field (txn_date, amount, category, description, account_number, period). A value is a column name, a 0-based index, or an array of columns joined with \" / \" as a composite key (category: [\"Sens\", \"Catégorie\"] routes \"Charge / Hébergement\" and \"Produit / Hébergement\" separately). `amount` and `txn_date` are single-column (`measures` covers several value columns). A change re-fetches and re-stages. API connectors (Qonto, Pennylane, Stripe): a payload field name per description / category / account_number, taken from `source_fields` in layerz_list_integrations (e.g. Pennylane ledger `{ description: \"entryLabel\", category: \"accountLabel\" }`); a change re-derives the staged rows from their payloads and replays, no provider fetch. Rejected on a csv/fec file source, whose columns are fixed at import." - changed
Input schema / properties / config / properties / label_field / descriptionPrevious value: -"Qonto: field the rows are grouped by (counterparty | label | category)."New value: +"Deprecated Qonto alias of `classify_by` + `field_map.description` (counterparty | label | category); still accepted, folded on the next projection."
- Changed
layerz_patch1 field changed- changed
Input schema / properties / ops / items / oneOfPrevious value: -[ - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "by_branch": { - "description": "Chart only: one styled series per branch; false reverts to the checked-out view.", - "type": "boolean" - }, - "cagr": { - "description": "mini_table (axis=time): show the trailing CAGR column. Default true", - "type": "boolean" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "anyOf": [ - { - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Flags the line as a control judged on its own value: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. `null` removes the check. Failing periods surface in `layerz_read` under `health`, non-blocking." - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (null clears it)." - }, - "compare_to": { - "anyOf": [ - { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "KPI native comparison; null clears it." - }, - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "anyOf": [ - { - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." - }, - "name": { - "description": "New display label (also the formula identifier)", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "update", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "New parent UID (null to move to root)" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Change the item role. Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "description": "Chart series UIDs", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch; supersedes `series`.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "description": "Existing item UID to update", - "minLength": 1, - "type": "string" - }, - "unit": { - "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", - "enum": [ - "none", - "k", - "m", - "auto" - ], - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "by_branch": { - "description": "Chart only: plot one styled series per branch instead of the checked-out view.", - "type": "boolean" - }, - "cagr": { - "description": "mini_table (axis=time): show the trailing CAGR column. Default true", - "type": "boolean" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "create", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", - "minLength": 1, - "type": "string" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "unit": { - "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", - "enum": [ - "none", - "k", - "m", - "auto" - ], - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name", - "op" - ], - "type": "object" - }, - { - "properties": { - "from_uid": { - "description": "Existing item UID to replace atomically (section/dashboard unsupported)", - "minLength": 1, - "type": "string" - }, - "new_item": { - "description": "Inline payload describing the replacement item", - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "by_branch": { - "description": "Chart only: plot one styled series per branch instead of the checked-out view.", - "type": "boolean" - }, - "cagr": { - "description": "mini_table (axis=time): show the trailing CAGR column. Default true", - "type": "boolean" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "unit": { - "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", - "enum": [ - "none", - "k", - "m", - "auto" - ], - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name" - ], - "type": "object" - }, - "op": { - "const": "replace", - "type": "string" - }, - "preserve_order": { - "type": "boolean" - }, - "preserve_parent": { - "type": "boolean" - }, - "preserve_presentation": { - "description": "Default true: every presentation / annotation field `new_item` omits (item_style, format, comment, check, span, position, period_window, compare_to, sparkline, series_defs, …) is carried over from the replaced item when the new role can hold it; `false` rebuilds the item from `new_item` alone.", - "type": "boolean" - }, - "preserve_style": { - "type": "boolean" - } - }, - "required": [ - "op", - "from_uid", - "new_item" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "delete", - "type": "string" - }, - "uid": { - "description": "UID of item to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "after": { - "description": "UID of item to place after (for ordering)", - "minLength": 1, - "type": "string" - }, - "op": { - "const": "move", - "type": "string" - }, - "parent": { - "description": "New parent UID", - "minLength": 1, - "type": "string" - }, - "uid": { - "description": "UID of item to move", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_create", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "properties": { - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_update", - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "list_delete", - "type": "string" - }, - "uid": { - "description": "UID of list to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "finance_md": { - "anyOf": [ - { - "maxLength": 5000, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "formats": { - "additionalProperties": { - "properties": { - "display_name": { - "maxLength": 80, - "type": "string" - }, - "excel_format": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "name": { - "maxLength": 50, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "name", - "excel_format" - ], - "type": "object" - }, - "propertyNames": { - "maxLength": 50, - "minLength": 1, - "type": "string" - }, - "type": "object" - }, - "glyph": { - "anyOf": [ - { - "maxLength": 60, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "number_locale": { - "anyOf": [ - { - "enum": [ - "en", - "fr", - "de" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "op": { - "const": "meta", - "type": "string" - }, - "subtitle": { - "anyOf": [ - { - "maxLength": 160, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "timelines": { - "properties": { - "monthly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "quarterly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "yearly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - } - }, - "type": "object" - } - }, - "required": [ - "op" - ], - "type": "object" - } -]New value: +[ + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: one styled series per branch; false reverts to the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "anyOf": [ + { + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Flags the line as a control judged on its own value: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. `null` removes the check. Failing periods surface in `layerz_read` under `health`, non-blocking." + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (null clears it)." + }, + "compare_to": { + "anyOf": [ + { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "KPI native comparison; null clears it." + }, + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "anyOf": [ + { + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." + }, + "name": { + "description": "New display label (also the formula identifier)", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "update", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "New parent UID (null to move to root)" + }, + "period_mode": { + "description": "kpi / mini_table period mode. 'ytd' shows the year to date: the cumulative from January through the target period (each column for a time-axis mini_table). Flows are summed, an `average` line averaged, a `stock` line read at the period, and a ratio line (formula `a / b`) recomputed on the cumulated operands. Branch columns, écarts and the KPI comparison follow (prev_year = year to date of the same month a year earlier). Default 'period'.", + "enum": [ + "period", + "ytd" + ], + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Change the item role. Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "description": "Chart series UIDs", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch; supersedes `series`.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "description": "Existing item UID to update", + "minLength": 1, + "type": "string" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: plot one styled series per branch instead of the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "create", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", + "minLength": 1, + "type": "string" + }, + "period_mode": { + "description": "kpi / mini_table period mode. 'ytd' shows the year to date: the cumulative from January through the target period (each column for a time-axis mini_table). Flows are summed, an `average` line averaged, a `stock` line read at the period, and a ratio line (formula `a / b`) recomputed on the cumulated operands. Branch columns, écarts and the KPI comparison follow (prev_year = year to date of the same month a year earlier). Default 'period'.", + "enum": [ + "period", + "ytd" + ], + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name", + "op" + ], + "type": "object" + }, + { + "properties": { + "from_uid": { + "description": "Existing item UID to replace atomically (section/dashboard unsupported)", + "minLength": 1, + "type": "string" + }, + "new_item": { + "description": "Inline payload describing the replacement item", + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: plot one styled series per branch instead of the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "period_mode": { + "description": "kpi / mini_table period mode. 'ytd' shows the year to date: the cumulative from January through the target period (each column for a time-axis mini_table). Flows are summed, an `average` line averaged, a `stock` line read at the period, and a ratio line (formula `a / b`) recomputed on the cumulated operands. Branch columns, écarts and the KPI comparison follow (prev_year = year to date of the same month a year earlier). Default 'period'.", + "enum": [ + "period", + "ytd" + ], + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name" + ], + "type": "object" + }, + "op": { + "const": "replace", + "type": "string" + }, + "preserve_order": { + "type": "boolean" + }, + "preserve_parent": { + "type": "boolean" + }, + "preserve_presentation": { + "description": "Default true: every presentation / annotation field `new_item` omits (item_style, format, comment, check, span, position, period_window, compare_to, sparkline, series_defs, …) is carried over from the replaced item when the new role can hold it; `false` rebuilds the item from `new_item` alone.", + "type": "boolean" + }, + "preserve_style": { + "type": "boolean" + } + }, + "required": [ + "op", + "from_uid", + "new_item" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "delete", + "type": "string" + }, + "uid": { + "description": "UID of item to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "after": { + "description": "UID of item to place after (for ordering)", + "minLength": 1, + "type": "string" + }, + "op": { + "const": "move", + "type": "string" + }, + "parent": { + "description": "New parent UID", + "minLength": 1, + "type": "string" + }, + "uid": { + "description": "UID of item to move", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_create", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "properties": { + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_update", + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "list_delete", + "type": "string" + }, + "uid": { + "description": "UID of list to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "finance_md": { + "anyOf": [ + { + "maxLength": 5000, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "formats": { + "additionalProperties": { + "properties": { + "display_name": { + "maxLength": 80, + "type": "string" + }, + "excel_format": { + "maxLength": 120, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 50, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "excel_format" + ], + "type": "object" + }, + "propertyNames": { + "maxLength": 50, + "minLength": 1, + "type": "string" + }, + "type": "object" + }, + "glyph": { + "anyOf": [ + { + "maxLength": 60, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." + }, + "name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "number_locale": { + "anyOf": [ + { + "enum": [ + "en", + "fr", + "de" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "op": { + "const": "meta", + "type": "string" + }, + "subtitle": { + "anyOf": [ + { + "maxLength": 160, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "timelines": { + "properties": { + "monthly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "quarterly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "yearly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + } + }, + "required": [ + "op" + ], + "type": "object" + } +]
2 tool updates
- Changed
layerz_external_ref1 field changed- added
Input schema / properties / linksAdded value: +{ + "description": "`link`: up to 200 links written as one batch, in place of the flat fields.", + "items": { + "properties": { + "name": { + "description": "`link`: display name (default: the source item's name).", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "parent": { + "description": "`link` (create): parent container uid (section / formula / balance / dashboard).", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "source_model_id": { + "description": "`link`: the model read from.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" + }, + "source_uid": { + "description": "`link`: uid of the source item in that model.", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "timeline_ref": { + "description": "`link`: grain of the linked line (default: the source's grain when active here, else the finest active grain).", + "enum": [ + "constant", + "monthly", + "quarterly", + "yearly" + ], + "type": "string" + }, + "uid": { + "description": "`link`: existing non-list assumption to retarget.", + "maxLength": 100, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "source_model_id", + "source_uid" + ], + "type": "object" + }, + "maxItems": 200, + "minItems": 1, + "type": "array" +}
- Changed
layerz_manage_mapping8 fields changed- added
Input schema / properties / mapping_idsAdded value: +{ + "description": "`delete`: up to 500 rule ids removed as one batch.", + "items": { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" + }, + "maxItems": 500, + "minItems": 1, + "type": "array" +} - changed
Input schema / properties / pattern / descriptionPrevious value: -"`set`: primary key glob (`*` anywhere) or exact value."New value: +"Primary key glob (`*` anywhere) or exact value." - added
Input schema / properties / pattern / maxLengthAdded value: +200 - added
Input schema / properties / rulesAdded value: +{ + "description": "`set`: up to 500 rules written as one batch, in place of the flat fields.", + "items": { + "properties": { + "pattern": { + "description": "Primary key glob (`*` anywhere) or exact value.", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "secondary_pattern": { + "description": "Secondary key glob tested against the source's `dimension_by` field (default `*` = any).", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "sign": { + "description": "Sign convention applied to the matched amount (default natural).", + "enum": [ + "natural", + "negate", + "absolute" + ], + "type": "string" + }, + "source_id": { + "description": "Scopes the rule to one data source of the model (id from layerz_list_integrations). Omitted = model-wide.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" + }, + "target": { + "description": "Target item name or uid; `uid::entryUid` pins one list entry; `__ignore__` routes nowhere on purpose.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "pattern", + "target" + ], + "type": "object" + }, + "maxItems": 500, + "minItems": 1, + "type": "array" +} - changed
Input schema / properties / secondary_pattern / descriptionPrevious value: -"`set`: secondary key glob tested against the source's `dimension_by` field (default `*` = any)."New value: +"Secondary key glob tested against the source's `dimension_by` field (default `*` = any)." - changed
Input schema / properties / sign / descriptionPrevious value: -"`set`: sign convention applied to the matched amount (default natural)."New value: +"Sign convention applied to the matched amount (default natural)." - changed
Input schema / properties / source_id / descriptionPrevious value: -"`set`: scopes the rule to one data source of the model (id from layerz_list_integrations). Omitted = model-wide."New value: +"Scopes the rule to one data source of the model (id from layerz_list_integrations). Omitted = model-wide." - changed
Input schema / properties / target / descriptionPrevious value: -"`set`: target item name or uid; `uid::entryUid` pins one list entry; `__ignore__` routes nowhere on purpose."New value: +"Target item name or uid; `uid::entryUid` pins one list entry; `__ignore__` routes nowhere on purpose."
1 tool update
- Changed
layerz_read2 fields changed- changed
Input schema / properties / branch_id / descriptionPrevious value: -"Branch whose `[base, branch]` checkout the values are computed under, instead of the stacked-all view (base-vs-overlay audit); `default` = the base plan alone. Requires with_values."New value: +"Branch whose `[base, branch]` checkout the values `v` and the check report `health` are computed under, instead of the stacked-all view (base-vs-overlay audit); `default` = the base plan alone. An unknown id is rejected. Requires with_values or health." - changed
Input schema / properties / health / descriptionPrevious value: -"Also returns the `health` block: non-blocking check violations (lines flagged `check`), evaluated at the finest active grain. `with_values: true` already includes `health` when the model has checks; this flag returns `health` without the full value series."New value: +"Also returns the `health` block: non-blocking check violations (lines flagged `check`), evaluated at the finest active grain under a branch checkout rather than the stacked view: the `branch_id` requested, else on a multi-branch model the branch the model opens on (`health.branch_id`) with every branch's verdict under `health.by_branch`. `with_values: true` already includes `health` when the model has checks; this flag returns `health` without the full value series."
44 tool updates
- Changed
layerz_build_from_blueprint6 fields changed- changed
Input schema / properties / blueprint / descriptionPrevious value: -"Strict Blueprint object. Shape: { project?: string, timelines?: { monthly?: {start,end}, yearly?: {start,end}, quarterly?: {start,end} }, lists?: [{ display_name, items: [{label, mapped_to?: Record<targetListDisplayName, sourceLabels[]>}] }], sections: [{ name: \"P&L\", items: [{ role: \"assumption\" | \"formula\" | \"callup\" | \"balance\" | \"chart\" | \"dashboard\" | \"kpi\" | \"mini_table\", name, timeline_ref?, value?, values?, formula?, item_style?, opening_balance?, liste_ref?, timeline_values?: Record<elementLabel, (number|null)[]>, children?: [{name}], … }] }], dashboards?: [{ name, items: [{role: \"chart\" | \"kpi\" | \"mini_table\", …}] }] }. At least one section is required. Items reference each other by display_name (formula inputs, callup source, chart series). Stock-flow pattern: declare a `balance` with `opening_balance` and put `callup` items inside its `items` referencing the source flows by name; outflows must be formulas that return negative values; break self-referencing cycles with the lag suffix `_M-1` / `_Q-1` / `_Y-1` on the balance name. Roll-forwards & recursion use the lag suffix `<name>_M-N` / `_Q-N` / `_Y-N` (self-referencing lag is allowed, e.g. `Tariff = Tariff_Y-1 * (1 + Inflation)`); for BOP/EOP prefer a `balance` with `opening_balance` and `children` for the period flows. Full roll-forward cookbook (debt, DSRA, rolling min DSCR, balance-list) in docs/mdk-format.md. List mode (per-element series via the lists registry): declare named lists in `lists: [{display_name:\"Functions\", items:[{label:\"Engineering\"},{label:\"Sales\"},{label:\"G&A\"}]}]`, then bind a consumer item via `liste_ref:\"Functions\"` and provide `timeline_values: {Engineering:[1200000,1500000], Sales:[800000,950000], \"G&A\":[400000,420000]}` keyed by **element label** (resolved to UIDs at compile time). Works for `assumption`, `formula`, and `balance`. Two-dimension breakdowns compose with `mapped_to` on the list element: `lists:[{display_name:\"Products\", items:[{label:\"Pro\"},{label:\"Enterprise\"}]}, {display_name:\"Channels\", items:[{label:\"Direct\", mapped_to:{Products:[\"Pro\",\"Enterprise\"]}}, {label:\"Partner\"}, {label:\"Self-serve\"}]}]` says channel Direct rolls up products Pro and Enterprise, enabling Revenue × Products × Channels cross-aggregation. Balance-list rule: a `balance` with `liste_ref` requires every list-typed child to share the same `liste_ref`; scalar children broadcast. Set `dry_run: true` to validate without mutating; errors come back with a path you can fix. Full schema: src/core/blueprint-schema.ts."New value: +"Strict Blueprint object: { project?, timelines?: { monthly? | quarterly? | yearly?: { start, end } }, lists?: [{ display_name, items: [{ label, mapped_to?: Record<listName, labels[]> }] }], sections: [{ name, items: [{ role, name, timeline_ref?, value?, values?, formula?, item_style?, opening_balance?, liste_ref?, timeline_values?: Record<elementLabel, (number|null)[]>, children?, … }] }], dashboards?: [{ name, items: [{ role: chart | kpi | mini_table, … }] }] }. At least one section is required. Items reference each other by name (formula inputs, callup source, chart series); a callup omits `name` and inherits its source's. A list-mode item binds a declared list through `liste_ref` and carries `timeline_values` keyed by element label; a `balance` with `liste_ref` requires every list-typed child to share it. Accepted as a JSON object or a JSON-encoded string." - changed
Input schema / properties / dry_run / descriptionPrevious value: -"Validate the blueprint and report errors without writing anything."New value: +"Validates the blueprint and reports errors without writing." - changed
Input schema / properties / mode / descriptionPrevious value: -"'merge' (default): add to the existing model, reusing items by display_name; a re-declared item keeps every presentation/annotation field the blueprint omits (style, format, note, check, placement). 'replace': rebuild the WHOLE model (structure, timelines, values, branches) from the blueprint — only the style colours, custom formats, locale, glyph, subtitle and FINANCE.md are kept."New value: +"'merge' (default): adds to the existing model, reusing items by name. 'replace': rebuilds the whole model from the blueprint." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / summary / descriptionPrevious value: -"Required: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\")."New value: +"Short note (3–100 chars) describing the change, stored as the label of the resulting version. Examples: \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\"." - changed
Input schema / properties / template_id / descriptionPrevious value: -"Apply a stored template as a module instead of an inline blueprint (mutually exclusive with `blueprint`). Discover ids via layerz_list_templates."New value: +"Stored template to apply as a module (mutually exclusive with `blueprint`)."
- Changed
layerz_create_upload_url1 field changed- changed
Input schema / properties / content_type / descriptionPrevious value: -"MIME type, e.g. \"text/csv\" or the xlsx spreadsheet content type."New value: +"MIME type, e.g. \"text/csv\" or the xlsx content type."
- Changed
layerz_delete_branch2 fields changed- changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / summary / descriptionPrevious value: -"Required: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\")."New value: +"Short note (3–100 chars) describing the change, stored as the label of the resulting version. Examples: \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\"."
- Removed
layerz_delete_integration - Removed
layerz_delete_mapping - Changed
layerz_dependencies1 field changed- changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys."
- Changed
layerz_diff7 fields changed- changed
Input schema / properties / from / descriptionPrevious value: -"Older endpoint. Exactly one of revision_id / version_number / id."New value: +"Older endpoint: exactly one of revision_id / version_number / id." - changed
Input schema / properties / from / properties / id / descriptionPrevious value: -"Raw model_versions row UUID (rare)."New value: +"Raw model_versions row UUID." - changed
Input schema / properties / from / properties / revision_id / descriptionPrevious value: -"Content fingerprint (sha256:hex). Preferred — stable across DB renames."New value: +"Content fingerprint (sha256:hex), stable across DB renames." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / to / descriptionPrevious value: -"Newer endpoint. Omit (or set to null) to diff against the current live model."New value: +"Newer endpoint; omitted = the current live model." - changed
Input schema / properties / to / properties / id / descriptionPrevious value: -"Raw model_versions row UUID (rare)."New value: +"Raw model_versions row UUID." - changed
Input schema / properties / to / properties / revision_id / descriptionPrevious value: -"Content fingerprint (sha256:hex). Preferred — stable across DB renames."New value: +"Content fingerprint (sha256:hex), stable across DB renames."
- Changed
layerz_export2 fields changed- changed
Input schema / properties / branch_id / descriptionPrevious value: -"Branch to export for the statements/single-value widgets. Defaults to the base branch. Dashboards keep every branch regardless. Discover ids via layerz_list_branches."New value: +"Branch the statements and single-value widgets follow (default: the base branch). Dashboards keep every branch." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys."
- Changed
layerz_external_ref11 fields changed- changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / name / descriptionPrevious value: -"`link` only: display name (defaults to the source item's name)."New value: +"`link`: display name (default: the source item's name)." - changed
Input schema / properties / op / descriptionPrevious value: -"link (create/retarget a ref) | refresh (re-sync snapshot values)."New value: +"link | refresh | unlink." - changed
Input schema / properties / op / enumPrevious value: -[ - "link", - "refresh" -]New value: +[ + "link", + "refresh", + "unlink" +] - changed
Input schema / properties / parent / descriptionPrevious value: -"`link` create only: parent container uid (section/formula/balance/dashboard)."New value: +"`link` (create): parent container uid (section / formula / balance / dashboard)." - changed
Input schema / properties / source_model_id / descriptionPrevious value: -"`link` only: the model to read from (id from layerz_list_models)."New value: +"`link`: the model read from." - changed
Input schema / properties / source_uid / descriptionPrevious value: -"`link` only: uid of the source item in that model."New value: +"`link`: uid of the source item in that model." - changed
Input schema / properties / summary / descriptionPrevious value: -"Required: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\")."New value: +"Short note (3–100 chars) describing the change, stored as the label of the resulting version. Examples: \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\"." - changed
Input schema / properties / timeline_ref / descriptionPrevious value: -"`link` only: grain of the linked line (default: the source's grain when active here, else the finest active grain)."New value: +"`link`: grain of the linked line (default: the source's grain when active here, else the finest active grain)." - changed
Input schema / properties / uid / descriptionPrevious value: -"`link` only: retarget this existing non-list assumption instead of creating one."New value: +"`link`: existing non-list assumption to retarget. `unlink`: the linked line to detach." - changed
Input schema / properties / uids / descriptionPrevious value: -"`refresh` only: restrict to these linked items (default: all)."New value: +"`refresh`: restricts the refresh to these linked items (default: all)."
- Removed
layerz_external_ref_status - Added
layerz_get_conventions - Removed
layerz_get_custom_instructions - Removed
layerz_get_finance_md - Changed
layerz_get_model1 field changed- changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys."
- Removed
layerz_get_template - Changed
layerz_history4 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Max number of versions to return (default 100, clamped to plan retention)."New value: +"Max versions returned (default 100; with `uid`, max versions scanned, default 50)." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / since / descriptionPrevious value: -"ISO timestamp lower bound — only versions created at or after this time."New value: +"ISO timestamp lower bound on the version list (ignored with `uid`)." - added
Input schema / properties / uidAdded value: +{ + "description": "Item UID: returns that item's change events instead of the version list.", + "maxLength": 64, + "minLength": 1, + "type": "string" +}
- Removed
layerz_history_for_item - Changed
layerz_import_branch3 fields changed- changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / structure_override / properties / header_row_index / descriptionPrevious value: -"0-based header row. Omitted: re-detected for the requested `layout` (a columnar override never inherits the wide-layout period-row guess); the row used is reported back (`file_structure` on layerz_import_branch, `suggested_structure` on layerz_parse_file)."New value: +"0-based header row. Omitted: re-detected for the requested `layout` (a columnar override is re-detected rather than inheriting the wide-layout period-row guess); the row used is reported back (`file_structure` on layerz_import_branch, `suggested_structure` on layerz_parse_file)." - changed
Input schema / properties / summary / descriptionPrevious value: -"Required: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\")."New value: +"Short note (3–100 chars) describing the change, stored as the label of the resulting version. Examples: \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\"."
- Removed
layerz_import_mapping_template - Removed
layerz_list_branches - Changed
layerz_list_integrations2 fields changed- added
Input schema / properties / include_external_refsAdded value: +{ + "description": "Also return the external references (links to other models) with their staleness.", + "type": "boolean" +} - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys."
- Changed
layerz_list_mappings3 fields changed- changed
Input schema / properties / include_coverage / descriptionPrevious value: -"Also compute, per rule, `match_count` (staged rows it captures) and a `coverage` block (auto-created count + top unrouted keys). Off by default — it classifies every source, so it is a heavier read."New value: +"Also compute `match_count` per rule and a `coverage` block (classifies every source: heavier read)." - changed
Input schema / properties / include_user_mappings / descriptionPrevious value: -"Also surface your account-wide (model-independent) user catalogs in `sets`/`mappings` for discovery. Default false: only what this model actually classifies with (system + this-model rules)."New value: +"Also list the account-wide user catalogs in `sets` / `mappings` (default: only what the model classifies with)." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys."
- Changed
layerz_list_models2 fields changed- changed
Input schema / properties / q / descriptionPrevious value: -"Optional name search filter. Substring, case-insensitive (e.g. \"swoop\" matches \"SWOOP - BP 2026\"). Use \"*\" as a wildcard; \"%\", \"_\" and \"&\" are literal."New value: +"Name filter: case-insensitive substring (\"swoop\" matches \"SWOOP - BP 2026\"); `*` is a wildcard, `%`, `_` and `&` are literal." - changed
Input schema / properties / writable / descriptionPrevious value: -"When true, returns only models the caller can edit (owner or editor). Omit to return all accessible models."New value: +"When true, only models the caller can edit (owner or editor)."
- Removed
layerz_list_shares - Changed
layerz_list_templates2 fields changed- changed
Input schema / properties / scope / descriptionPrevious value: -"Filter by scope: \"system\" (curated) or \"user\" (your own)."New value: +"Scope filter: \"system\" (curated) or \"user\" (own templates)." - added
Input schema / properties / template_idAdded value: +{ + "description": "One template in full (blueprint + usage guide) instead of the list.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" +}
- Changed
layerz_list_transactions5 fields changed- changed
Input schema / properties / connection_id / descriptionPrevious value: -"Connection id from layerz_list_integrations."New value: +"Source id as returned by layerz_list_integrations." - changed
Input schema / properties / format / descriptionPrevious value: -"Default json (annotated rows inline). `csv`: export every staged row of the source as a CSV file and return a signed `download_url` (same file as the web Sources panel download)."New value: +"json (default): annotated rows inline. csv: a signed `download_url` of every staged row as a CSV file." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / period / descriptionPrevious value: -"Keep only this YYYY-MM period."New value: +"Keeps only this YYYY-MM period." - changed
Input schema / properties / q / descriptionPrevious value: -"Keep rows whose description/category contains this text."New value: +"Keeps rows whose description/category contains this text."
- Changed
layerz_manage_integration22 fields changed- changed
Input schema / properties / branch_ids / descriptionPrevious value: -"`rebind` only: the existing branches this connector should feed (e.g. [\"default\"])."New value: +"`rebind` only: the existing branches the source feeds afterwards (e.g. [\"default\"])." - changed
Input schema / properties / config / properties / airtable / descriptionPrevious value: -"Airtable only: base/table (and optional view) to pull records from."New value: +"Airtable: base / table (and optional view) the records come from." - changed
Input schema / properties / config / properties / classify_by / descriptionPrevious value: -"Which staged field the rules key on: account_number | description (label) | category. Re-projects from the staged rows."New value: +"Staged field the mapping rules key on: account_number | description (label) | category. Re-projects from the staged rows." - changed
Input schema / properties / config / properties / field_map / descriptionPrevious value: -"Tabular sources: source column → canonical field (txn_date, amount, category, description, account_number, period). A value is a column name, a 0-based index, or an ARRAY of columns (composite key, cells joined with \" / \" — e.g. category: [\"Sens\",\"Catégorie\"] routes \"Charge / Hébergement\" and \"Produit / Hébergement\" separately). `amount` and `txn_date` are single-column (a list is rejected — use `measures` for several value columns). Changing it re-fetches + re-stages (tabular sources only — rejected on a csv/fec file source, whose columns are fixed at import: re-import the file instead)."New value: +"Tabular sources: source column → canonical field (txn_date, amount, category, description, account_number, period). A value is a column name, a 0-based index, or an array of columns joined with \" / \" as a composite key (category: [\"Sens\", \"Catégorie\"] routes \"Charge / Hébergement\" and \"Produit / Hébergement\" separately). `amount` and `txn_date` are single-column (`measures` covers several value columns). A change re-fetches and re-stages; rejected on a csv/fec file source, whose columns are fixed at import." - changed
Input schema / properties / config / properties / import_level / descriptionPrevious value: -"Pennylane only: which resource to read — trial_balance (per-account monthly balances, default; account source), ledger (individual ledger entry lines keyed by PCG account; account source), or transactions (bank transactions keyed by label; label source). Changing it RE-FETCHES from Pennylane and re-stages (external ids differ per level), unlike the other config fields which only re-project."New value: +"Pennylane: resource read — trial_balance (per-account monthly balances, default), ledger (ledger entry lines keyed by PCG account) or transactions (bank transactions keyed by label). A change re-fetches and re-stages (external ids differ per level)." - changed
Input schema / properties / config / properties / label_field / descriptionPrevious value: -"Qonto field to group by: counterparty | label | category."New value: +"Qonto: field the rows are grouped by (counterparty | label | category)." - changed
Input schema / properties / config / properties / measures / descriptionPrevious value: -"Tabular sources: N measures per connection for object tables (one row = one object, measures in columns). Each measure stages one row per source row — amount from its column, or 1 when `count: true` — classified under its `key`, so ordinary mapping rules route each measure to its own item (e.g. [{column:\"Jours\"},{column:\"Participants\"},{count:true,key:\"Séjours réalisés\"}] feeds three items from ONE connection, no helper columns in the source). Mutually exclusive with field_map.amount; `classify_by` must be \"category\" or unset; `field_map` still carries txn_date/period. Changing it re-fetches + re-stages (tabular sources only)."New value: +"Tabular sources: several measures per connection for object tables (one row = one object, measures in columns). Each measure stages one row per source row (its column value, or 1 with `count: true`) classified under its `key`, so ordinary mapping rules route each measure to its own item. Mutually exclusive with `field_map.amount`; `classify_by` is \"category\" or unset. A change re-fetches and re-stages (tabular sources only)." - changed
Input schema / properties / config / properties / measures / items / properties / column / descriptionPrevious value: -"Value column (header name or 0-based index). Omit for count: true."New value: +"Value column (header name or 0-based index); absent with `count: true`." - changed
Input schema / properties / config / properties / measures / items / properties / count / descriptionPrevious value: -"Read the literal 1 per source row (native row counter)."New value: +"Stages the literal 1 per source row (row counter)." - changed
Input schema / properties / config / properties / measures / items / properties / key / descriptionPrevious value: -"Classification key the mapping rules route on. Defaults to the column header; required on a count measure."New value: +"Classification key the mapping rules route on; defaults to the column header, required on a count measure." - changed
Input schema / properties / config / properties / metabase / descriptionPrevious value: -"Metabase only: the saved question whose results feed the source."New value: +"Metabase: the saved question whose results feed the source." - changed
Input schema / properties / config / properties / sign_rule / descriptionPrevious value: -"Tabular sources: negate `amount` when `field` equals one of `negate_values` (e.g. { field: \"Sens\", negate_values: [\"Charge\"] }). Changing it re-fetches + re-stages (tabular sources only)."New value: +"Tabular sources: negates `amount` when `field` equals one of `negate_values` ({ field: \"Sens\", negate_values: [\"Charge\"] }). A change re-fetches and re-stages (tabular sources only)." - changed
Input schema / properties / config / properties / window / descriptionPrevious value: -"Confirm the source's authoritative window (months YYYY-MM, inclusive). It BOUNDS the projection: staged rows whose period falls outside [start, end] produce no input in the branch (skipped as `outside confirmed window` — e.g. the in-progress month past the last closed one), inside it every line the source owns (an item a rule targets) with no transaction is forced to 0, and the window `end` becomes the real/forecast cutover (`actuals_through` — the branch keeps the LATEST window end across its sources; any rewrite is reported in `cutover_changes` + `warnings`). Tightening the window then `update`/`sync` also purges previously projected out-of-window inputs (the re-projection replaces this source's whole contribution). A `sync`/`dry_run` returns `proposed_window` to confirm here. Requires full mapping in-window (unmapped keys are surfaced but do not block)."New value: +"The source's authoritative window (months YYYY-MM, inclusive). Staged rows outside [start, end] produce no input (skipped as `outside confirmed window`); inside it, every line the source owns with no transaction reads an explicit 0, and `end` becomes the branch's real/forecast cutover (`actuals_through`, latest window end across its sources; rewrites are reported in `cutover_changes`). Tightening the window purges previously projected out-of-window inputs on the next projection. A `sync` returns `proposed_window` (the staged min/max month)." - added
Input schema / properties / confirm_purgeAdded value: +{ + "description": "`delete` only: confirms the purge after a `PURGE_CONFIRMATION_REQUIRED` refusal.", + "type": "boolean" +} - changed
Input schema / properties / connection_id / descriptionPrevious value: -"Connection id from layerz_list_integrations."New value: +"Source id as returned by layerz_list_integrations." - changed
Input schema / properties / display_name / descriptionPrevious value: -"`update` only: new display label for the source (Sources panel + layerz_list_integrations). Pure metadata — no reprojection."New value: +"`update` only: new label of the source (metadata, no reprojection)." - changed
Input schema / properties / dry_run / descriptionPrevious value: -"`sync` only: preview the impact without persisting."New value: +"`sync` only: reports the impact without persisting." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / op / descriptionPrevious value: -"Which mutation to run on the connector."New value: +"Operation to run on the source." - changed
Input schema / properties / op / enumPrevious value: -[ - "sync", - "update", - "rebind" -]New value: +[ + "sync", + "update", + "rebind", + "delete" +] - changed
Input schema / properties / summary / descriptionPrevious value: -"Required: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\")."New value: +"Short note (3–100 chars) describing the change, stored as the label of the resulting version. Examples: \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\"." - changed
Input schema / properties / target_branch_id / descriptionPrevious value: -"`sync` only: existing branch id to feed for a still-unbound connection (e.g. `default`). Ignored once the connection is bound."New value: +"`sync` only: existing branch a still-unbound connection feeds (e.g. `default`). Ignored once bound."
- Added
layerz_manage_mapping - Added
layerz_manage_version - Changed
layerz_match_items2 fields changed- changed
Input schema / properties / labels / descriptionPrevious value: -"Free-text labels to resolve (e.g. row labels from an Excel sheet)."New value: +"Free-text labels to resolve." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys."
- Changed
layerz_parse_file3 fields changed- changed
Input schema / properties / file_id / descriptionPrevious value: -"Upload identifier returned by layerz_create_upload_url (format: 'f_' + 16 hex chars)."New value: +"Upload id as returned by layerz_create_upload_url ('f_' + 16 hex chars)." - changed
Input schema / properties / sheet_name / descriptionPrevious value: -"Target sheet name for multi-sheet workbooks. Omit to get sheet summaries."New value: +"Sheet of a multi-sheet workbook. Omitted: sheet summaries only." - changed
Input schema / properties / structure_override / properties / header_row_index / descriptionPrevious value: -"0-based header row. Omitted: re-detected for the requested `layout` (a columnar override never inherits the wide-layout period-row guess); the row used is reported back (`file_structure` on layerz_import_branch, `suggested_structure` on layerz_parse_file)."New value: +"0-based header row. Omitted: re-detected for the requested `layout` (a columnar override is re-detected rather than inheriting the wide-layout period-row guess); the row used is reported back (`file_structure` on layerz_import_branch, `suggested_structure` on layerz_parse_file)."
- Changed
layerz_patch6 fields changed- added
Input schema / properties / branch_idAdded value: +{ + "description": "Branch that receives the value writes of this batch (`update` / `replace` `value`, `values`, `timeline_values` on assumptions): only the cells that differ from the base are stored as that branch's overlay, the same record a value edit makes in the web editor with the branch checked out; no data source is created. Absent or `default`: the base plan. Structural ops and non-value fields apply to the model as a whole; a `create` carrying values under a branch is rejected. An unknown id is rejected.", + "minLength": 1, + "type": "string" +} - changed
Input schema / properties / compute_branch / descriptionPrevious value: -"Return the `compute` values under a single branch's `[base, branch]` checkout instead of the stacked-all view (mirror of `layerz_read` `branch_id`). Pass `default` to audit the base plan alone — e.g. verify a base correction without a live import layer polluting the result. Requires `compute`. Does not change where the write lands (always the base)."New value: +"Branch whose `[base, branch]` checkout the `compute` values are returned under, instead of the stacked-all view (mirror of `layerz_read` `branch_id`); `default` = the base plan alone. Defaults to `branch_id` when that is set. Requires `compute`." - changed
Input schema / properties / dry_run / descriptionPrevious value: -"Validate and compute the batch without persisting it"New value: +"Validates and computes the batch without persisting it." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / ops / items / oneOfPrevious value: -[ - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "by_branch": { - "description": "Chart only: one styled series per branch; false reverts to the checked-out view.", - "type": "boolean" - }, - "cagr": { - "description": "mini_table (axis=time): show the trailing CAGR column. Default true", - "type": "boolean" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "anyOf": [ - { - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Flag the line as a control, judged on its OWN value: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. Pass `null` to remove the check. Failing periods surface in `layerz_read` under `health` — non-blocking." - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (null clears it)." - }, - "compare_to": { - "anyOf": [ - { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "KPI native comparison; null clears it." - }, - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "anyOf": [ - { - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." - }, - "name": { - "description": "New display label (also the formula identifier)", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "update", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "New parent UID (null to move to root)" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Change the item role. Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "description": "Chart series UIDs", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch; supersedes `series`.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "description": "Existing item UID to update", - "minLength": 1, - "type": "string" - }, - "unit": { - "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are never divided", - "enum": [ - "none", - "k", - "m", - "auto" - ], - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "by_branch": { - "description": "Chart only: plot one styled series per branch instead of the checked-out view.", - "type": "boolean" - }, - "cagr": { - "description": "mini_table (axis=time): show the trailing CAGR column. Default true", - "type": "boolean" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "create", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", - "minLength": 1, - "type": "string" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "unit": { - "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are never divided", - "enum": [ - "none", - "k", - "m", - "auto" - ], - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name", - "op" - ], - "type": "object" - }, - { - "properties": { - "from_uid": { - "description": "Existing item UID to replace atomically (section/dashboard unsupported)", - "minLength": 1, - "type": "string" - }, - "new_item": { - "description": "Inline payload describing the replacement item", - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "by_branch": { - "description": "Chart only: plot one styled series per branch instead of the checked-out view.", - "type": "boolean" - }, - "cagr": { - "description": "mini_table (axis=time): show the trailing CAGR column. Default true", - "type": "boolean" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "unit": { - "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are never divided", - "enum": [ - "none", - "k", - "m", - "auto" - ], - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name" - ], - "type": "object" - }, - "op": { - "const": "replace", - "type": "string" - }, - "preserve_order": { - "type": "boolean" - }, - "preserve_parent": { - "type": "boolean" - }, - "preserve_presentation": { - "description": "Default true: every presentation / annotation field `new_item` omits (item_style, format, comment, check, span, position, period_window, compare_to, sparkline, series_defs, …) is carried over from the replaced item when the new role can hold it. Pass false to rebuild the item from `new_item` alone.", - "type": "boolean" - }, - "preserve_style": { - "type": "boolean" - } - }, - "required": [ - "op", - "from_uid", - "new_item" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "delete", - "type": "string" - }, - "uid": { - "description": "UID of item to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "after": { - "description": "UID of item to place after (for ordering)", - "minLength": 1, - "type": "string" - }, - "op": { - "const": "move", - "type": "string" - }, - "parent": { - "description": "New parent UID", - "minLength": 1, - "type": "string" - }, - "uid": { - "description": "UID of item to move", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_create", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "properties": { - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_update", - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "list_delete", - "type": "string" - }, - "uid": { - "description": "UID of list to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "finance_md": { - "anyOf": [ - { - "maxLength": 5000, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "formats": { - "additionalProperties": { - "properties": { - "display_name": { - "maxLength": 80, - "type": "string" - }, - "excel_format": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "name": { - "maxLength": 50, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "name", - "excel_format" - ], - "type": "object" - }, - "propertyNames": { - "maxLength": 50, - "minLength": 1, - "type": "string" - }, - "type": "object" - }, - "glyph": { - "anyOf": [ - { - "maxLength": 60, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "number_locale": { - "anyOf": [ - { - "enum": [ - "en", - "fr", - "de" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "op": { - "const": "meta", - "type": "string" - }, - "subtitle": { - "anyOf": [ - { - "maxLength": 160, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "timelines": { - "properties": { - "monthly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "quarterly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "yearly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - } - }, - "type": "object" - } - }, - "required": [ - "op" - ], - "type": "object" - } -]New value: +[ + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: one styled series per branch; false reverts to the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "anyOf": [ + { + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Flags the line as a control judged on its own value: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. `null` removes the check. Failing periods surface in `layerz_read` under `health`, non-blocking." + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (null clears it)." + }, + "compare_to": { + "anyOf": [ + { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "KPI native comparison; null clears it." + }, + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "anyOf": [ + { + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." + }, + "name": { + "description": "New display label (also the formula identifier)", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "update", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "New parent UID (null to move to root)" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Change the item role. Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "description": "Chart series UIDs", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch; supersedes `series`.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "description": "Existing item UID to update", + "minLength": 1, + "type": "string" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: plot one styled series per branch instead of the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "create", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", + "minLength": 1, + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name", + "op" + ], + "type": "object" + }, + { + "properties": { + "from_uid": { + "description": "Existing item UID to replace atomically (section/dashboard unsupported)", + "minLength": 1, + "type": "string" + }, + "new_item": { + "description": "Inline payload describing the replacement item", + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: plot one styled series per branch instead of the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flags the line as a control judged on its own value at every period: a plain amount passes when within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals, so a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (\"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health`, non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is rejected with code UNKNOWN_FORMAT listing what is available (`percent` is the current name of the legacy `\"%\"`). Full catalog with each excel_format and rendered positive/negative/zero examples (`default` = dash-for-zero accounting style, `integer` = plain 0): MCP resource `format-spec://reference`.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as one series (a list total charted without a mirror formula).", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. `null` clears it (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item. assumption: a typed input (value / values / timeline_values), the only role a branch overlays. formula: a computed transformation (formula). formula with no expression and children: an aggregator that sums its children (a subtotal value; a nested section is structural grouping with no value). callup: a typed mirror of one source whose value flows into its parent (source): balance flows, a value surfaced in a section or subtotal; a formula references its source directly instead. balance: a rolling stock (opening_balance) that sums its callup-child flows. section: structural container, no value. dashboard / chart / kpi / mini_table: presentational. A formula carrying both an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry, keyed by the element UID, label, or a `list_create` temp id of the same batch (a temp id is scoped to the batch that declares it; later batches key by label or UID). Non-list item: a sparse per-period patch keyed by period id (`{\"2026-06\": -119.19}`, keys YYYY / YYYY-MM / YYYY-Qn) that rewrites the named cells of the typed `values` series and leaves the rest intact; a full series keyed by a canonical grain (`monthly` / `quarterly` / `yearly` / `constant`) is import-owned and rejected here. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are not divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name" + ], + "type": "object" + }, + "op": { + "const": "replace", + "type": "string" + }, + "preserve_order": { + "type": "boolean" + }, + "preserve_parent": { + "type": "boolean" + }, + "preserve_presentation": { + "description": "Default true: every presentation / annotation field `new_item` omits (item_style, format, comment, check, span, position, period_window, compare_to, sparkline, series_defs, …) is carried over from the replaced item when the new role can hold it; `false` rebuilds the item from `new_item` alone.", + "type": "boolean" + }, + "preserve_style": { + "type": "boolean" + } + }, + "required": [ + "op", + "from_uid", + "new_item" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "delete", + "type": "string" + }, + "uid": { + "description": "UID of item to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "after": { + "description": "UID of item to place after (for ordering)", + "minLength": 1, + "type": "string" + }, + "op": { + "const": "move", + "type": "string" + }, + "parent": { + "description": "New parent UID", + "minLength": 1, + "type": "string" + }, + "uid": { + "description": "UID of item to move", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_create", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "properties": { + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_update", + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "list_delete", + "type": "string" + }, + "uid": { + "description": "UID of list to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "finance_md": { + "anyOf": [ + { + "maxLength": 5000, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "formats": { + "additionalProperties": { + "properties": { + "display_name": { + "maxLength": 80, + "type": "string" + }, + "excel_format": { + "maxLength": 120, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 50, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "excel_format" + ], + "type": "object" + }, + "propertyNames": { + "maxLength": 50, + "minLength": 1, + "type": "string" + }, + "type": "object" + }, + "glyph": { + "anyOf": [ + { + "maxLength": 60, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." + }, + "name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "number_locale": { + "anyOf": [ + { + "enum": [ + "en", + "fr", + "de" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "op": { + "const": "meta", + "type": "string" + }, + "subtitle": { + "anyOf": [ + { + "maxLength": 160, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "timelines": { + "properties": { + "monthly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "quarterly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "yearly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + } + }, + "required": [ + "op" + ], + "type": "object" + } +] - changed
Input schema / properties / summary / descriptionPrevious value: -"Required: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\")."New value: +"Short note (3–100 chars) describing the change, stored as the label of the resulting version. Examples: \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\"."
- Removed
layerz_pin_version - Changed
layerz_promote_template5 fields changed- changed
Input schema / properties / description / descriptionPrevious value: -"Optional usage guide (FINANCE.md). Leaves the model’s own when omitted."New value: +"Usage guide (FINANCE.md). The model keeps its own when omitted." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / name / descriptionPrevious value: -"Template title — renames the model in place when it differs."New value: +"Template title; renames the model in place when it differs." - changed
Input schema / properties / scope / descriptionPrevious value: -"Template scope. `user` (default) = private, owner-only, Pro-gated. `system` = curated public catalog, admin-only."New value: +"`user` (default): private, owner-only, Pro-gated. `system`: curated public catalog, admin-only." - changed
Input schema / properties / summary / descriptionPrevious value: -"Required: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\")."New value: +"Short note (3–100 chars) describing the change, stored as the label of the resulting version. Examples: \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\"."
- Changed
layerz_read3 fields changed- changed
Input schema / properties / branch_id / descriptionPrevious value: -"Compute values under a single branch's `[base, branch]` checkout instead of the stacked-all view — lets you audit base-vs-overlay (e.g. confirm the budget on the base, or what one branch changed). Pass `default` for the base plan alone. Requires with_values."New value: +"Branch whose `[base, branch]` checkout the values are computed under, instead of the stacked-all view (base-vs-overlay audit); `default` = the base plan alone. Requires with_values." - changed
Input schema / properties / health / descriptionPrevious value: -"Also return the `health` block: non-blocking check violations (lines flagged `check`). Checks are evaluated at the finest active grain. `with_values: true` already includes `health` when the model has checks; set this to get `health` without the full value series."New value: +"Also returns the `health` block: non-blocking check violations (lines flagged `check`), evaluated at the finest active grain. `with_values: true` already includes `health` when the model has checks; this flag returns `health` without the full value series." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys."
- Removed
layerz_restore_version - Removed
layerz_revoke_share - Changed
layerz_set_custom_instructions1 field changed- changed
Input schema / properties / content / descriptionPrevious value: -"Account-wide custom instructions (plain Markdown/text). Max 3000 chars. Empty string clears them."New value: +"Account-wide custom instructions, max 3000 chars. Empty string clears them."
- Changed
layerz_set_finance_md4 fields changed- changed
Input schema / properties / content / descriptionPrevious value: -"Full FINANCE.md Markdown (YAML front-matter + body). Max 5,000 chars — keep conventions in prose."New value: +"Full FINANCE.md Markdown (YAML front matter + body), max 5,000 chars." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - changed
Input schema / properties / source / descriptionPrevious value: -"Provenance: 'imported' when syncing from a project-root FINANCE.md, 'user' when editing in place. Defaults to 'user'."New value: +"Provenance: 'imported' (synced from a project-root FINANCE.md) or 'user' (default)." - changed
Input schema / properties / summary / descriptionPrevious value: -"Required: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\")."New value: +"Short note (3–100 chars) describing the change, stored as the label of the resulting version. Examples: \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\"."
- Removed
layerz_set_mapping - Changed
layerz_share_model6 fields changed- changed
Input schema / properties / email / descriptionPrevious value: -"The email address to invite."New value: +"`invite`: the email address to invite." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - added
Input schema / properties / opAdded value: +{ + "default": "invite", + "description": "invite (default) | revoke.", + "enum": [ + "invite", + "revoke" + ], + "type": "string" +} - added
Input schema / properties / principal_idAdded value: +{ + "description": "`revoke`: collaborator user id or pending-invite id from `sharing.members`.", + "minLength": 1, + "type": "string" +} - changed
Input schema / properties / role / descriptionPrevious value: -"viewer (read-only) or editor (can edit). Defaults to viewer."New value: +"`invite`: viewer (read-only, default) or editor." - removed
Input schema / requiredRemoved value: -[ - "email" -]
- Removed
layerz_unlink_external_ref - Changed
layerz_update_branch5 fields changed- added
Input schema / properties / dataset_id / descriptionAdded value: +"Branch to update. Absent: the branch named `patch.display_name` is updated when it exists, created otherwise." - changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys." - added
Input schema / properties / patch / properties / actuals_through / descriptionAdded value: +"Real/forecast cutover (YYYY-MM). A value pins it against source syncs; `null` resets it to auto." - changed
Input schema / properties / summary / descriptionPrevious value: -"Required: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\")."New value: +"Short note (3–100 chars) describing the change, stored as the label of the resulting version. Examples: \"Renamed Revenue to Net Revenue\", \"Imported 2024 OPEX from Excel\"." - changed
Input schema / requiredPrevious value: -[ - "summary", - "dataset_id", - "patch" -]New value: +[ + "summary", + "patch" +]
- Changed
layerz_validate_model1 field changed- changed
Input schema / properties / model_id / descriptionPrevious value: -"Target model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys."New value: +"Target model UUID. Required for user-scoped API keys; validated against the bound model for model-scoped keys."
4 tool updates
- Changed
layerz_import_branch1 field changed- added
Input schema / properties / structure_override / properties / header_row_index / descriptionAdded value: +"0-based header row. Omitted: re-detected for the requested `layout` (a columnar override never inherits the wide-layout period-row guess); the row used is reported back (`file_structure` on layerz_import_branch, `suggested_structure` on layerz_parse_file)."
- Changed
layerz_manage_integration3 fields changed- changed
Input schema / properties / config / properties / field_map / descriptionPrevious value: -"Tabular sources: source column → canonical field (txn_date, amount, category, description, account_number, period). A value is a column name, a 0-based index, or an ARRAY of columns (composite key, cells joined with \" / \" — e.g. category: [\"Sens\",\"Catégorie\"] routes \"Charge / Hébergement\" and \"Produit / Hébergement\" separately). `amount` and `txn_date` are single-column (a list is rejected — use `measures` for several value columns). Changing it re-fetches + re-stages."New value: +"Tabular sources: source column → canonical field (txn_date, amount, category, description, account_number, period). A value is a column name, a 0-based index, or an ARRAY of columns (composite key, cells joined with \" / \" — e.g. category: [\"Sens\",\"Catégorie\"] routes \"Charge / Hébergement\" and \"Produit / Hébergement\" separately). `amount` and `txn_date` are single-column (a list is rejected — use `measures` for several value columns). Changing it re-fetches + re-stages (tabular sources only — rejected on a csv/fec file source, whose columns are fixed at import: re-import the file instead)." - changed
Input schema / properties / config / properties / measures / descriptionPrevious value: -"Tabular sources: N measures per connection for object tables (one row = one object, measures in columns). Each measure stages one row per source row — amount from its column, or 1 when `count: true` — classified under its `key`, so ordinary mapping rules route each measure to its own item (e.g. [{column:\"Jours\"},{column:\"Participants\"},{count:true,key:\"Séjours réalisés\"}] feeds three items from ONE connection, no helper columns in the source). Mutually exclusive with field_map.amount; `classify_by` must be \"category\" or unset; `field_map` still carries txn_date/period. Changing it re-fetches + re-stages."New value: +"Tabular sources: N measures per connection for object tables (one row = one object, measures in columns). Each measure stages one row per source row — amount from its column, or 1 when `count: true` — classified under its `key`, so ordinary mapping rules route each measure to its own item (e.g. [{column:\"Jours\"},{column:\"Participants\"},{count:true,key:\"Séjours réalisés\"}] feeds three items from ONE connection, no helper columns in the source). Mutually exclusive with field_map.amount; `classify_by` must be \"category\" or unset; `field_map` still carries txn_date/period. Changing it re-fetches + re-stages (tabular sources only)." - changed
Input schema / properties / config / properties / sign_rule / descriptionPrevious value: -"Tabular sources: negate `amount` when `field` equals one of `negate_values` (e.g. { field: \"Sens\", negate_values: [\"Charge\"] }). Changing it re-fetches + re-stages."New value: +"Tabular sources: negate `amount` when `field` equals one of `negate_values` (e.g. { field: \"Sens\", negate_values: [\"Charge\"] }). Changing it re-fetches + re-stages (tabular sources only)."
- Changed
layerz_parse_file1 field changed- added
Input schema / properties / structure_override / properties / header_row_index / descriptionAdded value: +"0-based header row. Omitted: re-detected for the requested `layout` (a columnar override never inherits the wide-layout period-row guess); the row used is reported back (`file_structure` on layerz_import_branch, `suggested_structure` on layerz_parse_file)."
- Changed
layerz_set_mapping3 fields changed- changed
Input schema / properties / pattern / descriptionPrevious value: -"Wildcard-prefix or exact pattern, e.g. \"Apport*\"."New value: +"Primary key glob (`*` anywhere) or exact value, e.g. \"Apport*\"." - added
Input schema / properties / secondary_patternAdded value: +{ + "description": "Secondary key glob tested against the source's `dimension_by` field (default `*` = any). Omitted = single-key rule.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / source_idAdded value: +{ + "description": "Scope the rule to ONE data source of this model (id from layerz_list_integrations). Omitted = model-wide.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" +}
2 tool updates
- Changed
layerz_build_from_blueprint1 field changed- changed
Input schema / properties / mode / descriptionPrevious value: -"'merge' (default): add to the existing model, reusing items by display_name. 'replace': wipe non-default sections first."New value: +"'merge' (default): add to the existing model, reusing items by display_name; a re-declared item keeps every presentation/annotation field the blueprint omits (style, format, note, check, placement). 'replace': rebuild the WHOLE model (structure, timelines, values, branches) from the blueprint — only the style colours, custom formats, locale, glyph, subtitle and FINANCE.md are kept."
- Changed
layerz_patch1 field changed- changed
Input schema / properties / ops / items / oneOfPrevious value: -[ - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "anyOf": [ - { - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Flag the line as a control, judged on its OWN value: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. Pass `null` to remove the check. Failing periods surface in `layerz_read` under `health` — non-blocking." - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (null clears it)." - }, - "compare_to": { - "anyOf": [ - { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "KPI native comparison; null clears it." - }, - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "anyOf": [ - { - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." - }, - "name": { - "description": "New display label (also the formula identifier)", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "update", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "New parent UID (null to move to root)" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Change the item role. Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "description": "Chart series UIDs", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch; supersedes `series`.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "description": "Existing item UID to update", - "minLength": 1, - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "create", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", - "minLength": 1, - "type": "string" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name", - "op" - ], - "type": "object" - }, - { - "properties": { - "from_uid": { - "description": "Existing item UID to replace atomically (section/dashboard unsupported)", - "minLength": 1, - "type": "string" - }, - "new_item": { - "description": "Inline payload describing the replacement item", - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name" - ], - "type": "object" - }, - "op": { - "const": "replace", - "type": "string" - }, - "preserve_order": { - "type": "boolean" - }, - "preserve_parent": { - "type": "boolean" - }, - "preserve_style": { - "type": "boolean" - } - }, - "required": [ - "op", - "from_uid", - "new_item" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "delete", - "type": "string" - }, - "uid": { - "description": "UID of item to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "after": { - "description": "UID of item to place after (for ordering)", - "minLength": 1, - "type": "string" - }, - "op": { - "const": "move", - "type": "string" - }, - "parent": { - "description": "New parent UID", - "minLength": 1, - "type": "string" - }, - "uid": { - "description": "UID of item to move", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_create", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "properties": { - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_update", - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "list_delete", - "type": "string" - }, - "uid": { - "description": "UID of list to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "finance_md": { - "anyOf": [ - { - "maxLength": 5000, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "formats": { - "additionalProperties": { - "properties": { - "display_name": { - "maxLength": 80, - "type": "string" - }, - "excel_format": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "name": { - "maxLength": 50, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "name", - "excel_format" - ], - "type": "object" - }, - "propertyNames": { - "maxLength": 50, - "minLength": 1, - "type": "string" - }, - "type": "object" - }, - "glyph": { - "anyOf": [ - { - "maxLength": 60, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "number_locale": { - "anyOf": [ - { - "enum": [ - "en", - "fr", - "de" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "op": { - "const": "meta", - "type": "string" - }, - "subtitle": { - "anyOf": [ - { - "maxLength": 160, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "timelines": { - "properties": { - "monthly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "quarterly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "yearly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - } - }, - "type": "object" - } - }, - "required": [ - "op" - ], - "type": "object" - } -]New value: +[ + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: one styled series per branch; false reverts to the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "anyOf": [ + { + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Flag the line as a control, judged on its OWN value: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. Pass `null` to remove the check. Failing periods surface in `layerz_read` under `health` — non-blocking." + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (null clears it)." + }, + "compare_to": { + "anyOf": [ + { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "KPI native comparison; null clears it." + }, + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "anyOf": [ + { + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." + }, + "name": { + "description": "New display label (also the formula identifier)", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "update", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "New parent UID (null to move to root)" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Change the item role. Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "description": "Chart series UIDs", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch; supersedes `series`.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "description": "Existing item UID to update", + "minLength": 1, + "type": "string" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are never divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: plot one styled series per branch instead of the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "create", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", + "minLength": 1, + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are never divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name", + "op" + ], + "type": "object" + }, + { + "properties": { + "from_uid": { + "description": "Existing item UID to replace atomically (section/dashboard unsupported)", + "minLength": 1, + "type": "string" + }, + "new_item": { + "description": "Inline payload describing the replacement item", + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "by_branch": { + "description": "Chart only: plot one styled series per branch instead of the checked-out view.", + "type": "boolean" + }, + "cagr": { + "description": "mini_table (axis=time): show the trailing CAGR column. Default true", + "type": "boolean" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "unit": { + "description": "mini_table amount scaling: 'none' (default), 'k' (thousands), 'm' (millions), 'auto' (per-table scale); %, multiples and dates are never divided", + "enum": [ + "none", + "k", + "m", + "auto" + ], + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name" + ], + "type": "object" + }, + "op": { + "const": "replace", + "type": "string" + }, + "preserve_order": { + "type": "boolean" + }, + "preserve_parent": { + "type": "boolean" + }, + "preserve_presentation": { + "description": "Default true: every presentation / annotation field `new_item` omits (item_style, format, comment, check, span, position, period_window, compare_to, sparkline, series_defs, …) is carried over from the replaced item when the new role can hold it. Pass false to rebuild the item from `new_item` alone.", + "type": "boolean" + }, + "preserve_style": { + "type": "boolean" + } + }, + "required": [ + "op", + "from_uid", + "new_item" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "delete", + "type": "string" + }, + "uid": { + "description": "UID of item to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "after": { + "description": "UID of item to place after (for ordering)", + "minLength": 1, + "type": "string" + }, + "op": { + "const": "move", + "type": "string" + }, + "parent": { + "description": "New parent UID", + "minLength": 1, + "type": "string" + }, + "uid": { + "description": "UID of item to move", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_create", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "properties": { + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_update", + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "list_delete", + "type": "string" + }, + "uid": { + "description": "UID of list to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "finance_md": { + "anyOf": [ + { + "maxLength": 5000, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "formats": { + "additionalProperties": { + "properties": { + "display_name": { + "maxLength": 80, + "type": "string" + }, + "excel_format": { + "maxLength": 120, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 50, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "excel_format" + ], + "type": "object" + }, + "propertyNames": { + "maxLength": 50, + "minLength": 1, + "type": "string" + }, + "type": "object" + }, + "glyph": { + "anyOf": [ + { + "maxLength": 60, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." + }, + "name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "number_locale": { + "anyOf": [ + { + "enum": [ + "en", + "fr", + "de" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "op": { + "const": "meta", + "type": "string" + }, + "subtitle": { + "anyOf": [ + { + "maxLength": 160, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "timelines": { + "properties": { + "monthly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "quarterly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "yearly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + } + }, + "required": [ + "op" + ], + "type": "object" + } +]
1 tool update
- Changed
layerz_patch1 field changed- changed
Input schema / properties / ops / items / oneOfPrevious value: -[ - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "anyOf": [ - { - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Flag the line as a control, judged on its OWN value: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. Pass `null` to remove the check. Failing periods surface in `layerz_read` under `health` — non-blocking." - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (null clears it)." - }, - "compare_to": { - "anyOf": [ - { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "KPI native comparison; null clears it." - }, - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "anyOf": [ - { - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." - }, - "name": { - "description": "New display label (also the formula identifier)", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "update", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "New parent UID (null to move to root)" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Change the item role. Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "description": "Chart series UIDs", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch; supersedes `series`.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "description": "Existing item UID to update", - "minLength": 1, - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "create", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", - "minLength": 1, - "type": "string" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name", - "op" - ], - "type": "object" - }, - { - "properties": { - "from_uid": { - "description": "Existing item UID to replace atomically (section/dashboard unsupported)", - "minLength": 1, - "type": "string" - }, - "new_item": { - "description": "Inline payload describing the replacement item", - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "position": { - "anyOf": [ - { - "properties": { - "col": { - "maximum": 11, - "minimum": 0, - "type": "integer" - }, - "row": { - "maximum": 400, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "col", - "row" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name" - ], - "type": "object" - }, - "op": { - "const": "replace", - "type": "string" - }, - "preserve_order": { - "type": "boolean" - }, - "preserve_parent": { - "type": "boolean" - }, - "preserve_style": { - "type": "boolean" - } - }, - "required": [ - "op", - "from_uid", - "new_item" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "delete", - "type": "string" - }, - "uid": { - "description": "UID of item to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "after": { - "description": "UID of item to place after (for ordering)", - "minLength": 1, - "type": "string" - }, - "op": { - "const": "move", - "type": "string" - }, - "parent": { - "description": "New parent UID", - "minLength": 1, - "type": "string" - }, - "uid": { - "description": "UID of item to move", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_create", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "properties": { - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_update", - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "list_delete", - "type": "string" - }, - "uid": { - "description": "UID of list to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "finance_md": { - "anyOf": [ - { - "maxLength": 5000, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "formats": { - "additionalProperties": { - "properties": { - "display_name": { - "maxLength": 80, - "type": "string" - }, - "excel_format": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "name": { - "maxLength": 50, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "name", - "excel_format" - ], - "type": "object" - }, - "propertyNames": { - "maxLength": 50, - "minLength": 1, - "type": "string" - }, - "type": "object" - }, - "glyph": { - "anyOf": [ - { - "maxLength": 60, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "number_locale": { - "anyOf": [ - { - "enum": [ - "en", - "fr", - "de" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "op": { - "const": "meta", - "type": "string" - }, - "subtitle": { - "anyOf": [ - { - "maxLength": 160, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "timelines": { - "properties": { - "monthly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "quarterly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "yearly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - } - }, - "type": "object" - } - }, - "required": [ - "op" - ], - "type": "object" - } -]New value: +[ + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "anyOf": [ + { + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Flag the line as a control, judged on its OWN value: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. Pass `null` to remove the check. Failing periods surface in `layerz_read` under `health` — non-blocking." + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (null clears it)." + }, + "compare_to": { + "anyOf": [ + { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "KPI native comparison; null clears it." + }, + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "anyOf": [ + { + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." + }, + "name": { + "description": "New display label (also the formula identifier)", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "update", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "New parent UID (null to move to root)" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Change the item role. Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "description": "Chart series UIDs", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch; supersedes `series`.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "description": "Existing item UID to update", + "minLength": 1, + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "create", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", + "minLength": 1, + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name", + "op" + ], + "type": "object" + }, + { + "properties": { + "from_uid": { + "description": "Existing item UID to replace atomically (section/dashboard unsupported)", + "minLength": 1, + "type": "string" + }, + "new_item": { + "description": "Inline payload describing the replacement item", + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK); the excel_format follows Excel / Google Sheets, bracket colours and conditions included (`[Green]0;[Red](0);-` paints negatives red on the web and in Excel). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name" + ], + "type": "object" + }, + "op": { + "const": "replace", + "type": "string" + }, + "preserve_order": { + "type": "boolean" + }, + "preserve_parent": { + "type": "boolean" + }, + "preserve_style": { + "type": "boolean" + } + }, + "required": [ + "op", + "from_uid", + "new_item" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "delete", + "type": "string" + }, + "uid": { + "description": "UID of item to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "after": { + "description": "UID of item to place after (for ordering)", + "minLength": 1, + "type": "string" + }, + "op": { + "const": "move", + "type": "string" + }, + "parent": { + "description": "New parent UID", + "minLength": 1, + "type": "string" + }, + "uid": { + "description": "UID of item to move", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_create", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "properties": { + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_update", + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "list_delete", + "type": "string" + }, + "uid": { + "description": "UID of list to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "finance_md": { + "anyOf": [ + { + "maxLength": 5000, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "formats": { + "additionalProperties": { + "properties": { + "display_name": { + "maxLength": 80, + "type": "string" + }, + "excel_format": { + "maxLength": 120, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 50, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "excel_format" + ], + "type": "object" + }, + "propertyNames": { + "maxLength": 50, + "minLength": 1, + "type": "string" + }, + "type": "object" + }, + "glyph": { + "anyOf": [ + { + "maxLength": 60, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." + }, + "name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "number_locale": { + "anyOf": [ + { + "enum": [ + "en", + "fr", + "de" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "op": { + "const": "meta", + "type": "string" + }, + "subtitle": { + "anyOf": [ + { + "maxLength": 160, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "timelines": { + "properties": { + "monthly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "quarterly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "yearly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + } + }, + "required": [ + "op" + ], + "type": "object" + } +]
1 tool update
- Changed
layerz_patch1 field changed- changed
Input schema / properties / ops / items / oneOfPrevious value: -[ - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "anyOf": [ - { - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Flag the line as a control, judged on its OWN value: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. Pass `null` to remove the check. Failing periods surface in `layerz_read` under `health` — non-blocking." - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (null clears it)." - }, - "compare_to": { - "anyOf": [ - { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "KPI native comparison; null clears it." - }, - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "anyOf": [ - { - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." - }, - "name": { - "description": "New display label (also the formula identifier)", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "update", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "New parent UID (null to move to root)" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "role": { - "description": "Change the item role. Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break: start this widget on a fresh grid row", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "description": "Chart series UIDs", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch; supersedes `series`.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "description": "Existing item UID to update", - "minLength": 1, - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "create", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", - "minLength": 1, - "type": "string" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break: start this widget on a fresh grid row", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name", - "op" - ], - "type": "object" - }, - { - "properties": { - "from_uid": { - "description": "Existing item UID to replace atomically (section/dashboard unsupported)", - "minLength": 1, - "type": "string" - }, - "new_item": { - "description": "Inline payload describing the replacement item", - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "check": { - "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "tolerance": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break: start this widget on a fresh grid row", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name" - ], - "type": "object" - }, - "op": { - "const": "replace", - "type": "string" - }, - "preserve_order": { - "type": "boolean" - }, - "preserve_parent": { - "type": "boolean" - }, - "preserve_style": { - "type": "boolean" - } - }, - "required": [ - "op", - "from_uid", - "new_item" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "delete", - "type": "string" - }, - "uid": { - "description": "UID of item to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "after": { - "description": "UID of item to place after (for ordering)", - "minLength": 1, - "type": "string" - }, - "op": { - "const": "move", - "type": "string" - }, - "parent": { - "description": "New parent UID", - "minLength": 1, - "type": "string" - }, - "uid": { - "description": "UID of item to move", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_create", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "properties": { - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_update", - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "list_delete", - "type": "string" - }, - "uid": { - "description": "UID of list to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "finance_md": { - "anyOf": [ - { - "maxLength": 5000, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "formats": { - "additionalProperties": { - "properties": { - "display_name": { - "maxLength": 80, - "type": "string" - }, - "excel_format": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "name": { - "maxLength": 50, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "name", - "excel_format" - ], - "type": "object" - }, - "propertyNames": { - "maxLength": 50, - "minLength": 1, - "type": "string" - }, - "type": "object" - }, - "glyph": { - "anyOf": [ - { - "maxLength": 60, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "number_locale": { - "anyOf": [ - { - "enum": [ - "en", - "fr", - "de" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "op": { - "const": "meta", - "type": "string" - }, - "subtitle": { - "anyOf": [ - { - "maxLength": 160, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "timelines": { - "properties": { - "monthly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "quarterly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "yearly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - } - }, - "type": "object" - } - }, - "required": [ - "op" - ], - "type": "object" - } -]New value: +[ + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "anyOf": [ + { + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Flag the line as a control, judged on its OWN value: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. Pass `null` to remove the check. Failing periods surface in `layerz_read` under `health` — non-blocking." + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (null clears it)." + }, + "compare_to": { + "anyOf": [ + { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "KPI native comparison; null clears it." + }, + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "anyOf": [ + { + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." + }, + "name": { + "description": "New display label (also the formula identifier)", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "update", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "New parent UID (null to move to root)" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Change the item role. Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "description": "Chart series UIDs", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch; supersedes `series`.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "description": "Existing item UID to update", + "minLength": 1, + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "create", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", + "minLength": 1, + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name", + "op" + ], + "type": "object" + }, + { + "properties": { + "from_uid": { + "description": "Existing item UID to replace atomically (section/dashboard unsupported)", + "minLength": 1, + "type": "string" + }, + "new_item": { + "description": "Inline payload describing the replacement item", + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "position": { + "anyOf": [ + { + "properties": { + "col": { + "maximum": 11, + "minimum": 0, + "type": "integer" + }, + "row": { + "maximum": 400, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "col", + "row" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "dashboard-child pinned grid origin {col 0–11, row ≥ 0}: the widget sits at these cells (pushed down when it collides with an earlier pinned widget). Absent = flow placement (first-fit dense in sort order — a new widget fills the topmost free hole); null clears the pin. The web canvas pins every widget on the first drag" + }, + "role": { + "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break (flow widgets only): start this widget on a fresh grid row. Inert once the widget has a position", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name" + ], + "type": "object" + }, + "op": { + "const": "replace", + "type": "string" + }, + "preserve_order": { + "type": "boolean" + }, + "preserve_parent": { + "type": "boolean" + }, + "preserve_style": { + "type": "boolean" + } + }, + "required": [ + "op", + "from_uid", + "new_item" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "delete", + "type": "string" + }, + "uid": { + "description": "UID of item to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "after": { + "description": "UID of item to place after (for ordering)", + "minLength": 1, + "type": "string" + }, + "op": { + "const": "move", + "type": "string" + }, + "parent": { + "description": "New parent UID", + "minLength": 1, + "type": "string" + }, + "uid": { + "description": "UID of item to move", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_create", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "properties": { + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_update", + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "list_delete", + "type": "string" + }, + "uid": { + "description": "UID of list to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "finance_md": { + "anyOf": [ + { + "maxLength": 5000, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "formats": { + "additionalProperties": { + "properties": { + "display_name": { + "maxLength": 80, + "type": "string" + }, + "excel_format": { + "maxLength": 120, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 50, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "excel_format" + ], + "type": "object" + }, + "propertyNames": { + "maxLength": 50, + "minLength": 1, + "type": "string" + }, + "type": "object" + }, + "glyph": { + "anyOf": [ + { + "maxLength": 60, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." + }, + "name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "number_locale": { + "anyOf": [ + { + "enum": [ + "en", + "fr", + "de" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "op": { + "const": "meta", + "type": "string" + }, + "subtitle": { + "anyOf": [ + { + "maxLength": 160, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "timelines": { + "properties": { + "monthly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "quarterly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "yearly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + } + }, + "required": [ + "op" + ], + "type": "object" + } +]
2 tool updates
- Changed
layerz_patch1 field changed- changed
Input schema / properties / ops / items / oneOfPrevious value: -[ - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (null clears it)." - }, - "compare_to": { - "anyOf": [ - { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "KPI native comparison; null clears it." - }, - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "anyOf": [ - { - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Business watchpoint. `assert` is a boolean expression where the reserved token `value` is this item's computed value (a leading comparator like \"== 0\" implies it). Values are evaluated rounded to 2 decimals, so \"== 0\" already absorbs sub-0.005 residuals (a balance netting to 1e-11 reads as 0 and passes); use a wider tolerance (\"ABS(value) < 1\") only to ignore a materially larger gap. Pass `null` to clear. Failing periods surface in `layerz_read` under `health` — non-blocking." - }, - "name": { - "description": "New display label (also the formula identifier)", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "update", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "New parent UID (null to move to root)" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "role": { - "description": "Change the item role. Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break: start this widget on a fresh grid row", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "description": "Chart series UIDs", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch; supersedes `series`.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "description": "Existing item UID to update", - "minLength": 1, - "type": "string" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Business watchpoint. `assert` is a boolean expression in the formula language; the reserved token `value` is this item's computed value (a leading comparator like \"== 0\" implies it). Values are evaluated rounded to 2 decimals, so \"== 0\" already absorbs sub-0.005 residuals (a balance netting to 1e-11 reads as 0 and passes); use a wider tolerance (\"ABS(value) < 1\") only to ignore a materially larger gap — a sub-cent tolerance adds nothing over \"== 0\". Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "create", - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "parent": { - "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", - "minLength": 1, - "type": "string" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break: start this widget on a fresh grid row", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name", - "op" - ], - "type": "object" - }, - { - "properties": { - "from_uid": { - "description": "Existing item UID to replace atomically (section/dashboard unsupported)", - "minLength": 1, - "type": "string" - }, - "new_item": { - "description": "Inline payload describing the replacement item", - "properties": { - "aggregation": { - "enum": [ - "sum", - "average", - "stock" - ], - "type": "string" - }, - "axis": { - "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", - "enum": [ - "time", - "branches", - "list" - ], - "type": "string" - }, - "branches": { - "description": "mini_table compared branch ids (axis=branches); first = écart base", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "bridge_config": { - "properties": { - "axis": { - "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", - "enum": [ - "time", - "branches" - ], - "type": "string" - }, - "branches": { - "description": "Branch ids compared when axis=branches: [from, to].", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "contributions": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "end_period": { - "maxLength": 50, - "type": "string" - }, - "endpoint": { - "description": "Single endpoint item (stock/balance) read at start & end periods", - "minLength": 1, - "type": "string" - }, - "endpoint_branch": { - "description": "Read endpoint + contributions under this branch basis (axis=time only).", - "minLength": 1, - "type": "string" - }, - "residual_label": { - "maxLength": 200, - "type": "string" - }, - "start_period": { - "maxLength": 50, - "type": "string" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "chart_type": { - "enum": [ - "line", - "bar", - "area", - "bridge" - ], - "type": "string" - }, - "comment": { - "anyOf": [ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." - }, - "compare_to": { - "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "display": { - "enum": [ - "abs", - "pct", - "both" - ], - "type": "string" - }, - "mode": { - "enum": [ - "branch", - "prev_period", - "prev_year" - ], - "type": "string" - }, - "thresholds": { - "properties": { - "green": { - "type": "number" - }, - "red": { - "type": "number" - } - }, - "required": [ - "green", - "red" - ], - "type": "object" - } - }, - "required": [ - "mode" - ], - "type": "object" - }, - "default_branch": { - "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", - "minLength": 1, - "type": "string" - }, - "format": { - "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", - "maxLength": 50, - "type": "string" - }, - "formula": { - "maxLength": 2000, - "type": "string" - }, - "inputs": { - "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "item_style": { - "enum": [ - "normal", - "subtotal", - "total", - "kpi" - ], - "type": "string" - }, - "list_mode": { - "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", - "enum": [ - "split", - "total" - ], - "type": "string" - }, - "list_total": { - "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", - "type": "boolean" - }, - "liste": { - "description": "mini_table list whose entries become the columns (axis=list)", - "minLength": 1, - "type": "string" - }, - "liste_ref": { - "minLength": 1, - "type": "string" - }, - "monitor": { - "description": "Business watchpoint. `assert` is a boolean expression in the formula language; the reserved token `value` is this item's computed value (a leading comparator like \"== 0\" implies it). Values are evaluated rounded to 2 decimals, so \"== 0\" already absorbs sub-0.005 residuals (a balance netting to 1e-11 reads as 0 and passes); use a wider tolerance (\"ABS(value) < 1\") only to ignore a materially larger gap — a sub-cent tolerance adds nothing over \"== 0\". Failing periods surface in `layerz_read` under `health` — non-blocking.", - "properties": { - "assert": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "type": "string" - } - }, - "required": [ - "assert" - ], - "type": "object" - }, - "name": { - "description": "Display name", - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "opening_balance": { - "type": "number" - }, - "period_window": { - "anyOf": [ - { - "properties": { - "end": { - "maxLength": 20, - "type": "string" - }, - "fit": { - "enum": [ - "data", - "full" - ], - "type": "string" - }, - "start": { - "maxLength": 20, - "type": "string" - } - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." - }, - "role": { - "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", - "enum": [ - "assumption", - "formula", - "callup", - "balance", - "section", - "chart", - "dashboard", - "kpi", - "mini_table" - ], - "type": "string" - }, - "row_break": { - "description": "dashboard-child line break: start this widget on a fresh grid row", - "type": "boolean" - }, - "rows": { - "description": "mini_table curated row item UIDs / temp IDs / names", - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "series_defs": { - "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", - "items": { - "properties": { - "branch_id": { - "minLength": 1, - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "uid" - ], - "type": "object" - }, - "type": "array" - }, - "source": { - "description": "Callup source UID or temp ID", - "minLength": 1, - "type": "string" - }, - "span": { - "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", - "properties": { - "cols": { - "maximum": 12, - "minimum": 1, - "type": "integer" - }, - "rows": { - "maximum": 4, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sparkline": { - "description": "KPI trend mini-chart under the value.", - "type": "boolean" - }, - "target": { - "description": "KPI/mini_table target UID or temp ID", - "minLength": 1, - "type": "string" - }, - "target_period": { - "maxLength": 50, - "type": "string" - }, - "timeline_ref": { - "enum": [ - "constant", - "monthly", - "yearly", - "quarterly" - ], - "type": "string" - }, - "timeline_values": { - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - } - ] - }, - "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "values": { - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "type": "array" - }, - "variance": { - "description": "mini_table écart columns (axis=branches)", - "properties": { - "abs": { - "type": "boolean" - }, - "color": { - "type": "boolean" - }, - "pct": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "role", - "name" - ], - "type": "object" - }, - "op": { - "const": "replace", - "type": "string" - }, - "preserve_order": { - "type": "boolean" - }, - "preserve_parent": { - "type": "boolean" - }, - "preserve_style": { - "type": "boolean" - } - }, - "required": [ - "op", - "from_uid", - "new_item" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "delete", - "type": "string" - }, - "uid": { - "description": "UID of item to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "after": { - "description": "UID of item to place after (for ordering)", - "minLength": 1, - "type": "string" - }, - "op": { - "const": "move", - "type": "string" - }, - "parent": { - "description": "New parent UID", - "minLength": 1, - "type": "string" - }, - "uid": { - "description": "UID of item to move", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "id": { - "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_create", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "properties": { - "items": { - "items": { - "properties": { - "id": { - "maxLength": 32, - "minLength": 1, - "type": "string" - }, - "label": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "mapped_to": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "label" - ], - "type": "object" - }, - "maxItems": 500, - "type": "array" - }, - "name": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "op": { - "const": "list_update", - "type": "string" - }, - "uid": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "op": { - "const": "list_delete", - "type": "string" - }, - "uid": { - "description": "UID of list to delete", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "op", - "uid" - ], - "type": "object" - }, - { - "properties": { - "default_branch": { - "anyOf": [ - { - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "finance_md": { - "anyOf": [ - { - "maxLength": 5000, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "formats": { - "additionalProperties": { - "properties": { - "display_name": { - "maxLength": 80, - "type": "string" - }, - "excel_format": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "name": { - "maxLength": 50, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "name", - "excel_format" - ], - "type": "object" - }, - "propertyNames": { - "maxLength": 50, - "minLength": 1, - "type": "string" - }, - "type": "object" - }, - "glyph": { - "anyOf": [ - { - "maxLength": 60, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "number_locale": { - "anyOf": [ - { - "enum": [ - "en", - "fr", - "de" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "op": { - "const": "meta", - "type": "string" - }, - "subtitle": { - "anyOf": [ - { - "maxLength": 160, - "type": "string" - }, - { - "type": "null" - } - ] - }, - "timelines": { - "properties": { - "monthly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "quarterly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "yearly": { - "anyOf": [ - { - "properties": { - "end": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - }, - "start": { - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "type": "string" - } - }, - "required": [ - "start", - "end" - ], - "type": "object" - }, - { - "type": "null" - } - ] - } - }, - "type": "object" - } - }, - "required": [ - "op" - ], - "type": "object" - } -]New value: +[ + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "anyOf": [ + { + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Flag the line as a control, judged on its OWN value: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals); a formula that is itself a comparison / AND / OR / NOT passes when true. Pass `null` to remove the check. Failing periods surface in `layerz_read` under `health` — non-blocking." + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (null clears it)." + }, + "compare_to": { + "anyOf": [ + { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "KPI native comparison; null clears it." + }, + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Dashboard-only: branch checked out when this dashboard opens; null clears it." + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: 'split' (default) = one series per list element, 'total' = the list-backed item's aggregate as one series.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; forced ON while the aggregate is referenced.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "anyOf": [ + { + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert is converted, any other rule is rejected; `null` removes the check." + }, + "name": { + "description": "New display label (also the formula identifier)", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "update", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "New parent UID (null to move to root)" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "role": { + "description": "Change the item role. Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break: start this widget on a fresh grid row", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "description": "Chart series UIDs", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch; supersedes `series`.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "description": "Existing item UID to update", + "minLength": 1, + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can use this value as their `parent`, `source`, `target`, or `series` to wire references before the real UID is generated. Numeric (`_1`, `_2`) keeps it short; descriptive (`_starting_customers`) is fine up to 32 chars.", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "create", + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "parent": { + "description": "Parent container: either an 8-char UID of an existing section/balance/dashboard, or a temp `id` from an earlier create op in the same batch (e.g. set `id: \"_pl\"` on a section create, then `parent: \"_pl\"` on each child item).", + "minLength": 1, + "type": "string" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "role": { + "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break: start this widget on a fresh grid row", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name", + "op" + ], + "type": "object" + }, + { + "properties": { + "from_uid": { + "description": "Existing item UID to replace atomically (section/dashboard unsupported)", + "minLength": 1, + "type": "string" + }, + "new_item": { + "description": "Inline payload describing the replacement item", + "properties": { + "aggregation": { + "enum": [ + "sum", + "average", + "stock" + ], + "type": "string" + }, + "axis": { + "description": "mini_table column axis: 'time' (periods, default), 'branches', or 'list' (list entries + Total)", + "enum": [ + "time", + "branches", + "list" + ], + "type": "string" + }, + "branches": { + "description": "mini_table compared branch ids (axis=branches); first = écart base", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "bridge_config": { + "properties": { + "axis": { + "description": "Bridge dimension: 'time' (default, delta start→end period) or 'branches' (gap between two branches at one period — budget vs actual waterfall).", + "enum": [ + "time", + "branches" + ], + "type": "string" + }, + "branches": { + "description": "Branch ids compared when axis=branches: [from, to].", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "contributions": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "end_period": { + "maxLength": 50, + "type": "string" + }, + "endpoint": { + "description": "Single endpoint item (stock/balance) read at start & end periods", + "minLength": 1, + "type": "string" + }, + "endpoint_branch": { + "description": "Read endpoint + contributions under this branch basis (axis=time only).", + "minLength": 1, + "type": "string" + }, + "residual_label": { + "maxLength": 200, + "type": "string" + }, + "start_period": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, + "chart_type": { + "enum": [ + "line", + "bar", + "area", + "bridge" + ], + "type": "string" + }, + "check": { + "description": "Flag the line as a control, judged on its OWN value at every period: a plain amount must stay within `tolerance` of 0 (ABS(value) < tolerance, default 1, on values rounded to 2 decimals — a balance netting to 1e-11 passes); a formula that is itself a comparison / AND / OR / NOT (e.g. \"Revenue >= 0\") passes when true. Assumption and formula only. Failing periods surface in `layerz_read` under `health` — non-blocking.", + "properties": { + "tolerance": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "comment": { + "anyOf": [ + { + "maxLength": 2000, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Free-form annotation on the item (hypothesis source, rationale, review note)." + }, + "compare_to": { + "description": "KPI native comparison: a delta vs another branch (`mode:\"branch\"` + `branch_id`) or a shifted period (`prev_period`/`prev_year`). Rendered as the value plus a signed delta %.", + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "display": { + "enum": [ + "abs", + "pct", + "both" + ], + "type": "string" + }, + "mode": { + "enum": [ + "branch", + "prev_period", + "prev_year" + ], + "type": "string" + }, + "thresholds": { + "properties": { + "green": { + "type": "number" + }, + "red": { + "type": "number" + } + }, + "required": [ + "green", + "red" + ], + "type": "object" + } + }, + "required": [ + "mode" + ], + "type": "object" + }, + "default_branch": { + "description": "Dashboard-only: branch checked out when this dashboard opens; overrides the model default.", + "minLength": 1, + "type": "string" + }, + "format": { + "description": "Number format name. Pre-installed: default, decimal_2, decimal_neg, integer, multiple, percent, percent_neg, percent_2, date_dmy, date_dmmmyy, date_mmmyy, date_dmmm (+ the magic `date`, and `currency`/`currency_2` — resolved live from the FINANCE.md `currency` code: `$1,234` for USD, `1 234 €` for EUR). Custom names refer to entries in model.formats — add one with the `meta` op `formats` field (same batch OK). An unknown name is REJECTED with code UNKNOWN_FORMAT listing what is available; prefer `percent` over legacy `\"%\"`. Full catalog with each excel_format and rendered positive/negative/zero examples (e.g. `default` = dash-for-zero accounting style vs `integer` = plain 0): MCP resource `format-spec://reference`. See docs/mdk-format.md § 4.4.", + "maxLength": 50, + "type": "string" + }, + "formula": { + "maxLength": 2000, + "type": "string" + }, + "inputs": { + "description": "Direct contributors override (item UIDs or same-batch temp ids). Useful for `balance` items, which carry no formula but can declare the flows that roll into the stock; for `formula` items inputs are derived from the expression, so set this only to override that derivation.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "item_style": { + "enum": [ + "normal", + "subtotal", + "total", + "kpi" + ], + "type": "string" + }, + "list_mode": { + "description": "Chart only: how a list-backed series renders — 'split' (default) plots one series per list element, 'total' plots the item's aggregate as ONE series. Use 'total' to chart a list total without a mirror formula.", + "enum": [ + "split", + "total" + ], + "type": "string" + }, + "list_total": { + "description": "List-mode only: show the list's total (aggregate row values). Default off; the total is forced ON while any formula/callup/parent/widget reads the aggregate.", + "type": "boolean" + }, + "liste": { + "description": "mini_table list whose entries become the columns (axis=list)", + "minLength": 1, + "type": "string" + }, + "liste_ref": { + "minLength": 1, + "type": "string" + }, + "monitor": { + "description": "Deprecated (MDK 0.13.0) — use `check`. A canonical assert (\"ABS(value) < t\", \"== 0\") is converted; any other rule is rejected: put it in the line's formula and set `check: {}`.", + "properties": { + "assert": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "type": "string" + } + }, + "required": [ + "assert" + ], + "type": "object" + }, + "name": { + "description": "Display name", + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "opening_balance": { + "type": "number" + }, + "period_window": { + "anyOf": [ + { + "properties": { + "end": { + "maxLength": 20, + "type": "string" + }, + "fit": { + "enum": [ + "data", + "full" + ], + "type": "string" + }, + "start": { + "maxLength": 20, + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Display period window of a chart / kpi sparkline / time mini_table. `start`/`end` are period labels at the widget's grain (bounds independent — either alone is valid); `fit` governs the unset bounds: 'data' (the default, absent field included) auto-fits to the periods actually carrying data and follows imports, 'full' spans the whole timeline. Pass null to clear (back to fit-to-data)." + }, + "role": { + "description": "Role of the item — pick by intent. assumption: an input/driver you own (set value/values/timeline_values; the only role a Dataset can override). formula: a computed transformation (set formula). formula with NO expression + children: a Σ aggregator that sums its children (a subtotal VALUE — use a nested section instead when you only need structural grouping). callup: a typed mirror of ONE source whose value flows into its parent (set source) — for balance flows and surfacing a value in a section/subtotal, NOT for arithmetic (reference the source directly inside a real formula). balance: a rolling stock (set opening_balance; sums its callup-child flows). section: structural container, no value. dashboard/chart/kpi/mini_table: presentational. A formula is an aggregator XOR a transformation (never both expression AND children). A single bare reference written as a formula is auto-posed as a callup mirror.", + "enum": [ + "assumption", + "formula", + "callup", + "balance", + "section", + "chart", + "dashboard", + "kpi", + "mini_table" + ], + "type": "string" + }, + "row_break": { + "description": "dashboard-child line break: start this widget on a fresh grid row", + "type": "boolean" + }, + "rows": { + "description": "mini_table curated row item UIDs / temp IDs / names", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "series_defs": { + "description": "Per-series branch: supersedes `series`. Each entry plots one item under a specific branch (e.g. Revenue@budget vs Revenue@actuals). `uid` accepts a temp id.", + "items": { + "properties": { + "branch_id": { + "minLength": 1, + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "description": "Callup source UID or temp ID", + "minLength": 1, + "type": "string" + }, + "span": { + "description": "dashboard-child grid footprint (chart/kpi/mini_table): cols on the 12-column grid × 140-px row units. Defaults: kpi 3×1, chart/mini_table 6×2", + "properties": { + "cols": { + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "rows": { + "maximum": 4, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "sparkline": { + "description": "KPI trend mini-chart under the value.", + "type": "boolean" + }, + "target": { + "description": "KPI/mini_table target UID or temp ID", + "minLength": 1, + "type": "string" + }, + "target_period": { + "maxLength": 50, + "type": "string" + }, + "timeline_ref": { + "enum": [ + "constant", + "monthly", + "yearly", + "quarterly" + ], + "type": "string" + }, + "timeline_values": { + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + }, + "description": "Periodic values, keyed by context. List-mode item: one series per list entry — key is the element UID, label, OR a `list_create` temp id from the SAME batch (temp ids do not survive to a later batch; use the label/UID then). Non-list item: either a full series keyed by a canonical grain (`monthly`/`quarterly`/`yearly`/`constant`, the import path) OR a sparse per-period patch keyed by period id (e.g. `{\"2026-06\": -119.19}`) that overwrites only the named cells and leaves the rest — the future projection — intact. A single number is coerced to a 1-element array. List cells left unset read as 0 (no forward-fill).", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "values": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + "variance": { + "description": "mini_table écart columns (axis=branches)", + "properties": { + "abs": { + "type": "boolean" + }, + "color": { + "type": "boolean" + }, + "pct": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": [ + "role", + "name" + ], + "type": "object" + }, + "op": { + "const": "replace", + "type": "string" + }, + "preserve_order": { + "type": "boolean" + }, + "preserve_parent": { + "type": "boolean" + }, + "preserve_style": { + "type": "boolean" + } + }, + "required": [ + "op", + "from_uid", + "new_item" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "delete", + "type": "string" + }, + "uid": { + "description": "UID of item to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "after": { + "description": "UID of item to place after (for ordering)", + "minLength": 1, + "type": "string" + }, + "op": { + "const": "move", + "type": "string" + }, + "parent": { + "description": "New parent UID", + "minLength": 1, + "type": "string" + }, + "uid": { + "description": "UID of item to move", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "id": { + "description": "Temp ID for intra-batch references — other create ops in the same batch can pass this value as their `liste_ref` so a list and its consumers can ship in one patch (e.g. set `id: \"_emp\"` on `list_create`, then `liste_ref: \"_emp\"` on an assumption created in the same batch).", + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_create", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "properties": { + "items": { + "items": { + "properties": { + "id": { + "maxLength": 32, + "minLength": 1, + "type": "string" + }, + "label": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "mapped_to": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "maxItems": 500, + "type": "array" + }, + "name": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "op": { + "const": "list_update", + "type": "string" + }, + "uid": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "op": { + "const": "list_delete", + "type": "string" + }, + "uid": { + "description": "UID of list to delete", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "op", + "uid" + ], + "type": "object" + }, + { + "properties": { + "default_branch": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "finance_md": { + "anyOf": [ + { + "maxLength": 5000, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "formats": { + "additionalProperties": { + "properties": { + "display_name": { + "maxLength": 80, + "type": "string" + }, + "excel_format": { + "maxLength": 120, + "minLength": 1, + "type": "string" + }, + "name": { + "maxLength": 50, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "excel_format" + ], + "type": "object" + }, + "propertyNames": { + "maxLength": 50, + "minLength": 1, + "type": "string" + }, + "type": "object" + }, + "glyph": { + "anyOf": [ + { + "maxLength": 60, + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model emblem icon: a kebab-case Lucide icon name (https://lucide.dev/icons), e.g. \"trending-up\", \"coffee\", \"landmark\", \"rocket\", \"house\", \"users\". Shown in-app and on the social/OG card. Emojis and free text are rejected with suggestions. Aliases normalized: cash/money→banknote, bank→landmark, cart→shopping-cart, chart/graph→chart-line. `null` clears it (falls back to a monogram of the model name)." + }, + "name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "number_locale": { + "anyOf": [ + { + "enum": [ + "en", + "fr", + "de" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "op": { + "const": "meta", + "type": "string" + }, + "subtitle": { + "anyOf": [ + { + "maxLength": 160, + "type": "string" + }, + { + "type": "null" + } + ] + }, + "timelines": { + "properties": { + "monthly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "quarterly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "yearly": { + "anyOf": [ + { + "properties": { + "end": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "start": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + } + }, + "required": [ + "op" + ], + "type": "object" + } +]
- Changed
layerz_read1 field changed- changed
Input schema / properties / health / descriptionPrevious value: -"Also return the `health` block: non-blocking business-monitor violations. Monitors are evaluated at the finest active grain. `with_values: true` already includes `health` when the model has monitors; set this to get `health` without the full value series."New value: +"Also return the `health` block: non-blocking check violations (lines flagged `check`). Checks are evaluated at the finest active grain. `with_values: true` already includes `health` when the model has checks; set this to get `health` without the full value series."
Related MCP Connectors
Personal finance for AI agents — onboard, import statements, categorize & budget over MCP.
Connect AI agents to financial institution origination, analytics, and compliance workflows.
Build financial models as code. Cloud execution, GSheets MCP, version control, collaboration.
Complete financial infrastructure for AI agents — payments, lending, escrow & more.
Related MCP Servers
AlicenseNot gradedqualityBmaintenanceEnables AI agents to perform variance analysis, generate reconciling driver bridges and three-statement forecasts, export Excel workbooks, and analyze SEC or private-company financial data locally with engine-verified calculations.MIT- FlicenseAqualityCmaintenanceProvides AI agents with deterministic, offline finance tools for commodity margin analysis, loan covenant compliance, invoice auditing, AP exception classification, and five-day close readiness.143 npm-
- AlicenseNot gradedqualityCmaintenanceFinancial intelligence for AI agents. Give Claude access to insider trading data, SEC filings, economic indicators, and multi-signal analysis — all through a single MCP server.MIT
- AlicenseNot gradedqualityBmaintenanceEnables agents to synthesize multi-tab enterprise spreadsheet financial models, detect circular references, compile formula dependency DAGs with functions like SUMIFS/XLOOKUP/CAGR/NPV, and evaluate deterministic preview cell matrices.7MIT
Glama MCP Gateway
Add one secure layer between your agents and this server.