Skip to content

API reference

Everything the package exports from suno_valuation. Nothing outside this list is public: names that do not appear here may change without a version bump.

The whole surface is typed and the package ships py.typed, so a type checker sees these signatures directly.

Running a valuation

run_valuation(inputs: ValuationInputs) -> ValuationResult

Runs the model and returns the result. Pure: no I/O, no globals, no clock. The same inputs always produce the same output.

Raises EvalDateBeforeCommissioningError, EvalDateAfterEndOfLifeError, InvalidProjectDataError, EmptyMacroSeriesError, MacroYearMissingError, EmptySeriesError, PpaYearMissingError, UnknownIndexationIndexError or RiskScoreOutOfRangeError — all subclasses of SunoValuationError, which is itself a ValueError.

load_inputs_snapshot(snapshot: Mapping[str, Any]) -> ValuationInputs

Rebuilds a published valuation's inputs from its snapshot document — the inverse of build_inputs_snapshot, and the function that makes a published valuation checkable from outside Suno.

It never guesses. A snapshot whose snapshot_schema it does not know is refused by number rather than read on the assumption that the format did not change, and a missing or unreadable key is refused by its path rather than defaulted.

Raises:

  • UnsupportedSnapshotSchemaErrorsnapshot_schema is absent, is not an integer, or is not in SUPPORTED_SNAPSHOT_SCHEMAS.
  • MalformedSnapshotError — a supported schema missing a key the format guarantees, or carrying an unreadable value for one.

run_sensitivities(inputs: ValuationInputs, base: ValuationResult) -> list[SensitivityRow]

Runs the fixed set of sensitivity levers against a base case and returns one row per lever. This is the tornado published alongside each valuation.

Serialising a valuation

build_inputs_snapshot(inputs, ctx, sensitivities=()) -> dict[str, Any]

Produces the snapshot document: everything the engine consumed, plus the identifiers (SnapshotContext) that live only in Suno's systems. Those identifiers are why an outside reader generally consumes snapshots rather than building them.

serialize_yearly_table(result: ValuationResult) -> list[dict[str, Any]]

The complete year-by-year table as plain dictionaries, one per row, with every column the engine produced.

SNAPSHOT_SCHEMA: int

The schema version build_inputs_snapshot writes. Currently 15.

SUPPORTED_SNAPSHOT_SCHEMAS: tuple[int, ...]

The schemas load_inputs_snapshot reads: (14, 15). Schema 14 is read by defaulting local_currency to "COP", which is what every valuation published under it was denominated in.

Inputs

ValuationInputs

Frozen dataclass; everything one valuation consumes. Required fields first, then the optional ones with their defaults.

Field Type Meaning
kwp float Installed capacity.
design_prod float Specific production, kWh per kWp per day — not annual energy.
est_opex float Projected monthly O&M, local currency.
eff_y0 float Panel efficiency at year zero (fraction).
deg float Annual degradation (fraction).
lifetime_years int Useful life from commissioning.
commissioning date Commissioning date.
eval_date date Valuation date.
suno_fee float Fee as a fraction of total revenue.
rec_price float REC price in local currency (legacy route).
country_premium float Sovereign spread added to the discount rate.
macro list[MacroYear] The macro series.
ppa list[PpaPoint] The PPA price curve.
risk list[RiskDimension] Scored risk dimensions.
hist_prod_monthly list[float] Metered monthly production. Default [].
hist_opex_monthly list[float] Invoiced monthly O&M, oldest first. Default [].
ppa_indexation_index str "ppi", "inflation" or "plain". Default "ppi".
ppa_base_year int \| None Year the PPA curve's money is stated in. Default: the first point's year.
ppa_anchor_month int Month the 12-month price batches start in. Default 1.
equipment_base_year int \| None Year replacement costs are stated in. Default: the evaluation year.
additional_income_base_year int \| None Same, for additional income.
specific_prod_override float \| None Pinned specific production, replacing the blend.
opex_real_escalation float Annual real O&M growth over CPI. Default 0.0.
rec_price_usd float \| None REC price in USD; takes precedence over rec_price.
local_currency str Default "COP"; "USD" means no conversion.
equipment_replacements list[EquipmentReplacement] Replacement events. Default [].
additional_incomes list[AdditionalIncome] Additional income items. Default [].
investment_amount_usd float \| None Capital invested, for the withholding calculation.
capital_repayment_fractions dict[int, float] Repayment curve, keyed on years relative to the investment (1-based).
investment_date date \| None Anchors that curve. None means the commissioning date.
wht_rate float Withholding rate on distributions. Default 0.15.

MacroYear

year, fx_rate (local currency per USD), inflation (local CPI), ppi, fed_rate, usd_inflation (float | None; None holds the FX rate flat past the forecast and raises a warning).

PpaPoint

year, present_value — a price in the curve's base-year money.

RiskDimension

dimension, max_bps, score. max_bps is basis points per point of score, not a cap; the scale runs 0 to 2, so a dimension's effective ceiling is twice max_bps. The field name is historical.

EquipmentReplacement

year_index (1-based row), amount (today's unit cost; negative means salvage), label, quantity, currency ("LOCAL" or "USD"), usd_share, real_delta_usd, real_delta_cop, uplift, tax, contingency, probability, downtime. The defaults reproduce the legacy treatment: one unit in local currency escalating at CPI, certain, with no downtime.

AdditionalIncome

year_index (1-based), amount (local currency), concept. Several items may share a year.

Outputs

ValuationResult

npv_usd, npv_after_tax_usd, pwatt_nav_usd, currency, eval_date, specific_prod_used, monthly_opex_used, yearly_table (list[YearRow]), warnings (list[str]).

Each headline number is reconcilable from the table: npv_usd is the sum of discounted_cashflow_usd, npv_after_tax_usd the sum of discounted_net_investor_cashflow_usd, and pwatt_nav_usd the latter divided by kwp × 1,000.

YearRow

One calendar row. Carries year, year_index, time_factor, cpi_rel, cpi_index, ppi_index, discount_rate, discount_factor, discount_index, fx_rate, efficiency, generation_kwh, recs, energy_price, energy_sales_lc, rec_sales_lc, opex_lc, equipment_replacement_lc, additional_income_lc, suno_fee_lc, net_cashflow_lc, net_cashflow_usd, discounted_cashflow_usd, capital_repayment_usd, wht_usd, net_investor_cashflow_usd, discounted_net_investor_cashflow_usd and equipment_downtime.

Fields suffixed _lc are in the project's local currency; _usd ones are in USD, converted at that row's fx_rate.

SensitivityRow

key, kind ("quantile", "parameter" or "scenario"), perturbation (the exact magnitude applied), npv_usd, npv_after_tax_usd, pwatt_nav_usd, delta_npv_pct, delta_nav_pct.

SnapshotContext

Metadata build_inputs_snapshot records alongside the inputs: project and macro-set identifiers and names, country, OPEX source, the chosen capital repayment curve, and the default evaluation date. None of it enters the calculation.

Errors

Every error the package raises derives from SunoValuationError, which derives from ValueError. Catching SunoValuationError catches all of them; catching ValueError does too, for a caller that does not want to import the package's names.

Error Raised when Attributes
SunoValuationError base class — never raised directly
InvalidProjectDataError an input outside the range the model is defined on, physical or financial field, value
EvalDateBeforeCommissioningError the valuation date precedes commissioning eval_date, commissioning
EvalDateAfterEndOfLifeError the valuation date is past the end of useful life
EmptyMacroSeriesError the macro series is empty
MacroYearMissingError a year carrying cash flow has no macro coverage and precedes the series year
EmptySeriesError a trimmed mean was asked for over a series with no usable values
PpaYearMissingError a year carrying cash flow precedes the first PPA point year
UnknownIndexationIndexError ppa_indexation_index is not one of the three known values kind
RiskScoreOutOfRangeError a risk score outside the 0–2 scale score
UnsupportedSnapshotSchemaError the snapshot declares a schema the loader does not read schema
MalformedSnapshotError a supported schema is missing a key or carries a bad value key, reason

MalformedSnapshotError.key is a dotted path into the document (assumptions.suno_fee, macro[2].fx_rate), so the offending entry can be found in the JSON without reading any code.

Command line

suno-valuation is installed as a console script by the package.

suno-valuation run    SNAPSHOT [--json]
suno-valuation verify SNAPSHOT [--expect-nav X] [--expect-npv Y] [--tolerance T]

main(argv: Sequence[str] | None = None, file: TextIO | None = None) -> int is the same entry point as a Python function, returning the exit code rather than raising. Exit codes: 0 reproduced, 1 mismatch, 2 cannot be evaluated — see Reproduce a valuation.

Version

__version__ carries the installed package version. A published valuation records both the version and the commit SHA of the engine that computed it; pin the SHA, since a tag can move.