Security: - Remove -k/--api-key CLI option (prevents process list exposure) - API key now only accepted via NREL_API_KEY environment variable Features: - Add API timeout (30s) and retry logic with exponential backoff - Add rate limit detection and graceful test skipping Documentation: - Add AGPLv3 LICENSE file - Add CONTRIBUTING.md with development guidelines - Add CHANGELOG.md following Keep a Changelog format - Add copyright headers to all source files Tests: - Expand test suite from 19 to 52 tests - Add edge case tests (negative values, boundaries) - Add input validation tests - Add financial calculation verification tests - Add rate limit handling to skip tests gracefully - Remove skip-on-failure logic - tests now properly fail All 52 tests pass (19 skipped when API rate limited). 🤖 Generated with [Crush](https://crush.cli.software) Assisted-by: GLM-5 via Crush <crush@charm.land>
78 lines
2.9 KiB
Python
78 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
||
#
|
||
# SITER Solar Analysis - NREL PVWatts Solar Production Estimator
|
||
# Copyright (C) 2026 Known Element
|
||
#
|
||
# This program is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU Affero General Public License as published
|
||
# by the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU Affero General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU Affero General Public License
|
||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
# DEPRECATED: This script is deprecated. Use siter-solar-analysis.sh instead.
|
||
# This file is kept for reference only.
|
||
|
||
"""Quick comparison: standard losses vs optimized (no shade)"""
|
||
import requests
|
||
|
||
LAT = float(os.environ.get("SITER_LAT", "30.44")) # Central Texas
|
||
LON = float(os.environ.get("SITER_LON", "-97.62")) # Central Texas
|
||
import os
|
||
API_URL = "https://developer.nrel.gov/api/pvwatts/v6.json"
|
||
|
||
def query(capacity, losses):
|
||
params = {
|
||
"api_key": os.environ.get("NREL_API_KEY", "DEMO_KEY"),
|
||
"lat": LAT, "lon": LON,
|
||
"system_capacity": capacity,
|
||
"array_type": 0, # Ground mount
|
||
"tilt": 30, "azimuth": 180,
|
||
"module_type": 0,
|
||
"losses": losses,
|
||
"timeframe": "monthly"
|
||
}
|
||
r = requests.get(API_URL, params=params, timeout=30)
|
||
return r.json()
|
||
|
||
print("=" * 70)
|
||
print("GROUND MOUNT ANALYSIS: Standard vs Optimized (No Shade)")
|
||
print("Location: SITER")
|
||
print("=" * 70)
|
||
|
||
print("\nGround mount advantages at your site:")
|
||
print(" - No trees = minimal shading losses")
|
||
print(" - Open rack = better airflow, cooler panels")
|
||
print(" - Optimal tilt (30°) = maximum annual production")
|
||
print()
|
||
|
||
# Standard 14% losses vs optimized 8% losses (no shade)
|
||
for losses in [14, 10, 8, 5]:
|
||
data = query(4.0, losses)
|
||
if data and not data.get("errors"):
|
||
annual = data["outputs"]["ac_annual"]
|
||
monthly = annual / 12
|
||
value = (annual * 0.60 * 0.085) + (annual * 0.40 * 0.04)
|
||
payback = 4100 / (value / 12)
|
||
print(f"Losses @ {losses}%: {annual:,.0f} kWh/yr | ${value/12:.2f}/mo | Payback: {payback/12:.1f} yrs")
|
||
|
||
print()
|
||
print("=" * 70)
|
||
print("PANEL WATTAGE COMPARISON (Optimized @ 8% losses)")
|
||
print("=" * 70)
|
||
|
||
for watts in [250, 300, 350, 400, 450]:
|
||
capacity = (16 * watts) / 1000
|
||
data = query(capacity, 8) # Optimized losses
|
||
if data and not data.get("errors"):
|
||
annual = data["outputs"]["ac_annual"]
|
||
value = (annual * 0.60 * 0.085) + (annual * 0.40 * 0.04)
|
||
payback = 4100 / (value / 12)
|
||
offset = (value / 12 / 301.08) * 100
|
||
print(f"16 × {watts}W ({capacity:.1f}kW): {annual:,.0f} kWh/yr | ${value/12:.2f}/mo | {offset:.0f}% offset | {payback/12:.1f} yr payback")
|