Vancetope — Document Kind finance-tree
Hierarchical financial modeling tool: a tree of income/expense nodes, which the system calculates bottom-up to a canonical per-year figure. For project financial planning, budgets, cashflow, and cost/revenue models — a thinking/modeling tool (calculating scenarios), not accounting. Interchangeable report processors generate output documents from the calculated model in existing Kinds (
sheet,chart,markdown). See also: doc-kind-sheet, doc-kind-chart (report target Kinds), light-llm-service (assessment report), web-ui §7. Standalone addonvance-addon-brain-finance. Implementation track:planning/app-finance-tree.md.
1. Purpose
A kind: finance-tree document is a tree of nodes. Each node carries
additive Value-Records (amounts with period, optional interest) and is
summed up bottom-up. A node’s sign flips the entire subtree, so
an expense branch automatically subtracts positively entered amounts. The
result is a simple, yet hierarchical cost accounting.
Analogy Compiler: the finance-tree is the source (raw data in human
units), the mathematics (FinanceCalculator/FinanceProjector) the compiler,
report documents the compiled artifacts.
A finance-tree is one document — versionable, exportable, embeddable via
vance:-URI. No Doc-per-node.
2. Data Model
2.1 Annual Invariant
1 year = 365 days = 12 months = 52 weeks. From this: 1 month = 365/12 ≈ 30.42 days, 1 week = 365/52 ≈ 7.02 days. Everything is canonically normalized to per year; day/week/month are division.
2.2 Node
| Field | Meaning |
|---|---|
name |
unique business key in the tree (edges/reports reference it) |
title / icon / color |
Display |
sign |
+1 / -1 — affects the total contribution: total = sign × (Σ own Values + Σ children's Total) |
description / notesRef |
Description + Ref to a notes document |
values |
List of Value-Records |
children |
recursive sub-nodes |
An expense branch is a node with sign: -1 over positively entered
children — no negative numbers need to be entered.
2.3 Value-Record
| Field | Meaning |
|---|---|
value |
Base amount (required) |
mode |
recurring (rate, Default) | one_time (lump sum at a date) |
period |
{count, unit}, unit ∈ day\|week\|month\|year — required for recurring |
validFrom / validTo |
ISO yyyy-MM-dd, optional; for one_time, validFrom is the date (required) |
sign |
optional per-record escape hatch (+1/-1), multiplies only this record |
interest |
optional {rate (%), period, basis, compound} — tracked separately from the base value |
basis ∈ vom_hundert | im_hundert | auf_hundert. v1 implements only
vom_hundert (percentage of the base value); the others round-trip but are
calculated like vom_hundert (v2).
3. On-Disk Format & Web-UI
YAML canonically, JSON dual (FinanceTreeCodec, byte-stable model round-trip via
the $meta-header). The Body only holds Input; calculated values are under
$computed (derived, regenerable, never Source of Truth). A version-field is
always set (v1: 1; missing is read as 1).
The YAML serialization prepends a fixed comment header, which immediately
orients Agent (in document_read) and human:
# vance finance-tree v1 · amounts are per record; a node's sign flips its whole subtree
# agent: run manual_read('finance-tree') before interpreting · computed values under $computed
$meta:
kind: finance-tree
version: 1
title: Q1 Finanzplanung
root:
name: projekt
sign: 1
children:
- name: einnahmen
values:
- { value: 50, period: { count: 3, unit: month }, interest: { rate: 5.0, period: { count: 1, unit: year } } }
- name: ausgaben
sign: -1
values:
- { value: 800, period: { count: 1, unit: month } }
- { value: 5000, mode: one_time, validFrom: 2026-03-01 }
$computed:
computedAt: 2026-07-24T10:00:00Z
nodes:
projekt: { perYear: -9400, perMonth: -783.33, perWeek: -180.77, perDay: -25.75, base: -9400, interest: 2.5, oneTimeSum: -5000 }
Web-UI: kind: finance-tree opens the FinanceKind-editor in Cortex
(Master-Detail): on the left the tree (Select + CRUD + Reorder/Indent/Outdent), on the
right the detail panel of the selected node (fixed fields at the top, Value blocks below).
Topbar: Reload (calculate snapshot, Day/Week/Month/Year toggle) and
Report (Processor dropdown + Params → generate, inline or as document).
In the Embed/Chat channel, FinanceSummaryView renders a data-only card
(net annual value + top nodes) with an “Edit” dialog (full editor) —
read-only via GET /snapshot (calculates without persistence).
4. Calculation Model
The mathematics lives exactly once (pure, deterministic, testable), never in a processor or client.
Snapshot (FinanceCalculator, “Reload”). Current value for “today”:
RECURRING records whose validity window includes today, normalized to per year
(value / period.years()); interest linear on the base value
(value × rate/100 / interestPeriod.years()), shown separately; sign
rollup total = sign × (Σ own + Σ children). ONE_TIME records do not
flow into the annual rate, but separately as oneTimeSum. The result is written back
to $computed as server-authoritative.
Projection (FinanceProjector, Period Table). Parameterized with
from/to + granularity; buckets are calendar-aligned, each value integrated
over the actual days per bucket. ONE_TIME lands in the bucket of its date; interest
compounds here (base × (1+r)^n) if compound: true + validFrom is set,
otherwise linear. Pure function of (root, from, to, granularity), on-demand, not
persisted (persisted only as a report document).
5. Report Processors
Two-layer architecture: one input Kind, N interchangeable processors
(SPI FinanceReportProcessor, self-registered Beans; a Kit can contribute its own).
A processor is pure presentation — it calls the shared math with
the params and writes the payload through the codec of the target Kind (byte-identical).
| Processor | Target Kind | Content |
|---|---|---|
table |
sheet |
Period matrix (rows = nodes, columns = periods, total) |
series |
chart |
Time series per node, category X-axis, chartType line/bar/area/scatter |
assessment |
markdown |
LLM-generated prose report via LightLlmService (Recipe finance-report, internal: true) — no period, snapshot with sign applied (expenses negative) |
table/series are pure; assessment is service-supported (receives Scope via
ReportContext). Discovery: finance_report_processors / GET .../processors.
6. LLM Tools + REST
Tools (vance-addon-brain-finance): finance_tree_create, finance_node_add
(Root without parentName, otherwise child), finance_node_update, finance_node_remove,
finance_node_value_set, finance_tree_calc (Reload), finance_report_generate
(inline or persist), finance_report_processors. Thin adapters over
FinanceService — Node/Value params pass through the codec grammar
(FinanceTreeCodec.nodeFromMap/valueFromMap), a grammar.
REST under /brain/{tenant}/addon/finance/…: GET/PUT /tree, POST /create,
POST /calc, GET /project, POST /report, GET /processors — with
RequestAuthority-Enforcement (READ/WRITE/CREATE).
Both interfaces delegate to the same FinanceService; the math is written and tested once.
(Script-API adapter is reserved, once pluggable JS tooling exists.)
7. Agent Understanding of Data
If the user asks for an assessment in chat, the Agent reads the tree via
document_read as YAML. Three interacting components:
- Manual
_vance/manuals/finance-tree.md— Data model concise, on-demand viamanual_read('finance-tree'). - Engine-Prompt-Hook
_vance/prompts/arthur/finance-tree.md— Trigger + Tool- Syntax +manual_readreference. - In-File-Comment-Hint — the fixed YAML header (§3), visible in
document_read.
8. Anti-Patterns / v1-Scope
- Not accounting/administration — a modeling tool (see
vision.md§7). - Single-currency (pure numbers); multi-currency/conversion is Out of Scope.
- A document holds up to ~hundreds of nodes; no pagination.
- Interest v1: only
vom_hundert, Compound only in projection (not in snapshot). - v2: the three percentage types, interest on
ONE_TIMElump sums, Script-API, Workbook-Embed/Compose regeneration.