StraightWatt

Methodology

This page is the engine, written out. Every number on the site — the pre-rendered state pages and the interactive calculator alike — comes from one module, src/lib/engine/, that implements exactly what follows. If a formula changes, this page changes in the same commit.

Principles

  1. Federal policy is data, not a constant. The credit percentage lives in a versioned file keyed by year. Nothing in the calculation code knows the number 30.
  2. Every input carries a source and a verification date.
  3. A value we have not verified is null. The engine falls back to a national figure and raises a flag that the page must show. We never type in a plausible number.
  4. One engine feeds everything. The state pages are rendered at build time by the same function the calculator runs in your browser. They cannot disagree.

The federal credit in 2026

Two different statutes, two different recipients, and in 2026 they diverge completely.

You buy (cash or loan)Someone else owns it (lease / PPA)
StatuteIRC 25D (expired)IRC 48E (Clean Electricity Investment Credit)
Credit in 202530%30%
Credit in 20260%30%
Who claims itYou, on your returnsystem owner (lessor / PPA provider), passed through in pricing - NOT claimable by the homeowner
Verified2026-09-052026-09-04

25D unavailable for property placed in service after 2025-12-31 (One Big Beautiful Bill Act).

Why the owner gets 30% rather than the 6% base rate. One-megawatt exception: a facility with maximum net output under 1 MW AC is entitled to the increased credit rate without meeting prevailing-wage and apprenticeship requirements (26 CFR 1.45Y-3). Residential arrays are 5-15 kW, far under the threshold.26 CFR 1.45Y-3.

Risks a lease customer should ask about:

1 · Your consumption

We start from the one number you know: your monthly bill. Part of it is a fixed charge solar cannot remove.

energyBill  = max(0, monthlyBill − fixedMonthlyFee) × 12
annualKwh   = energyBill ÷ rate

rate is your state's average residential price in $/kWh from the EIA. Overridable in the calculator.

2 · Sizing the system

The system is sized to produce a target share of your annual use, given how much a kilowatt of panels yields where you live.

sizeKw           = annualKwh × offsetTarget ÷ specificYield
panels           = ⌈ sizeKw × 1000 ÷ panelWatts ⌉
roofSqft         = panels × sqftPerPanel
year1Production  = sizeKw × specificYield

specificYield is kWh per kW DC per year from NREL PVWatts v8 for your city, or a statewide default, at 20° tilt, due south, 14% system losses, fixed open rack.

3 · What it costs

grossSolar      = sizeKw × 1000 × usdPerWatt
grossTotal      = grossSolar + (battery ? batteryCost : 0)

federalCredit   = grossTotal × policy[year].purchase.creditPct ÷ 100
stateIncentives = Σ over each verified incentive:
                    tax_credit_pct   → min(grossTotal × pct ÷ 100, cap)
                    tax_credit_flat  → value
                    rebate_flat      → value
                    rebate_per_watt  → min(sizeKw × 1000 × value, cap)

netCost         = max(0, grossTotal − federalCredit − stateIncentives)

usdPerWatt is the installed cost before incentives on the modelled basis. The engine runs the identical formulas a second time on the reference basis where one exists, and every page shows both (see Two price bases). A state without its own cost record is not published.

4 · Twenty-five years of bills

For each year y from 0 to 24, we compute what you would have paid without solar and what you pay with it. The difference is your gross saving.

rateY        = rate × (1 + escalation)^y
productionY  = year1Production × (1 − degradation)^y

selfUsed     = min(productionY × selfConsumption, annualKwh)
exported     = max(0, productionY − selfUsed)
imported     = max(0, annualKwh − selfUsed)

billNoSolar   = annualKwh × rateY + fixedFee × 12
billWithSolar = imported × rateY + fixedFee × 12 − exported × rateY × exportRatio

grossSavings  = billNoSolar − billWithSolar
netSavings    = grossSavings − om − (y + 1 = inverterYear ? inverterCost : 0)

exportRatio is the single number that makes California different from Massachusetts. Under full-retail net metering it is 1.0 and self-consumption does not matter. Under California's NEM 3.0 it is roughly 0.25, and a battery — which raises self-consumption from 40% to 75% — becomes decisive.

5 · Cash, loan, lease

Cash

cashFlows   = [ −netCost, netSavings₁, netSavings₂, … netSavings₂₅ ]
payback     = breakEvenYear(cashFlows)
netSavings25 = Σ netSavings − netCost
roi         = netSavings25 ÷ netCost
irr         = irr(cashFlows)

Loan

payment      = monthlyPayment(netCost − downPayment, apr, termYears)
loanFlows    = [ −downPayment, netSavingsᵧ − 12 × payment (while y ≤ term), then netSavingsᵧ ]
totalInterest = 12 × payment × termYears − (netCost − downPayment)

The federal credit, when there is one, is applied to the amount financed.

The curves: cumulative position

position[y] = Σ flows[0..y]      for each way of paying, y = 0 … 25
cash:   flows = [ −netCost, netSavings₁ … netSavings₂₅ ]
loan:   flows = [ −downPayment, netSavingsᵧ − 12 × payment … ]
lease:  flows = [ 0, grossSavingsᵧ − leasePaymentᵧ + stateCreditᵧ … ]

Every chart on the site is drawn from these series, at build time or in your browser, so a picture can never disagree with the table beside it. Year 0 is the day the system is switched on; the cash curve crosses zero at the payback year printed in the table.

Zero-upfront scenarios have no payback

Nothing was paid up front, so "payback" is meaningless for a zero-down loan or a lease. The engine reports a cash-flow verdict instead, and the pages print it in words:

threshold  = annualBill × immediateMinShareOfBill
immediate  if netSavings₁ ≥ threshold
neutral    if 0 ≤ netSavings₁ < threshold      → "cash-flow neutral from year 1 (+$x/yr)"
later      if netSavings₁ < 0 and Σ netSavings₁..ᵧ ≥ 0 for some y   → "positive from year y"
never      otherwise

A one-dollar margin must never read as "immediate". The 25-year net is the column to compare; it is printed first and in bold.

Lease / PPA

ppaRateY     = rate × ppaRateVsRetail × (1 + escalator)^y
leasePayment = productionY × ppaRateY
netSavingsY  = grossSavingsY − leasePayment
upfront      = 0
federalCreditToYou = 0

The lease is priced below your retail rate because the owner keeps the 30% credit. You see it only as a discount. The engine never credits it to you.

Finance functions

monthlyPayment(P, apr, years)
  r = apr ÷ 12,  n = years × 12
  = P × r ÷ (1 − (1 + r)^−n)            (P ÷ n when apr = 0)

breakEvenYear(flows)
  flows[0] is the outlay at t = 0; flows[i] arrives over year i (t = i−1 → i)
  find the first i where Σ flows[0..i] ≥ 0, then interpolate inside that year:
  payback = (i − 1) + (−Σ flows[0..i−1]) ÷ flows[i];  null if never
  (checked by a test: the cash curve crosses zero at the printed payback)

irr(flows)
  the r where Σ flows[i] ÷ (1 + r)^i = 0,
  found by bisection on [−90%, +150%], 200 iterations; null if no sign change

Two price bases

Installed cost is the one input with two defensible answers, and they differ by roughly 60%. A single number would be false either way — and a reader who is later quoted the other price would rightly stop trusting the site — so the engine computes the economics on both bases, through the same code path, and every state page and the calculator show the range.

competitive quoted pricestypical reported prices
What it isThe average of real, competitive quotes on a marketplace — what you get if you shop around.The median price recorded in permit and incentive data across all installers and equipment tiers — what the average buyer actually paid.
SourceEnergySage Marketplace real-quote average, as of Sep 2026; page updated 2026-08-28 (EnergySage 2026)LBNL Tracking the Sun 2024 Edition (OSTI 2438480), slide 37, median installed price, residential systems installed in 2023, 2023$/W DC, read from the chart labels (LBNL 2023)
Used forEvery modelled figure: the tables, the state ranking, the calculator.The second line beside payback, net cost and 25-year net.
Coverage11 states9 of 11 states. LBNL publishes no median where it has fewer than 20 observations (IL, PA); those pages say so rather than borrow a neighbour's figure.

The gap is methodology, not staleness. Which basis matches the quote in your hand decides your payback — which is exactly why both are shown.

Assumptions

Read straight from src/data/finance/defaults.json, last verified 2026-09-04. Each one is exposed as an adjustable control wherever it matters.

AssumptionDefaultBasis
Panel rating400 WTypical residential module, 2026
Roof area per panel17.5 sq ftModule footprint incl. spacing
Offset target100% of annual useSystem sized to cover the whole bill
Panel degradation0.5% / yrManufacturer warranty curves
Operations & maintenance$150 / yrCleaning, monitoring, minor repair
Inverter replacement$1,800 in year 12Single string inverter lifetime
Analysis horizon25 yearsStandard panel warranty
Fixed monthly utility charge$12Non-bypassable customer charge; solar does not remove it.
Electricity price escalation2.5% / yrThe single most contested assumption in any solar calculator. Always show it as an adjustable slider.
Self-consumption, no battery40%Share of generation consumed on site rather than exported. Irrelevant under 1:1 net metering, decisive under NEM 3.0.
Self-consumption, with battery75%Battery shifts evening load onto daytime production
Loan6.99% APR, 20 yr, $0 downTypical unsecured solar loan
Lease / PPA price70% of retail, +2.9000000000000004% / yrThird-party owner keeps the 48E credit and prices below the retail rate. Zero down, lower savings.
Battery13.5 kWh usable, $12,000 installedOne residential unit
"Immediate" threshold10% of the annual bill, in year-1 net cash flowA zero-upfront scenario is called immediate only if its year-1 net cash flow is at least this share of the annual bill. Below it: cash-flow neutral. A one-dollar margin must never read as a win.

Data and sources

DatasetSourceStatus
Electricity ratesU.S. EIA API v2, electricity/retail-sales, residential sector2026-06
Installed costEnergySage Marketplace real-quote average, as of Sep 2026; page updated 2026-08-28 — one source for every state. National average: EnergySage Marketplace national average before incentives (12 kW typical system, $31,135); page updated 2026-06-30. LBNL Tracking the Sun 2023 medians are recorded for reference only.2026-09-05
ProductionNLR (formerly NREL) PVWatts v8 API, 1 kW reference systemverified
Net meteringDSIRE and utility tariffs2026-09-04
State incentivesPrimary state sources (statute, tax department, program administrator, regulator), curated by hand, one record at a time; DSIRE as the index2026-09-05
Federal policyIRS; 26 CFR 1.45Y-32026-09-05

What is not modelled

Being clear about this is part of the method. This is a screening estimate, not a site survey.