1Energy production model
Annual production is computed by NREL's PVWatts V8 API, which simulates 8,760 hourly performance values across a typical meteorological year using the NSRDB satellite weather dataset. A client-side fallback formula is used if PVWatts is unavailable.
1.1 Primary model: NREL PVWatts V8
PVWatts is the US Department of Energy's reference solar production model. balco.nyc calls it with parameters tuned for vertical, railing-mounted panels in an urban setting:
| Parameter | Value | Why |
|---|---|---|
system_capacity | 0.4 to 1.6 kW | From user's railing width (1 to 4 panels × 400W) |
module_type | 1 (Premium) | 19% efficiency, better temperature coefficient |
array_type | 0 (Fixed Open Rack) | Balcony rails have open airflow; PVWatts models cell temperature from TMY weather under this setting, so no separate thermal multiplier is applied |
tilt | 35°, 60°, 70°, or 90° | 90° = vertical railing, 70°/60° = angled mounts, 35° ≈ optimal for NYC latitude |
azimuth | 0 to 315° | User's balcony direction (8 compass points) |
losses | 14% | PVWatts default. Soiling is broken out into the monthly array below to avoid double-counting |
dc_ac_ratio | 1.1 | A micro-inverter matched to one or two modules; the previous 1.2 modelled more clipping than actually occurs |
inv_eff | 96.5% | Micro-inverter efficiency (Enphase IQ8 ~97%, budget ~96%) |
dataset | nsrdb | Satellite-derived TMY data, best for US locations |
soiling | [3,3,4,5,6,7,7,7,6,5,4,3] | Monthly soiling %, NYC urban profile calibrated to NREL, Sandia, and Fraunhofer urban-PV studies (3 to 7% range, summer-heavy from pollen) |
albedo | 0.20 | Concrete balcony floor reflectance (light-painted walls would be ~0.30) |
bifaciality | 0 | Monofacial panels (would be 0.75 for bifacial) |
timeframe | monthly | Returns 12-month production array |
Soiling parameter robustness. NREL documents array parameters as pipe-delimited. If the parameter is rejected the entire request fails, which would silently drop every estimate onto the client-side fallback while the interface still claimed an hourly simulation. The client therefore degrades explicitly: pipe-delimited, then bracketed JSON, then no soiling array at all with losses raised to 18.3% (14% compounded with the array's ~5% annual mean). Whichever variant succeeded is recorded on the result.
PVWatts outputs used:
ac_monthly: 12 values of monthly AC energy (kWh), used for the production chart and for applying per-month 3D shade factors.ac_annual: total annual AC output (kWh), used when the shade model returns only an annual factor.
Losses decomposition
The 14% bundle approximates: mismatch 3%, wiring 4% (longer DC runs from balcony to junction than rooftop), connections 0.5%, light-induced degradation 1.5%, nameplate 1%, availability 3%, snow 1%. Soiling and shading are modeled separately. There is no rooftop-vs-balcony "balcony loss adder". The difference shows up in the soiling array and in the shade factor.
1.2 Fallback model (no API available)
When PVWatts is unavailable (no API key, network failure, no address entered), the calculator uses a client-side formula:
annual_kwh = BASELINE × system_kw × tilt_factor × azimuth_factor
× urban_soiling × thermal_bonus × shade_factor
Where:
- BASELINE = 1,300 kWh/kW/year. NYC PVWatts reference at optimal ~40° tilt with default losses. Aligned with the NYSERDA NY Solar Map published assumption of 1,238 kWh/kW/yr.
- tilt_factor, calibrated against PVWatts vertical NYC and the HTW Berlin Stecker-Solar reference:
- 35° → 1.00 (top-mount, near-optimal for NYC)
- 60° → 0.85
- 70° → 0.78
- 90° → 0.60 (vertical railing)
- azimuth_factor: S=1.00, SE/SW=0.92, E/W=0.72, NE/NW=0.45, N=0.32
- urban_soiling = 0.95. Matches the annual average of the PVWatts soiling array.
- thermal_bonus = 1.0. No double-count; PVWatts (and this fallback for consistency) treats cell-temperature effects as part of the baseline.
- shade_factor: see Section 2.
Monthly distribution uses either NREL Solar Resource API data (location-specific GHI) or a hardcoded NYC seasonal curve:
Jan: 5.6%, Feb: 6.8%, Mar: 8.2%, Apr: 9.2%, May: 10.5%, Jun: 11.2%
Jul: 11.4%, Aug: 10.3%, Sep: 8.8%, Oct: 7.3%, Nov: 5.6%, Dec: 5.1%
2Shadow derating model
PVWatts assumes an unobstructed installation. NYC balconies face building-level shading that PVWatts cannot see. We apply a post-PVWatts shade multiplier computed either from a 3D model of the surrounding buildings (when available) or from a static lookup as a fallback.
2.1 3D shadow model (when 3D scene is loaded)
For addresses with NYC Building Footprints data, we build a local 3D scene of all buildings within 200m and compute how much of the balcony's plane-of-array irradiance those buildings block.
What this factor must and must not measure. PVWatts is given the panel's real tilt and azimuth, so it already prices orientation: it knows a north-facing vertical panel yields a fraction of a south-facing one. The shade factor is a second multiplier on that output, so it must answer only "how much of what PVWatts assumed actually reaches this balcony". Anything else is counted twice.
An earlier version charged every hour when the sun sat behind the facade as shading, crediting those hours only a fixed 30% diffuse share. That penalised orientation a second time: an entirely unobstructed east-facing balcony scored 0.54, and a north-facing one 0.32, with no neighbouring building present at all. The current model returns 1.00 for an unobstructed balcony at every orientation, and the test suite asserts exactly that.
Step 1 — horizon profile. Every neighbouring footprint is projected onto a 360-bin azimuth skyline as seen from the balcony point. Each polygon edge is subdivided finely enough that no bin is skipped (every ~2m, and at least every 0.5° of angular extent), and each bin keeps the highest obstruction altitude found in that direction. Because the nearest sampled point in a direction wins, concave and L-shaped footprints resolve correctly, buildings that straddle due north wrap without seams, and buildings shorter than the balcony drop out entirely. This replaces an earlier per-sample loop that paired a polygon's full angular span with its single nearest vertex, and so could block sunlight through an L-shaped building's notch.
Step 2 — sky openness. The fraction of the panel's sky view that survives the horizon, computed by integrating cos(θ) × cos(altitude) over the visible hemisphere — the standard view-factor integral for an isotropic sky. Because θ is the angle of incidence on the panel, this is tilt-aware: a 35° panel sees more sky than a vertical one, and loses a different share of it.
Step 3 — daylight sweep (computeAnnualShadeProfile, sampling every 10 min from sunrise to sunset for each of 12 representative days):
- Sun position: altitude and azimuth from
SunPosition.calculate(month, minute)(Section 5). - Clear-sky proxy:
ghi = sin(altitude)^0.75, which keeps solar noon worth several times dawn without over-spiking. - Beam component:
BEAM_SHARE × ghi × max(0, cos(θ)), wherecos(θ)uses the full tilted-surface incidence formula and the panel's actual tilt. It reduces tocos(altitude) × cos(azimuth difference)for a vertical panel. - Diffuse component:
DIFFUSE_SHARE × ghi × (1 + cos(tilt)) / 2, the isotropic sky view factor of the panel itself. - Blocking: the beam is lost when the sun's altitude falls below the horizon profile in its azimuth bin. The diffuse component is scaled by sky openness.
received = beam × (sun visible) + diffuse × sky_openness
assumed = beam + diffuse
shade_factor = Σ(received) / Σ(assumed)
Hours when the sun is behind the facade contribute near-zero beam to both sums, so they neither reward nor penalise — which is exactly right, because PVWatts has already accounted for them.
Constants. BEAM_SHARE = 0.60 / DIFFUSE_SHARE = 0.40, matching the NSRDB annual diffuse fraction of GHI for the Northeast (~0.35–0.42). Only their ratio matters.
Per-month shade factor = received / assumed, clamped to [0.10, 1.00].
Annual shade factor = monthly factors weighted by the NYC GHI distribution (the same array used in §1.2).
Physics-vs-display separation. The colored shadow heatmap in the 3D scene uses display_score = min(1, physics_score × 1.8) for UI contrast only, and that scorer now serves the heatmap alone. The energy path reads the horizon profile and never the display score.
Direct sun hours. The "hours of direct sun" figure in the info panel is derived from the same horizon profile as the energy model, so the number shown and the number used can no longer disagree.
Railing obstruction. The railing, mounting hardware and the balcony floor above clip the bottom edge of a rail-mounted panel. Footprints cannot see any of it and PVWatts assumes an unobstructed module, so it is applied as a separate tilt-dependent factor: 0.95 at 90°, 0.97 at 70°, 0.98 at 60°, 0.99 at 35°. Field reports for vertical mounts put the loss at 5–8%; we take the conservative end. It applies to the fallback model too.
2.2 Static shade factor (fallback)
When the 3D scene isn't loaded (no footprint data, mobile path, or in-page calculator without WebGL), shade is interpolated from a continuous function:
ratio = floor / total_floors
base_exposure = 0.5 + 0.5 × tanh(3 × (ratio − 0.45))
shade_factor = range.min + base_exposure × (range.max − range.min)
Per-shading-environment ranges:
| Shading | min (low floors) | max (top floors) |
|---|---|---|
| Open | 0.85 | 0.97 |
| Some buildings | 0.65 | 0.94 |
| Dense canyon | 0.45 | 0.87 |
| Wide avenue | 0.70 | 0.96 |
The tanh sigmoid is centred at the 45th-percentile floor and is steepest in the middle of the building, so the floor-tier transition zones are smooth rather than step-functions.
2.3 Neighbor building query
When address data is available, we query NYC Building Footprints within a 200-meter radius. These footprints feed the 3D scene and the polygon-edge shadow projection. Without them, the calculator falls back to the static shade factor above.
2.4 Final energy formula
final_kwh = pvwatts_ac_annual × shade_factor (uniform shade case)
final_kwh = Σ(pvwatts_ac_monthly[i] × monthly_shade_factor[i]) (3D case)
THERMAL_BONUS is set to 1.0, so it doesn't appear above. The shade factor is the only post-PVWatts multiplier.
3Building orientation detection
When the user enters an address, we query NYC Building Footprints for the polygon geometry and detect facade directions.
Algorithm:
- Extract exterior ring coordinates from the building footprint polygon.
- Project to local metres: multiply every longitude delta by
cos(latitude). A degree of longitude at NYC's 40.7°N covers only ~76% of the ground distance a degree of latitude does, so raw degree deltas stretch east-west edges by roughly 1.3× and skew every bearing by up to 7°. - Compute edge vectors (dx, dy) between consecutive vertices, in metres.
- Calculate each edge's compass bearing:
atan2(dx, dy)converted to degrees. - Compute perpendicular facade directions:
edge_bearing ± 90°. - Sort edges by length (longest edge = primary facade).
- Map facade directions to the nearest 45° compass increment.
- Rank by solar potential: S > SE/SW > E/W > NE/NW > N.
- Return the best solar-facing direction as the suggestion.
What is returned. bestDirection is the best solar-facing wall anywhere on the building. primaryDirections is the pair of walls belonging to the longest edge, which is where most units in the building actually face; they differ on, say, a north-south tower whose only south-facing wall is its short end.
Confidence is "high" when the longest edge exceeds 1.3× the longest roughly-perpendicular edge (at least 30° off the primary), and "medium" otherwise. Comparing against the simple second-longest edge made "high" almost unreachable, because in any rectangle the two longest edges are the parallel long walls and are near-identical in length.
Manhattan grid note. Manhattan's street grid runs ~29° east of true north. A building that "faces the avenue" actually faces ~209° (SSW) or ~29° (NE). The algorithm reads this from the actual polygon, not from grid assumptions.
4Financial model
4.1 Electricity rate
Con Edison SC-1 residential rate: $0.34/kWh all-in marginal rate (supply + delivery + GRT + sales tax, excluding the flat customer charge). Sources: Con Edison historical bill table 2023 to 2025, projected forward by the 2026 PSC-approved rate-case settlement (+3.5% in 2026).
The marginal rate is the right number for solar offset: every kWh produced replaces one extra kWh the household would have bought. The Customer Charge is intentionally excluded because solar can't offset a flat fee.
4.2 Annual savings
annual_savings = annual_kwh × 0.34
monthly_savings = annual_savings / 12
billable = max(0, monthly_bill − 20) # strip the fixed Customer Charge
bill_offset_% = annual_kwh / (billable / 0.34 × 12) × 100
Bill offset is clamped to a max of 100% and guarded against a $0 monthly bill (the consumption denominator is clamped to ≥1 kWh).
4.3 Payback period
Simple payback:
simple_payback = adjusted_cost / annual_savings
Escalated payback runs inside the same 25-year loop as lifetime savings (below), interpolating within the crossover year for a fractional result. It compounds the rate escalation and panel degradation but applies no discount rate, so it is a nominal figure, not a net present value. It was previously labelled "NPV payback", which overstated its rigour. The 25-year total is likewise nominal.
4.4 25-year lifetime value
lifetime_savings = Σ(annual_kwh × (1 − degradation)^i × 0.34 × (1 + escalation)^i, i=0..24)
Where:
- degradation is tier-aware (default mid-tier 0.5%/yr), per the NREL 2024 PV degradation review:
- Premium: 0.4%/yr → 90.5% of original output at year 25
- Mid: 0.5%/yr → 88.7%
- Budget: 0.7%/yr → 84.3%
- escalation is user-selectable, default 3%/yr (mid), with low (2%) and high (4%) presets exposed in the Customize panel. Long-run national EIA data tracks ~2 to 2.5%/yr; recent Con Ed history is closer to 7%/yr but skewed by one-off settlements. 3% is a reasonable central estimate; the band conveys honest uncertainty.
4.5 System cost scaling
The user selects a cost tier (budget / mid / premium) calibrated to an 800W kit. For other system sizes, cost scales linearly:
adjusted_cost = tier_cost × (system_watts / 800)
Hardware-only planning assumptions (800W system, 2026):
| Tier | Cost | Notes |
|---|---|---|
| Budget | $850 | Low-end US hardware benchmark; not a verified offer available in New York |
| Mid | $1,200 | Mid-market hardware benchmark; electrical work excluded |
| Premium | $1,600 | Anker SOLIX RS40P, Craftstrom complete kit |
These tiers are calculator inputs, not a list of systems New Yorkers can currently buy. As of September 2026, Bright Saver lists a 360W kit at $414.17 for members, plus a $29 annual membership, and $699 without membership. It says it cannot ship the kit to New York because no system on the market yet has the whole-system certification New York requires.
The Federal Residential Clean Energy Credit (§25D) expired for expenditures after December 31, 2025 under P.L. 119-21. Cost figures are gross; no federal credit is netted out.
4.6 Offset assumption
Production is assumed to offset household consumption 1:1 at the marginal retail rate. For typical balcony users, production is well below consumption so there is no excess export to model.
Regulatory status (as of 31 August 2026). NY's SUNNY Act (S8512C/A9111C) exempts compliant plug-in devices up to 1,200W AC from interconnection and net-metering requirements. It passed the Senate unanimously in April 2026 and the Assembly on 28 May 2026, and awaits the Governor's signature; it takes effect 90 days after signing, so the realistic opening is early 2027.
Two consequences for this model: the 1:1 offset assumption is forward-looking rather than current, and the Act grants no right to install — it removes the utility barrier only, so a landlord or co-op board can still refuse. The payback figures are conditional on a permission the model does not represent.
5Sun position (NOAA simplified algorithm)
js/sun-position.js implements a simplified NOAA solar-position algorithm tuned for NYC.
- Day of year uses the 15th of each month (
DOY_TABLE) as the representative day. - Year is read dynamically from
new Date().getFullYear()so the Julian-day base advances with time instead of drifting. - DST switch is keyed off day-of-year, not month: EDT (UTC−4) for DOY 67 to 304 (Mar 8 to Nov 1 in 2026), EST (UTC−5) otherwise. This avoids the off-by-week errors that month-based switching produced in early March and late October.
- Azimuth is computed via
atan2(sin_az, cos_az), robust at all altitudes, with no separateacosbranch. With the sine and cosine terms as defined, this already returns azimuth measured clockwise from north, and no further rotation is applied.
getDayBounds(month) searches for sunrise and sunset by scanning altitude crossings, used by the 3D shade simulation to bound its sampling loop.
A fixed sign error, and why it is tested now. Until 31 August 2026 this function added a further 180° after the atan2, which inverted every azimuth: June solar noon reported the sun as due north, and the morning sun as due west. The error fed the 3D scene lighting, the sun arc and the shadow model alike, and was invisible to inspection because altitude — and therefore sunrise, sunset and day length — stayed correct throughout.
The regression suite now pins June and December solar noon to the south, checks peak altitudes against NOAA reference values, and asserts that azimuth sweeps monotonically eastward through the day.
6Environmental impact
CO₂ offset
co2_lbs = annual_kwh × 0.89
0.89 lbs CO₂/kWh, from the EPA eGRID2023 output emission rate for the NYCW subregion (released 2025, latest available). This is the "average grid" number, conservative relative to the eGRID non-baseload rate (~0.97 lbs/kWh).
Equivalencies
- Trees:
co2_lbs / 48, the EPA's averaged-across-all-trees figure for annual sequestration. - Driving miles offset:
co2_lbs / 0.89, the EPA 2024 average passenger-car emission rate (0.906 lbs/mile rounded). - Smartphone charges:
annual_kwh × 1000 / 12, ~12 Wh per full smartphone charge.
7Data pipeline
7.1 Address resolution
- Google Places Autocomplete: user types address, gets type-ahead suggestions bounded to NYC (40.48°N to 40.92°N, 74.26°W to 73.70°W).
- On selection, extract lat/lon, formatted address, and address components.
7.2 Building data lookup
NYC Geoclient runs first (its output BBL/BIN feeds the other queries), then three queries fire concurrently via Promise.allSettled():
a) NYC Geoclient → PLUTO
- Parse address into houseNumber, street, borough (with a corrected USPS ZIP atlas: prefix
104→ Bronx; Long Island110is intentionally excluded as it isn't NYC). - Call NYC Geoclient (via server proxy for CORS), get BBL and BIN.
- Query PLUTO by BBL, get
numfloors,yearbuilt,bldgclass,unitsres,bldgarea,zonedist1.
b) NYC Building Footprints
- Query by BIN, get polygon,
heightroof,groundelev. - Run orientation detection (Section 3), suggest a balcony direction.
c) NREL Solar Resource
- Query by lat/lon, get monthly GHI.
- Normalize into a per-month distribution.
d) Neighbor query (background, non-blocking)
- 200m radius, up to 500 buildings, feeds the 3D scene and shadow model.
7.3 Form pre-fill
Auto-populated from building data:
- Total floors ← PLUTO
numfloors - Direction picker ← footprint orientation algorithm
- Building info card ← address, floors, year built, building class, height
7.4 User-adjustable inputs (Customize panel)
The breakdown modal exposes the previously hardcoded modeling assumptions:
| Input | Options | Default |
|---|---|---|
| Mount tilt | 35° / 60° / 70° / 90° | 90° |
| System size | 400W / 800W / 1200W / 1600W | 800W |
| Equipment tier | Budget ($850) / Mid ($1,200) / Premium ($1,600) | Mid |
| Surrounding shading | Open / Some / Dense / Wide avenue | Some |
| Monthly electric bill | $20 to $800 | $140 |
| Rate escalation | Low (2%) / Mid (3%) / High (4%) | Mid |
7.5 Energy calculation
- Map form inputs to PVWatts parameters.
- Call PVWatts V8 API (or use the fallback formula).
- If a 3D scene is initialized, apply per-month shade factors; otherwise apply the static shade factor.
- Run the financial model with the selected tier and escalation preset.
- Compute environmental impact.
7.6 Graceful degradation
Every API has a fallback, and every degraded path tells the visitor: when an estimate did not use the full pipeline, a notice above the breakdown names what was missing and what was substituted in its place.
The manual entry panel is also the keyboard- and screen-reader-accessible route to a result. Selecting a balcony in the 3D scene means clicking a WebGL mesh, which is not operable without a pointer, so the same form serves both purposes.
| API failure | Fallback behavior |
|---|---|
| Google Places unavailable | Typed address is geocoded on submit; manual entry panel if that also fails |
| Geoclient fails | Query PLUTO by address string |
| PLUTO fails | Sliders keep defaults |
| Footprints fail | Manual entry panel (floor, direction, shading); static shade factor |
| PVWatts soiling param rejected | Retry bracketed, then fold soiling into losses: 18.3 |
| PVWatts fails entirely | Client-side fallback formula (Section 1.2), notice shown above the breakdown |
| Solar Resource fails | Hardcoded NYC monthly distribution |
| Neighbor query fails | No 3D shade; static shade factor used, notice shown |
| No WebGL support | Manual entry panel; static shade factor |
8Data sources
Every number in the calculator traces to one of the public datasets below.
| Source | Used for |
|---|---|
| NREL PVWatts V8 developer.nrel.gov | Hourly-simulated production |
| NREL Solar Resource developer.nrel.gov | Monthly GHI irradiance |
| NYSERDA NY Solar Map nysolarmap.com | Yield baseline cross-check (1,238 kWh/kW/yr) |
| NYC PLUTO data.cityofnewyork.us | Building floors, class, year, units |
| NYC Building Footprints data.cityofnewyork.us | Polygon, height, elevation, neighbor query |
| NYC Geoclient api.nyc.gov | BBL and BIN from address |
| Google Places developers.google.com | Address autocomplete and geocoding |
| Con Edison SC-1 historical bills coned.com | Electricity rate |
| Con Edison 2026 to 2028 rate case dps.ny.gov | Rate escalation |
| EPA eGRID2023 epa.gov | CO₂ factor (NYCW subregion) |
| HTW Berlin Stecker-Solar Simulator solar.htw-berlin.de | Vertical-mount yield calibration |
| NY SUNNY Act (S8512C/A9111C) nysenate.gov | Plug-in solar regulatory status |
| Bright Saver (nonprofit at-cost kits) brightsaver.org | August 2026 kit cost calibration |
| NREL 2024 PV Degradation Review nrel.gov | Degradation rates by tier |
9Accuracy & limitations
Expected accuracy:
- About ±15% on annual production with PVWatts V8 and the 3D shadow model active
- About ±20% with the client-side fallback (no PVWatts, no 3D)
This band is modeled, not measured. It is derived from the uncertainty of the inputs, not from comparing predictions against metered NYC installations. No such comparison has been run. Treat it as a considered estimate of our own uncertainty rather than a validated tolerance, and read the calibration anchors below as consistency checks against other models rather than against reality.
What the model captures:
- Latitude-specific solar resource and seasonal variation (PVWatts NSRDB)
- Vertical and near-vertical tilt production loss, calibrated against PVWatts and HTW Berlin
- Azimuth-dependent production across all 8 compass directions
- NYC urban soiling, in the right ballpark (3 to 7% monthly) rather than the older 11 to 17% over-derate
- Floor-level shadow estimation with a smooth tanh response, plus a full horizon-profile 3D model when neighbor footprints are available
- Isotropic diffuse sky, reduced by the balcony's actual sky openness rather than a fixed constant
- Tilt-aware angle of incidence in both the energy and shade models
- Concave and L-shaped neighbouring footprints, and buildings straddling due north
- Railing and mounting-hardware obstruction of the panel's lower edge
- Irradiance-weighted sampling, so a blocked noon hour costs more than a blocked dawn hour
- Tier-aware degradation and rate-escalation bands in the 25-year financial projection
What the model still does NOT capture:
- Micro-shading from things not in the building footprint dataset, like trees, awnings, AC units, signage
- Snow coverage in winter months (a vertical panel sheds quickly but not instantly)
- Future electricity-rate trajectory, the single largest swing factor in lifetime savings (2% vs 4% escalation ≈ ±15% on lifetime $)
- Bifacial panels and light-painted walls (
albedoandbifacialityare currently fixed) - The overhang of the balcony directly above, which can sweep across a panel within an hour
- Time-of-use rate structure — a vertical panel shifts output toward winter and the shoulders of the day
- Ground-reflected irradiance is derated at the same rate as beam and sky, rather than modelled separately
Regression suite. tests/ exercises the shipped model files directly under Node (npm test, no dependencies). It pins solar position against NOAA reference values, asserts the invariant that an unobstructed balcony scores 1.00 at every orientation, checks horizon-profile geometry against concave and seam-straddling footprints, and locks the financial and environmental arithmetic. Both defects fixed on 31 August 2026 — an azimuth sign error and the self-shading double-count — were invisible to inspection and are now covered by failing-first tests.
Validation anchors:
- HTW Berlin Stecker-Solar Simulator: 800W vertical south at 52.5°N → ~500 kWh/yr; scaled to NYC's 40.7°N latitude (~+25% irradiance) → ~625 kWh/yr unshaded vertical south, which the fallback model now reproduces within ±5%.
- NYSERDA NY Solar Map baseline: 1,238 kWh/kW/yr at optimal tilt; PVWatts with the calculator's parameters produces ~1,300 kWh/kW/yr for premium fixed-tilt NYC, within bounds.
Or read the guides this model feeds: whether your balcony gets enough sun, what a kWh really costs in NYC, what a kit costs and when it pays back, and where the law stands.