Analysis methods¶
A valuation is a point estimate built on uncertain assumptions. Reviewers ask the same questions every time: which discount rate gives this price, how the value moves across a range of assumptions, what the value is in a downside case. Analysis methods answer them inside the engine, with every evaluation reproducible and every convention reported.
An analysis method does not value anything itself. It evaluates another registered method, the inner method, repeatedly, changing one or more of its decimal inputs, and reports the outputs.
| Method | Status | Question it answers |
|---|---|---|
analysis.goal_seek |
Available | Which value of one input makes one output equal a target |
analysis.sensitivity |
Available | How one output moves across a one-way or two-way table of input values |
analysis.scenarios |
Available | What the outputs are under named sets of input changes, with deltas against the base case |
analysis.tornado |
Available | Which inputs move one output the most, each varied alone between a low and a high value |
The four methods are specified in SPEC-0008, and every method of the analysis theme follows
the rules on this page. Sensitivity tables, scenarios and tornado first evaluate base_input
unchanged and report its outputs as the base case.
Composition through the registry¶
The inner method is chosen by the caller at run time, by its id: "method": "dcf.fcff". The
analysis passes base_input to the registry inside fairmark-core, exactly as an adapter
would pass it to run. The inner method parses, validates and computes as it always does, so
its own schema and its own errors apply (ADR 0010). Consequences:
- Any method works as an inner method, including methods added later, with no change to the analysis.
- The schema of an analysis method describes
base_inputonly as an object; the inner method validates it at run time. Analysis schemas do not change when an inner method does. - Every result records the inner method and the version this engine ran, in
inner_method. Reproducing a result needs that version and the engine version inmeta. - Composition is one level deep: an analysis method cannot evaluate another analysis method
(
FM-E017).
Pointers¶
Variables and outputs are named by JSON Pointers (RFC 6901), the standard notation for one location in a JSON document:
| Pointer | Refers to |
|---|---|
/wacc |
the member wacc |
/stages/0/flows/0 |
the first flow of the first stage |
/terminal/tv.gordon/growth |
the member growth of the member tv.gordon of terminal (a . needs no escape) |
/a~1b |
the member a/b (~1 stands for /, ~0 for ~) |
A variable pointer resolves in base_input; an output pointer resolves in the value of the
inner result. Both must start with / and lead to an existing decimal, given as a JSON number
or a string that parses as a decimal. A pointer that does not resolve, that uses the - token
(past the end of a list), or that leads to text, a list or an object returns FM-E015 naming
the pointer. Variable pointers are checked before any evaluation, output pointers on the first.
Only decimal inputs can vary. Integers such as a number of periods, choices such as timing
and whole lists are out of scope.
Variables are written as decimal strings¶
When the analysis sets a variable, it writes the decimal as a JSON string at the pointer and
leaves every other member of base_input unchanged. Strings keep the value exact: no binary
floating point is involved. A base input that gives a value as the number 0.1 or as the
string "0.1" produces an identical value; meta.input_hash differs, because the hash
covers the request as written.
Bounded cost¶
One request performs at most 10,000 evaluations of the inner method, the base case included.
The number of evaluations is known in advance, so a request above the cap returns FM-E016
before the first evaluation:
| Method | Evaluations |
|---|---|
| Goal seek | at most 2 + max_iterations, and max_iterations is capped at 1,000 |
| Sensitivity table | rows × columns + 1, so at most 9,999 cells (a 100 × 100 table is refused) |
| Scenarios | scenarios + 1 |
| Tornado | 2 × variables + 1 |
Failed cells and failed scenarios count against the limit like any other evaluation.
Each evaluation parses, validates and computes the whole inner input, so the cost also grows
with the size of base_input. A second bound applies: evaluations × values in base_input
(every number, string, list and object counts one) at most 1,000,000. A 21 × 21 table on a ten-year DCF
uses about 6,000 units; a 60 × 60 table on a DCF with 300 yearly flows is refused with
FM-E016, again before the first evaluation.
Determinism¶
Evaluations run one after another in a fixed, documented order: the bracket ends then the
iterations in goal seek; rows then columns in a table; scenarios and tornado variables in
declaration order, low before high. The same input and versions give the same output bit for
bit, including the order of explain.steps.
meta.input_hash is computed with the keys of base_input sorted recursively, so two requests
that differ only in key order have the same hash. See the determinism contract.
Errors of the inner method¶
An error raised by the inner method keeps its own code. In goal seek and in tornado any such error stops the request, because the search or the ranking is not defined without that value.
In sensitivity tables and scenarios the errors are per cell: a failing cell or scenario holds
{"error": {"code", "message"}} in place of its value, the others are still returned, and the
result counts them in failed_cells or failed_scenarios. A WACC × g table naturally contains
cells where growth is not below the discount rate (FM-E004), and a spreadsheet data table
shows an error in those cells too. Two errors still stop the request: an error on the base case,
which every other cell or scenario is read against, and an internal defect (FM-E012).