Skip to main content
ntro.valuations turns a property’s facts and a set of assumptions into a forecast cashflow and the comparable valuation lanes the Analysis UI renders. It has three parts: a pure engine, a typed domain model, and a versioned store. The whole namespace obeys one law:
value = f(facts, assumptions, engine_version)
Outputs are never an input. You store the facts (assets) and the assumptions, record which engine produced a snapshot, and re-derive (or read a cache) on demand. Four data kinds flow through:

Engine

run_model is deterministic, pure, and entity-agnostic — same inputs, same bytes out. It is the single source of truth; the ui-tenant sensitivity controls call this one engine rather than reimplementing it.
ENGINE_VERSION (re-0.1.0) stamps every snapshot. Bump it when the calc changes; cached forecasts produced by an older version are treated as stale.

Output shape

MonthRow — one row per model month: FYRow — fiscal-year rollup: gross_rental_income, ebitda, npat, gross_yield, net_yield, property_value (NBV at the FY-end month).

Domain model

ntro.valuations.models is the typed input surface. All money is Decimal.

Store

ntro.valuations.store persists versioned valuation sets. Two set kinds, discriminated by engine_version:
Each save_* for an (entity, label) writes model_version = max(existing) + 1 — old versions are retained so you can diff Forecast-v2 vs v3, or Bank vs Indicative.

Caching: inputs_hash / forecast_is_stale

A saved DCF set caches its forecast snapshot keyed by inputs_hash — a sha256 of (assumptions, assets, engine_version) via compute_inputs_hash. Before trusting a cached forecast, check forecast_is_stale(row, assumptions, assets): True when the inputs or the engine version moved, and you should re-run_model.

Storage layout

Two schemas, deliberately split:
  • ledgers.assets — the facts (one AssetRow per asset). Lives in the ledgers schema alongside the GL because it is reconciliation-grade truth.
  • analysis.valuations — the forward / analytical model (assumption set, engine version, cached forecast, asset references). The analysis schema is the forward lane, never an accounting ledger. This is the model’s own store — not a subledger you code against directly; go through ntro.valuations.store.
A CashflowModel references the AssetRow ids it was built from, so re-loading reconstructs the exact facts the snapshot was derived from.

Worked example

The engine is authoritative. The Analysis page’s sensitivity controls send adjusted assumptions to this same run_model — there is no JavaScript reimplementation to drift out of sync.

assets

AssetRow — the per-title facts the engine models.

Subledgers overview

Row base + the ledgers / analysis schema split.

Data capability

Data-plane access used by the store helpers.

Typing

Period and the shared field types.