> ## Documentation Index
> Fetch the complete documentation index at: https://docs.models.dunsky.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SAM solar and storage calculation specification

> Specify SAM customer cash flows, solar compensation, storage applications, financial response curves, adoption, competition and deployment outputs.

SAM evaluates solar, solar-plus-storage and standalone storage scenarios by customer segment. Its active sequence imports workbook data, builds scenarios, calculates customer economics, estimates adoption, competes options and writes deployment results.

## Input and indexing contracts

The workbook's `Scenarios` sheet selects segments. Technology sheets define system sizes, CAPEX, OPEX, maximum markets, incentives, profiles, financing and adoption parameters. Parameter sheets use `Input`; annual sheets use `Year`. `Sector Selector` and the newer storage-incentive sheets have optional handling; absence of other directly indexed sheets or required segment columns raises lookup errors.

Each acquisition year has a lifetime cash-flow column, including time zero. Forecast arrays include an initial installed-system observation plus subsequent adoption years. The number of forecast positions, project-life rows and tariff years must align; tariff and cost series must cover late purchases' lifetime slices.

| Quantity | Contract |
| - | - |
| PV size | kW, multiplied by the matching generation profile |
| Storage size and duration | kW and hours; their product is kWh |
| PV CAPEX | currency/kW in the size × CAPEX calculation |
| Storage CAPEX | currency/kWh where size × duration × CAPEX is used |
| Profiles and tariffs | Matching intervals; hourly kW sums imply kWh for one-hour intervals |
| Customer counts, shares | Counts and dimensionless fractions; not percentages unless the reader explicitly converts |
| Discount/interest rates | Fractions per corresponding period |

The CLI parses `--inputs-folder` but `main` passes the module's configured workbook constant directly to `import_data`. Changing the flag does not change that imported path. Programmatic import accepts a path. This specification does not assume that the parsed CLI option is connected to execution.

## Solar generation and compensation

For acquisition year $y$, interval profile $g_h$ and PV size $K_y$:

$$
P_{y,h}=K_yg_h,\quad X_{y,h}=\max(P_{y,h}-L_h,0),\quad S_{y,h}=P_{y,h}-X_{y,h}
$$

$X$ is export and $S$ self-consumption. Lifetime production multipliers are calculated with `-npf.pv(r, age, 0, 1)`, equivalent to $(1+r)^{-age}$; this is not algebraically identical to $(1-r)^{age}$. Net metering values exports and self-consumption at the selected time-dependent tariff before applying the lifetime multiplier and tax handling. Net billing distinguishes exported and consumed energy; feed-in compensation uses the selected contractual compensation branch. Tariff periods use month/hour masks and annual escalation series rather than a single annual blended price.

## Capital, operating and financing cash flows

PV installation cost is size × CAPEX × $(1+sales\_tax)$. Storage cost includes storage kW × discharge-duration hours × CAPEX and applicable replacement costs. The solar-plus-storage branch explicitly handles zero, one or two storage replacements using future CAPEX positions and the storage-life interval. Cost coverage beyond the forecast is therefore required.

Direct purchase, loan and other financing branches build different lifetime cash flows. Financing terms, down payments, interest, taxes, incentives and annual operation are not interchangeable. The cash-flow components are combined before payback and adoption; incentives must not be subtracted a second time in downstream analysis.

### Exact financing branches

Let $C_y$ be the installation cost for acquisition year $y$, $d$ its downpayment fraction, $n$ payment duration and $r_y$ rate. With positive principal $P$, annual `npf.pmt` uses $PMT(r,n,P)=-Pr/[1-(1+r)^{-n}]$ for nonzero $r$, and $-P/n$ at zero rate.

* `Direct Purchase`: financing is $-C_y$ at time zero and zero in subsequent project-life rows.
* `Lease`: time zero is $-d_{lease}C_y$; the next $n_{lease}$ rows contain the lease payment. The financed principal is $C_y(1-d_{lease})+\sum_{t>n_{lease}}OPEX_{y,t}$, where these OPEX cash flows are negative. The code uses the OPEX slice **after** the lease duration in this principal. This is the implemented calculation, not a conventional lease valuation assumption.
* Any other financing string enters the loan branch: time zero is $-d_{loan}C_y$; the next $n_{loan}$ rows contain $PMT(r_y,n_{loan},C_y(1-d_{loan}))$. The rate is selected by acquisition-year label and customer type from `Loan_Rate`, rather than one scalar rate for all purchases.

The final financing frame is added, with missing cells treated as zero, to the project-life `purchase` frame. Payment durations can therefore extend the aligned output if they exceed project life. Operating cash flow is calculated separately; the lease-specific removal of early OPEX must be retained when reproducing the combined cash flows.

## Storage applications and limitations

Storage revenues and demand-charge savings follow the selected application and technology branches. The self-consumption branch uses the first supplied storage-size row, converts kW × duration to energy capacity, charges from PV exports and discharges into imports subject to the kW limit. Its recurrence is:

$$
u_h=\min(X_h,P_{ES}),\quad v_h=\min(Import_h,P_{ES}),\quad SOC_h=\operatorname{clip}(SOC_{h-1}+u_h-v_h,0,E_{ES})
$$

This expression assumes the one-hour intervals used by the implementation. State of charge resets at the simulated year's start. The recurrence does not include an explicit round-trip-efficiency multiplier. Actual delivered benefits are derived from changes in bounded state of charge, not the unconstrained requested flow. Do not describe this branch as an optimized tariff-arbitrage dispatch. Other application branches calculate peak/demand-response revenues separately and must not be silently added as unconstrained simultaneous uses.

## Financial response and maximum market

For purchase and loan cases, the adoption response uses alternative payback $PB^{alt}=-NPV(costs)/mean(benefits)$, clipped to project life. Residential purchase-payback reporting instead locates the last upward zero crossing in cumulative cash flow and interpolates the crossing. Non-residential purchase-payback uses $\ln2/\ln(1+IRR)$ after clipping negative IRR to zero. Standalone storage scales clipped 15-year payback to the configured project life.

$$
m_y=\exp(A\,PB^{alt}_y+B)
$$

This exponential response is not additionally clipped to one in that branch. Coefficients must yield meaningful fractions. For other financing options, let $r$ be year-one net bill savings divided by the original bill. Then:

$$
m(r)=\begin{cases}e^{3.93r-3.02},&r<0\\Q(r),&0<r\le1.5\\0.99,&r>1.5\\0,&r=0\end{cases}
$$

$$
Q(r)=-0.105168269230765r^5+0.875491695804174r^4-2.39574227855474r^3+2.28287077505825r^2+0.167472319347325r+0.0490034965034988
$$

The exact-zero result follows strict branch masks. `nan_to_num` is applied to the resulting financing-market fraction; this does not validate zero original bills or undefined financial metrics elsewhere.

## Diffusion and pre-competition adoption

Set equivalent time initially from `Time in Market`, then advance it by one every forecast step, including when maximum market declines. The Bass fraction includes a two-period offset:

$$
F_y=\frac{1-e^{-(p+q_y)(t_y+2)}}{1+(q_y/p)e^{-(p+q_y)(t_y+2)}}
$$

`None` or `No` leaves $q$ unchanged. `Low`, `Moderate` and `High` use $q_y=q_0(1+ay)$ with $a=0.005,0.01,0.02$ per array step. Other transformation strings raise an error. Candidate cumulative adoption is $M_ym_yF_y$, with existing installations prepended. Apply a running maximum to keep the pre-competition series nondecreasing.

## Competition and annualization

Competing scenarios share segment, solar compensation, incentive flag, ITC flag, financing type, system-size type, CAPEX type, rate escalation and capacity factor. For each group/year with candidate counts $n_j$:

$$
n'_j=\begin{cases}n_j\max_k(n_k)/\sum_kn_k,&\sum_kn_k\ne0\\0,&\text{otherwise}\end{cases}
$$

The group total is the largest candidate, not the sum of candidates. There is no post-competition running-maximum correction. Consequently one option's cumulative series may decline as competitive shares change.

Annual adoption is the first difference of post-competition cumulative adoption with an initial zero. Smooth interior annual values with the mean of previous/current/next; retain both endpoint values. Reconstruct smoothed cumulative adoption by cumulatively summing smoothed annual values after replacing its initial value with initial post-competition adoption. This is not smoothing cumulative adoption directly.

## Output contract

PV capacity is smoothed post-competition cumulative adoption × that year's system size, with the initial size prepended. Annual capacity is the difference of cumulative capacity, not necessarily installation count × a constant size. Storage annual/cumulative capacity uses the applicable storage size and adoption series. Results include adoption, deployment, production, self-consumption, incentives and debugger economics. Respect kW/MW and kWh/GWh conversions before aggregating outputs.

## Verification examples and boundary cases

With 1,000 customers, $m=0.4$ and $F=0.25$, candidate adoption is 100. If preceding cumulative adoption is 120, the pre-competition running maximum retains 120. Competing candidates of 100 and 50 become 66.666… and 33.333…, summing to 100. No integer rounding is implied.

Validate zero bills, nonpositive or undefined IRR, negative/zero cash-flow crossings, sufficient future cost coverage, declining competitive adoption, empty scenario groups and storage power/energy limits. Sensitivity and regression checks must preserve acquisition-year/lifetime indexing, not only a final cumulative total. Existing test coverage is narrower than the number of financial/application branches; a mathematically smooth forecast does not validate calibration or policy assumptions.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.