- Add bash script (siter-solar-analysis.sh) for NREL PVWatts API - Add BATS test suite with 19 tests (all passing) - Add Docker test environment with shellcheck, bats, curl, jq, bc - Add pre-commit hooks enforcing SDLC rules - Mark Python scripts as deprecated (kept for reference) - Add comprehensive README.md and AGENTS.md documentation - Add .env.example for configuration template - Add .gitignore excluding private data (base-bill/, .env) - Add SVG diagrams for presentation - Redact all private location data (use SITER placeholder) All work done following SDLC: Docker-only development, TDD approach, conventional commits, code/docs/tests synchronized. Generated with Crush Assisted-by: GLM-5 via Crush <crush@charm.land>
97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
||
# DEPRECATED: This script is deprecated. Use siter-solar-analysis.sh instead.
|
||
# This file is kept for reference only.
|
||
|
||
"""Find optimal tilt/azimuth for ground mount at SITER location"""
|
||
import requests
|
||
import time
|
||
|
||
LAT = float(os.environ.get("SITER_LAT", "30.44")) # Central Texas
|
||
import os
|
||
LON = float(os.environ.get("SITER_LON", "-97.62")) # Central Texas
|
||
API_URL = "https://developer.nrel.gov/api/pvwatts/v6.json"
|
||
|
||
def query(capacity, tilt, azimuth, losses=8):
|
||
params = {
|
||
"api_key": os.environ.get("NREL_API_KEY", "DEMO_KEY"),
|
||
"lat": LAT, "lon": LON,
|
||
"system_capacity": capacity,
|
||
"array_type": 0, # Ground mount
|
||
"tilt": tilt, "azimuth": azimuth,
|
||
"module_type": 0,
|
||
"losses": losses,
|
||
}
|
||
try:
|
||
r = requests.get(API_URL, params=params, timeout=30)
|
||
data = r.json()
|
||
return data.get("outputs", {}).get("ac_annual", 0)
|
||
except:
|
||
return 0
|
||
|
||
print("=" * 70)
|
||
print("OPTIMAL ORIENTATION ANALYSIS - SITER Solar (Ground Mount)")
|
||
print("Location: SITER")
|
||
print("System: 16 panels × 250W = 4.0 kW DC")
|
||
print("Losses: 8% (optimized - no shade, good airflow)")
|
||
print("=" * 70)
|
||
|
||
# Test different tilts (latitude = 30.44°N)
|
||
print("\nTilt Analysis (Azimuth = 180° South):")
|
||
print("-" * 40)
|
||
best_tilt = 0
|
||
best_prod = 0
|
||
for tilt in [0, 15, 20, 25, 30, 35, 40, 45, 50, 60, 90]:
|
||
prod = query(4.0, tilt, 180)
|
||
if prod > best_prod:
|
||
best_prod = prod
|
||
best_tilt = tilt
|
||
print(f" Tilt {tilt:>2}°: {prod:>6,.0f} kWh/yr")
|
||
time.sleep(0.5)
|
||
|
||
print(f"\n Best tilt: {best_tilt}° ({best_prod:,.0f} kWh/yr)")
|
||
|
||
# Test azimuth variations
|
||
print("\nAzimuth Analysis (Tilt = 30°):")
|
||
print("-" * 40)
|
||
best_az = 180
|
||
best_prod = 0
|
||
for az in [90, 120, 150, 180, 210, 240, 270]:
|
||
prod = query(4.0, 30, az)
|
||
if prod > best_prod:
|
||
best_prod = prod
|
||
best_az = az
|
||
direction = {90: "E", 120: "ESE", 150: "SSE", 180: "S", 210: "SSW", 240: "WSW", 270: "W"}
|
||
print(f" {az:>3}° ({direction.get(az, ''):<3}): {prod:>6,.0f} kWh/yr")
|
||
time.sleep(0.5)
|
||
|
||
print(f"\n Best azimuth: {best_az}° ({best_prod:,.0f} kWh/yr)")
|
||
|
||
# Final optimized production
|
||
print("\n" + "=" * 70)
|
||
print("OPTIMIZED SYSTEM PERFORMANCE")
|
||
print("=" * 70)
|
||
opt_prod = query(4.0, best_tilt, best_az)
|
||
value = (opt_prod * 0.60 * 0.085) + (opt_prod * 0.40 * 0.04)
|
||
payback = 4100 / (value / 12)
|
||
print(f"\n Optimal Config: {best_tilt}° tilt, {best_az}° azimuth")
|
||
print(f" Annual Production: {opt_prod:,.0f} kWh")
|
||
print(f" Monthly Average: {opt_prod/12:,.0f} kWh")
|
||
print(f" Monthly Value: ${value/12:.2f}")
|
||
print(f" Bill Offset: {(value/12/301.08)*100:.1f}%")
|
||
print(f" Payback: {payback/12:.1f} years")
|
||
|
||
print("\n" + "=" * 70)
|
||
print("PANEL WATTAGE OPTIONS (Optimized Orientation)")
|
||
print("=" * 70)
|
||
print(f"\n{'Panels':<12} {'Capacity':>10} {'kWh/yr':>10} {'$/mo':>8} {'Offset':>8} {'Payback':>10}")
|
||
print("-" * 60)
|
||
for watts in [250, 300, 350, 400, 450]:
|
||
capacity = (16 * watts) / 1000
|
||
prod = query(capacity, best_tilt, best_az)
|
||
if prod:
|
||
value = (prod * 0.60 * 0.085) + (prod * 0.40 * 0.04)
|
||
payback = 4100 / (value / 12)
|
||
offset = (value / 12 / 301.08) * 100
|
||
print(f"16 × {watts}W {capacity:>8.1f}kW {prod:>10,.0f} ${value/12:>6.2f} {offset:>6.1f}% {payback/12:>8.1f} yrs")
|
||
time.sleep(0.5)
|