{"tools":[{"name":"run_simulation","description":"Run a Monte Carlo simulation on a supplied graph. Small runs return the full summarised result inline; large runs return a run_id to stream (POST /api/v1/runs SSE) or poll (get_results). Debits credits per run for authenticated callers.","input_schema":{"$defs":{"AggregationRequest":{"additionalProperties":false,"description":"A post-simulation, series-aware metric over one node's full series.\n\nNPV/IRR/payback/CAGR/cumsum are NOT per-period node values: they consume a\ntarget node's complete ``(time_periods x trials)`` matrix AFTER the run and\nreturn one value per trial, summarised into a distribution.\n\nDiscount convention (0-based series index): period ``t=0`` is the present\n(undiscounted) cash flow; period ``t`` is discounted by ``(1+rate)**t``. The\nfirst period is undiscounted.\n\nWarning — two discount conventions coexist; do not mix them. These\naggregations use the **0-based** series index above (first period\nundiscounted). The per-period formula functions ``DISCOUNT(rate,\n{time_period})`` and ``PV(rate, value, {time_period})`` use the **1-based**\n``{time_period}``, so they discount the first period by ``(1+rate)**1``.\n\n\"Investment at period 0\" pattern: model an up-front outlay as a negative\nperiod-0 cash flow in the target node's series; NPV's t=0-undiscounted\nconvention then treats it as the present-value outlay. Do not additionally\ndiscount that first period with ``PV(...,{time_period})`` inside the target\nnode's formula when also requesting an aggregation ``npv`` — combining the\n1-based per-period discount with the 0-based ``npv`` double-shifts the first\nperiod.\n\nPer-period node vs aggregate metric (``prob``): pointing ``prob`` at a\nmulti-period cash-flow NODE via ``target_node_id`` thresholds only that node's\nFINAL-period per-trial value — i.e. ``P(per-period value > x)``, NOT\n``P(NPV > x)``. To get a true ``P(NPV > 0)``, define an ``npv`` aggregation\nfirst, then add a ``prob`` aggregation whose ``target_aggregation_id`` points\nat that ``npv`` id; ``prob`` then thresholds the NPV per-trial vector directly.\nA ``prob`` request targets EITHER a node (``target_node_id``) OR an earlier\naggregation (``target_aggregation_id``) — exactly one, never both.","properties":{"id":{"description":"Result key for this metric (unique within the request).","title":"Id","type":"string"},"target_node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The node whose full per-period series the metric consumes. Required for every metric except a ``prob`` that targets a prior aggregation via ``target_aggregation_id``. Exactly one of ``target_node_id`` / ``target_aggregation_id`` may be set on a ``prob`` request.","title":"Target Node Id"},"target_aggregation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"For a ``prob`` request only: the id of an aggregation defined EARLIER in this output_request whose per-trial vector (e.g. an ``npv``) is thresholded. Lets ``P(NPV > 0)`` be expressed directly instead of the per-period ``P(value > x)`` you get by targeting a multi-period node. The referenced aggregation must produce a per-trial vector (npv/irr/payback/discounted_payback/cagr/cumsum/terminal_value/mirr).","title":"Target Aggregation Id"},"function":{"description":"One of: npv, irr, payback, discounted_payback, cagr, cumsum, prob, terminal_value, mirr. discounted_payback requires params.rate; npv requires params.rate OR a per-period params.rates curve; prob requires params.op (one of >, >=, <, <=, ==) and params.value; terminal_value requires params.g and params.rate; mirr requires params.finance_rate and params.reinvest_rate.","title":"Function","type":"string"},"params":{"additionalProperties":true,"description":"Function params, e.g. {'rate': 0.1} for npv/discounted_payback, {'op': '>', 'value': 0} for prob, {'g': 0.02, 'rate': 0.1} for terminal_value, {'finance_rate': 0.1, 'reinvest_rate': 0.08} for mirr.","title":"Params","type":"object"}},"required":["id","function"],"title":"AggregationRequest","type":"object"},"CalcNode":{"additionalProperties":false,"description":"A computed node. ``formula`` is OPTIONAL.\n\nA null/empty/whitespace formula is allowed: the node is treated as a\nconstant ``0`` every period/trial (incoming edges are ignored — there is no\nimplicit \"sum of incoming edges\"). ``validate_graph`` surfaces this as a\nWARNING, not an error. To sum incoming edges, write the sum explicitly,\ne.g. ``{e1}+{e2}``; for pass-through use ``{e1}``.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"formula":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional expression over incoming edge ids, e.g. '{e1} * 0.5 + {e2}'. To sum incoming edges write '{e1}+{e2}'; for pass-through use '{e1}'. If omitted/empty the node is treated as a constant 0 (sum-of-incoming is not implied); validate_graph reports this as a warning.","title":"Formula"}},"required":["id"],"title":"CalcNode","type":"object"},"Edge":{"additionalProperties":false,"description":"A directed connection carrying a value from source to target.","properties":{"id":{"description":"Unique edge id within the graph.","title":"Id","type":"string"},"source":{"description":"id of the source node.","title":"Source","type":"string"},"target":{"description":"id of the target node.","title":"Target","type":"string"},"delay":{"default":0,"description":"Periods the value is delayed in transit.","minimum":0,"title":"Delay","type":"integer"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional label.","title":"Name"}},"required":["id","source","target"],"title":"Edge","type":"object"},"OutputRequest":{"additionalProperties":false,"description":"Which nodes/edges/periods to include in the result.\n\nEmpty lists mean 'all': all nodes, all edges, all time periods.\n``aggregations`` requests post-simulation series metrics (NPV/IRR/...).","properties":{"nodes":{"items":{"type":"string"},"title":"Nodes","type":"array"},"edges":{"items":{"type":"string"},"title":"Edges","type":"array"},"time_periods":{"items":{"type":"integer"},"title":"Time Periods","type":"array"},"aggregations":{"items":{"$ref":"#/$defs/AggregationRequest"},"title":"Aggregations","type":"array"}},"title":"OutputRequest","type":"object"},"PublicGraph":{"additionalProperties":false,"description":"A complete, validated simulation request — the agent-facing input.\n\nThis is the canonical public shape. Reference integrity (edges point at real\nnodes, output_request references real ids) is validated here so malformed\ngraphs are rejected before they ever reach the engine.","properties":{"contract_version":{"const":"1.0","default":"1.0","description":"Schema version this graph targets.","title":"Contract Version","type":"string"},"root_nodes":{"items":{"$ref":"#/$defs/RootNode"},"title":"Root Nodes","type":"array"},"calc_nodes":{"items":{"$ref":"#/$defs/CalcNode"},"title":"Calc Nodes","type":"array"},"edges":{"items":{"$ref":"#/$defs/Edge"},"title":"Edges","type":"array"},"run_params":{"$ref":"#/$defs/RunParams"}},"required":["run_params"],"title":"PublicGraph","type":"object"},"RootNode":{"additionalProperties":false,"description":"A source node that samples from a probability distribution each period.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"distribution_type":{"description":"One of the catalog distributions (GET /api/v1/contract).","title":"Distribution Type","type":"string"},"distribution_params":{"additionalProperties":true,"description":"Params for the chosen distribution, e.g. {'mean': 100, 'std': 15}.","title":"Distribution Params","type":"object"},"interval":{"default":1,"description":"Sample every N periods (1 = every period).","minimum":0,"title":"Interval","type":"integer"},"delay":{"default":0,"description":"Periods to wait before the first sample.","minimum":0,"title":"Delay","type":"integer"},"sampling_frequency":{"default":"per_period","description":"'once' draws a single value reused across periods.","enum":["per_period","once"],"title":"Sampling Frequency","type":"string"}},"required":["id","distribution_type","distribution_params"],"title":"RootNode","type":"object"},"RunParams":{"additionalProperties":false,"description":"Top-level run configuration.","properties":{"trials":{"description":"Number of Monte Carlo trials.","exclusiveMinimum":0,"title":"Trials","type":"integer"},"time_periods":{"description":"Number of time periods to simulate.","exclusiveMinimum":0,"title":"Time Periods","type":"integer"},"output_request":{"$ref":"#/$defs/OutputRequest"}},"required":["trials","time_periods"],"title":"RunParams","type":"object"}},"description":"Arguments for ``run_simulation``.","properties":{"graph":{"$ref":"#/$defs/PublicGraph","description":"The full simulation graph (root_nodes, calc_nodes, edges, run_params). See GET /api/v1/contract for valid distribution types."},"seed":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"Optional RNG seed; reproducible only on the small/inline path.","title":"Seed"},"include_histogram":{"default":false,"description":"Include a per-element histogram in each summary element.","title":"Include Histogram","type":"boolean"},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Your API key from POST /api/v1/accounts. Debits credits per run. Omit to run anonymously (free, but no credit accounting).","title":"Api Key"}},"required":["graph"],"title":"RunSimulationInput","type":"object"},"output_schema":{"$defs":{"BasicStats":{"description":"Summary statistics for one element at one time period.","properties":{"mean":{"title":"Mean","type":"number"},"std":{"title":"Std","type":"number"},"min":{"title":"Min","type":"number"},"max":{"title":"Max","type":"number"}},"required":["mean","std","min","max"],"title":"BasicStats","type":"object"},"Histogram":{"description":"Optional histogram of the trial values for one element.","properties":{"counts":{"items":{"type":"integer"},"title":"Counts","type":"array"},"bins":{"items":{"type":"number"},"title":"Bins","type":"array"}},"required":["counts","bins"],"title":"Histogram","type":"object"},"Percentiles":{"description":"Distribution percentiles for one element at one time period.","properties":{"p10":{"title":"P10","type":"number"},"p25":{"title":"P25","type":"number"},"p50":{"title":"P50","type":"number"},"p75":{"title":"P75","type":"number"},"p90":{"title":"P90","type":"number"}},"required":["p10","p25","p50","p75","p90"],"title":"Percentiles","type":"object"},"SimulationResult":{"description":"The agent-facing result of a simulation run.\n\n``summary_statistics`` carries one entry per requested time period. Each\nelement is reduced to percentiles + basic stats, with optional histogram and\nraw per-trial arrays.","properties":{"contract_version":{"const":"1.0","default":"1.0","title":"Contract Version","type":"string"},"trials":{"title":"Trials","type":"integer"},"time_periods":{"title":"Time Periods","type":"integer"},"summary_statistics":{"items":{"$ref":"#/$defs/SummaryTimePeriod"},"title":"Summary Statistics","type":"array"},"aggregations":{"additionalProperties":{"$ref":"#/$defs/SummaryElement"},"description":"Post-simulation series metrics, keyed by the request id. Each is the per-trial distribution (percentiles + basic stats) of NPV/IRR/etc. Undefined trials (e.g. non-convergent IRR) are dropped from the distribution, so basic_stats reflect only the finite trials.","title":"Aggregations","type":"object"}},"required":["trials","time_periods"],"title":"SimulationResult","type":"object"},"SummaryElement":{"description":"Summarised result for a single node or edge at a single time period.","properties":{"percentiles":{"$ref":"#/$defs/Percentiles"},"basic_stats":{"$ref":"#/$defs/BasicStats"},"histogram":{"anyOf":[{"$ref":"#/$defs/Histogram"},{"type":"null"}],"default":null},"raw":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"default":null,"description":"Per-trial values; only present when raw output is requested.","title":"Raw"}},"required":["percentiles","basic_stats"],"title":"SummaryElement","type":"object"},"SummaryTimePeriod":{"description":"All summarised nodes/edges for a single time period.","properties":{"time_period":{"title":"Time Period","type":"integer"},"nodes":{"additionalProperties":{"$ref":"#/$defs/SummaryElement"},"title":"Nodes","type":"object"},"edges":{"additionalProperties":{"$ref":"#/$defs/SummaryElement"},"title":"Edges","type":"object"}},"required":["time_period"],"title":"SummaryTimePeriod","type":"object"}},"description":"Result of ``run_simulation``.\n\nBoth tiers run to completion and carry the full summarised\n``SimulationResult``. The small tier runs inline; the large tier fans across\nthe local multiprocessing pool — either way the tool blocks until the run\nfinishes (seconds) and returns the real result with aggregations.","properties":{"run_id":{"title":"Run Id","type":"string"},"status":{"description":"Always 'completed' on a 2xx response. A run that fails propagates as an error response (non-2xx + error envelope), not a 'failed' result.","title":"Status","type":"string"},"tier":{"description":"'small' or 'large'.","title":"Tier","type":"string"},"total_trials":{"title":"Total Trials","type":"integer"},"credits_debited":{"default":0,"description":"Credits charged to the account (0 for anonymous).","title":"Credits Debited","type":"integer"},"result":{"$ref":"#/$defs/SimulationResult","description":"The summarised result with aggregations."}},"required":["run_id","status","tier","total_trials","result"],"title":"RunSimulationResult","type":"object"}},{"name":"oat_sensitivity","description":"Run a one-at-a-time (OAT) tornado sensitivity analysis: for each scalar root node, sweep it over a set of quantiles (others held at baseline) and measure the target node's response. Returns a tornado-ranked list (response_curve, elasticity, rank). Debits one credit per analysis for authenticated callers.","input_schema":{"$defs":{"AggregationRequest":{"additionalProperties":false,"description":"A post-simulation, series-aware metric over one node's full series.\n\nNPV/IRR/payback/CAGR/cumsum are NOT per-period node values: they consume a\ntarget node's complete ``(time_periods x trials)`` matrix AFTER the run and\nreturn one value per trial, summarised into a distribution.\n\nDiscount convention (0-based series index): period ``t=0`` is the present\n(undiscounted) cash flow; period ``t`` is discounted by ``(1+rate)**t``. The\nfirst period is undiscounted.\n\nWarning — two discount conventions coexist; do not mix them. These\naggregations use the **0-based** series index above (first period\nundiscounted). The per-period formula functions ``DISCOUNT(rate,\n{time_period})`` and ``PV(rate, value, {time_period})`` use the **1-based**\n``{time_period}``, so they discount the first period by ``(1+rate)**1``.\n\n\"Investment at period 0\" pattern: model an up-front outlay as a negative\nperiod-0 cash flow in the target node's series; NPV's t=0-undiscounted\nconvention then treats it as the present-value outlay. Do not additionally\ndiscount that first period with ``PV(...,{time_period})`` inside the target\nnode's formula when also requesting an aggregation ``npv`` — combining the\n1-based per-period discount with the 0-based ``npv`` double-shifts the first\nperiod.\n\nPer-period node vs aggregate metric (``prob``): pointing ``prob`` at a\nmulti-period cash-flow NODE via ``target_node_id`` thresholds only that node's\nFINAL-period per-trial value — i.e. ``P(per-period value > x)``, NOT\n``P(NPV > x)``. To get a true ``P(NPV > 0)``, define an ``npv`` aggregation\nfirst, then add a ``prob`` aggregation whose ``target_aggregation_id`` points\nat that ``npv`` id; ``prob`` then thresholds the NPV per-trial vector directly.\nA ``prob`` request targets EITHER a node (``target_node_id``) OR an earlier\naggregation (``target_aggregation_id``) — exactly one, never both.","properties":{"id":{"description":"Result key for this metric (unique within the request).","title":"Id","type":"string"},"target_node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The node whose full per-period series the metric consumes. Required for every metric except a ``prob`` that targets a prior aggregation via ``target_aggregation_id``. Exactly one of ``target_node_id`` / ``target_aggregation_id`` may be set on a ``prob`` request.","title":"Target Node Id"},"target_aggregation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"For a ``prob`` request only: the id of an aggregation defined EARLIER in this output_request whose per-trial vector (e.g. an ``npv``) is thresholded. Lets ``P(NPV > 0)`` be expressed directly instead of the per-period ``P(value > x)`` you get by targeting a multi-period node. The referenced aggregation must produce a per-trial vector (npv/irr/payback/discounted_payback/cagr/cumsum/terminal_value/mirr).","title":"Target Aggregation Id"},"function":{"description":"One of: npv, irr, payback, discounted_payback, cagr, cumsum, prob, terminal_value, mirr. discounted_payback requires params.rate; npv requires params.rate OR a per-period params.rates curve; prob requires params.op (one of >, >=, <, <=, ==) and params.value; terminal_value requires params.g and params.rate; mirr requires params.finance_rate and params.reinvest_rate.","title":"Function","type":"string"},"params":{"additionalProperties":true,"description":"Function params, e.g. {'rate': 0.1} for npv/discounted_payback, {'op': '>', 'value': 0} for prob, {'g': 0.02, 'rate': 0.1} for terminal_value, {'finance_rate': 0.1, 'reinvest_rate': 0.08} for mirr.","title":"Params","type":"object"}},"required":["id","function"],"title":"AggregationRequest","type":"object"},"CalcNode":{"additionalProperties":false,"description":"A computed node. ``formula`` is OPTIONAL.\n\nA null/empty/whitespace formula is allowed: the node is treated as a\nconstant ``0`` every period/trial (incoming edges are ignored — there is no\nimplicit \"sum of incoming edges\"). ``validate_graph`` surfaces this as a\nWARNING, not an error. To sum incoming edges, write the sum explicitly,\ne.g. ``{e1}+{e2}``; for pass-through use ``{e1}``.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"formula":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional expression over incoming edge ids, e.g. '{e1} * 0.5 + {e2}'. To sum incoming edges write '{e1}+{e2}'; for pass-through use '{e1}'. If omitted/empty the node is treated as a constant 0 (sum-of-incoming is not implied); validate_graph reports this as a warning.","title":"Formula"}},"required":["id"],"title":"CalcNode","type":"object"},"Edge":{"additionalProperties":false,"description":"A directed connection carrying a value from source to target.","properties":{"id":{"description":"Unique edge id within the graph.","title":"Id","type":"string"},"source":{"description":"id of the source node.","title":"Source","type":"string"},"target":{"description":"id of the target node.","title":"Target","type":"string"},"delay":{"default":0,"description":"Periods the value is delayed in transit.","minimum":0,"title":"Delay","type":"integer"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional label.","title":"Name"}},"required":["id","source","target"],"title":"Edge","type":"object"},"OutputRequest":{"additionalProperties":false,"description":"Which nodes/edges/periods to include in the result.\n\nEmpty lists mean 'all': all nodes, all edges, all time periods.\n``aggregations`` requests post-simulation series metrics (NPV/IRR/...).","properties":{"nodes":{"items":{"type":"string"},"title":"Nodes","type":"array"},"edges":{"items":{"type":"string"},"title":"Edges","type":"array"},"time_periods":{"items":{"type":"integer"},"title":"Time Periods","type":"array"},"aggregations":{"items":{"$ref":"#/$defs/AggregationRequest"},"title":"Aggregations","type":"array"}},"title":"OutputRequest","type":"object"},"PublicGraph":{"additionalProperties":false,"description":"A complete, validated simulation request — the agent-facing input.\n\nThis is the canonical public shape. Reference integrity (edges point at real\nnodes, output_request references real ids) is validated here so malformed\ngraphs are rejected before they ever reach the engine.","properties":{"contract_version":{"const":"1.0","default":"1.0","description":"Schema version this graph targets.","title":"Contract Version","type":"string"},"root_nodes":{"items":{"$ref":"#/$defs/RootNode"},"title":"Root Nodes","type":"array"},"calc_nodes":{"items":{"$ref":"#/$defs/CalcNode"},"title":"Calc Nodes","type":"array"},"edges":{"items":{"$ref":"#/$defs/Edge"},"title":"Edges","type":"array"},"run_params":{"$ref":"#/$defs/RunParams"}},"required":["run_params"],"title":"PublicGraph","type":"object"},"RootNode":{"additionalProperties":false,"description":"A source node that samples from a probability distribution each period.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"distribution_type":{"description":"One of the catalog distributions (GET /api/v1/contract).","title":"Distribution Type","type":"string"},"distribution_params":{"additionalProperties":true,"description":"Params for the chosen distribution, e.g. {'mean': 100, 'std': 15}.","title":"Distribution Params","type":"object"},"interval":{"default":1,"description":"Sample every N periods (1 = every period).","minimum":0,"title":"Interval","type":"integer"},"delay":{"default":0,"description":"Periods to wait before the first sample.","minimum":0,"title":"Delay","type":"integer"},"sampling_frequency":{"default":"per_period","description":"'once' draws a single value reused across periods.","enum":["per_period","once"],"title":"Sampling Frequency","type":"string"}},"required":["id","distribution_type","distribution_params"],"title":"RootNode","type":"object"},"RunParams":{"additionalProperties":false,"description":"Top-level run configuration.","properties":{"trials":{"description":"Number of Monte Carlo trials.","exclusiveMinimum":0,"title":"Trials","type":"integer"},"time_periods":{"description":"Number of time periods to simulate.","exclusiveMinimum":0,"title":"Time Periods","type":"integer"},"output_request":{"$ref":"#/$defs/OutputRequest"}},"required":["trials","time_periods"],"title":"RunParams","type":"object"}},"description":"Arguments for ``oat_sensitivity`` (one-at-a-time tornado analysis).","properties":{"graph":{"$ref":"#/$defs/PublicGraph","description":"The full simulation graph to analyse (same shape as run_simulation)."},"target_node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The output node to measure. Defaults to the graph's last calc node (the usual goal node).","title":"Target Node Id"},"time_period":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"0-based time period to analyse. Defaults to the last period (time_periods - 1).","title":"Time Period"},"quantiles":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"default":null,"description":"Explicit quantiles to sweep each input over (0..1). Mutually exclusive with num_quantiles. Default: [0.05, 0.5, 0.95] (tornado).","title":"Quantiles"},"num_quantiles":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"Even count (2-10) of symmetrical quantiles around the 0.5 baseline. Mutually exclusive with quantiles.","title":"Num Quantiles"},"seed":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"Optional RNG seed for reproducible perturbed runs.","title":"Seed"},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Your API key from POST /api/v1/accounts. Debits credits per analysis. Omit to run anonymously (free).","title":"Api Key"}},"required":["graph"],"title":"OatSensitivityInput","type":"object"},"output_schema":{"$defs":{"InputVariable":{"description":"The perturbed input variable (krobar parity).","properties":{"type":{"title":"Type","type":"string"},"id":{"title":"Id","type":"string"},"baseline_value":{"title":"Baseline Value","type":"number"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Name"}},"required":["type","id","baseline_value"],"title":"InputVariable","type":"object"},"ResponseCurvePoint":{"description":"One point of a variable's response curve (krobar parity).","properties":{"quantile":{"title":"Quantile","type":"number"},"input_value":{"title":"Input Value","type":"number"},"output_value":{"title":"Output Value","type":"number"},"delta_output":{"title":"Delta Output","type":"number"},"sensitivity":{"title":"Sensitivity","type":"number"}},"required":["quantile","input_value","output_value","delta_output","sensitivity"],"title":"ResponseCurvePoint","type":"object"},"SensitivityResult":{"description":"One input variable's tornado entry (krobar parity).","properties":{"input_variable":{"$ref":"#/$defs/InputVariable"},"target_output":{"$ref":"#/$defs/TargetOutput"}},"required":["input_variable","target_output"],"title":"SensitivityResult","type":"object"},"TargetOutput":{"description":"The measured target output for one input variable (krobar parity).","properties":{"node_id":{"title":"Node Id","type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Name"},"time_period":{"title":"Time Period","type":"integer"},"baseline_value":{"title":"Baseline Value","type":"number"},"response_curve":{"items":{"$ref":"#/$defs/ResponseCurvePoint"},"title":"Response Curve","type":"array"},"elasticity":{"title":"Elasticity","type":"number"},"rank":{"title":"Rank","type":"integer"}},"required":["node_id","time_period","baseline_value","response_curve","elasticity","rank"],"title":"TargetOutput","type":"object"}},"description":"Result of ``oat_sensitivity``.\n\n``results`` is the tornado ordering: one ``SensitivityResult`` per scalar root\nnode, ranked by elasticity (rank 1 = most impactful), in rank order. Shape\nmirrors krobar-back's ``SensitivityResponse`` so the M2 gateway is a drop-in.","properties":{"target_node_id":{"title":"Target Node Id","type":"string"},"time_period":{"title":"Time Period","type":"integer"},"quantiles":{"items":{"type":"number"},"title":"Quantiles","type":"array"},"runs_executed":{"description":"Simulation runs the analysis cost (1 baseline + roots×quantiles).","title":"Runs Executed","type":"integer"},"credits_debited":{"default":0,"description":"Credits charged to the account (0 for anonymous).","title":"Credits Debited","type":"integer"},"results":{"items":{"$ref":"#/$defs/SensitivityResult"},"title":"Results","type":"array"}},"required":["target_node_id","time_period","quantiles","runs_executed"],"title":"OatSensitivityResult","type":"object"}},{"name":"scenario_analysis","description":"Best/median/worst scenario summary for a target node across trials. Runs the simulation once (reusing the run pipeline) and returns, per requested percentile (default worst p0 / median p50 / best p100), the target node's output value plus a best-effort per-root driving input. Debits one credit per analysis for authenticated callers.","input_schema":{"$defs":{"AggregationRequest":{"additionalProperties":false,"description":"A post-simulation, series-aware metric over one node's full series.\n\nNPV/IRR/payback/CAGR/cumsum are NOT per-period node values: they consume a\ntarget node's complete ``(time_periods x trials)`` matrix AFTER the run and\nreturn one value per trial, summarised into a distribution.\n\nDiscount convention (0-based series index): period ``t=0`` is the present\n(undiscounted) cash flow; period ``t`` is discounted by ``(1+rate)**t``. The\nfirst period is undiscounted.\n\nWarning — two discount conventions coexist; do not mix them. These\naggregations use the **0-based** series index above (first period\nundiscounted). The per-period formula functions ``DISCOUNT(rate,\n{time_period})`` and ``PV(rate, value, {time_period})`` use the **1-based**\n``{time_period}``, so they discount the first period by ``(1+rate)**1``.\n\n\"Investment at period 0\" pattern: model an up-front outlay as a negative\nperiod-0 cash flow in the target node's series; NPV's t=0-undiscounted\nconvention then treats it as the present-value outlay. Do not additionally\ndiscount that first period with ``PV(...,{time_period})`` inside the target\nnode's formula when also requesting an aggregation ``npv`` — combining the\n1-based per-period discount with the 0-based ``npv`` double-shifts the first\nperiod.\n\nPer-period node vs aggregate metric (``prob``): pointing ``prob`` at a\nmulti-period cash-flow NODE via ``target_node_id`` thresholds only that node's\nFINAL-period per-trial value — i.e. ``P(per-period value > x)``, NOT\n``P(NPV > x)``. To get a true ``P(NPV > 0)``, define an ``npv`` aggregation\nfirst, then add a ``prob`` aggregation whose ``target_aggregation_id`` points\nat that ``npv`` id; ``prob`` then thresholds the NPV per-trial vector directly.\nA ``prob`` request targets EITHER a node (``target_node_id``) OR an earlier\naggregation (``target_aggregation_id``) — exactly one, never both.","properties":{"id":{"description":"Result key for this metric (unique within the request).","title":"Id","type":"string"},"target_node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The node whose full per-period series the metric consumes. Required for every metric except a ``prob`` that targets a prior aggregation via ``target_aggregation_id``. Exactly one of ``target_node_id`` / ``target_aggregation_id`` may be set on a ``prob`` request.","title":"Target Node Id"},"target_aggregation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"For a ``prob`` request only: the id of an aggregation defined EARLIER in this output_request whose per-trial vector (e.g. an ``npv``) is thresholded. Lets ``P(NPV > 0)`` be expressed directly instead of the per-period ``P(value > x)`` you get by targeting a multi-period node. The referenced aggregation must produce a per-trial vector (npv/irr/payback/discounted_payback/cagr/cumsum/terminal_value/mirr).","title":"Target Aggregation Id"},"function":{"description":"One of: npv, irr, payback, discounted_payback, cagr, cumsum, prob, terminal_value, mirr. discounted_payback requires params.rate; npv requires params.rate OR a per-period params.rates curve; prob requires params.op (one of >, >=, <, <=, ==) and params.value; terminal_value requires params.g and params.rate; mirr requires params.finance_rate and params.reinvest_rate.","title":"Function","type":"string"},"params":{"additionalProperties":true,"description":"Function params, e.g. {'rate': 0.1} for npv/discounted_payback, {'op': '>', 'value': 0} for prob, {'g': 0.02, 'rate': 0.1} for terminal_value, {'finance_rate': 0.1, 'reinvest_rate': 0.08} for mirr.","title":"Params","type":"object"}},"required":["id","function"],"title":"AggregationRequest","type":"object"},"CalcNode":{"additionalProperties":false,"description":"A computed node. ``formula`` is OPTIONAL.\n\nA null/empty/whitespace formula is allowed: the node is treated as a\nconstant ``0`` every period/trial (incoming edges are ignored — there is no\nimplicit \"sum of incoming edges\"). ``validate_graph`` surfaces this as a\nWARNING, not an error. To sum incoming edges, write the sum explicitly,\ne.g. ``{e1}+{e2}``; for pass-through use ``{e1}``.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"formula":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional expression over incoming edge ids, e.g. '{e1} * 0.5 + {e2}'. To sum incoming edges write '{e1}+{e2}'; for pass-through use '{e1}'. If omitted/empty the node is treated as a constant 0 (sum-of-incoming is not implied); validate_graph reports this as a warning.","title":"Formula"}},"required":["id"],"title":"CalcNode","type":"object"},"Edge":{"additionalProperties":false,"description":"A directed connection carrying a value from source to target.","properties":{"id":{"description":"Unique edge id within the graph.","title":"Id","type":"string"},"source":{"description":"id of the source node.","title":"Source","type":"string"},"target":{"description":"id of the target node.","title":"Target","type":"string"},"delay":{"default":0,"description":"Periods the value is delayed in transit.","minimum":0,"title":"Delay","type":"integer"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional label.","title":"Name"}},"required":["id","source","target"],"title":"Edge","type":"object"},"OutputRequest":{"additionalProperties":false,"description":"Which nodes/edges/periods to include in the result.\n\nEmpty lists mean 'all': all nodes, all edges, all time periods.\n``aggregations`` requests post-simulation series metrics (NPV/IRR/...).","properties":{"nodes":{"items":{"type":"string"},"title":"Nodes","type":"array"},"edges":{"items":{"type":"string"},"title":"Edges","type":"array"},"time_periods":{"items":{"type":"integer"},"title":"Time Periods","type":"array"},"aggregations":{"items":{"$ref":"#/$defs/AggregationRequest"},"title":"Aggregations","type":"array"}},"title":"OutputRequest","type":"object"},"PublicGraph":{"additionalProperties":false,"description":"A complete, validated simulation request — the agent-facing input.\n\nThis is the canonical public shape. Reference integrity (edges point at real\nnodes, output_request references real ids) is validated here so malformed\ngraphs are rejected before they ever reach the engine.","properties":{"contract_version":{"const":"1.0","default":"1.0","description":"Schema version this graph targets.","title":"Contract Version","type":"string"},"root_nodes":{"items":{"$ref":"#/$defs/RootNode"},"title":"Root Nodes","type":"array"},"calc_nodes":{"items":{"$ref":"#/$defs/CalcNode"},"title":"Calc Nodes","type":"array"},"edges":{"items":{"$ref":"#/$defs/Edge"},"title":"Edges","type":"array"},"run_params":{"$ref":"#/$defs/RunParams"}},"required":["run_params"],"title":"PublicGraph","type":"object"},"RootNode":{"additionalProperties":false,"description":"A source node that samples from a probability distribution each period.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"distribution_type":{"description":"One of the catalog distributions (GET /api/v1/contract).","title":"Distribution Type","type":"string"},"distribution_params":{"additionalProperties":true,"description":"Params for the chosen distribution, e.g. {'mean': 100, 'std': 15}.","title":"Distribution Params","type":"object"},"interval":{"default":1,"description":"Sample every N periods (1 = every period).","minimum":0,"title":"Interval","type":"integer"},"delay":{"default":0,"description":"Periods to wait before the first sample.","minimum":0,"title":"Delay","type":"integer"},"sampling_frequency":{"default":"per_period","description":"'once' draws a single value reused across periods.","enum":["per_period","once"],"title":"Sampling Frequency","type":"string"}},"required":["id","distribution_type","distribution_params"],"title":"RootNode","type":"object"},"RunParams":{"additionalProperties":false,"description":"Top-level run configuration.","properties":{"trials":{"description":"Number of Monte Carlo trials.","exclusiveMinimum":0,"title":"Trials","type":"integer"},"time_periods":{"description":"Number of time periods to simulate.","exclusiveMinimum":0,"title":"Time Periods","type":"integer"},"output_request":{"$ref":"#/$defs/OutputRequest"}},"required":["trials","time_periods"],"title":"RunParams","type":"object"}},"description":"Arguments for ``scenario_analysis`` (best/median/worst across trials).","properties":{"graph":{"$ref":"#/$defs/PublicGraph","description":"The full simulation graph (same shape as run_simulation)."},"target_node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The output node to summarise. Defaults to the graph's last calc node (the usual goal node).","title":"Target Node Id"},"time_period":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"0-based time period to analyse. Defaults to the last period (time_periods - 1).","title":"Time Period"},"percentiles":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"default":null,"description":"Scenario percentiles in [0,1]. Default [0.0, 0.5, 1.0] = worst / median / best.","title":"Percentiles"},"seed":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"Optional RNG seed for a reproducible run.","title":"Seed"},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Your API key from POST /api/v1/accounts. Debits credits per analysis (priced like one run). Omit to run anonymously (free).","title":"Api Key"}},"required":["graph"],"title":"ScenarioAnalysisInput","type":"object"},"output_schema":{"$defs":{"DrivingInput":{"description":"A best-effort driving input value for a scenario.","properties":{"node_id":{"title":"Node Id","type":"string"},"value":{"description":"The root node's own distribution value at the scenario percentile (best-effort marginal driver; exact for monotone single-input graphs).","title":"Value","type":"number"}},"required":["node_id","value"],"title":"DrivingInput","type":"object"},"ScenarioCase":{"description":"One scenario: the target's outcome at a percentile + its driving inputs.","properties":{"label":{"description":"'worst' | 'median' | 'best' | 'p<N>'.","title":"Label","type":"string"},"percentile":{"title":"Percentile","type":"number"},"output_value":{"description":"The target node's value at this percentile of its distribution.","title":"Output Value","type":"number"},"driving_inputs":{"items":{"$ref":"#/$defs/DrivingInput"},"title":"Driving Inputs","type":"array"}},"required":["label","percentile","output_value"],"title":"ScenarioCase","type":"object"}},"description":"Result of ``scenario_analysis``.\n\n``scenarios`` is ascending by percentile (worst first). ``output_value`` for\np0/p100 is the exact min/max; interior percentiles are histogram-estimated\n(same estimate the public SimulationResult exposes). ``driving_inputs`` are a\nbest-effort per-root marginal reconstruction — the bounded aggregate discards\nraw per-trial arrays so the exact driving tuple is not recoverable.","properties":{"target_node_id":{"title":"Target Node Id","type":"string"},"time_period":{"title":"Time Period","type":"integer"},"trials":{"title":"Trials","type":"integer"},"runs_executed":{"description":"Simulation runs the analysis cost (always 1 — one run reused).","title":"Runs Executed","type":"integer"},"credits_debited":{"default":0,"description":"Credits charged to the account (0 for anonymous).","title":"Credits Debited","type":"integer"},"scenarios":{"items":{"$ref":"#/$defs/ScenarioCase"},"title":"Scenarios","type":"array"}},"required":["target_node_id","time_period","trials","runs_executed"],"title":"ScenarioAnalysisResult","type":"object"}},{"name":"validate_graph","description":"Validate a graph with the engine's ModularGraphValidator and return a structured list of issues (rule, severity, affected elements).","input_schema":{"$defs":{"AggregationRequest":{"additionalProperties":false,"description":"A post-simulation, series-aware metric over one node's full series.\n\nNPV/IRR/payback/CAGR/cumsum are NOT per-period node values: they consume a\ntarget node's complete ``(time_periods x trials)`` matrix AFTER the run and\nreturn one value per trial, summarised into a distribution.\n\nDiscount convention (0-based series index): period ``t=0`` is the present\n(undiscounted) cash flow; period ``t`` is discounted by ``(1+rate)**t``. The\nfirst period is undiscounted.\n\nWarning — two discount conventions coexist; do not mix them. These\naggregations use the **0-based** series index above (first period\nundiscounted). The per-period formula functions ``DISCOUNT(rate,\n{time_period})`` and ``PV(rate, value, {time_period})`` use the **1-based**\n``{time_period}``, so they discount the first period by ``(1+rate)**1``.\n\n\"Investment at period 0\" pattern: model an up-front outlay as a negative\nperiod-0 cash flow in the target node's series; NPV's t=0-undiscounted\nconvention then treats it as the present-value outlay. Do not additionally\ndiscount that first period with ``PV(...,{time_period})`` inside the target\nnode's formula when also requesting an aggregation ``npv`` — combining the\n1-based per-period discount with the 0-based ``npv`` double-shifts the first\nperiod.\n\nPer-period node vs aggregate metric (``prob``): pointing ``prob`` at a\nmulti-period cash-flow NODE via ``target_node_id`` thresholds only that node's\nFINAL-period per-trial value — i.e. ``P(per-period value > x)``, NOT\n``P(NPV > x)``. To get a true ``P(NPV > 0)``, define an ``npv`` aggregation\nfirst, then add a ``prob`` aggregation whose ``target_aggregation_id`` points\nat that ``npv`` id; ``prob`` then thresholds the NPV per-trial vector directly.\nA ``prob`` request targets EITHER a node (``target_node_id``) OR an earlier\naggregation (``target_aggregation_id``) — exactly one, never both.","properties":{"id":{"description":"Result key for this metric (unique within the request).","title":"Id","type":"string"},"target_node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The node whose full per-period series the metric consumes. Required for every metric except a ``prob`` that targets a prior aggregation via ``target_aggregation_id``. Exactly one of ``target_node_id`` / ``target_aggregation_id`` may be set on a ``prob`` request.","title":"Target Node Id"},"target_aggregation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"For a ``prob`` request only: the id of an aggregation defined EARLIER in this output_request whose per-trial vector (e.g. an ``npv``) is thresholded. Lets ``P(NPV > 0)`` be expressed directly instead of the per-period ``P(value > x)`` you get by targeting a multi-period node. The referenced aggregation must produce a per-trial vector (npv/irr/payback/discounted_payback/cagr/cumsum/terminal_value/mirr).","title":"Target Aggregation Id"},"function":{"description":"One of: npv, irr, payback, discounted_payback, cagr, cumsum, prob, terminal_value, mirr. discounted_payback requires params.rate; npv requires params.rate OR a per-period params.rates curve; prob requires params.op (one of >, >=, <, <=, ==) and params.value; terminal_value requires params.g and params.rate; mirr requires params.finance_rate and params.reinvest_rate.","title":"Function","type":"string"},"params":{"additionalProperties":true,"description":"Function params, e.g. {'rate': 0.1} for npv/discounted_payback, {'op': '>', 'value': 0} for prob, {'g': 0.02, 'rate': 0.1} for terminal_value, {'finance_rate': 0.1, 'reinvest_rate': 0.08} for mirr.","title":"Params","type":"object"}},"required":["id","function"],"title":"AggregationRequest","type":"object"},"CalcNode":{"additionalProperties":false,"description":"A computed node. ``formula`` is OPTIONAL.\n\nA null/empty/whitespace formula is allowed: the node is treated as a\nconstant ``0`` every period/trial (incoming edges are ignored — there is no\nimplicit \"sum of incoming edges\"). ``validate_graph`` surfaces this as a\nWARNING, not an error. To sum incoming edges, write the sum explicitly,\ne.g. ``{e1}+{e2}``; for pass-through use ``{e1}``.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"formula":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional expression over incoming edge ids, e.g. '{e1} * 0.5 + {e2}'. To sum incoming edges write '{e1}+{e2}'; for pass-through use '{e1}'. If omitted/empty the node is treated as a constant 0 (sum-of-incoming is not implied); validate_graph reports this as a warning.","title":"Formula"}},"required":["id"],"title":"CalcNode","type":"object"},"Edge":{"additionalProperties":false,"description":"A directed connection carrying a value from source to target.","properties":{"id":{"description":"Unique edge id within the graph.","title":"Id","type":"string"},"source":{"description":"id of the source node.","title":"Source","type":"string"},"target":{"description":"id of the target node.","title":"Target","type":"string"},"delay":{"default":0,"description":"Periods the value is delayed in transit.","minimum":0,"title":"Delay","type":"integer"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional label.","title":"Name"}},"required":["id","source","target"],"title":"Edge","type":"object"},"OutputRequest":{"additionalProperties":false,"description":"Which nodes/edges/periods to include in the result.\n\nEmpty lists mean 'all': all nodes, all edges, all time periods.\n``aggregations`` requests post-simulation series metrics (NPV/IRR/...).","properties":{"nodes":{"items":{"type":"string"},"title":"Nodes","type":"array"},"edges":{"items":{"type":"string"},"title":"Edges","type":"array"},"time_periods":{"items":{"type":"integer"},"title":"Time Periods","type":"array"},"aggregations":{"items":{"$ref":"#/$defs/AggregationRequest"},"title":"Aggregations","type":"array"}},"title":"OutputRequest","type":"object"},"PublicGraph":{"additionalProperties":false,"description":"A complete, validated simulation request — the agent-facing input.\n\nThis is the canonical public shape. Reference integrity (edges point at real\nnodes, output_request references real ids) is validated here so malformed\ngraphs are rejected before they ever reach the engine.","properties":{"contract_version":{"const":"1.0","default":"1.0","description":"Schema version this graph targets.","title":"Contract Version","type":"string"},"root_nodes":{"items":{"$ref":"#/$defs/RootNode"},"title":"Root Nodes","type":"array"},"calc_nodes":{"items":{"$ref":"#/$defs/CalcNode"},"title":"Calc Nodes","type":"array"},"edges":{"items":{"$ref":"#/$defs/Edge"},"title":"Edges","type":"array"},"run_params":{"$ref":"#/$defs/RunParams"}},"required":["run_params"],"title":"PublicGraph","type":"object"},"RootNode":{"additionalProperties":false,"description":"A source node that samples from a probability distribution each period.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"distribution_type":{"description":"One of the catalog distributions (GET /api/v1/contract).","title":"Distribution Type","type":"string"},"distribution_params":{"additionalProperties":true,"description":"Params for the chosen distribution, e.g. {'mean': 100, 'std': 15}.","title":"Distribution Params","type":"object"},"interval":{"default":1,"description":"Sample every N periods (1 = every period).","minimum":0,"title":"Interval","type":"integer"},"delay":{"default":0,"description":"Periods to wait before the first sample.","minimum":0,"title":"Delay","type":"integer"},"sampling_frequency":{"default":"per_period","description":"'once' draws a single value reused across periods.","enum":["per_period","once"],"title":"Sampling Frequency","type":"string"}},"required":["id","distribution_type","distribution_params"],"title":"RootNode","type":"object"},"RunParams":{"additionalProperties":false,"description":"Top-level run configuration.","properties":{"trials":{"description":"Number of Monte Carlo trials.","exclusiveMinimum":0,"title":"Trials","type":"integer"},"time_periods":{"description":"Number of time periods to simulate.","exclusiveMinimum":0,"title":"Time Periods","type":"integer"},"output_request":{"$ref":"#/$defs/OutputRequest"}},"required":["trials","time_periods"],"title":"RunParams","type":"object"}},"description":"Arguments for ``validate_graph``.","properties":{"graph":{"$ref":"#/$defs/PublicGraph","description":"The graph to validate."},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional API key (validation is free).","title":"Api Key"}},"required":["graph"],"title":"ValidateGraphInput","type":"object"},"output_schema":{"$defs":{"ValidationIssue":{"description":"One structured validation finding.","properties":{"rule":{"title":"Rule","type":"string"},"severity":{"title":"Severity","type":"string"},"message":{"title":"Message","type":"string"},"nodes":{"items":{"type":"string"},"title":"Nodes","type":"array"},"edges":{"items":{"type":"string"},"title":"Edges","type":"array"}},"required":["rule","severity","message"],"title":"ValidationIssue","type":"object"}},"description":"Structured result of ``validate_graph``.","properties":{"is_valid":{"title":"Is Valid","type":"boolean"},"issues":{"items":{"$ref":"#/$defs/ValidationIssue"},"title":"Issues","type":"array"}},"required":["is_valid"],"title":"ValidateGraphResult","type":"object"}},{"name":"estimate","description":"Pre-flight runtime/memory estimate for a graph without running it: estimated peak memory, complexity, the tier it would run as, and the credit cost.","input_schema":{"$defs":{"AggregationRequest":{"additionalProperties":false,"description":"A post-simulation, series-aware metric over one node's full series.\n\nNPV/IRR/payback/CAGR/cumsum are NOT per-period node values: they consume a\ntarget node's complete ``(time_periods x trials)`` matrix AFTER the run and\nreturn one value per trial, summarised into a distribution.\n\nDiscount convention (0-based series index): period ``t=0`` is the present\n(undiscounted) cash flow; period ``t`` is discounted by ``(1+rate)**t``. The\nfirst period is undiscounted.\n\nWarning — two discount conventions coexist; do not mix them. These\naggregations use the **0-based** series index above (first period\nundiscounted). The per-period formula functions ``DISCOUNT(rate,\n{time_period})`` and ``PV(rate, value, {time_period})`` use the **1-based**\n``{time_period}``, so they discount the first period by ``(1+rate)**1``.\n\n\"Investment at period 0\" pattern: model an up-front outlay as a negative\nperiod-0 cash flow in the target node's series; NPV's t=0-undiscounted\nconvention then treats it as the present-value outlay. Do not additionally\ndiscount that first period with ``PV(...,{time_period})`` inside the target\nnode's formula when also requesting an aggregation ``npv`` — combining the\n1-based per-period discount with the 0-based ``npv`` double-shifts the first\nperiod.\n\nPer-period node vs aggregate metric (``prob``): pointing ``prob`` at a\nmulti-period cash-flow NODE via ``target_node_id`` thresholds only that node's\nFINAL-period per-trial value — i.e. ``P(per-period value > x)``, NOT\n``P(NPV > x)``. To get a true ``P(NPV > 0)``, define an ``npv`` aggregation\nfirst, then add a ``prob`` aggregation whose ``target_aggregation_id`` points\nat that ``npv`` id; ``prob`` then thresholds the NPV per-trial vector directly.\nA ``prob`` request targets EITHER a node (``target_node_id``) OR an earlier\naggregation (``target_aggregation_id``) — exactly one, never both.","properties":{"id":{"description":"Result key for this metric (unique within the request).","title":"Id","type":"string"},"target_node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The node whose full per-period series the metric consumes. Required for every metric except a ``prob`` that targets a prior aggregation via ``target_aggregation_id``. Exactly one of ``target_node_id`` / ``target_aggregation_id`` may be set on a ``prob`` request.","title":"Target Node Id"},"target_aggregation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"For a ``prob`` request only: the id of an aggregation defined EARLIER in this output_request whose per-trial vector (e.g. an ``npv``) is thresholded. Lets ``P(NPV > 0)`` be expressed directly instead of the per-period ``P(value > x)`` you get by targeting a multi-period node. The referenced aggregation must produce a per-trial vector (npv/irr/payback/discounted_payback/cagr/cumsum/terminal_value/mirr).","title":"Target Aggregation Id"},"function":{"description":"One of: npv, irr, payback, discounted_payback, cagr, cumsum, prob, terminal_value, mirr. discounted_payback requires params.rate; npv requires params.rate OR a per-period params.rates curve; prob requires params.op (one of >, >=, <, <=, ==) and params.value; terminal_value requires params.g and params.rate; mirr requires params.finance_rate and params.reinvest_rate.","title":"Function","type":"string"},"params":{"additionalProperties":true,"description":"Function params, e.g. {'rate': 0.1} for npv/discounted_payback, {'op': '>', 'value': 0} for prob, {'g': 0.02, 'rate': 0.1} for terminal_value, {'finance_rate': 0.1, 'reinvest_rate': 0.08} for mirr.","title":"Params","type":"object"}},"required":["id","function"],"title":"AggregationRequest","type":"object"},"CalcNode":{"additionalProperties":false,"description":"A computed node. ``formula`` is OPTIONAL.\n\nA null/empty/whitespace formula is allowed: the node is treated as a\nconstant ``0`` every period/trial (incoming edges are ignored — there is no\nimplicit \"sum of incoming edges\"). ``validate_graph`` surfaces this as a\nWARNING, not an error. To sum incoming edges, write the sum explicitly,\ne.g. ``{e1}+{e2}``; for pass-through use ``{e1}``.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"formula":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional expression over incoming edge ids, e.g. '{e1} * 0.5 + {e2}'. To sum incoming edges write '{e1}+{e2}'; for pass-through use '{e1}'. If omitted/empty the node is treated as a constant 0 (sum-of-incoming is not implied); validate_graph reports this as a warning.","title":"Formula"}},"required":["id"],"title":"CalcNode","type":"object"},"Edge":{"additionalProperties":false,"description":"A directed connection carrying a value from source to target.","properties":{"id":{"description":"Unique edge id within the graph.","title":"Id","type":"string"},"source":{"description":"id of the source node.","title":"Source","type":"string"},"target":{"description":"id of the target node.","title":"Target","type":"string"},"delay":{"default":0,"description":"Periods the value is delayed in transit.","minimum":0,"title":"Delay","type":"integer"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional label.","title":"Name"}},"required":["id","source","target"],"title":"Edge","type":"object"},"OutputRequest":{"additionalProperties":false,"description":"Which nodes/edges/periods to include in the result.\n\nEmpty lists mean 'all': all nodes, all edges, all time periods.\n``aggregations`` requests post-simulation series metrics (NPV/IRR/...).","properties":{"nodes":{"items":{"type":"string"},"title":"Nodes","type":"array"},"edges":{"items":{"type":"string"},"title":"Edges","type":"array"},"time_periods":{"items":{"type":"integer"},"title":"Time Periods","type":"array"},"aggregations":{"items":{"$ref":"#/$defs/AggregationRequest"},"title":"Aggregations","type":"array"}},"title":"OutputRequest","type":"object"},"PublicGraph":{"additionalProperties":false,"description":"A complete, validated simulation request — the agent-facing input.\n\nThis is the canonical public shape. Reference integrity (edges point at real\nnodes, output_request references real ids) is validated here so malformed\ngraphs are rejected before they ever reach the engine.","properties":{"contract_version":{"const":"1.0","default":"1.0","description":"Schema version this graph targets.","title":"Contract Version","type":"string"},"root_nodes":{"items":{"$ref":"#/$defs/RootNode"},"title":"Root Nodes","type":"array"},"calc_nodes":{"items":{"$ref":"#/$defs/CalcNode"},"title":"Calc Nodes","type":"array"},"edges":{"items":{"$ref":"#/$defs/Edge"},"title":"Edges","type":"array"},"run_params":{"$ref":"#/$defs/RunParams"}},"required":["run_params"],"title":"PublicGraph","type":"object"},"RootNode":{"additionalProperties":false,"description":"A source node that samples from a probability distribution each period.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"distribution_type":{"description":"One of the catalog distributions (GET /api/v1/contract).","title":"Distribution Type","type":"string"},"distribution_params":{"additionalProperties":true,"description":"Params for the chosen distribution, e.g. {'mean': 100, 'std': 15}.","title":"Distribution Params","type":"object"},"interval":{"default":1,"description":"Sample every N periods (1 = every period).","minimum":0,"title":"Interval","type":"integer"},"delay":{"default":0,"description":"Periods to wait before the first sample.","minimum":0,"title":"Delay","type":"integer"},"sampling_frequency":{"default":"per_period","description":"'once' draws a single value reused across periods.","enum":["per_period","once"],"title":"Sampling Frequency","type":"string"}},"required":["id","distribution_type","distribution_params"],"title":"RootNode","type":"object"},"RunParams":{"additionalProperties":false,"description":"Top-level run configuration.","properties":{"trials":{"description":"Number of Monte Carlo trials.","exclusiveMinimum":0,"title":"Trials","type":"integer"},"time_periods":{"description":"Number of time periods to simulate.","exclusiveMinimum":0,"title":"Time Periods","type":"integer"},"output_request":{"$ref":"#/$defs/OutputRequest"}},"required":["trials","time_periods"],"title":"RunParams","type":"object"}},"description":"Arguments for ``estimate`` (runtime/memory pre-flight).","properties":{"graph":{"$ref":"#/$defs/PublicGraph","description":"The graph to size."},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional API key (estimation is free).","title":"Api Key"}},"required":["graph"],"title":"EstimateInput","type":"object"},"output_schema":{"description":"Pre-flight runtime/memory estimate.","properties":{"trials":{"title":"Trials","type":"integer"},"time_periods":{"title":"Time Periods","type":"integer"},"node_count":{"title":"Node Count","type":"integer"},"edge_count":{"title":"Edge Count","type":"integer"},"estimated_memory_mb":{"description":"Estimated peak RSS increase, MB.","title":"Estimated Memory Mb","type":"number"},"graph_complexity":{"description":"Engine complexity score.","title":"Graph Complexity","type":"integer"},"tier":{"description":"The tier this run would execute as.","title":"Tier","type":"string"},"credit_cost":{"description":"Credits an authenticated run would cost.","title":"Credit Cost","type":"integer"}},"required":["trials","time_periods","node_count","edge_count","estimated_memory_mb","graph_complexity","tier","credit_cost"],"title":"EstimateResult","type":"object"}},{"name":"solve_distribution_parameters","description":"Solve distribution parameters from a target min/max range and confidence so a human-style estimate becomes engine-ready distribution_params.","input_schema":{"description":"Arguments for ``solve_distribution_parameters``.\n\nFind distribution parameters that fit a min/max range at a given confidence,\nso an agent can turn a human-style 'between X and Y' estimate into engine-ready\n``distribution_params``.","properties":{"distribution_type":{"description":"A catalog distribution, e.g. 'normal', 'triangle', 'beta'.","title":"Distribution Type","type":"string"},"min_estimate":{"anyOf":[{"type":"number"},{"type":"null"}],"default":null,"description":"Low end of the range (required for most types).","title":"Min Estimate"},"max_estimate":{"anyOf":[{"type":"number"},{"type":"null"}],"default":null,"description":"High end of the range (required for most types).","title":"Max Estimate"},"percentile_range":{"default":0.9,"description":"Confidence the [min,max] interval covers, e.g. 0.9 for 90%.","exclusiveMaximum":1.0,"exclusiveMinimum":0.0,"title":"Percentile Range","type":"number"},"mode_estimate":{"anyOf":[{"type":"number"},{"type":"null"}],"default":null,"description":"Most-likely value; required for constant/bernoulli/binomial.","title":"Mode Estimate"},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional API key (solving is free).","title":"Api Key"}},"required":["distribution_type"],"title":"SolveDistributionInput","type":"object"},"output_schema":{"description":"Solved parameters, ready to drop into a root node's distribution_params.","properties":{"distribution_type":{"title":"Distribution Type","type":"string"},"parameters":{"additionalProperties":true,"title":"Parameters","type":"object"},"input":{"additionalProperties":true,"title":"Input","type":"object"}},"required":["distribution_type","parameters","input"],"title":"SolveDistributionResult","type":"object"}},{"name":"get_results","description":"Fetch the status and summary for a previously-submitted run.","input_schema":{"description":"Arguments for ``get_results``.","properties":{"run_id":{"description":"A run id returned by run_simulation.","title":"Run Id","type":"string"}},"required":["run_id"],"title":"GetResultsInput","type":"object"},"output_schema":{"description":"Status + (when available) summary for a previously-submitted run.","properties":{"run_id":{"title":"Run Id","type":"string"},"status":{"title":"Status","type":"string"},"tier":{"title":"Tier","type":"string"},"total_trials":{"title":"Total Trials","type":"integer"},"trials_completed":{"title":"Trials Completed","type":"integer"},"created_at":{"title":"Created At","type":"string"},"summary":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"title":"Summary"},"error":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"default":null,"title":"Error"}},"required":["run_id","status","tier","total_trials","trials_completed","created_at"],"title":"GetResultsResult","type":"object"}},{"name":"feedback","description":"Send feedback, a feature request, or a bug report to the Neumann team. Emails the submission to the team and CCs your sender_email so you keep a copy. Optionally pass api_key to attach your account id for context; anonymous submissions are allowed.","input_schema":{"description":"Arguments for ``feedback`` (neumann#114; mirrors fermi's schema).","properties":{"feedback_type":{"description":"The kind of feedback: 'feedback' for general comments, 'feature_request' for new capabilities, 'bug' for issues.","enum":["feedback","feature_request","bug"],"title":"Feedback Type","type":"string"},"message":{"description":"Your feedback or request. Be as specific as possible.","maxLength":5000,"minLength":10,"title":"Message","type":"string"},"sender_email":{"description":"Your email address. A copy of your feedback is CC'd here so you have a record of what you submitted.","format":"email","title":"Sender Email","type":"string"},"sender_name":{"anyOf":[{"maxLength":200,"type":"string"},{"type":"null"}],"default":null,"description":"Your name (optional).","title":"Sender Name"},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Your API key from POST /api/v1/accounts (optional). If provided, your account id is attached to the message for context.","title":"Api Key"}},"required":["feedback_type","message","sender_email"],"title":"FeedbackInput","type":"object"},"output_schema":{"description":"Result of ``feedback``.","properties":{"status":{"description":"'sent' on success.","title":"Status","type":"string"},"message":{"description":"Confirmation message.","title":"Message","type":"string"}},"required":["status","message"],"title":"FeedbackResult","type":"object"}}]}