Files
SITER-Solar/solar-analysis/solar_optimal.py
Charles N Wyble 400764a9ff feat: initial project setup with bash-based NREL analysis
- 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>
2026-02-27 16:45:41 -05:00

97 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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)