Statement Templates – Template Registry & Engine (TRE)
Introduced in v0.4-beta – July 2025
Financial-statement templates let you save a fully-wired calculation graph (nodes, edges, periods, metrics, adjustments) and instantiate it later – either programmatically or from the command-line – in a single line of code.
Built-in templates are shipped as self-contained JSON bundle files inside the library and are automatically discovered by
install_builtin_templates()
- no network calls or hard-coded builders involved.
Why bother? Templates turn hours of repetitive model scaffolding into seconds, ensure naming conventions remain intact and enable structural diffs between versions. They are the foundation for collaborative modelling workflows.
Quick-start (3 minutes)
>>> from fin_statement_model.templates import TemplateRegistry
>>> from fin_statement_model.templates.builtin import install_builtin_templates
>>> install_builtin_templates() # idempotent helper
✅ Installed 2 built-in templates.
>>> TemplateRegistry.list()
['lbo.standard_v1', 'real_estate.lending_v2']
>>> g = TemplateRegistry.instantiate('lbo.standard_v1', periods=["2024", "2025", "2026"])
>>> g.calculate("NetIncome", "2025")
123.4
>>> diff = TemplateRegistry.diff('lbo.standard_v1', 'lbo.standard_v2', include_values=True)
>>> diff.structure.changed_nodes
{'InterestExpense': 'formula'}
>>> diff.values.max_delta
0.005
CLI cheatsheet
Task | Command |
---|---|
List available templates | fsm template ls |
Instantiate template | fsm template apply lbo.standard_v1 --periods 2024:2028 --output model.fsm |
Show structural & value diff | fsm template diff lbo.standard_v1 lbo.standard_v2 |
The CLI mirrors the Python API and respects the FSM_TEMPLATES_PATH
environment variable when reading/writing from the local registry.
Internals in 60 seconds
flowchart TD
subgraph Registry folder
direction TB
A[index.json] --> B[bundle.json]
end
B --> C[IO facade `read_data()`] --> D(Graph)
D -->|clone| E(Graph copy)
style A fill:#f9f,stroke:#333,stroke-width:1px
- Each TemplateBundle (
bundle.json
) is a frozen Pydantic v2 object containing metadata, a graph-definition dictionary and a SHA-256 checksum. - The registry index (
index.json
) maps template-id (lbo.standard_v1
) to the bundle path. instantiate()
re-hydrates the graph via the IO facade, then performs a deep clone to avoid shared caches.
Authoring your own template
from fin_statement_model.core.graph import Graph
from fin_statement_model.templates import TemplateRegistry
# 1· build or load your Graph (see core_basic_usage.py)
g = Graph(periods=["2023", "2024"])
# … add nodes, calculations, adjustments …
# 2· register it
TemplateRegistry.register_graph(
graph=g,
name="infra.ppp",
meta={"category": "infrastructure", "description": "PPP concession 25-year model"},
)
The call persists a bundle.json under ~/.fin_statement_model/templates/store/infra/ppp/v1/
. Subsequent calls with the same name but no explicit version will auto-increment (v2
, v3
, …).
Versioning rules
- Semantic suffix
_<vX>
whereX ∈ ℕ⁺
is enforced by the registry. - Duplicate (
name + version
) raisesValueError
. TemplateRegistry._resolve_next_version()
picks the highest existingv<N>
and bumps by +1.
Diff semantics
TemplateRegistry.diff()
wraps two helpers:
- compare_structure → added / removed / changed nodes
- compare_values → per-cell Δ between two graphs (optional)
The result is an immutable DiffResult
model you can pretty-print or serialise.
FAQ & troubleshooting
Symptom | Likely cause | Fix |
---|---|---|
ValueError: Checksum does not match |
Bundle file edited manually | Re-register template or delete bundle path |
No common periods to compare |
diff(include_values=True) but graphs share no periods |
Specify periods= argument |
Template 'xyz' not found |
Registry index corrupt | Remove index.json and reinstall templates |
Last updated: {{ git_commit_hash }}