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>
11
.env.example
Normal file
@@ -0,0 +1,11 @@
|
||||
# SITER Solar - Environment Configuration
|
||||
# Copy this file to .env and fill in your values
|
||||
|
||||
# NREL API Key (required for solar production estimates)
|
||||
# Get your free API key at: https://developer.nrel.gov/signup/
|
||||
NREL_API_KEY=DEMO_KEY
|
||||
|
||||
# Site Location (optional - defaults to central Texas coordinates)
|
||||
# These are used by the NREL PVWatts API for production estimates
|
||||
SITER_LAT=30.44
|
||||
SITER_LON=-97.62
|
||||
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Environment files (contain sensitive location data)
|
||||
.env
|
||||
|
||||
# Billing PDFs (may contain account numbers and personal info)
|
||||
base-bill/
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
venv/
|
||||
.venv/
|
||||
|
||||
# Analysis outputs
|
||||
solar-analysis/nrel_solar_data.json
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
161
AGENTS.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# SITER Solar Project - Agent Documentation
|
||||
|
||||
This document provides technical reference for AI agents working on this project.
|
||||
|
||||
## Software Development Lifecycle (SDLC)
|
||||
|
||||
### Host System: Git and Docker ONLY
|
||||
|
||||
**All work must be done inside Docker containers.** The host system is ONLY for:
|
||||
|
||||
1. **Git operations** - commit, push, pull, branch, etc.
|
||||
2. **Docker operations** - build, run, exec, etc.
|
||||
|
||||
**Never run code, tests, or other tools directly on the host system.**
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. **Build Docker image:**
|
||||
```bash
|
||||
docker build -f Dockerfile.test -t siter-solar-test .
|
||||
```
|
||||
|
||||
2. **Run tests inside Docker:**
|
||||
```bash
|
||||
docker run --rm siter-solar-test
|
||||
```
|
||||
|
||||
3. **Run analysis inside Docker:**
|
||||
```bash
|
||||
docker run --rm -e NREL_API_KEY=your_key siter-solar-test /app/solar-analysis/siter-solar-analysis.sh --scenarios
|
||||
```
|
||||
|
||||
4. **Make atomic commits** using conventional commit format:
|
||||
- `feat:` - New feature
|
||||
- `fix:` - Bug fix
|
||||
- `docs:` - Documentation only
|
||||
- `test:` - Adding/updating tests
|
||||
- `refactor:` - Code refactoring
|
||||
- `chore:` - Maintenance tasks
|
||||
|
||||
### Code/Docs/Tests Synchronization
|
||||
|
||||
**Code, documentation, and tests MUST be kept in sync at all times.**
|
||||
|
||||
- When changing code, update tests and documentation in the same commit
|
||||
- When changing documentation, verify code still matches
|
||||
- When changing tests, ensure code passes new tests
|
||||
- Pre-commit hooks enforce this synchronization
|
||||
|
||||
## Privacy Requirements
|
||||
|
||||
**CRITICAL: This is a public repository. All private data must be removed.**
|
||||
|
||||
### What MUST NOT be committed:
|
||||
- Real addresses or locations (use "SITER" as placeholder)
|
||||
- Account numbers, contract IDs, or meter numbers
|
||||
- The `base-bill/` directory (contains personal billing data)
|
||||
- `.env` file (contains real coordinates and API keys)
|
||||
|
||||
### What should be committed:
|
||||
- `.env.example` - Template with placeholder values
|
||||
- All code and documentation with "SITER" as location identifier
|
||||
- SVG images and analysis scripts
|
||||
|
||||
### Location Data:
|
||||
- Use configurable environment variables: `SITER_LAT`, `SITER_LON`
|
||||
- Default to central Texas coordinates (30.44, -97.62)
|
||||
- Never commit specific addresses
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
siter-solar/
|
||||
├── AGENTS.md # This file
|
||||
├── README.md # Project documentation
|
||||
├── .env.example # Environment template
|
||||
├── .env # Gitignored - actual values
|
||||
├── .gitignore # Excludes private data
|
||||
├── Dockerfile.test # Test environment
|
||||
├── v2-siter-solar-plan.md # Main proposal document
|
||||
├── images/ # SVG diagrams for presentation
|
||||
│ ├── v2-image-1.svg # System architecture diagram
|
||||
│ ├── v2-image-2.svg # Monthly production chart
|
||||
│ ├── v2-image-3.svg # ROI/payback timeline
|
||||
│ └── v2-image-4.svg # System size comparison
|
||||
└── solar-analysis/ # NREL PVWatts analysis tools
|
||||
├── siter-solar-analysis.sh # Main bash script (production)
|
||||
├── tests/ # BATS test suite
|
||||
│ └── siter-solar-analysis.bats
|
||||
├── solar_estimate.py # DEPRECATED - kept for reference
|
||||
├── solar_optimal.py # DEPRECATED - kept for reference
|
||||
├── solar_optimized.py # DEPRECATED - kept for reference
|
||||
├── Dockerfile # Production image
|
||||
└── run-analysis.sh # Docker wrapper
|
||||
```
|
||||
|
||||
## Running NREL Analysis
|
||||
|
||||
### Using Bash Script (Recommended)
|
||||
|
||||
Inside Docker:
|
||||
```bash
|
||||
# Basic analysis
|
||||
./siter-solar-analysis.sh
|
||||
|
||||
# With custom API key
|
||||
./siter-solar-analysis.sh -k YOUR_API_KEY
|
||||
|
||||
# Compare system sizes
|
||||
./siter-solar-analysis.sh --scenarios
|
||||
|
||||
# JSON output
|
||||
./siter-solar-analysis.sh --json
|
||||
|
||||
# Custom configuration
|
||||
./siter-solar-analysis.sh -p 20 -w 400 --lat 30.5 --lon -97.7
|
||||
```
|
||||
|
||||
### Python Scripts (DEPRECATED)
|
||||
|
||||
The Python scripts are kept for reference only. Use the bash script instead.
|
||||
|
||||
## Key Parameters
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-----------|-------|-------|
|
||||
| System Capacity | 4.0 kW DC | 16 × 250W panels |
|
||||
| Array Type | Fixed Open Rack | Ground mount |
|
||||
| Tilt | 30° | Optimal for latitude |
|
||||
| Azimuth | 180° | South-facing |
|
||||
| Losses | 14% (default) | Can optimize to 8% |
|
||||
|
||||
## Base Power Contract Details
|
||||
|
||||
- **Energy Rate:** $0.085/kWh (fixed through Oct 2028)
|
||||
- **Solar Buyback:** $0.04/kWh (credits apply to entire bill)
|
||||
- **Subscription Fee:** $10/month
|
||||
|
||||
## ROI Calculation Method
|
||||
|
||||
1. Annual production from NREL PVWatts (kWh/year)
|
||||
2. Self-consumption: 60% × production × $0.085/kWh
|
||||
3. Grid export: 40% × production × $0.04/kWh
|
||||
4. Total annual value = self-consumption + export
|
||||
5. Payback = Total investment / annual value
|
||||
|
||||
## Testing
|
||||
|
||||
Run tests inside Docker:
|
||||
```bash
|
||||
docker build -f Dockerfile.test -t siter-solar-test .
|
||||
docker run --rm siter-solar-test
|
||||
```
|
||||
|
||||
## Updating the Analysis
|
||||
|
||||
1. Modify `siter-solar-analysis.sh` as needed
|
||||
2. Update tests in `tests/siter-solar-analysis.bats`
|
||||
3. Run tests to verify changes
|
||||
4. Update `v2-siter-solar-plan.md` with new estimates
|
||||
5. Commit all changes together in atomic commit
|
||||
32
Dockerfile.test
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
jq \
|
||||
bc \
|
||||
shellcheck \
|
||||
ca-certificates \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install bats (Bash Automated Testing System)
|
||||
RUN curl -sL https://github.com/bats-core/bats-core/archive/refs/tags/v1.11.0.tar.gz | tar xz \
|
||||
&& cd bats-core-1.11.0 \
|
||||
&& ./install.sh /usr/local \
|
||||
&& cd .. \
|
||||
&& rm -rf bats-core-1.11.0
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy scripts
|
||||
COPY solar-analysis/ /app/solar-analysis/
|
||||
|
||||
# Make scripts executable
|
||||
RUN chmod +x /app/solar-analysis/*.sh
|
||||
|
||||
WORKDIR /app/solar-analysis
|
||||
|
||||
ENTRYPOINT ["bats", "tests/"]
|
||||
181
README.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# SITER Solar Installation Project
|
||||
|
||||
A solar energy installation proposal using NREL PVWatts API for production estimates and analysis of actual billing data.
|
||||
|
||||
## Overview
|
||||
|
||||
This project provides tools for analyzing solar panel installation economics, including:
|
||||
|
||||
- NREL PVWatts API integration for production estimates
|
||||
- Financial analysis with ROI and payback calculations
|
||||
- Multiple system size scenario comparisons
|
||||
- Integration with Base Power battery backup systems
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker (recommended)
|
||||
- Or: bash, curl, jq, bc
|
||||
|
||||
### Run Analysis
|
||||
|
||||
```bash
|
||||
# Build and run in Docker (recommended)
|
||||
docker build -f Dockerfile.test -t siter-solar-test .
|
||||
docker run --rm siter-solar-test /app/solar-analysis/siter-solar-analysis.sh
|
||||
|
||||
# Or run directly (if dependencies are installed)
|
||||
./solar-analysis/siter-solar-analysis.sh
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Basic analysis with defaults
|
||||
./solar-analysis/siter-solar-analysis.sh
|
||||
|
||||
# Compare different system sizes
|
||||
./solar-analysis/siter-solar-analysis.sh --scenarios
|
||||
|
||||
# Custom configuration
|
||||
./solar-analysis/siter-solar-analysis.sh -p 20 -w 400 --lat 30.5 --lon -97.7
|
||||
|
||||
# JSON output for programmatic use
|
||||
./solar-analysis/siter-solar-analysis.sh --json
|
||||
|
||||
# With your NREL API key (avoids rate limits)
|
||||
NREL_API_KEY=your_key ./solar-analysis/siter-solar-analysis.sh --scenarios
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `NREL_API_KEY` | `DEMO_KEY` | Your NREL API key |
|
||||
| `SITER_LAT` | `30.44` | Site latitude |
|
||||
| `SITER_LON` | `-97.62` | Site longitude |
|
||||
|
||||
### Command Line Options
|
||||
|
||||
```
|
||||
Usage: siter-solar-analysis.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
-k, --api-key KEY NREL API key (or set NREL_API_KEY env var)
|
||||
--lat VALUE Site latitude (default: 30.44)
|
||||
--lon VALUE Site longitude (default: -97.62)
|
||||
-p, --panels NUM Number of panels (default: 16)
|
||||
-w, --watts WATTS Watts per panel (default: 250)
|
||||
-t, --tilt DEGREES Array tilt in degrees (default: 30)
|
||||
-a, --azimuth DEGREES Array azimuth (default: 180)
|
||||
-l, --losses PERCENT System losses percent (default: 14)
|
||||
--scenarios Run multiple system size scenarios
|
||||
--json Output as JSON
|
||||
--verbose Enable verbose output
|
||||
-h, --help Show help message
|
||||
-v, --version Show version
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the test suite inside Docker:
|
||||
|
||||
```bash
|
||||
docker build -f Dockerfile.test -t siter-solar-test .
|
||||
docker run --rm siter-solar-test
|
||||
```
|
||||
|
||||
Tests use [BATS (Bash Automated Testing System)](https://github.com/bats-core/bats-core).
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
siter-solar/
|
||||
├── README.md # This file
|
||||
├── AGENTS.md # Agent documentation (SDLC rules)
|
||||
├── .env.example # Environment template
|
||||
├── .gitignore # Excludes private data
|
||||
├── Dockerfile.test # Test environment
|
||||
├── v2-siter-solar-plan.md # Main proposal document
|
||||
├── images/ # SVG diagrams
|
||||
│ ├── v2-image-1.svg # System architecture
|
||||
│ ├── v2-image-2.svg # Monthly production chart
|
||||
│ ├── v2-image-3.svg # ROI/payback timeline
|
||||
│ └── v2-image-4.svg # System size comparison
|
||||
└── solar-analysis/ # Analysis tools
|
||||
├── siter-solar-analysis.sh # Main bash script
|
||||
├── tests/ # BATS test suite
|
||||
│ └── siter-solar-analysis.bats
|
||||
├── solar_estimate.py # DEPRECATED
|
||||
├── solar_optimal.py # DEPRECATED
|
||||
├── solar_optimized.py # DEPRECATED
|
||||
├── Dockerfile # Production image
|
||||
└── run-analysis.sh # Docker wrapper
|
||||
```
|
||||
|
||||
## Key Parameters
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-----------|-------|-------|
|
||||
| System Capacity | 4.0 kW DC | 16 × 250W panels |
|
||||
| Array Type | Fixed Open Rack | Ground mount |
|
||||
| Tilt | 30° | Optimal for latitude |
|
||||
| Azimuth | 180° | South-facing |
|
||||
| Losses | 14% (default) | Can optimize to 8% |
|
||||
|
||||
## Financial Model
|
||||
|
||||
The analysis uses the following assumptions:
|
||||
|
||||
- **Self-consumption:** 60% of production (avoids $0.085/kWh)
|
||||
- **Grid export:** 40% of production (credits $0.04/kWh)
|
||||
- **Base Power contract:** Fixed rate through Oct 2028
|
||||
- **Solar buyback:** Credits apply to entire bill
|
||||
|
||||
### ROI Calculation
|
||||
|
||||
1. Annual production from NREL PVWatts (kWh/year)
|
||||
2. Self-consumption value: 60% × production × $0.085/kWh
|
||||
3. Export value: 40% × production × $0.04/kWh
|
||||
4. Total annual value = self-consumption + export
|
||||
5. Payback = Total investment / annual value
|
||||
|
||||
## NREL API
|
||||
|
||||
This project uses the [NREL PVWatts API](https://developer.nrel.gov/docs/solar/pvwatts/v6/).
|
||||
|
||||
Get your free API key at: https://developer.nrel.gov/signup/
|
||||
|
||||
**Rate Limits:**
|
||||
- DEMO_KEY: 1 request/second, 1000 requests/hour
|
||||
- Personal key: Higher limits
|
||||
|
||||
## Development
|
||||
|
||||
See [AGENTS.md](AGENTS.md) for development guidelines, including:
|
||||
|
||||
- SDLC requirements (Docker-only development)
|
||||
- Testing requirements
|
||||
- Privacy requirements
|
||||
- Commit conventions
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](LICENSE) file.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make changes following SDLC rules in AGENTS.md
|
||||
4. Ensure all tests pass
|
||||
5. Submit a pull request
|
||||
|
||||
## Resources
|
||||
|
||||
- [NREL PVWatts API Documentation](https://developer.nrel.gov/docs/solar/pvwatts/v6/)
|
||||
- [Base Power Solar Integration](https://www.basepowercompany.com/with-solar)
|
||||
- [Sol-Ark Inverters](https://www.ecodirect.com/)
|
||||
162
images/image-1.svg
Normal file
@@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 500">
|
||||
<defs>
|
||||
<linearGradient id="solarGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#f59e0b;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#d97706;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gridGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="batteryGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#10b981;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#059669;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.2"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="500" fill="#f8fafc"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="35" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#1e293b">SITER Solar System Architecture</text>
|
||||
|
||||
<!-- Sun -->
|
||||
<circle cx="100" cy="120" r="40" fill="#fbbf24"/>
|
||||
<g stroke="#fbbf24" stroke-width="3">
|
||||
<line x1="100" y1="60" x2="100" y2="45"/>
|
||||
<line x1="100" y1="180" x2="100" y2="195"/>
|
||||
<line x1="40" y1="120" x2="25" y2="120"/>
|
||||
<line x1="160" y1="120" x2="175" y2="120"/>
|
||||
<line x1="55" y1="75" x2="45" y2="65"/>
|
||||
<line x1="145" y1="165" x2="155" y2="175"/>
|
||||
<line x1="55" y1="165" x2="45" y2="175"/>
|
||||
<line x1="145" y1="75" x2="155" y2="65"/>
|
||||
</g>
|
||||
<text x="100" y="215" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#64748b">Solar Radiation</text>
|
||||
<text x="100" y="230" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">5.52 kWh/m²/day</text>
|
||||
|
||||
<!-- Solar Panels -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="180" y="80" width="140" height="100" rx="8" fill="url(#solarGrad)"/>
|
||||
<g fill="#92400e" opacity="0.3">
|
||||
<rect x="190" y="90" width="25" height="35" rx="2"/>
|
||||
<rect x="220" y="90" width="25" height="35" rx="2"/>
|
||||
<rect x="250" y="90" width="25" height="35" rx="2"/>
|
||||
<rect x="280" y="90" width="25" height="35" rx="2"/>
|
||||
<rect x="190" y="130" width="25" height="35" rx="2"/>
|
||||
<rect x="220" y="130" width="25" height="35" rx="2"/>
|
||||
<rect x="250" y="130" width="25" height="35" rx="2"/>
|
||||
<rect x="280" y="130" width="25" height="35" rx="2"/>
|
||||
</g>
|
||||
</g>
|
||||
<text x="250" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">Solar Array</text>
|
||||
<text x="250" y="218" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#64748b">16 × 250W = 4.0 kW DC</text>
|
||||
<text x="250" y="233" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">Ground Mount, 30° Tilt</text>
|
||||
|
||||
<!-- Arrow Sun to Panels -->
|
||||
<path d="M145 120 L175 120" stroke="#f59e0b" stroke-width="3" marker-end="url(#arrowOrange)"/>
|
||||
<defs>
|
||||
<marker id="arrowOrange" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#f59e0b"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Inverter -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="370" y="100" width="100" height="60" rx="6" fill="#475569"/>
|
||||
<rect x="380" y="110" width="30" height="40" rx="2" fill="#1e293b"/>
|
||||
<rect x="415" y="110" width="45" height="15" rx="2" fill="#22c55e"/>
|
||||
<rect x="415" y="130" width="45" height="8" rx="1" fill="#3b82f6"/>
|
||||
<rect x="415" y="142" width="45" height="8" rx="1" fill="#eab308"/>
|
||||
</g>
|
||||
<text x="420" y="180" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#1e293b">Sol-Ark 5K</text>
|
||||
<text x="420" y="195" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Hybrid Inverter</text>
|
||||
|
||||
<!-- Arrow Panels to Inverter -->
|
||||
<path d="M320 130 L365 130" stroke="#f59e0b" stroke-width="2" marker-end="url(#arrowOrange)"/>
|
||||
<text x="342" y="122" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">DC</text>
|
||||
|
||||
<!-- Home -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="540" y="70" width="120" height="90" rx="8" fill="#e2e8f0"/>
|
||||
<polygon points="540,70 600,30 660,70" fill="#64748b"/>
|
||||
<rect x="580" y="110" width="40" height="50" fill="#3b82f6" opacity="0.3"/>
|
||||
<rect x="555" y="100" width="25" height="20" fill="#1e293b" opacity="0.2"/>
|
||||
<rect x="620" y="100" width="25" height="20" fill="#1e293b" opacity="0.2"/>
|
||||
</g>
|
||||
<text x="600" y="180" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">SITER</text>
|
||||
<text x="600" y="198" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">~2,000 kWh/month</text>
|
||||
|
||||
<!-- Arrow Inverter to Home -->
|
||||
<path d="M470 130 L535 115" stroke="#22c55e" stroke-width="2" marker-end="url(#arrowGreen)"/>
|
||||
<defs>
|
||||
<marker id="arrowGreen" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#22c55e"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<text x="502" y="115" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">AC</text>
|
||||
|
||||
<!-- Base Power Battery -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="540" y="260" width="120" height="80" rx="8" fill="url(#batteryGrad)"/>
|
||||
<rect x="590" y="250" width="20" height="10" rx="2" fill="#059669"/>
|
||||
<rect x="555" y="280" width="90" height="15" rx="2" fill="#064e3b"/>
|
||||
<rect x="555" y="280" width="70" height="15" rx="2" fill="#34d399"/>
|
||||
<text x="600" y="318" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="white">78%</text>
|
||||
</g>
|
||||
<text x="600" y="360" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#1e293b">Base Power Battery</text>
|
||||
<text x="600" y="378" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">+ Automatic Transfer Switch</text>
|
||||
|
||||
<!-- Arrow Home to Battery -->
|
||||
<path d="M600 165 L600 255" stroke="#10b981" stroke-width="2" stroke-dasharray="5,3" marker-end="url(#arrowGreen)"/>
|
||||
<text x="615" y="210" font-family="Arial, sans-serif" font-size="9" fill="#64748b">charge</text>
|
||||
|
||||
<!-- Grid -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="370" y="260" width="100" height="80" rx="6" fill="url(#gridGrad)"/>
|
||||
<g stroke="white" stroke-width="2" opacity="0.5">
|
||||
<line x1="380" y1="280" x2="460" y2="280"/>
|
||||
<line x1="380" y1="300" x2="460" y2="300"/>
|
||||
<line x1="380" y1="320" x2="460" y2="320"/>
|
||||
<line x1="400" y1="270" x2="400" y2="330"/>
|
||||
<line x1="420" y1="270" x2="420" y2="330"/>
|
||||
<line x1="440" y1="270" x2="440" y2="330"/>
|
||||
</g>
|
||||
</g>
|
||||
<text x="420" y="360" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#1e293b">Oncor Grid</text>
|
||||
<text x="420" y="378" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Utility Delivery</text>
|
||||
|
||||
<!-- Arrow Inverter to Grid -->
|
||||
<path d="M420 160 L420 255" stroke="#3b82f6" stroke-width="2" marker-end="url(#arrowBlue)"/>
|
||||
<defs>
|
||||
<marker id="arrowBlue" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#3b82f6"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<text x="435" y="210" font-family="Arial, sans-serif" font-size="9" fill="#64748b">export</text>
|
||||
<text x="435" y="220" font-family="Arial, sans-serif" font-size="8" fill="#94a3b8">@ $0.04/kWh</text>
|
||||
|
||||
<!-- Grid to Battery -->
|
||||
<path d="M470 300 L535 300" stroke="#3b82f6" stroke-width="2" stroke-dasharray="5,3" marker-end="url(#arrowBlue)"/>
|
||||
<text x="502" y="292" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">backup</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="50" y="420" width="700" height="60" rx="6" fill="#f1f5f9"/>
|
||||
<text x="400" y="440" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" font-weight="bold" fill="#475569">Energy Flow</text>
|
||||
|
||||
<line x1="100" y1="460" x2="140" y2="460" stroke="#f59e0b" stroke-width="3"/>
|
||||
<text x="150" y="464" font-family="Arial, sans-serif" font-size="10" fill="#64748b">DC Power</text>
|
||||
|
||||
<line x1="250" y1="460" x2="290" y2="460" stroke="#22c55e" stroke-width="3"/>
|
||||
<text x="300" y="464" font-family="Arial, sans-serif" font-size="10" fill="#64748b">AC to Home</text>
|
||||
|
||||
<line x1="400" y1="460" x2="440" y2="460" stroke="#3b82f6" stroke-width="3"/>
|
||||
<text x="450" y="464" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Grid Export</text>
|
||||
|
||||
<line x1="550" y1="460" x2="590" y2="460" stroke="#10b981" stroke-width="2" stroke-dasharray="5,3"/>
|
||||
<text x="600" y="464" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Battery Charge</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
112
images/image-2.svg
Normal file
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400">
|
||||
<defs>
|
||||
<linearGradient id="barGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#f59e0b;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#d97706;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="400" fill="#f8fafc"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#1e293b">Monthly Solar Production Estimate (NREL PVWatts)</text>
|
||||
<text x="400" y="50" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#64748b">16 × 250W panels = 4.0 kW DC | SITER, Pflugerville, TX</text>
|
||||
|
||||
<!-- Y-axis -->
|
||||
<line x1="80" y1="70" x2="80" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="40" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#64748b" transform="rotate(-90, 40, 200)">AC Output (kWh)</text>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="70" y="324" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">0</text>
|
||||
<text x="70" y="274" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">150</text>
|
||||
<text x="70" y="224" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">300</text>
|
||||
<text x="70" y="174" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">450</text>
|
||||
<text x="70" y="124" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">600</text>
|
||||
<text x="70" y="74" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">750</text>
|
||||
|
||||
<!-- Grid lines -->
|
||||
<g stroke="#e2e8f0" stroke-width="1">
|
||||
<line x1="80" y1="270" x2="760" y2="270"/>
|
||||
<line x1="80" y1="220" x2="760" y2="220"/>
|
||||
<line x1="80" y1="170" x2="760" y2="170"/>
|
||||
<line x1="80" y1="120" x2="760" y2="120"/>
|
||||
<line x1="80" y1="70" x2="760" y2="70"/>
|
||||
</g>
|
||||
|
||||
<!-- X-axis -->
|
||||
<line x1="80" y1="320" x2="760" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
|
||||
<!-- Bars - Monthly data: Jan=450, Feb=453, Mar=492, Apr=508, May=534, Jun=514, Jul=555, Aug=571, Sep=514, Oct=516, Nov=469, Dec=429 -->
|
||||
<!-- Scale: 600 kWh = 250px height, so each kWh = 0.417px -->
|
||||
|
||||
<!-- Jan: 450 kWh = 187.5px -->
|
||||
<rect x="95" y="132.5" width="45" height="187.5" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="117.5" y="125" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">450</text>
|
||||
<text x="117.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Jan</text>
|
||||
|
||||
<!-- Feb: 453 kWh = 188.75px -->
|
||||
<rect x="150" y="131.25" width="45" height="188.75" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="172.5" y="124" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">453</text>
|
||||
<text x="172.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Feb</text>
|
||||
|
||||
<!-- Mar: 492 kWh = 205px -->
|
||||
<rect x="205" y="115" width="45" height="205" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="227.5" y="108" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">492</text>
|
||||
<text x="227.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Mar</text>
|
||||
|
||||
<!-- Apr: 508 kWh = 211.67px -->
|
||||
<rect x="260" y="108.33" width="45" height="211.67" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="282.5" y="101" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">508</text>
|
||||
<text x="282.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Apr</text>
|
||||
|
||||
<!-- May: 534 kWh = 222.5px -->
|
||||
<rect x="315" y="97.5" width="45" height="222.5" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="337.5" y="90" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">534</text>
|
||||
<text x="337.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">May</text>
|
||||
|
||||
<!-- Jun: 514 kWh = 214.17px -->
|
||||
<rect x="370" y="105.83" width="45" height="214.17" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="392.5" y="98" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">514</text>
|
||||
<text x="392.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Jun</text>
|
||||
|
||||
<!-- Jul: 555 kWh = 231.25px -->
|
||||
<rect x="425" y="88.75" width="45" height="231.25" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="447.5" y="81" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">555</text>
|
||||
<text x="447.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Jul</text>
|
||||
|
||||
<!-- Aug: 571 kWh = 237.92px (highest) -->
|
||||
<rect x="480" y="82.08" width="45" height="237.92" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="502.5" y="75" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b" font-weight="bold">571</text>
|
||||
<text x="502.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Aug</text>
|
||||
|
||||
<!-- Sep: 514 kWh = 214.17px -->
|
||||
<rect x="535" y="105.83" width="45" height="214.17" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="557.5" y="98" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">514</text>
|
||||
<text x="557.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Sep</text>
|
||||
|
||||
<!-- Oct: 516 kWh = 215px -->
|
||||
<rect x="590" y="105" width="45" height="215" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="612.5" y="98" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">516</text>
|
||||
<text x="612.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Oct</text>
|
||||
|
||||
<!-- Nov: 469 kWh = 195.42px -->
|
||||
<rect x="645" y="124.58" width="45" height="195.42" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="667.5" y="117" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">469</text>
|
||||
<text x="667.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Nov</text>
|
||||
|
||||
<!-- Dec: 429 kWh = 178.75px (lowest) -->
|
||||
<rect x="700" y="141.25" width="45" height="178.75" fill="url(#barGrad)" rx="3"/>
|
||||
<text x="722.5" y="134" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">429</text>
|
||||
<text x="722.5" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Dec</text>
|
||||
|
||||
<!-- Average line -->
|
||||
<line x1="80" y1="111.5" x2="760" y2="111.5" stroke="#ef4444" stroke-width="2" stroke-dasharray="8,4"/>
|
||||
<text x="770" y="115" font-family="Arial, sans-serif" font-size="10" fill="#ef4444">500 kWh/mo avg</text>
|
||||
|
||||
<!-- Summary box -->
|
||||
<rect x="600" y="355" width="180" height="35" rx="4" fill="#fef3c7"/>
|
||||
<text x="690" y="372" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#92400e">Annual: 6,004 kWh</text>
|
||||
<text x="690" y="385" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#a16207">Daily Avg: 16 kWh</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
125
images/image-3.svg
Normal file
@@ -0,0 +1,125 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400">
|
||||
<defs>
|
||||
<linearGradient id="paybackGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#ef4444;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#22c55e;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="savingsGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#22c55e;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#16a34a;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="costGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ef4444;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#dc2626;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="400" fill="#f8fafc"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#1e293b">10-Year Payback Timeline</text>
|
||||
<text x="400" y="50" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#64748b">16 × 250W System | $4,100 Investment | $402/yr Savings</text>
|
||||
|
||||
<!-- Y-axis -->
|
||||
<line x1="100" y1="70" x2="100" y2="330" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="35" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#64748b" transform="rotate(-90, 35, 200)">Cumulative ($)</text>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="90" y="334" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">-$4,500</text>
|
||||
<text x="90" y="274" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">-$3,000</text>
|
||||
<text x="90" y="214" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">-$1,500</text>
|
||||
<text x="90" y="154" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">$0</text>
|
||||
<text x="90" y="94" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">$1,500</text>
|
||||
|
||||
<!-- Zero line -->
|
||||
<line x1="100" y1="154" x2="750" y2="154" stroke="#64748b" stroke-width="2" stroke-dasharray="5,5"/>
|
||||
<text x="760" y="158" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Breakeven</text>
|
||||
|
||||
<!-- Grid lines -->
|
||||
<g stroke="#e2e8f0" stroke-width="1">
|
||||
<line x1="100" y1="274" x2="750" y2="274"/>
|
||||
<line x1="100" y1="214" x2="750" y2="214"/>
|
||||
<line x1="100" y1="94" x2="750" y2="94"/>
|
||||
</g>
|
||||
|
||||
<!-- X-axis -->
|
||||
<line x1="100" y1="330" x2="750" y2="330" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="425" y="380" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#64748b">Years</text>
|
||||
|
||||
<!-- X-axis labels -->
|
||||
<text x="100" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">0</text>
|
||||
<text x="159" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">1</text>
|
||||
<text x="218" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">2</text>
|
||||
<text x="277" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">3</text>
|
||||
<text x="336" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">4</text>
|
||||
<text x="395" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">5</text>
|
||||
<text x="454" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">6</text>
|
||||
<text x="513" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">7</text>
|
||||
<text x="572" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">8</text>
|
||||
<text x="631" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">9</text>
|
||||
<text x="690" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">10</text>
|
||||
<text x="749" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">11</text>
|
||||
|
||||
<!-- Area fill under the line (red zone) -->
|
||||
<polygon points="100,154 100,334 749,159.5 749,154" fill="#fef2f2" opacity="0.7"/>
|
||||
|
||||
<!-- Area fill under the line (green zone after breakeven) -->
|
||||
<polygon points="100,154 749,154 749,159.5" fill="#f0fdf4" opacity="0.7"/>
|
||||
|
||||
<!-- Net position line -->
|
||||
<!-- Year 0: -$4,100 (y=154 + 4100*0.04 = 318) -->
|
||||
<!-- Year 1: -$3,698 (y=154 + 3698*0.04 = 302) -->
|
||||
<!-- Year 2: -$3,296 (y=154 + 3296*0.04 = 286) -->
|
||||
<!-- Year 3: -$2,894 (y=154 + 2894*0.04 = 270) -->
|
||||
<!-- Year 4: -$2,491 (y=154 + 2491*0.04 = 254) -->
|
||||
<!-- Year 5: -$2,089 (y=154 + 2089*0.04 = 238) -->
|
||||
<!-- Year 6: -$1,687 (y=154 + 1687*0.04 = 221) -->
|
||||
<!-- Year 7: -$1,284 (y=154 + 1284*0.04 = 205) -->
|
||||
<!-- Year 8: -$882 (y=154 + 882*0.04 = 189) -->
|
||||
<!-- Year 9: -$480 (y=154 + 480*0.04 = 173) -->
|
||||
<!-- Year 10: -$78 (y=154 + 78*0.04 = 157) -->
|
||||
<!-- Year 11: +$325 (y=154 - 325*0.04 = 141) -->
|
||||
|
||||
<polyline
|
||||
points="100,318 159,302 218,286 277,270 336,254 395,238 454,221 513,205 572,189 631,173 690,157 749,141"
|
||||
fill="none"
|
||||
stroke="url(#paybackGrad)"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
|
||||
<!-- Data points -->
|
||||
<g fill="#1e293b">
|
||||
<circle cx="100" cy="318" r="5"/>
|
||||
<circle cx="159" cy="302" r="4"/>
|
||||
<circle cx="218" cy="286" r="4"/>
|
||||
<circle cx="277" cy="270" r="4"/>
|
||||
<circle cx="336" cy="254" r="4"/>
|
||||
<circle cx="395" cy="238" r="4"/>
|
||||
<circle cx="454" cy="221" r="4"/>
|
||||
<circle cx="513" cy="205" r="4"/>
|
||||
<circle cx="572" cy="189" r="4"/>
|
||||
<circle cx="631" cy="173" r="4"/>
|
||||
<circle cx="690" cy="157" r="4"/>
|
||||
<circle cx="749" cy="141" r="5" fill="#22c55e"/>
|
||||
</g>
|
||||
|
||||
<!-- Key milestones labels -->
|
||||
<text x="100" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#ef4444">-$4,100</text>
|
||||
<text x="395" y="252" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">-$2,089</text>
|
||||
<text x="690" y="171" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">-$78</text>
|
||||
<text x="749" y="130" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#22c55e" font-weight="bold">+$325</text>
|
||||
|
||||
<!-- Breakeven marker -->
|
||||
<line x1="715" y1="154" x2="715" y2="330" stroke="#22c55e" stroke-width="2" stroke-dasharray="4,4"/>
|
||||
<text x="715" y="365" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#22c55e" font-weight="bold">~10.2 yrs</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="120" y="70" width="200" height="50" rx="4" fill="white" stroke="#e2e8f0"/>
|
||||
<circle cx="140" cy="88" r="6" fill="#ef4444"/>
|
||||
<text x="155" y="92" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Investment (Year 0)</text>
|
||||
<circle cx="140" cy="108" r="6" fill="#22c55e"/>
|
||||
<text x="155" y="112" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Profit (Year 11+)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
145
images/image-4.svg
Normal file
@@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450">
|
||||
<defs>
|
||||
<linearGradient id="scenarioGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="highlightGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#22c55e;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#16a34a;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="450" fill="#f8fafc"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#1e293b">System Size Comparison</text>
|
||||
<text x="400" y="50" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#64748b">NREL PVWatts Analysis: 16 Panels at Different Wattages</text>
|
||||
|
||||
<!-- Left Chart: Annual Production -->
|
||||
<text x="200" y="80" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">Annual Production (kWh)</text>
|
||||
|
||||
<!-- Y-axis -->
|
||||
<line x1="60" y1="100" x2="60" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="25" y="210" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b" transform="rotate(-90, 25, 210)">kWh/year</text>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="55" y="324" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">0</text>
|
||||
<text x="55" y="264" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">5k</text>
|
||||
<text x="55" y="204" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">10k</text>
|
||||
<text x="55" y="144" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">15k</text>
|
||||
|
||||
<!-- Grid lines -->
|
||||
<g stroke="#e2e8f0" stroke-width="1">
|
||||
<line x1="60" y1="260" x2="340" y2="260"/>
|
||||
<line x1="60" y1="200" x2="340" y2="200"/>
|
||||
<line x1="60" y1="140" x2="340" y2="140"/>
|
||||
</g>
|
||||
|
||||
<!-- X-axis -->
|
||||
<line x1="60" y1="320" x2="340" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
|
||||
<!-- Bars: 250W=6004, 300W=7204, 350W=8405, 400W=9606, 450W=10806 -->
|
||||
<!-- Scale: 15000 kWh = 220px, so 1 kWh = 0.0147px -->
|
||||
|
||||
<!-- 250W: 6004 kWh = 88px -->
|
||||
<rect x="75" y="232" width="40" height="88" fill="url(#scenarioGrad)" rx="3"/>
|
||||
<text x="95" y="225" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">6,004</text>
|
||||
<text x="95" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">250W</text>
|
||||
|
||||
<!-- 300W: 7204 kWh = 106px -->
|
||||
<rect x="125" y="214" width="40" height="106" fill="url(#scenarioGrad)" rx="3"/>
|
||||
<text x="145" y="207" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">7,204</text>
|
||||
<text x="145" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">300W</text>
|
||||
|
||||
<!-- 350W: 8405 kWh = 123px -->
|
||||
<rect x="175" y="197" width="40" height="123" fill="url(#scenarioGrad)" rx="3"/>
|
||||
<text x="195" y="190" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">8,405</text>
|
||||
<text x="195" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">350W</text>
|
||||
|
||||
<!-- 400W: 9606 kWh = 141px -->
|
||||
<rect x="225" y="179" width="40" height="141" fill="url(#highlightGrad)" rx="3"/>
|
||||
<text x="245" y="172" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b" font-weight="bold">9,606</text>
|
||||
<text x="245" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#22c55e" font-weight="bold">400W</text>
|
||||
|
||||
<!-- 450W: 10806 kWh = 158px -->
|
||||
<rect x="275" y="162" width="40" height="158" fill="url(#scenarioGrad)" rx="3"/>
|
||||
<text x="295" y="155" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">10,806</text>
|
||||
<text x="295" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">450W</text>
|
||||
|
||||
<!-- Current system marker -->
|
||||
<text x="95" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="8" fill="#ef4444">(current)</text>
|
||||
|
||||
<!-- Right Chart: Payback Period -->
|
||||
<text x="580" y="80" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">Payback Period (Years)</text>
|
||||
|
||||
<!-- Y-axis -->
|
||||
<line x1="440" y1="100" x2="440" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="405" y="210" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b" transform="rotate(-90, 405, 210)">Years</text>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="435" y="324" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">0</text>
|
||||
<text x="435" y="264" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">5</text>
|
||||
<text x="435" y="204" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">10</text>
|
||||
<text x="435" y="144" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">15</text>
|
||||
|
||||
<!-- Grid lines -->
|
||||
<g stroke="#e2e8f0" stroke-width="1">
|
||||
<line x1="440" y1="260" x2="720" y2="260"/>
|
||||
<line x1="440" y1="200" x2="720" y2="200"/>
|
||||
<line x1="440" y1="140" x2="720" y2="140"/>
|
||||
</g>
|
||||
|
||||
<!-- X-axis -->
|
||||
<line x1="440" y1="320" x2="720" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
|
||||
<!-- Bars: 250W=10.2yr, 300W=8.5yr, 350W=7.3yr, 400W=6.4yr, 450W=5.7yr -->
|
||||
<!-- Scale: 15 yr = 220px, so 1 yr = 14.67px -->
|
||||
|
||||
<!-- 250W: 10.2 yr = 150px -->
|
||||
<rect x="455" y="170" width="40" height="150" fill="#ef4444" rx="3"/>
|
||||
<text x="475" y="163" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">10.2</text>
|
||||
<text x="475" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">250W</text>
|
||||
|
||||
<!-- 300W: 8.5 yr = 125px -->
|
||||
<rect x="505" y="195" width="40" height="125" fill="#f97316" rx="3"/>
|
||||
<text x="525" y="188" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">8.5</text>
|
||||
<text x="525" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">300W</text>
|
||||
|
||||
<!-- 350W: 7.3 yr = 107px -->
|
||||
<rect x="555" y="213" width="40" height="107" fill="#eab308" rx="3"/>
|
||||
<text x="575" y="206" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">7.3</text>
|
||||
<text x="575" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">350W</text>
|
||||
|
||||
<!-- 400W: 6.4 yr = 94px -->
|
||||
<rect x="605" y="226" width="40" height="94" fill="url(#highlightGrad)" rx="3"/>
|
||||
<text x="625" y="219" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b" font-weight="bold">6.4</text>
|
||||
<text x="625" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#22c55e" font-weight="bold">400W</text>
|
||||
|
||||
<!-- 450W: 5.7 yr = 84px -->
|
||||
<rect x="655" y="236" width="40" height="84" fill="#22c55e" rx="3"/>
|
||||
<text x="675" y="229" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">5.7</text>
|
||||
<text x="675" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">450W</text>
|
||||
|
||||
<!-- Target line -->
|
||||
<line x1="440" y1="260" x2="720" y2="260" stroke="#22c55e" stroke-width="2" stroke-dasharray="5,3"/>
|
||||
<text x="725" y="264" font-family="Arial, sans-serif" font-size="9" fill="#22c55e">5 yr target</text>
|
||||
|
||||
<!-- Summary table -->
|
||||
<rect x="150" y="365" width="500" height="75" rx="6" fill="white" stroke="#e2e8f0"/>
|
||||
<text x="400" y="385" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#1e293b">Recommendation Summary</text>
|
||||
|
||||
<text x="170" y="405" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Current (16×250W):</text>
|
||||
<text x="290" y="405" font-family="Arial, sans-serif" font-size="10" fill="#ef4444">10.2 yr payback, 11% offset</text>
|
||||
|
||||
<text x="170" y="425" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Optimal (16×400W):</text>
|
||||
<text x="290" y="425" font-family="Arial, sans-serif" font-size="10" fill="#22c55e" font-weight="bold">6.4 yr payback, 18% offset</text>
|
||||
|
||||
<text x="480" y="405" font-family="Arial, sans-serif" font-size="10" fill="#64748b">100% Offset requires:</text>
|
||||
<text x="600" y="405" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">~36 kW (89 panels)</text>
|
||||
|
||||
<text x="480" y="425" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Consumption:</text>
|
||||
<text x="570" y="425" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">~24,000 kWh/yr</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.7 KiB |
136
images/image-5.svg
Normal file
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 500">
|
||||
<defs>
|
||||
<linearGradient id="steelGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#9ca3af;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#6b7280;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#4b5563;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="panelGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#1e40af;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1e3a8a;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="groundGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#92400e;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#78350f;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="500" fill="#f8fafc"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#1e293b">Ground-Mount Rack Construction</text>
|
||||
<text x="400" y="50" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#64748b">Custom Steel Design - 2 Panels per Rack</text>
|
||||
|
||||
<!-- Ground -->
|
||||
<rect x="0" y="380" width="800" height="120" fill="url(#groundGrad)"/>
|
||||
<ellipse cx="200" cy="385" rx="150" ry="8" fill="#a3a38a" opacity="0.3"/>
|
||||
|
||||
<!-- Main Side View -->
|
||||
<text x="200" y="80" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">Side View</text>
|
||||
|
||||
<!-- Steel posts in ground -->
|
||||
<rect x="80" y="260" width="12" height="140" fill="url(#steelGrad)" rx="2"/>
|
||||
<rect x="150" y="260" width="12" height="140" fill="url(#steelGrad)" rx="2"/>
|
||||
<rect x="220" y="260" width="12" height="140" fill="url(#steelGrad)" rx="2"/>
|
||||
|
||||
<!-- Post labels -->
|
||||
<text x="86" y="415" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="white">Post 1</text>
|
||||
<text x="156" y="415" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="white">Post 2</text>
|
||||
<text x="226" y="415" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="white">Post 3</text>
|
||||
|
||||
<!-- Horizontal rails -->
|
||||
<rect x="75" y="230" width="165" height="8" fill="url(#steelGrad)" rx="2" transform="rotate(-15, 157, 234)"/>
|
||||
<rect x="75" y="270" width="165" height="8" fill="url(#steelGrad)" rx="2" transform="rotate(-15, 157, 274)"/>
|
||||
|
||||
<!-- Solar panel (tilted) -->
|
||||
<g transform="rotate(-15, 157, 200)">
|
||||
<rect x="80" y="140" width="155" height="90" fill="url(#panelGrad)" rx="3" stroke="#1e3a8a" stroke-width="2"/>
|
||||
<!-- Panel grid pattern -->
|
||||
<g stroke="#3b82f6" stroke-width="1" opacity="0.4">
|
||||
<line x1="80" y1="170" x2="235" y2="170"/>
|
||||
<line x1="80" y1="200" x2="235" y2="200"/>
|
||||
<line x1="120" y1="140" x2="120" y2="230"/>
|
||||
<line x1="160" y1="140" x2="160" y2="230"/>
|
||||
<line x1="200" y1="140" x2="200" y2="230"/>
|
||||
</g>
|
||||
<!-- Panel cells -->
|
||||
<rect x="85" y="145" width="30" height="22" fill="#2563eb" opacity="0.5"/>
|
||||
<rect x="120" y="145" width="35" height="22" fill="#2563eb" opacity="0.5"/>
|
||||
<rect x="160" y="145" width="35" height="22" fill="#2563eb" opacity="0.5"/>
|
||||
<rect x="200" y="145" width="30" height="22" fill="#2563eb" opacity="0.5"/>
|
||||
</g>
|
||||
|
||||
<!-- Tilt angle indicator -->
|
||||
<path d="M 250 250 L 250 340 L 180 340" fill="none" stroke="#22c55e" stroke-width="2"/>
|
||||
<text x="260" y="300" font-family="Arial, sans-serif" font-size="11" fill="#22c55e">30° tilt</text>
|
||||
|
||||
<!-- Post depth indicator -->
|
||||
<line x1="60" y1="380" x2="60" y2="400" stroke="#ef4444" stroke-width="2"/>
|
||||
<text x="55" y="395" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#ef4444">~4' in ground</text>
|
||||
|
||||
<!-- Top View -->
|
||||
<text x="580" y="80" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">Top View (Single Rack)</text>
|
||||
|
||||
<!-- Top view background -->
|
||||
<rect x="480" y="100" width="200" height="140" fill="#e2e8f0" rx="4"/>
|
||||
|
||||
<!-- Posts from above -->
|
||||
<circle cx="510" cy="130" r="8" fill="url(#steelGrad)"/>
|
||||
<circle cx="510" cy="210" r="8" fill="url(#steelGrad)"/>
|
||||
<circle cx="650" cy="130" r="8" fill="url(#steelGrad)"/>
|
||||
<circle cx="650" cy="210" r="8" fill="url(#steelGrad)"/>
|
||||
|
||||
<!-- Rails from above -->
|
||||
<rect x="510" y="125" width="140" height="6" fill="url(#steelGrad)" rx="2"/>
|
||||
<rect x="510" y="205" width="140" height="6" fill="url(#steelGrad)" rx="2"/>
|
||||
|
||||
<!-- Panels from above -->
|
||||
<rect x="520" y="120" width="60" height="100" fill="url(#panelGrad)" rx="2" stroke="#1e3a8a" stroke-width="1"/>
|
||||
<rect x="590" y="120" width="60" height="100" fill="url(#panelGrad)" rx="2" stroke="#1e3a8a" stroke-width="1"/>
|
||||
|
||||
<!-- Panel labels -->
|
||||
<text x="550" y="175" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="white">Panel 1</text>
|
||||
<text x="620" y="175" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="white">Panel 2</text>
|
||||
|
||||
<!-- Dimensions -->
|
||||
<line x1="480" y1="250" x2="680" y2="250" stroke="#64748b" stroke-width="1"/>
|
||||
<text x="580" y="265" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">~6' width</text>
|
||||
|
||||
<!-- Materials List -->
|
||||
<rect x="50" y="430" width="300" height="60" rx="4" fill="white" stroke="#e2e8f0"/>
|
||||
<text x="200" y="450" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" font-weight="bold" fill="#1e293b">Materials Per Rack</text>
|
||||
<text x="70" y="470" font-family="Arial, sans-serif" font-size="10" fill="#64748b">• 6× 8' Steel Posts ($34.97 ea)</text>
|
||||
<text x="220" y="470" font-family="Arial, sans-serif" font-size="10" fill="#64748b">• 14× U-bolts ($2.33 ea)</text>
|
||||
<text x="70" y="485" font-family="Arial, sans-serif" font-size="10" fill="#64748b">• 3× Adj Clamps ($3.77 ea)</text>
|
||||
<text x="220" y="485" font-family="Arial, sans-serif" font-size="10" fill="#64748b">• Total: ~$267/rack</text>
|
||||
|
||||
<!-- System Layout -->
|
||||
<rect x="400" y="280" width="350" height="140" rx="4" fill="white" stroke="#e2e8f0"/>
|
||||
<text x="575" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" font-weight="bold" fill="#1e293b">Full System Layout (8 Racks)</text>
|
||||
|
||||
<!-- Mini rack representations -->
|
||||
<g fill="#3b82f6" opacity="0.7">
|
||||
<rect x="420" y="320" width="30" height="20" rx="2"/>
|
||||
<rect x="460" y="320" width="30" height="20" rx="2"/>
|
||||
<rect x="500" y="320" width="30" height="20" rx="2"/>
|
||||
<rect x="540" y="320" width="30" height="20" rx="2"/>
|
||||
<rect x="580" y="320" width="30" height="20" rx="2"/>
|
||||
<rect x="620" y="320" width="30" height="20" rx="2"/>
|
||||
<rect x="660" y="320" width="30" height="20" rx="2"/>
|
||||
<rect x="700" y="320" width="30" height="20" rx="2"/>
|
||||
</g>
|
||||
|
||||
<text x="575" y="360" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">8 Racks × 2 Panels = 16 Panels Total</text>
|
||||
<text x="575" y="375" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">System: 4.0 kW DC | Array: ~48' × 6'</text>
|
||||
<text x="575" y="390" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#22c55e">1 rack completed ($360.95)</text>
|
||||
|
||||
<!-- U-bolt detail -->
|
||||
<rect x="720" y="430" width="60" height="60" rx="4" fill="white" stroke="#e2e8f0"/>
|
||||
<text x="750" y="448" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" font-weight="bold" fill="#1e293b">U-bolt</text>
|
||||
<path d="M 735 480 Q 750 460 765 480" fill="none" stroke="#6b7280" stroke-width="3"/>
|
||||
<circle cx="735" cy="480" r="3" fill="#6b7280"/>
|
||||
<circle cx="765" cy="480" r="3" fill="#6b7280"/>
|
||||
<text x="750" y="500" text-anchor="middle" font-family="Arial, sans-serif" font-size="8" fill="#94a3b8">5/16" × 5-3/8"</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
167
images/v2-image-1.svg
Normal file
@@ -0,0 +1,167 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 500">
|
||||
<defs>
|
||||
<linearGradient id="solarGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#f59e0b;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#d97706;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gridGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="batteryGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#10b981;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#059669;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.2"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="500" fill="#f8fafc"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="35" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#1e293b">SITER Solar System Architecture</text>
|
||||
|
||||
<!-- Sun -->
|
||||
<circle cx="100" cy="120" r="40" fill="#fbbf24"/>
|
||||
<g stroke="#fbbf24" stroke-width="3">
|
||||
<line x1="100" y1="60" x2="100" y2="45"/>
|
||||
<line x1="100" y1="180" x2="100" y2="195"/>
|
||||
<line x1="40" y1="120" x2="25" y2="120"/>
|
||||
<line x1="160" y1="120" x2="175" y2="120"/>
|
||||
<line x1="55" y1="75" x2="45" y2="65"/>
|
||||
<line x1="145" y1="165" x2="155" y2="175"/>
|
||||
<line x1="55" y1="165" x2="45" y2="175"/>
|
||||
<line x1="145" y1="75" x2="155" y2="65"/>
|
||||
</g>
|
||||
<text x="100" y="215" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#64748b">Solar Radiation</text>
|
||||
<text x="100" y="230" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">5.52 kWh/m²/day</text>
|
||||
|
||||
<!-- Solar Panels -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="180" y="80" width="140" height="100" rx="8" fill="url(#solarGrad)"/>
|
||||
<g fill="#92400e" opacity="0.3">
|
||||
<rect x="190" y="90" width="25" height="35" rx="2"/>
|
||||
<rect x="220" y="90" width="25" height="35" rx="2"/>
|
||||
<rect x="250" y="90" width="25" height="35" rx="2"/>
|
||||
<rect x="280" y="90" width="25" height="35" rx="2"/>
|
||||
<rect x="190" y="130" width="25" height="35" rx="2"/>
|
||||
<rect x="220" y="130" width="25" height="35" rx="2"/>
|
||||
<rect x="250" y="130" width="25" height="35" rx="2"/>
|
||||
<rect x="280" y="130" width="25" height="35" rx="2"/>
|
||||
</g>
|
||||
</g>
|
||||
<text x="250" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">Solar Array</text>
|
||||
<text x="250" y="218" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#64748b">16 × 250W = 4.0 kW DC</text>
|
||||
<text x="250" y="233" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">Ground Mount, 30° Tilt</text>
|
||||
|
||||
<!-- Arrow Sun to Panels -->
|
||||
<path d="M145 120 L175 120" stroke="#f59e0b" stroke-width="3" marker-end="url(#arrowOrange)"/>
|
||||
<defs>
|
||||
<marker id="arrowOrange" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#f59e0b"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Inverter -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="370" y="100" width="100" height="60" rx="6" fill="#475569"/>
|
||||
<rect x="380" y="110" width="30" height="40" rx="2" fill="#1e293b"/>
|
||||
<rect x="415" y="110" width="45" height="15" rx="2" fill="#22c55e"/>
|
||||
<rect x="415" y="130" width="45" height="8" rx="1" fill="#3b82f6"/>
|
||||
<rect x="415" y="142" width="45" height="8" rx="1" fill="#eab308"/>
|
||||
</g>
|
||||
<text x="420" y="180" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#1e293b">Sol-Ark 5K</text>
|
||||
<text x="420" y="195" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Hybrid Inverter</text>
|
||||
|
||||
<!-- Arrow Panels to Inverter -->
|
||||
<path d="M320 130 L365 130" stroke="#f59e0b" stroke-width="2" marker-end="url(#arrowOrange)"/>
|
||||
<text x="342" y="122" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">DC</text>
|
||||
|
||||
<!-- Home -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="540" y="70" width="120" height="90" rx="8" fill="#e2e8f0"/>
|
||||
<polygon points="540,70 600,30 660,70" fill="#64748b"/>
|
||||
<rect x="580" y="110" width="40" height="50" fill="#3b82f6" opacity="0.3"/>
|
||||
<rect x="555" y="100" width="25" height="20" fill="#1e293b" opacity="0.2"/>
|
||||
<rect x="620" y="100" width="25" height="20" fill="#1e293b" opacity="0.2"/>
|
||||
</g>
|
||||
<text x="600" y="180" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">SITER</text>
|
||||
<text x="600" y="198" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">~1,885 kWh/month</text>
|
||||
|
||||
<!-- Arrow Inverter to Home -->
|
||||
<path d="M470 130 L535 115" stroke="#22c55e" stroke-width="2" marker-end="url(#arrowGreen)"/>
|
||||
<defs>
|
||||
<marker id="arrowGreen" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#22c55e"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<text x="502" y="115" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">AC</text>
|
||||
|
||||
<!-- Base Power Battery -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="540" y="260" width="120" height="80" rx="8" fill="url(#batteryGrad)"/>
|
||||
<rect x="590" y="250" width="20" height="10" rx="2" fill="#059669"/>
|
||||
<rect x="555" y="280" width="90" height="15" rx="2" fill="#064e3b"/>
|
||||
<rect x="555" y="280" width="70" height="15" rx="2" fill="#34d399"/>
|
||||
<text x="600" y="318" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="white">78%</text>
|
||||
</g>
|
||||
<text x="600" y="360" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#1e293b">Base Power Battery</text>
|
||||
<text x="600" y="378" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">+ Automatic Transfer Switch</text>
|
||||
|
||||
<!-- Arrow Home to Battery -->
|
||||
<path d="M600 165 L600 255" stroke="#10b981" stroke-width="2" stroke-dasharray="5,3" marker-end="url(#arrowGreen)"/>
|
||||
<text x="615" y="210" font-family="Arial, sans-serif" font-size="9" fill="#64748b">charge</text>
|
||||
|
||||
<!-- Grid -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="370" y="260" width="100" height="80" rx="6" fill="url(#gridGrad)"/>
|
||||
<g stroke="white" stroke-width="2" opacity="0.5">
|
||||
<line x1="380" y1="280" x2="460" y2="280"/>
|
||||
<line x1="380" y1="300" x2="460" y2="300"/>
|
||||
<line x1="380" y1="320" x2="460" y2="320"/>
|
||||
<line x1="400" y1="270" x2="400" y2="330"/>
|
||||
<line x1="420" y1="270" x2="420" y2="330"/>
|
||||
<line x1="440" y1="270" x2="440" y2="330"/>
|
||||
</g>
|
||||
</g>
|
||||
<text x="420" y="360" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#1e293b">Oncor Grid</text>
|
||||
<text x="420" y="378" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Utility Delivery</text>
|
||||
|
||||
<!-- Arrow Inverter to Grid -->
|
||||
<path d="M420 160 L420 255" stroke="#3b82f6" stroke-width="2" marker-end="url(#arrowBlue)"/>
|
||||
<defs>
|
||||
<marker id="arrowBlue" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#3b82f6"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<text x="435" y="210" font-family="Arial, sans-serif" font-size="9" fill="#64748b">export</text>
|
||||
<text x="435" y="220" font-family="Arial, sans-serif" font-size="8" fill="#94a3b8">@ $0.04/kWh</text>
|
||||
|
||||
<!-- Grid to Battery -->
|
||||
<path d="M470 300 L535 300" stroke="#3b82f6" stroke-width="2" stroke-dasharray="5,3" marker-end="url(#arrowBlue)"/>
|
||||
<text x="502" y="292" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">backup</text>
|
||||
|
||||
<!-- Stats Box -->
|
||||
<rect x="50" y="420" width="350" height="60" rx="6" fill="#f1f5f9"/>
|
||||
<text x="225" y="440" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" font-weight="bold" fill="#475569">15-Month Billing Summary</text>
|
||||
<text x="70" y="460" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Avg: 1,885 kWh/mo | $264.47/mo</text>
|
||||
<text x="70" y="475" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Rate: $0.140/kWh effective</text>
|
||||
<text x="280" y="460" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Summer peak: 2,912 kWh</text>
|
||||
<text x="280" y="475" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Winter low: 1,333 kWh</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="420" y="420" width="330" height="60" rx="6" fill="#f1f5f9"/>
|
||||
<text x="585" y="440" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" font-weight="bold" fill="#475569">Energy Flow</text>
|
||||
|
||||
<line x1="440" y1="460" x2="480" y2="460" stroke="#f59e0b" stroke-width="3"/>
|
||||
<text x="490" y="464" font-family="Arial, sans-serif" font-size="10" fill="#64748b">DC Power</text>
|
||||
|
||||
<line x1="560" y1="460" x2="600" y2="460" stroke="#22c55e" stroke-width="3"/>
|
||||
<text x="610" y="464" font-family="Arial, sans-serif" font-size="10" fill="#64748b">AC to Home</text>
|
||||
|
||||
<line x1="680" y1="460" x2="720" y2="460" stroke="#3b82f6" stroke-width="3"/>
|
||||
<text x="730" y="464" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Grid</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.3 KiB |
121
images/v2-image-2.svg
Normal file
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450">
|
||||
<defs>
|
||||
<linearGradient id="barGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#f59e0b;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#d97706;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="consumptionGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="450" fill="#f8fafc"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#1e293b">Solar Production vs Monthly Consumption</text>
|
||||
<text x="400" y="50" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#64748b">16 × 250W panels = 4.0 kW DC | SITER, Pflugerville, TX</text>
|
||||
|
||||
<!-- Y-axis -->
|
||||
<line x1="80" y1="70" x2="80" y2="340" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="40" y="205" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#64748b" transform="rotate(-90, 40, 205)">kWh</text>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="70" y="344" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">0</text>
|
||||
<text x="70" y="289" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">750</text>
|
||||
<text x="70" y="234" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">1,500</text>
|
||||
<text x="70" y="179" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">2,250</text>
|
||||
<text x="70" y="124" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">3,000</text>
|
||||
<text x="70" y="74" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">3,750</text>
|
||||
|
||||
<!-- Grid lines -->
|
||||
<g stroke="#e2e8f0" stroke-width="1">
|
||||
<line x1="80" y1="284" x2="760" y2="284"/>
|
||||
<line x1="80" y1="229" x2="760" y2="229"/>
|
||||
<line x1="80" y1="174" x2="760" y2="174"/>
|
||||
<line x1="80" y1="119" x2="760" y2="119"/>
|
||||
<line x1="80" y1="74" x2="760" y2="74"/>
|
||||
</g>
|
||||
|
||||
<!-- X-axis -->
|
||||
<line x1="80" y1="340" x2="760" y2="340" stroke="#cbd5e1" stroke-width="1"/>
|
||||
|
||||
<!-- Monthly data: Consumption (blue) vs Solar (orange) -->
|
||||
<!-- Scale: 3500 kWh = 270px height, so each kWh = 0.077px -->
|
||||
<!-- Y offset: 340 - value * 0.077 -->
|
||||
|
||||
<!-- Jan: Consumption ~1333 (winter avg), Solar 450 -->
|
||||
<rect x="95" y="237" width="22" height="103" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="120" y="305" width="22" height="35" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="117" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Jan</text>
|
||||
|
||||
<!-- Feb: Consumption ~1333, Solar 453 -->
|
||||
<rect x="150" y="237" width="22" height="103" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="175" y="305" width="22" height="35" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="172" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Feb</text>
|
||||
|
||||
<!-- Mar: Consumption ~1777 (shoulder), Solar 492 -->
|
||||
<rect x="205" y="203" width="22" height="137" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="230" y="302" width="22" height="38" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="227" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Mar</text>
|
||||
|
||||
<!-- Apr: Consumption ~1777, Solar 508 -->
|
||||
<rect x="260" y="203" width="22" height="137" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="285" y="301" width="22" height="39" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="282" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Apr</text>
|
||||
|
||||
<!-- May: Consumption ~1777, Solar 534 -->
|
||||
<rect x="315" y="203" width="22" height="137" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="340" y="299" width="22" height="41" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="337" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">May</text>
|
||||
|
||||
<!-- Jun: Consumption ~2912 (summer), Solar 514 -->
|
||||
<rect x="370" y="116" width="22" height="224" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="395" y="300" width="22" height="40" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="392" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Jun</text>
|
||||
|
||||
<!-- Jul: Consumption ~2912, Solar 555 -->
|
||||
<rect x="425" y="116" width="22" height="224" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="450" y="297" width="22" height="43" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="447" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Jul</text>
|
||||
|
||||
<!-- Aug: Consumption ~2912, Solar 571 (peak) -->
|
||||
<rect x="480" y="116" width="22" height="224" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="505" y="296" width="22" height="44" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="502" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Aug</text>
|
||||
|
||||
<!-- Sep: Consumption ~2912, Solar 514 -->
|
||||
<rect x="535" y="116" width="22" height="224" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="560" y="300" width="22" height="40" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="557" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Sep</text>
|
||||
|
||||
<!-- Oct: Consumption ~1777, Solar 516 -->
|
||||
<rect x="590" y="203" width="22" height="137" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="615" y="300" width="22" height="40" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="612" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Oct</text>
|
||||
|
||||
<!-- Nov: Consumption ~1333, Solar 469 -->
|
||||
<rect x="645" y="237" width="22" height="103" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="670" y="304" width="22" height="36" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="667" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Nov</text>
|
||||
|
||||
<!-- Dec: Consumption ~1333, Solar 429 -->
|
||||
<rect x="700" y="237" width="22" height="103" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<rect x="725" y="307" width="22" height="33" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="722" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Dec</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="250" y="375" width="300" height="60" rx="6" fill="white" stroke="#e2e8f0"/>
|
||||
<rect x="270" y="395" width="20" height="15" fill="url(#consumptionGrad)" rx="2"/>
|
||||
<text x="300" y="407" font-family="Arial, sans-serif" font-size="11" fill="#64748b">Monthly Consumption (avg by season)</text>
|
||||
<rect x="270" y="415" width="20" height="15" fill="url(#barGrad)" rx="2"/>
|
||||
<text x="300" y="427" font-family="Arial, sans-serif" font-size="11" fill="#64748b">Solar Production (NREL estimate)</text>
|
||||
|
||||
<!-- Summary stats -->
|
||||
<rect x="580" y="375" width="180" height="60" rx="4" fill="#fef3c7"/>
|
||||
<text x="670" y="395" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#92400e">Annual Production: 6,004 kWh</text>
|
||||
<text x="670" y="412" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#a16207">Annual Consumption: ~22,600 kWh</text>
|
||||
<text x="670" y="427" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#92400e" font-weight="bold">Offset: 26.6%</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
131
images/v2-image-3.svg
Normal file
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400">
|
||||
<defs>
|
||||
<linearGradient id="paybackGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#ef4444;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#22c55e;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="savingsGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#22c55e;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#16a34a;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="costGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ef4444;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#dc2626;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="400" fill="#f8fafc"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#1e293b">10-Year Payback Timeline</text>
|
||||
<text x="400" y="50" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#64748b">16 × 250W System | $4,100 Investment | $402/yr Savings | $264/mo Avg Bill</text>
|
||||
|
||||
<!-- Y-axis -->
|
||||
<line x1="100" y1="70" x2="100" y2="330" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="35" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#64748b" transform="rotate(-90, 35, 200)">Cumulative ($)</text>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="90" y="334" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">-$4,500</text>
|
||||
<text x="90" y="274" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">-$3,000</text>
|
||||
<text x="90" y="214" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">-$1,500</text>
|
||||
<text x="90" y="154" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">$0</text>
|
||||
<text x="90" y="94" text-anchor="end" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">$1,500</text>
|
||||
|
||||
<!-- Zero line -->
|
||||
<line x1="100" y1="154" x2="750" y2="154" stroke="#64748b" stroke-width="2" stroke-dasharray="5,5"/>
|
||||
<text x="760" y="158" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Breakeven</text>
|
||||
|
||||
<!-- Grid lines -->
|
||||
<g stroke="#e2e8f0" stroke-width="1">
|
||||
<line x1="100" y1="274" x2="750" y2="274"/>
|
||||
<line x1="100" y1="214" x2="750" y2="214"/>
|
||||
<line x1="100" y1="94" x2="750" y2="94"/>
|
||||
</g>
|
||||
|
||||
<!-- X-axis -->
|
||||
<line x1="100" y1="330" x2="750" y2="330" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="425" y="380" text-anchor="middle" font-family="Arial, sans-serif" font-size="11" fill="#64748b">Years</text>
|
||||
|
||||
<!-- X-axis labels -->
|
||||
<text x="100" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">0</text>
|
||||
<text x="159" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">1</text>
|
||||
<text x="218" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">2</text>
|
||||
<text x="277" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">3</text>
|
||||
<text x="336" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">4</text>
|
||||
<text x="395" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">5</text>
|
||||
<text x="454" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">6</text>
|
||||
<text x="513" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">7</text>
|
||||
<text x="572" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">8</text>
|
||||
<text x="631" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">9</text>
|
||||
<text x="690" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">10</text>
|
||||
<text x="749" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#94a3b8">11</text>
|
||||
|
||||
<!-- Area fill under the line (red zone) -->
|
||||
<polygon points="100,154 100,334 749,159.5 749,154" fill="#fef2f2" opacity="0.7"/>
|
||||
|
||||
<!-- Area fill under the line (green zone after breakeven) -->
|
||||
<polygon points="100,154 749,154 749,159.5" fill="#f0fdf4" opacity="0.7"/>
|
||||
|
||||
<!-- Net position line -->
|
||||
<!-- Year 0: -$4,100 (y=154 + 4100*0.04 = 318) -->
|
||||
<!-- Year 1: -$3,698 (y=154 + 3698*0.04 = 302) -->
|
||||
<!-- Year 2: -$3,296 (y=154 + 3296*0.04 = 286) -->
|
||||
<!-- Year 3: -$2,894 (y=154 + 2894*0.04 = 270) -->
|
||||
<!-- Year 4: -$2,491 (y=154 + 2491*0.04 = 254) -->
|
||||
<!-- Year 5: -$2,089 (y=154 + 2089*0.04 = 238) -->
|
||||
<!-- Year 6: -$1,687 (y=154 + 1687*0.04 = 221) -->
|
||||
<!-- Year 7: -$1,284 (y=154 + 1284*0.04 = 205) -->
|
||||
<!-- Year 8: -$882 (y=154 + 882*0.04 = 189) -->
|
||||
<!-- Year 9: -$480 (y=154 + 480*0.04 = 173) -->
|
||||
<!-- Year 10: -$78 (y=154 + 78*0.04 = 157) -->
|
||||
<!-- Year 11: +$325 (y=154 - 325*0.04 = 141) -->
|
||||
|
||||
<polyline
|
||||
points="100,318 159,302 218,286 277,270 336,254 395,238 454,221 513,205 572,189 631,173 690,157 749,141"
|
||||
fill="none"
|
||||
stroke="url(#paybackGrad)"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
|
||||
<!-- Data points -->
|
||||
<g fill="#1e293b">
|
||||
<circle cx="100" cy="318" r="5"/>
|
||||
<circle cx="159" cy="302" r="4"/>
|
||||
<circle cx="218" cy="286" r="4"/>
|
||||
<circle cx="277" cy="270" r="4"/>
|
||||
<circle cx="336" cy="254" r="4"/>
|
||||
<circle cx="395" cy="238" r="4"/>
|
||||
<circle cx="454" cy="221" r="4"/>
|
||||
<circle cx="513" cy="205" r="4"/>
|
||||
<circle cx="572" cy="189" r="4"/>
|
||||
<circle cx="631" cy="173" r="4"/>
|
||||
<circle cx="690" cy="157" r="4"/>
|
||||
<circle cx="749" cy="141" r="5" fill="#22c55e"/>
|
||||
</g>
|
||||
|
||||
<!-- Key milestones labels -->
|
||||
<text x="100" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#ef4444">-$4,100</text>
|
||||
<text x="395" y="252" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">-$2,089</text>
|
||||
<text x="690" y="171" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">-$78</text>
|
||||
<text x="749" y="130" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#22c55e" font-weight="bold">+$325</text>
|
||||
|
||||
<!-- Breakeven marker -->
|
||||
<line x1="715" y1="154" x2="715" y2="330" stroke="#22c55e" stroke-width="2" stroke-dasharray="4,4"/>
|
||||
<text x="715" y="365" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#22c55e" font-weight="bold">~10.2 yrs</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="120" y="70" width="200" height="50" rx="4" fill="white" stroke="#e2e8f0"/>
|
||||
<circle cx="140" cy="88" r="6" fill="#ef4444"/>
|
||||
<text x="155" y="92" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Investment (Year 0)</text>
|
||||
<circle cx="140" cy="108" r="6" fill="#22c55e"/>
|
||||
<text x="155" y="112" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Profit (Year 11+)</text>
|
||||
|
||||
<!-- Stats box -->
|
||||
<rect x="400" y="70" width="200" height="50" rx="4" fill="#fef3c7"/>
|
||||
<text x="500" y="88" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#92400e">Monthly Savings: $33.52</text>
|
||||
<text x="500" y="103" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#92400e">Bill Offset: 12.7%</text>
|
||||
<text x="500" y="118" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#92400e">(of $264.47 avg bill)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
145
images/v2-image-4.svg
Normal file
@@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450">
|
||||
<defs>
|
||||
<linearGradient id="scenarioGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="highlightGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#22c55e;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#16a34a;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="450" fill="#f8fafc"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#1e293b">System Size Comparison (Updated)</text>
|
||||
<text x="400" y="50" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#64748b">NREL PVWatts Analysis: 16 Panels at Different Wattages | Avg Bill: $264.47/mo</text>
|
||||
|
||||
<!-- Left Chart: Annual Production -->
|
||||
<text x="200" y="80" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">Annual Production (kWh)</text>
|
||||
|
||||
<!-- Y-axis -->
|
||||
<line x1="60" y1="100" x2="60" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="25" y="210" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b" transform="rotate(-90, 25, 210)">kWh/year</text>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="55" y="324" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">0</text>
|
||||
<text x="55" y="264" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">5k</text>
|
||||
<text x="55" y="204" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">10k</text>
|
||||
<text x="55" y="144" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">15k</text>
|
||||
|
||||
<!-- Grid lines -->
|
||||
<g stroke="#e2e8f0" stroke-width="1">
|
||||
<line x1="60" y1="260" x2="340" y2="260"/>
|
||||
<line x1="60" y1="200" x2="340" y2="200"/>
|
||||
<line x1="60" y1="140" x2="340" y2="140"/>
|
||||
</g>
|
||||
|
||||
<!-- X-axis -->
|
||||
<line x1="60" y1="320" x2="340" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
|
||||
<!-- Bars: 250W=6004, 300W=7204, 350W=8405, 400W=9606, 450W=10806 -->
|
||||
<!-- Scale: 15000 kWh = 220px, so 1 kWh = 0.0147px -->
|
||||
|
||||
<!-- 250W: 6004 kWh = 88px -->
|
||||
<rect x="75" y="232" width="40" height="88" fill="url(#scenarioGrad)" rx="3"/>
|
||||
<text x="95" y="225" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">6,004</text>
|
||||
<text x="95" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">250W</text>
|
||||
|
||||
<!-- 300W: 7204 kWh = 106px -->
|
||||
<rect x="125" y="214" width="40" height="106" fill="url(#scenarioGrad)" rx="3"/>
|
||||
<text x="145" y="207" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">7,204</text>
|
||||
<text x="145" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">300W</text>
|
||||
|
||||
<!-- 350W: 8405 kWh = 123px -->
|
||||
<rect x="175" y="197" width="40" height="123" fill="url(#scenarioGrad)" rx="3"/>
|
||||
<text x="195" y="190" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">8,405</text>
|
||||
<text x="195" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">350W</text>
|
||||
|
||||
<!-- 400W: 9606 kWh = 141px -->
|
||||
<rect x="225" y="179" width="40" height="141" fill="url(#highlightGrad)" rx="3"/>
|
||||
<text x="245" y="172" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b" font-weight="bold">9,606</text>
|
||||
<text x="245" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#22c55e" font-weight="bold">400W</text>
|
||||
|
||||
<!-- 450W: 10806 kWh = 158px -->
|
||||
<rect x="275" y="162" width="40" height="158" fill="url(#scenarioGrad)" rx="3"/>
|
||||
<text x="295" y="155" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">10,806</text>
|
||||
<text x="295" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">450W</text>
|
||||
|
||||
<!-- Current system marker -->
|
||||
<text x="95" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="8" fill="#ef4444">(current)</text>
|
||||
|
||||
<!-- Right Chart: Payback Period -->
|
||||
<text x="580" y="80" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1e293b">Payback Period (Years)</text>
|
||||
|
||||
<!-- Y-axis -->
|
||||
<line x1="440" y1="100" x2="440" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="405" y="210" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#64748b" transform="rotate(-90, 405, 210)">Years</text>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="435" y="324" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">0</text>
|
||||
<text x="435" y="264" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">5</text>
|
||||
<text x="435" y="204" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">10</text>
|
||||
<text x="435" y="144" text-anchor="end" font-family="Arial, sans-serif" font-size="9" fill="#94a3b8">15</text>
|
||||
|
||||
<!-- Grid lines -->
|
||||
<g stroke="#e2e8f0" stroke-width="1">
|
||||
<line x1="440" y1="260" x2="720" y2="260"/>
|
||||
<line x1="440" y1="200" x2="720" y2="200"/>
|
||||
<line x1="440" y1="140" x2="720" y2="140"/>
|
||||
</g>
|
||||
|
||||
<!-- X-axis -->
|
||||
<line x1="440" y1="320" x2="720" y2="320" stroke="#cbd5e1" stroke-width="1"/>
|
||||
|
||||
<!-- Bars: 250W=10.2yr, 300W=8.5yr, 350W=7.3yr, 400W=6.4yr, 450W=5.7yr -->
|
||||
<!-- Scale: 15 yr = 220px, so 1 yr = 14.67px -->
|
||||
|
||||
<!-- 250W: 10.2 yr = 150px -->
|
||||
<rect x="455" y="170" width="40" height="150" fill="#ef4444" rx="3"/>
|
||||
<text x="475" y="163" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">10.2</text>
|
||||
<text x="475" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">250W</text>
|
||||
|
||||
<!-- 300W: 8.5 yr = 125px -->
|
||||
<rect x="505" y="195" width="40" height="125" fill="#f97316" rx="3"/>
|
||||
<text x="525" y="188" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">8.5</text>
|
||||
<text x="525" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">300W</text>
|
||||
|
||||
<!-- 350W: 7.3 yr = 107px -->
|
||||
<rect x="555" y="213" width="40" height="107" fill="#eab308" rx="3"/>
|
||||
<text x="575" y="206" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">7.3</text>
|
||||
<text x="575" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">350W</text>
|
||||
|
||||
<!-- 400W: 6.4 yr = 94px -->
|
||||
<rect x="605" y="226" width="40" height="94" fill="url(#highlightGrad)" rx="3"/>
|
||||
<text x="625" y="219" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b" font-weight="bold">6.4</text>
|
||||
<text x="625" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#22c55e" font-weight="bold">400W</text>
|
||||
|
||||
<!-- 450W: 5.7 yr = 84px -->
|
||||
<rect x="655" y="236" width="40" height="84" fill="#22c55e" rx="3"/>
|
||||
<text x="675" y="229" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#1e293b">5.7</text>
|
||||
<text x="675" y="335" text-anchor="middle" font-family="Arial, sans-serif" font-size="9" fill="#64748b">450W</text>
|
||||
|
||||
<!-- Target line -->
|
||||
<line x1="440" y1="260" x2="720" y2="260" stroke="#22c55e" stroke-width="2" stroke-dasharray="5,3"/>
|
||||
<text x="725" y="264" font-family="Arial, sans-serif" font-size="9" fill="#22c55e">5 yr target</text>
|
||||
|
||||
<!-- Summary table -->
|
||||
<rect x="150" y="365" width="500" height="75" rx="6" fill="white" stroke="#e2e8f0"/>
|
||||
<text x="400" y="385" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#1e293b">Recommendation Summary (Updated)</text>
|
||||
|
||||
<text x="170" y="405" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Current (16×250W):</text>
|
||||
<text x="290" y="405" font-family="Arial, sans-serif" font-size="10" fill="#ef4444">10.2 yr payback, 12.7% offset</text>
|
||||
|
||||
<text x="170" y="425" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Optimal (16×400W):</text>
|
||||
<text x="290" y="425" font-family="Arial, sans-serif" font-size="10" fill="#22c55e" font-weight="bold">6.4 yr payback, 20.3% offset</text>
|
||||
|
||||
<text x="480" y="405" font-family="Arial, sans-serif" font-size="10" fill="#64748b">100% Offset requires:</text>
|
||||
<text x="600" y="405" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">~32 kW (80 panels)</text>
|
||||
|
||||
<text x="480" y="425" font-family="Arial, sans-serif" font-size="10" fill="#64748b">Consumption:</text>
|
||||
<text x="570" y="425" font-family="Arial, sans-serif" font-size="10" fill="#1e293b">~22,600 kWh/yr</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.7 KiB |
9
solar-analysis/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip install --no-cache-dir requests
|
||||
|
||||
COPY solar_estimate.py /app/
|
||||
|
||||
ENTRYPOINT ["python", "solar_estimate.py"]
|
||||
16
solar-analysis/run-analysis.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Run NREL solar analysis in Docker container
|
||||
# Build once: docker build -t siter-solar-nrel .
|
||||
# Run anytime: ./run-analysis.sh --scenarios
|
||||
|
||||
set -e
|
||||
|
||||
IMAGE_NAME="siter-solar-nrel"
|
||||
|
||||
if ! docker image inspect "$IMAGE_NAME" &>/dev/null; then
|
||||
echo "Error: Docker image '$IMAGE_NAME' not found."
|
||||
echo "Build it first: docker build -t $IMAGE_NAME ."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker run --rm "$IMAGE_NAME" "$@"
|
||||
674
solar-analysis/siter-solar-analysis.sh
Executable file
@@ -0,0 +1,674 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck source=/dev/null
|
||||
#
|
||||
# siter-solar-analysis - NREL PVWatts Solar Production Estimator
|
||||
#
|
||||
# Usage:
|
||||
# ./siter-solar-analysis.sh # Basic analysis
|
||||
# ./siter-solar-analysis.sh --scenarios # Compare system sizes
|
||||
# ./siter-solar-analysis.sh --help # Show help
|
||||
#
|
||||
# Environment variables:
|
||||
# NREL_API_KEY - Your NREL API key (default: DEMO_KEY)
|
||||
# SITER_LAT - Site latitude (default: 30.44)
|
||||
# SITER_LON - Site longitude (default: -97.62)
|
||||
#
|
||||
# Get your free API key at: https://developer.nrel.gov/signup/
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# Version
|
||||
readonly VERSION="1.0.0"
|
||||
|
||||
# Default configuration
|
||||
readonly DEFAULT_API_KEY="DEMO_KEY"
|
||||
readonly DEFAULT_LAT="30.44"
|
||||
readonly DEFAULT_LON="-97.62"
|
||||
readonly DEFAULT_PANELS="16"
|
||||
readonly DEFAULT_PANEL_WATTS="250"
|
||||
readonly DEFAULT_TILT="30"
|
||||
readonly DEFAULT_AZIMUTH="180"
|
||||
readonly DEFAULT_LOSSES="14"
|
||||
readonly DEFAULT_ARRAY_TYPE="0"
|
||||
|
||||
# Financial parameters
|
||||
readonly PROJECT_COST="4100"
|
||||
readonly CURRENT_MONTHLY_BILL="301.08"
|
||||
readonly CURRENT_ANNUAL_CONSUMPTION="23952"
|
||||
readonly CONSUMPTION_RATE="0.085"
|
||||
readonly EXPORT_RATE="0.04"
|
||||
readonly SELF_CONSUMPTION_PCT="0.60"
|
||||
|
||||
# API endpoint
|
||||
readonly API_URL="https://developer.nrel.gov/api/pvwatts/v6.json"
|
||||
|
||||
# Colors for output
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[0;33m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Output control
|
||||
VERBOSE="${VERBOSE:-false}"
|
||||
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
|
||||
|
||||
#######################################
|
||||
# Print error message and exit
|
||||
# Arguments:
|
||||
# $1 - Error message
|
||||
# Outputs:
|
||||
# Writes error to stderr
|
||||
#######################################
|
||||
error() {
|
||||
echo -e "${RED}ERROR: ${1}${NC}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Print warning message
|
||||
# Arguments:
|
||||
# $1 - Warning message
|
||||
#######################################
|
||||
warn() {
|
||||
echo -e "${YELLOW}WARN: ${1}${NC}" >&2
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Print info message
|
||||
# Arguments:
|
||||
# $1 - Info message
|
||||
#######################################
|
||||
info() {
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
echo -e "${GREEN}INFO: ${1}${NC}" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Print usage information
|
||||
#######################################
|
||||
usage() {
|
||||
cat << EOF
|
||||
SITER Solar Analysis - NREL PVWatts Solar Production Estimator v${VERSION}
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
-k, --api-key KEY NREL API key (or set NREL_API_KEY env var)
|
||||
--lat VALUE Site latitude (default: ${DEFAULT_LAT})
|
||||
--lon VALUE Site longitude (default: ${DEFAULT_LON})
|
||||
-p, --panels NUM Number of panels (default: ${DEFAULT_PANELS})
|
||||
-w, --watts WATTS Watts per panel (default: ${DEFAULT_PANEL_WATTS})
|
||||
-t, --tilt DEGREES Array tilt in degrees (default: ${DEFAULT_TILT})
|
||||
-a, --azimuth DEGREES Array azimuth (default: ${DEFAULT_AZIMUTH})
|
||||
-l, --losses PERCENT System losses percent (default: ${DEFAULT_LOSSES})
|
||||
--scenarios Run multiple system size scenarios
|
||||
--json Output as JSON
|
||||
--verbose Enable verbose output
|
||||
-h, --help Show this help message
|
||||
-v, --version Show version
|
||||
|
||||
EXAMPLES:
|
||||
# Basic analysis with defaults
|
||||
$(basename "$0")
|
||||
|
||||
# With your API key
|
||||
$(basename "$0") -k YOUR_API_KEY
|
||||
|
||||
# Compare different system sizes
|
||||
$(basename "$0") --scenarios
|
||||
|
||||
# Custom configuration
|
||||
$(basename "$0") -p 20 -w 400 --lat 30.5 --lon -97.7
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
NREL_API_KEY Your NREL API key
|
||||
SITER_LAT Site latitude
|
||||
SITER_LON Site longitude
|
||||
|
||||
Get your free API key at: https://developer.nrel.gov/signup/
|
||||
EOF
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Check required dependencies
|
||||
# Globals:
|
||||
# None
|
||||
# Arguments:
|
||||
# None
|
||||
# Outputs:
|
||||
# Error if dependencies missing
|
||||
#######################################
|
||||
check_dependencies() {
|
||||
local missing=()
|
||||
|
||||
command -v curl >/dev/null 2>&1 || missing+=("curl")
|
||||
command -v jq >/dev/null 2>&1 || missing+=("jq")
|
||||
command -v bc >/dev/null 2>&1 || missing+=("bc")
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
error "Missing required dependencies: ${missing[*]}\nInstall with: apt-get install ${missing[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Validate numeric value
|
||||
# Arguments:
|
||||
# $1 - Value to validate
|
||||
# $2 - Parameter name for error message
|
||||
# Returns:
|
||||
# 0 if valid, 1 if invalid
|
||||
#######################################
|
||||
validate_numeric() {
|
||||
local value="$1"
|
||||
local name="$2"
|
||||
|
||||
if ! [[ "$value" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
||||
error "Invalid ${name}: '${value}' must be a number"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Validate range
|
||||
# Arguments:
|
||||
# $1 - Value to validate
|
||||
# $2 - Minimum
|
||||
# $3 - Maximum
|
||||
# $4 - Parameter name
|
||||
# Returns:
|
||||
# 0 if valid, exits with error if invalid
|
||||
#######################################
|
||||
validate_range() {
|
||||
local value="$1"
|
||||
local min="$2"
|
||||
local max="$3"
|
||||
local name="$4"
|
||||
|
||||
validate_numeric "$value" "$name"
|
||||
|
||||
if (( $(echo "$value < $min" | bc -l) )) || (( $(echo "$value > $max" | bc -l) )); then
|
||||
error "Invalid ${name}: ${value} (must be between ${min} and ${max})"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Query NREL PVWatts API
|
||||
# Globals:
|
||||
# API_URL
|
||||
# Arguments:
|
||||
# $1 - API key
|
||||
# $2 - System capacity (kW)
|
||||
# $3 - Latitude
|
||||
# $4 - Longitude
|
||||
# $5 - Tilt
|
||||
# $6 - Azimuth
|
||||
# $7 - Losses
|
||||
# Outputs:
|
||||
# JSON response from API
|
||||
#######################################
|
||||
query_nrel_api() {
|
||||
local api_key="$1"
|
||||
local capacity="$2"
|
||||
local lat="$3"
|
||||
local lon="$4"
|
||||
local tilt="$5"
|
||||
local azimuth="$6"
|
||||
local losses="$7"
|
||||
local array_type="${8:-$DEFAULT_ARRAY_TYPE}"
|
||||
|
||||
local url="${API_URL}?api_key=${api_key}&lat=${lat}&lon=${lon}"
|
||||
url+="&system_capacity=${capacity}&array_type=${array_type}"
|
||||
url+="&tilt=${tilt}&azimuth=${azimuth}&module_type=0"
|
||||
url+="&losses=${losses}&timeframe=monthly"
|
||||
|
||||
info "Querying NREL API: capacity=${capacity}kW"
|
||||
|
||||
local response
|
||||
local http_code
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
response=$(curl -s -w "\n%{http_code}" ${url} 2>/dev/null)
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
response=$(echo "$response" | sed '$d')
|
||||
|
||||
if [[ "$http_code" != "200" ]]; then
|
||||
error "API request failed with HTTP ${http_code}: ${response}"
|
||||
fi
|
||||
|
||||
# Check for API errors
|
||||
local api_errors
|
||||
api_errors=$(echo "$response" | jq -r '.errors // [] | if length > 0 then .[] else empty end')
|
||||
if [[ -n "$api_errors" ]]; then
|
||||
error "API returned errors: ${api_errors}"
|
||||
fi
|
||||
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Calculate financial projections
|
||||
# Arguments:
|
||||
# $1 - Annual production (kWh)
|
||||
# Outputs:
|
||||
# Financial metrics
|
||||
#######################################
|
||||
calculate_financials() {
|
||||
local annual_kwh="$1"
|
||||
|
||||
local self_consumed
|
||||
local exported
|
||||
local self_consumption_value
|
||||
local export_value
|
||||
local total_annual_value
|
||||
local monthly_savings
|
||||
local offset_pct
|
||||
local payback_months
|
||||
local payback_years
|
||||
|
||||
self_consumed=$(echo "$annual_kwh * $SELF_CONSUMPTION_PCT" | bc -l)
|
||||
exported=$(echo "$annual_kwh * (1 - $SELF_CONSUMPTION_PCT)" | bc -l)
|
||||
self_consumption_value=$(echo "$self_consumed * $CONSUMPTION_RATE" | bc -l)
|
||||
export_value=$(echo "$exported * $EXPORT_RATE" | bc -l)
|
||||
total_annual_value=$(echo "$self_consumption_value + $export_value" | bc -l)
|
||||
monthly_savings=$(echo "$total_annual_value / 12" | bc -l)
|
||||
offset_pct=$(echo "$monthly_savings / $CURRENT_MONTHLY_BILL * 100" | bc -l)
|
||||
|
||||
if (( $(echo "$monthly_savings > 0" | bc -l) )); then
|
||||
payback_months=$(echo "$PROJECT_COST / $monthly_savings" | bc -l)
|
||||
else
|
||||
payback_months="999999"
|
||||
fi
|
||||
payback_years=$(echo "$payback_months / 12" | bc -l)
|
||||
|
||||
cat << EOF
|
||||
{
|
||||
"annual_kwh": $(printf "%.0f" "$annual_kwh"),
|
||||
"monthly_avg_kwh": $(printf "%.0f" "$(echo "$annual_kwh / 12" | bc -l)"),
|
||||
"self_consumed_kwh": $(printf "%.0f" "$self_consumed"),
|
||||
"exported_kwh": $(printf "%.0f" "$exported"),
|
||||
"self_consumption_value": $(printf "%.2f" "$self_consumption_value"),
|
||||
"export_value": $(printf "%.2f" "$export_value"),
|
||||
"total_annual_value": $(printf "%.2f" "$total_annual_value"),
|
||||
"monthly_savings": $(printf "%.2f" "$monthly_savings"),
|
||||
"offset_pct": $(printf "%.1f" "$offset_pct"),
|
||||
"payback_months": $(printf "%.1f" "$payback_months"),
|
||||
"payback_years": $(printf "%.1f" "$payback_years")
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Format and display analysis results
|
||||
# Arguments:
|
||||
# $1 - API response JSON
|
||||
# $2 - System capacity (kW)
|
||||
# $3 - Panel watts
|
||||
# $4 - Number of panels
|
||||
# $5 - Latitude
|
||||
# $6 - Longitude
|
||||
# Outputs:
|
||||
# Formatted report
|
||||
#######################################
|
||||
format_output() {
|
||||
local data="$1"
|
||||
local capacity="$2"
|
||||
local panel_watts="$3"
|
||||
local num_panels="$4"
|
||||
local lat="$5"
|
||||
local lon="$6"
|
||||
|
||||
local annual_kwh
|
||||
local capacity_factor
|
||||
local solrad_annual
|
||||
local station_city
|
||||
local station_state
|
||||
|
||||
annual_kwh=$(echo "$data" | jq -r '.outputs.ac_annual')
|
||||
capacity_factor=$(echo "$data" | jq -r '.outputs.capacity_factor')
|
||||
solrad_annual=$(echo "$data" | jq -r '.outputs.solrad_annual')
|
||||
station_city=$(echo "$data" | jq -r '.station_info.city // "N/A"')
|
||||
station_state=$(echo "$data" | jq -r '.station_info.state // "Texas"')
|
||||
|
||||
local financials
|
||||
financials=$(calculate_financials "$annual_kwh")
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
|
||||
echo "$data" | jq --argjson fin "$financials" \
|
||||
--arg capacity "$capacity" \
|
||||
--arg panel_watts "$panel_watts" \
|
||||
--arg num_panels "$num_panels" \
|
||||
'{
|
||||
system: {
|
||||
capacity_kw: ($capacity | tonumber),
|
||||
panels: ($num_panels | tonumber),
|
||||
panel_watts: ($panel_watts | tonumber)
|
||||
},
|
||||
production: {
|
||||
annual_kwh: .outputs.ac_annual,
|
||||
monthly_kwh: .outputs.ac_monthly,
|
||||
capacity_factor: .outputs.capacity_factor,
|
||||
solrad_annual: .outputs.solrad_annual
|
||||
},
|
||||
financials: $fin
|
||||
}'
|
||||
return
|
||||
fi
|
||||
|
||||
# Text output
|
||||
echo "======================================================================"
|
||||
echo "SITER SOLAR PROJECT - NREL PVWATTS ANALYSIS"
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
echo "Location: SITER"
|
||||
echo " Coordinates: ${lat}, ${lon}"
|
||||
echo " Station: ${station_city}, ${station_state}"
|
||||
echo ""
|
||||
echo "System Configuration:"
|
||||
echo " Panels: ${num_panels} × ${panel_watts}W = ${capacity} kW DC"
|
||||
echo " Array Type: Fixed Open Rack (Ground Mount)"
|
||||
echo " Orientation: ${DEFAULT_TILT}° tilt, ${DEFAULT_AZIMUTH}° azimuth (South-facing)"
|
||||
echo " System Losses: ${DEFAULT_LOSSES}%"
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo "MONTHLY PRODUCTION ESTIMATE (NREL PVWatts)"
|
||||
echo "======================================================================"
|
||||
|
||||
local months=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
|
||||
local ac_monthly
|
||||
ac_monthly=$(echo "$data" | jq -r '.outputs.ac_monthly[]')
|
||||
local solrad_monthly
|
||||
solrad_monthly=$(echo "$data" | jq -r '.outputs.solrad_monthly[]')
|
||||
|
||||
printf "\n%-6s %-14s %-12s %-15s\n" "Month" "AC Output" "Daily Avg" "Solar Rad"
|
||||
printf "%-6s %-14s %-12s %-15s\n" "" "(kWh)" "(kWh/day)" "(kWh/m²/day)"
|
||||
echo "--------------------------------------------------"
|
||||
|
||||
local i=0
|
||||
local total_kwh=0
|
||||
for month in "${months[@]}"; do
|
||||
local ac
|
||||
ac=$(echo "$ac_monthly" | sed -n "$((i+1))p")
|
||||
local solrad
|
||||
solrad=$(echo "$solrad_monthly" | sed -n "$((i+1))p")
|
||||
local daily_avg
|
||||
daily_avg=$(echo "$ac / 30.5" | bc -l)
|
||||
printf "%-6s %10.0f %10.0f %14.2f\n" "$month" "$ac" "$daily_avg" "$solrad"
|
||||
total_kwh=$(echo "$total_kwh + $ac" | bc -l)
|
||||
((i++))
|
||||
done
|
||||
|
||||
echo "--------------------------------------------------"
|
||||
printf "%-6s %10.0f %10.0f\n" "ANNUAL" "$annual_kwh" "$(echo "$annual_kwh / 12" | bc -l)"
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo "ANNUAL PERFORMANCE SUMMARY"
|
||||
echo "======================================================================"
|
||||
printf " Annual AC Output: %'.0f kWh\n" "$annual_kwh"
|
||||
printf " Monthly Average: %'.0f kWh\n" "$(echo "$annual_kwh / 12" | bc -l)"
|
||||
printf " Daily Average: %'.0f kWh\n" "$(echo "$annual_kwh / 365" | bc -l)"
|
||||
echo " Capacity Factor: ${capacity_factor}%"
|
||||
echo " Avg Solar Radiation: ${solrad_annual} kWh/m²/day"
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo "FINANCIAL ANALYSIS"
|
||||
echo "======================================================================"
|
||||
|
||||
local self_consumed_kwh
|
||||
local exported_kwh
|
||||
local total_annual_value
|
||||
local monthly_savings
|
||||
local offset_pct
|
||||
local payback_years
|
||||
|
||||
self_consumed_kwh=$(echo "$financials" | jq -r '.self_consumed_kwh')
|
||||
exported_kwh=$(echo "$financials" | jq -r '.exported_kwh')
|
||||
total_annual_value=$(echo "$financials" | jq -r '.total_annual_value')
|
||||
monthly_savings=$(echo "$financials" | jq -r '.monthly_savings')
|
||||
offset_pct=$(echo "$financials" | jq -r '.offset_pct')
|
||||
payback_years=$(echo "$financials" | jq -r '.payback_years')
|
||||
|
||||
echo ""
|
||||
echo " Current Consumption: ${CURRENT_ANNUAL_CONSUMPTION} kWh/year"
|
||||
printf " Solar Production: %'.0f kWh/year\n" "$annual_kwh"
|
||||
printf " Self-Sufficiency: %.1f%%\n" "$(echo "$annual_kwh / $CURRENT_ANNUAL_CONSUMPTION * 100" | bc -l)"
|
||||
|
||||
echo ""
|
||||
printf " Self-Consumed (60%%): %'.0f kWh\n" "$self_consumed_kwh"
|
||||
printf " Value @ \$%s/kWh: \$%'.2f/year\n" "$CONSUMPTION_RATE" "$(echo "$financials" | jq -r '.self_consumption_value')"
|
||||
|
||||
echo ""
|
||||
printf " Exported to Grid (40%%): %'.0f kWh\n" "$exported_kwh"
|
||||
printf " Value @ \$%s/kWh: \$%'.2f/year\n" "$EXPORT_RATE" "$(echo "$financials" | jq -r '.export_value')"
|
||||
|
||||
echo ""
|
||||
printf " TOTAL ANNUAL VALUE: \$%'.2f\n" "$total_annual_value"
|
||||
printf " MONTHLY SAVINGS: \$%'.2f\n" "$monthly_savings"
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo "ROI ANALYSIS"
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
printf " Project Cost: \$%'.2f\n" "$PROJECT_COST"
|
||||
printf " Current Monthly Bill: \$%'.2f\n" "$CURRENT_MONTHLY_BILL"
|
||||
printf " Projected Monthly Savings: \$%'.2f\n" "$monthly_savings"
|
||||
printf " Bill Offset: %.1f%%\n" "$offset_pct"
|
||||
printf "\n PAYBACK PERIOD: %.0f months (%.1f years)\n" "$(echo "$financials" | jq -r '.payback_months')" "$payback_years"
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Run multiple system size scenarios
|
||||
# Arguments:
|
||||
# $1 - API key
|
||||
# $2 - Latitude
|
||||
# $3 - Longitude
|
||||
# $4 - Number of panels
|
||||
# Outputs:
|
||||
# Comparison table
|
||||
#######################################
|
||||
run_scenarios() {
|
||||
local api_key="$1"
|
||||
local lat="$2"
|
||||
local lon="$3"
|
||||
local num_panels="$4"
|
||||
|
||||
local scenarios=(
|
||||
"16:250:16 × 250W (older panels)"
|
||||
"16:300:16 × 300W"
|
||||
"16:350:16 × 350W"
|
||||
"16:400:16 × 400W (modern standard)"
|
||||
"16:450:16 × 450W (high efficiency)"
|
||||
"20:400:20 × 400W (expanded)"
|
||||
"24:400:24 × 400W (full offset target)"
|
||||
)
|
||||
|
||||
echo "======================================================================"
|
||||
echo "SITER SOLAR - SYSTEM SIZE SCENARIOS"
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
echo "Location: SITER (${lat}, ${lon})"
|
||||
echo "Current Annual Consumption: ${CURRENT_ANNUAL_CONSUMPTION} kWh"
|
||||
printf "Current Monthly Bill: \$%'.2f\n" "$CURRENT_MONTHLY_BILL"
|
||||
echo ""
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
|
||||
echo "["
|
||||
local first=true
|
||||
else
|
||||
printf "%-25s %6s %8s %8s %8s %10s\n" "System" "kW" "kWh/yr" "\$/mo" "Offset" "Payback"
|
||||
echo "----------------------------------------------------------------------"
|
||||
fi
|
||||
|
||||
for scenario in "${scenarios[@]}"; do
|
||||
IFS=':' read -r panels watts desc <<< "$scenario"
|
||||
local capacity
|
||||
capacity=$(echo "$panels * $watts / 1000" | bc -l)
|
||||
|
||||
local data
|
||||
data=$(query_nrel_api "$api_key" "$capacity" "$lat" "$lon" "$DEFAULT_TILT" "$DEFAULT_AZIMUTH" "$DEFAULT_LOSSES")
|
||||
|
||||
local annual_kwh
|
||||
annual_kwh=$(echo "$data" | jq -r '.outputs.ac_annual')
|
||||
|
||||
local financials
|
||||
financials=$(calculate_financials "$annual_kwh")
|
||||
|
||||
local monthly_savings
|
||||
local offset_pct
|
||||
local payback_years
|
||||
|
||||
monthly_savings=$(echo "$financials" | jq -r '.monthly_savings')
|
||||
offset_pct=$(echo "$financials" | jq -r '.offset_pct')
|
||||
payback_years=$(echo "$financials" | jq -r '.payback_years')
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
|
||||
if [[ "$first" != "true" ]]; then
|
||||
echo ","
|
||||
fi
|
||||
first=false
|
||||
echo "$financials" | jq --arg desc "$desc" \
|
||||
--arg capacity "$capacity" \
|
||||
--arg panels "$panels" \
|
||||
--arg watts "$watts" \
|
||||
'{
|
||||
description: $desc,
|
||||
panels: ($panels | tonumber),
|
||||
watts_per_panel: ($watts | tonumber),
|
||||
capacity_kw: ($capacity | tonumber)
|
||||
} + .'
|
||||
else
|
||||
printf "%-25s %6.1f %8,.0f \$%6.2f %6.1f%% %8.1f yrs\n" \
|
||||
"$desc" "$capacity" "$annual_kwh" "$monthly_savings" "$offset_pct" "$payback_years"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
|
||||
echo "]"
|
||||
else
|
||||
echo "----------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "Note: To achieve 100% bill offset would require ~32 kW system"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Main entry point
|
||||
# Globals:
|
||||
# All configuration variables
|
||||
# Arguments:
|
||||
# Command line arguments
|
||||
# Outputs:
|
||||
# Analysis results
|
||||
#######################################
|
||||
main() {
|
||||
local api_key="${NREL_API_KEY:-$DEFAULT_API_KEY}"
|
||||
local lat="${SITER_LAT:-$DEFAULT_LAT}"
|
||||
local lon="${SITER_LON:-$DEFAULT_LON}"
|
||||
local num_panels="$DEFAULT_PANELS"
|
||||
local panel_watts="$DEFAULT_PANEL_WATTS"
|
||||
local tilt="$DEFAULT_TILT"
|
||||
local azimuth="$DEFAULT_AZIMUTH"
|
||||
local losses="$DEFAULT_LOSSES"
|
||||
local scenarios_mode=false
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-k|--api-key)
|
||||
api_key="$2"
|
||||
shift 2
|
||||
;;
|
||||
--lat)
|
||||
lat="$2"
|
||||
shift 2
|
||||
;;
|
||||
--lon)
|
||||
lon="$2"
|
||||
shift 2
|
||||
;;
|
||||
-p|--panels)
|
||||
num_panels="$2"
|
||||
shift 2
|
||||
;;
|
||||
-w|--watts)
|
||||
panel_watts="$2"
|
||||
shift 2
|
||||
;;
|
||||
-t|--tilt)
|
||||
tilt="$2"
|
||||
shift 2
|
||||
;;
|
||||
-a|--azimuth)
|
||||
azimuth="$2"
|
||||
shift 2
|
||||
;;
|
||||
-l|--losses)
|
||||
losses="$2"
|
||||
shift 2
|
||||
;;
|
||||
--scenarios)
|
||||
scenarios_mode=true
|
||||
shift
|
||||
;;
|
||||
--json)
|
||||
OUTPUT_FORMAT="json"
|
||||
shift
|
||||
;;
|
||||
--verbose)
|
||||
VERBOSE="true"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-v|--version)
|
||||
echo "siter-solar-analysis version ${VERSION}"
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
error "Unknown option: $1\nTry --help for usage information."
|
||||
;;
|
||||
*)
|
||||
# Positional argument - treat as API key
|
||||
api_key="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check dependencies
|
||||
check_dependencies
|
||||
|
||||
# Validate inputs
|
||||
validate_numeric "$lat" "latitude"
|
||||
validate_numeric "$lon" "longitude"
|
||||
validate_numeric "$num_panels" "panels"
|
||||
validate_numeric "$panel_watts" "panel watts"
|
||||
validate_range "$lat" "-90" "90" "latitude"
|
||||
validate_range "$lon" "-180" "180" "longitude"
|
||||
|
||||
# Calculate system capacity
|
||||
local capacity
|
||||
capacity=$(echo "$num_panels * $panel_watts / 1000" | bc -l)
|
||||
|
||||
if [[ "$scenarios_mode" == "true" ]]; then
|
||||
run_scenarios "$api_key" "$lat" "$lon" "$num_panels"
|
||||
else
|
||||
info "Querying NREL PVWatts API (system: ${capacity} kW)..."
|
||||
local data
|
||||
data=$(query_nrel_api "$api_key" "$capacity" "$lat" "$lon" "$tilt" "$azimuth" "$losses")
|
||||
format_output "$data" "$capacity" "$panel_watts" "$num_panels" "$lat" "$lon"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main if not being sourced
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
299
solar-analysis/solar_estimate.py
Normal file
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env python3
|
||||
# DEPRECATED: This script is deprecated. Use siter-solar-analysis.sh instead.
|
||||
# This file is kept for reference only.
|
||||
|
||||
"""
|
||||
NREL PVWatts Solar Production Estimator for SITER Solar Project
|
||||
Location: SITER
|
||||
|
||||
Usage:
|
||||
python solar_estimate.py # Use defaults (16 panels @ 400W = 6.4kW)
|
||||
python solar_estimate.py YOUR_API_KEY # With your NREL API key
|
||||
python solar_estimate.py --scenarios # Run multiple system size scenarios
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# Location coordinates for SITER
|
||||
LAT = float(os.environ.get("SITER_LAT", "30.44")) # Central Texas
|
||||
LON = float(os.environ.get("SITER_LON", "-97.62")) # Central Texas
|
||||
|
||||
# Default system parameters
|
||||
NUM_PANELS = 16
|
||||
DEFAULT_PANEL_WATTS = 400 # Modern panels are typically 350-450W
|
||||
ARRAY_TYPE = 0 # 0 = Fixed - Open Rack (ground mount)
|
||||
TILT = 30 # degrees - optimal for Texas latitude (~30°N)
|
||||
AZIMUTH = 180 # South-facing
|
||||
MODULE_TYPE = 0 # 0 = Standard
|
||||
LOSSES = 14 # System losses percentage (typical)
|
||||
|
||||
# Financial parameters
|
||||
PROJECT_COST = 4100
|
||||
CURRENT_MONTHLY_BILL = 301.08
|
||||
CURRENT_ANNUAL_CONSUMPTION = 23952 # kWh/year (from actual bills: ~1996 kWh/month avg)
|
||||
BASE_POWER_CONSUMPTION_RATE = 0.085 # $/kWh avoided
|
||||
BASE_POWER_EXPORT_RATE = 0.04 # $/kWh buyback
|
||||
|
||||
# API endpoint
|
||||
API_URL = "https://developer.nrel.gov/api/pvwatts/v6.json"
|
||||
|
||||
def get_solar_estimate(api_key, system_capacity_kw):
|
||||
"""Query NREL PVWatts API for solar production estimate."""
|
||||
|
||||
params = {
|
||||
"api_key": api_key,
|
||||
"lat": LAT,
|
||||
"lon": LON,
|
||||
"system_capacity": system_capacity_kw,
|
||||
"array_type": ARRAY_TYPE,
|
||||
"tilt": TILT,
|
||||
"azimuth": AZIMUTH,
|
||||
"module_type": MODULE_TYPE,
|
||||
"losses": LOSSES,
|
||||
"timeframe": "monthly"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(API_URL, params=params, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"API request failed: {e}")
|
||||
return None
|
||||
|
||||
def calculate_financials(annual_kwh):
|
||||
"""Calculate financial projections based on annual production."""
|
||||
monthly_avg = annual_kwh / 12
|
||||
|
||||
# Estimate self-consumption vs export
|
||||
# During peak solar hours, home may not consume all production
|
||||
# Conservative estimate: 60% self-consumed, 40% exported
|
||||
self_consumed_pct = 0.60
|
||||
self_consumed = annual_kwh * self_consumed_pct
|
||||
exported = annual_kwh * (1 - self_consumed_pct)
|
||||
|
||||
self_consumption_value = self_consumed * BASE_POWER_CONSUMPTION_RATE
|
||||
export_value = exported * BASE_POWER_EXPORT_RATE
|
||||
total_annual_value = self_consumption_value + export_value
|
||||
|
||||
monthly_savings = total_annual_value / 12
|
||||
offset_pct = (monthly_savings / CURRENT_MONTHLY_BILL) * 100
|
||||
payback_months = PROJECT_COST / monthly_savings if monthly_savings > 0 else float('inf')
|
||||
|
||||
return {
|
||||
"annual_kwh": annual_kwh,
|
||||
"monthly_avg_kwh": monthly_avg,
|
||||
"self_consumed_kwh": self_consumed,
|
||||
"exported_kwh": exported,
|
||||
"self_consumption_value": self_consumption_value,
|
||||
"export_value": export_value,
|
||||
"total_annual_value": total_annual_value,
|
||||
"monthly_savings": monthly_savings,
|
||||
"offset_pct": offset_pct,
|
||||
"payback_months": payback_months,
|
||||
"payback_years": payback_months / 12
|
||||
}
|
||||
|
||||
def format_output(data, system_capacity_kw, panel_watts):
|
||||
"""Format API response into readable report."""
|
||||
if not data or data.get("errors"):
|
||||
print("Error:", data.get("errors", "Unknown error"))
|
||||
return None
|
||||
|
||||
outputs = data.get("outputs", {})
|
||||
station = data.get("station_info", {})
|
||||
|
||||
annual_kwh = outputs.get("ac_annual", 0)
|
||||
fin = calculate_financials(annual_kwh)
|
||||
|
||||
# Self-consumption percentage used in calculations
|
||||
self_consumed_pct = 0.60
|
||||
|
||||
print("=" * 70)
|
||||
print("SITER SOLAR PROJECT - NREL PVWATTS ANALYSIS")
|
||||
print("=" * 70)
|
||||
print(f"\nLocation: SITER")
|
||||
print(f" Coordinates: {LAT}, {LON}")
|
||||
print(f" Station: {station.get('city', 'N/A')}, {station.get('state', 'Texas')}")
|
||||
print(f" Distance: {station.get('distance', 'N/A')}m | Elevation: {station.get('elev', 'N/A')}m")
|
||||
|
||||
print(f"\nSystem Configuration:")
|
||||
print(f" Panels: {NUM_PANELS} × {panel_watts}W = {system_capacity_kw} kW DC")
|
||||
print(f" Array Type: Fixed Open Rack (Ground Mount)")
|
||||
print(f" Orientation: {TILT}° tilt, {AZIMUTH}° azimuth (South-facing)")
|
||||
print(f" System Losses: {LOSSES}%")
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print("MONTHLY PRODUCTION ESTIMATE (NREL PVWatts)")
|
||||
print(f"{'='*70}")
|
||||
|
||||
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||
|
||||
ac_monthly = outputs.get("ac_monthly", [])
|
||||
solrad_monthly = outputs.get("solrad_monthly", [])
|
||||
|
||||
print(f"\n{'Month':<6} {'AC Output':<14} {'Daily Avg':<12} {'Solar Rad':<15}")
|
||||
print(f"{'':6} {'(kWh)':<14} {'(kWh/day)':<12} {'(kWh/m²/day)':<15}")
|
||||
print("-" * 50)
|
||||
|
||||
for i, month in enumerate(months):
|
||||
ac = ac_monthly[i] if i < len(ac_monthly) else 0
|
||||
daily_avg = ac / 30.5 if ac else 0
|
||||
solrad = solrad_monthly[i] if i < len(solrad_monthly) else 0
|
||||
print(f"{month:<6} {ac:>10.0f} {daily_avg:>10.0f} {solrad:>14.2f}")
|
||||
|
||||
print("-" * 50)
|
||||
print(f"{'ANNUAL':<6} {fin['annual_kwh']:>10.0f} {fin['monthly_avg_kwh']:>10.0f}")
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print("ANNUAL PERFORMANCE SUMMARY")
|
||||
print(f"{'='*70}")
|
||||
print(f" Annual AC Output: {fin['annual_kwh']:,.0f} kWh")
|
||||
print(f" Monthly Average: {fin['monthly_avg_kwh']:,.0f} kWh")
|
||||
print(f" Daily Average: {fin['annual_kwh']/365:,.0f} kWh")
|
||||
print(f" Capacity Factor: {outputs.get('capacity_factor', 'N/A')}%")
|
||||
print(f" Avg Solar Radiation: {outputs.get('solrad_annual', 'N/A')} kWh/m²/day")
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print("FINANCIAL ANALYSIS")
|
||||
print(f"{'='*70}")
|
||||
|
||||
print(f"\n Current Consumption: {CURRENT_ANNUAL_CONSUMPTION:,} kWh/year")
|
||||
print(f" Solar Production: {fin['annual_kwh']:,.0f} kWh/year")
|
||||
print(f" Self-Sufficiency: {(fin['annual_kwh']/CURRENT_ANNUAL_CONSUMPTION)*100:.1f}%")
|
||||
|
||||
print(f"\n Self-Consumed ({self_consumed_pct*100:.0f}%): {fin['self_consumed_kwh']:,.0f} kWh")
|
||||
print(f" Value @ ${BASE_POWER_CONSUMPTION_RATE}/kWh: ${fin['self_consumption_value']:,.2f}/year")
|
||||
|
||||
print(f"\n Exported to Grid ({(1-self_consumed_pct)*100:.0f}%): {fin['exported_kwh']:,.0f} kWh")
|
||||
print(f" Value @ ${BASE_POWER_EXPORT_RATE}/kWh: ${fin['export_value']:,.2f}/year")
|
||||
|
||||
print(f"\n TOTAL ANNUAL VALUE: ${fin['total_annual_value']:,.2f}")
|
||||
print(f" MONTHLY SAVINGS: ${fin['monthly_savings']:,.2f}")
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print("ROI ANALYSIS")
|
||||
print(f"{'='*70}")
|
||||
print(f"\n Project Cost: ${PROJECT_COST:,.2f}")
|
||||
print(f" Current Monthly Bill: ${CURRENT_MONTHLY_BILL:,.2f}")
|
||||
print(f" Projected Monthly Savings: ${fin['monthly_savings']:,.2f}")
|
||||
print(f" Bill Offset: {fin['offset_pct']:.1f}%")
|
||||
print(f"\n PAYBACK PERIOD: {fin['payback_months']:.1f} months ({fin['payback_years']:.1f} years)")
|
||||
|
||||
# 5-year projection
|
||||
print(f"\n{'='*70}")
|
||||
print("5-YEAR FINANCIAL PROJECTION")
|
||||
print(f"{'='*70}")
|
||||
print(f"\n {'Year':<6} {'Cumulative Savings':<20} {'Net Position':<15}")
|
||||
print(f" {'-'*40}")
|
||||
for year in range(6):
|
||||
cum_savings = fin['total_annual_value'] * year
|
||||
net = cum_savings - PROJECT_COST
|
||||
print(f" {year:<6} ${cum_savings:>17,.2f} ${net:>12,.2f}")
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
|
||||
return fin
|
||||
|
||||
def run_scenarios(api_key):
|
||||
"""Run analysis for multiple system configurations."""
|
||||
print("=" * 70)
|
||||
print("SITER SOLAR - SYSTEM SIZE SCENARIOS")
|
||||
print("=" * 70)
|
||||
print(f"\nLocation: SITER")
|
||||
print(f"Current Annual Consumption: {CURRENT_ANNUAL_CONSUMPTION:,} kWh")
|
||||
print(f"Current Monthly Bill: ${CURRENT_MONTHLY_BILL:,.2f}")
|
||||
print()
|
||||
|
||||
scenarios = [
|
||||
# (panels, watts_per_panel, description)
|
||||
(16, 250, "16 × 250W (older panels)"),
|
||||
(16, 300, "16 × 300W"),
|
||||
(16, 350, "16 × 350W"),
|
||||
(16, 400, "16 × 400W (modern standard)"),
|
||||
(16, 450, "16 × 450W (high efficiency)"),
|
||||
(20, 400, "20 × 400W (expanded)"),
|
||||
(24, 400, "24 × 400W (full offset target)"),
|
||||
]
|
||||
|
||||
results = []
|
||||
|
||||
print(f"{'System':<25} {'kW':>6} {'kWh/yr':>8} {'$/mo':>8} {'Offset':>8} {'Payback':>10}")
|
||||
print("-" * 70)
|
||||
|
||||
for panels, watts, desc in scenarios:
|
||||
capacity_kw = (panels * watts) / 1000
|
||||
data = get_solar_estimate(api_key, capacity_kw)
|
||||
|
||||
if data and not data.get("errors"):
|
||||
annual_kwh = data.get("outputs", {}).get("ac_annual", 0)
|
||||
fin = calculate_financials(annual_kwh)
|
||||
|
||||
results.append({
|
||||
"description": desc,
|
||||
"panels": panels,
|
||||
"watts": watts,
|
||||
"capacity_kw": capacity_kw,
|
||||
**fin
|
||||
})
|
||||
|
||||
print(f"{desc:<25} {capacity_kw:>6.1f} {fin['annual_kwh']:>8,.0f} "
|
||||
f"${fin['monthly_savings']:>6.2f} {fin['offset_pct']:>6.1f}% "
|
||||
f"{fin['payback_years']:>8.1f} yrs")
|
||||
|
||||
print("-" * 70)
|
||||
|
||||
# Find system size for ~100% offset
|
||||
target_monthly_savings = CURRENT_MONTHLY_BILL
|
||||
target_annual_value = target_monthly_savings * 12
|
||||
|
||||
# Back-calculate required production
|
||||
# annual_value = (production * 0.60 * 0.085) + (production * 0.40 * 0.04)
|
||||
# annual_value = production * (0.051 + 0.016) = production * 0.067
|
||||
required_production = target_annual_value / 0.067 # ~$4,016 annual value needed
|
||||
required_kw = required_production / 1500 # Rough: 1kW produces ~1500 kWh/year in Texas
|
||||
|
||||
print(f"\nTo achieve 100% bill offset, estimated system size needed:")
|
||||
print(f" ~{required_kw:.0f} kW ({int(required_kw/0.4)} panels @ 400W each)")
|
||||
|
||||
return results
|
||||
|
||||
def main():
|
||||
global NUM_PANELS
|
||||
api_key = os.environ.get("NREL_API_KEY", "DEMO_KEY")
|
||||
run_scenarios_mode = False
|
||||
panel_watts = DEFAULT_PANEL_WATTS
|
||||
|
||||
for arg in sys.argv[1:]:
|
||||
if arg == "--scenarios":
|
||||
run_scenarios_mode = True
|
||||
elif arg.startswith("--panels="):
|
||||
NUM_PANELS = int(arg.split("=")[1])
|
||||
elif arg.startswith("--watts="):
|
||||
panel_watts = int(arg.split("=")[1])
|
||||
elif not arg.startswith("--"):
|
||||
api_key = arg
|
||||
|
||||
system_capacity_kw = (NUM_PANELS * panel_watts) / 1000
|
||||
|
||||
if run_scenarios_mode:
|
||||
run_scenarios(api_key)
|
||||
else:
|
||||
print(f"Querying NREL PVWatts API (system: {system_capacity_kw} kW)...")
|
||||
data = get_solar_estimate(api_key, system_capacity_kw)
|
||||
|
||||
if data:
|
||||
result = format_output(data, system_capacity_kw, panel_watts)
|
||||
|
||||
# Save JSON for further analysis
|
||||
with open("nrel_solar_data.json", "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
print(f"\nRaw data saved to nrel_solar_data.json")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
96
solar-analysis/solar_optimal.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/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)
|
||||
61
solar-analysis/solar_optimized.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
# 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")
|
||||
153
solar-analysis/tests/siter-solar-analysis.bats
Normal file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# BATS tests for siter-solar-analysis.sh
|
||||
# Run with: bats tests/
|
||||
#
|
||||
|
||||
SCRIPT_PATH="/app/solar-analysis/siter-solar-analysis.sh"
|
||||
|
||||
@test "script exists and is executable" {
|
||||
[ -f "$SCRIPT_PATH" ]
|
||||
[ -x "$SCRIPT_PATH" ]
|
||||
}
|
||||
|
||||
@test "help option displays usage" {
|
||||
run "$SCRIPT_PATH" --help
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"SITER Solar Analysis"* ]]
|
||||
[[ "$output" == *"USAGE:"* ]]
|
||||
[[ "$output" == *"--api-key"* ]]
|
||||
[[ "$output" == *"--scenarios"* ]]
|
||||
[[ "$output" == *"NREL_API_KEY"* ]]
|
||||
}
|
||||
|
||||
@test "version option displays version" {
|
||||
run "$SCRIPT_PATH" --version
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "siter-solar-analysis version "[0-9]+\.[0-9]+\.[0-9]+ ]]
|
||||
}
|
||||
|
||||
@test "invalid option returns error" {
|
||||
run "$SCRIPT_PATH" --invalid-option
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"Unknown option"* ]]
|
||||
}
|
||||
|
||||
@test "dependencies are available" {
|
||||
command -v curl
|
||||
command -v jq
|
||||
command -v bc
|
||||
}
|
||||
|
||||
@test "invalid latitude returns error" {
|
||||
run "$SCRIPT_PATH" --lat "invalid"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "out of range latitude returns error" {
|
||||
run "$SCRIPT_PATH" --lat "999"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "invalid longitude returns error" {
|
||||
run "$SCRIPT_PATH" --lon "abc"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "JSON output is valid JSON" {
|
||||
run "$SCRIPT_PATH" --json
|
||||
if [ "$status" -eq 0 ]; then
|
||||
echo "$output" | jq .
|
||||
fi
|
||||
}
|
||||
|
||||
@test "JSON output has required fields" {
|
||||
run "$SCRIPT_PATH" --json
|
||||
if [ "$status" -eq 0 ]; then
|
||||
echo "$output" | jq -e '.system'
|
||||
echo "$output" | jq -e '.production'
|
||||
echo "$output" | jq -e '.financials'
|
||||
fi
|
||||
}
|
||||
|
||||
@test "text output shows expected sections" {
|
||||
run "$SCRIPT_PATH"
|
||||
if [ "$status" -eq 0 ]; then
|
||||
[[ "$output" == *"SITER SOLAR PROJECT"* ]]
|
||||
[[ "$output" == *"NREL PVWATTS ANALYSIS"* ]]
|
||||
[[ "$output" == *"System Configuration:"* ]]
|
||||
[[ "$output" == *"FINANCIAL ANALYSIS"* ]]
|
||||
[[ "$output" == *"ROI ANALYSIS"* ]]
|
||||
[[ "$output" == *"PAYBACK PERIOD"* ]]
|
||||
fi
|
||||
}
|
||||
|
||||
@test "monthly production table shows all months" {
|
||||
run "$SCRIPT_PATH"
|
||||
if [ "$status" -eq 0 ]; then
|
||||
for month in Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec; do
|
||||
[[ "$output" == *"$month"* ]]
|
||||
done
|
||||
[[ "$output" == *"ANNUAL"* ]]
|
||||
fi
|
||||
}
|
||||
|
||||
@test "scenarios mode shows header" {
|
||||
run "$SCRIPT_PATH" --scenarios
|
||||
if [ "$status" -eq 0 ]; then
|
||||
[[ "$output" == *"SYSTEM SIZE SCENARIOS"* ]]
|
||||
fi
|
||||
}
|
||||
|
||||
@test "scenarios shows panel options" {
|
||||
run "$SCRIPT_PATH" --scenarios
|
||||
if [ "$status" -eq 0 ]; then
|
||||
[[ "$output" == *"250W"* ]]
|
||||
[[ "$output" == *"400W"* ]]
|
||||
fi
|
||||
}
|
||||
|
||||
@test "scenarios JSON output is array" {
|
||||
run "$SCRIPT_PATH" --scenarios --json
|
||||
if [ "$status" -eq 0 ]; then
|
||||
local type
|
||||
type=$(echo "$output" | jq -r 'type')
|
||||
[ "$type" == "array" ]
|
||||
fi
|
||||
}
|
||||
|
||||
@test "custom panel configuration works" {
|
||||
run "$SCRIPT_PATH" -p 20 -w 400
|
||||
if [ "$status" -eq 0 ]; then
|
||||
[[ "$output" == *"20 × 400W"* ]]
|
||||
[[ "$output" == *"8.0 kW"* ]]
|
||||
fi
|
||||
}
|
||||
|
||||
@test "invalid API key returns error" {
|
||||
run "$SCRIPT_PATH" -k "invalid_key_for_testing_12345"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "location from environment variables" {
|
||||
SITER_LAT=32.77 SITER_LON=-96.79 run "$SCRIPT_PATH"
|
||||
if [ "$status" -eq 0 ]; then
|
||||
[[ "$output" == *"32.77"* ]]
|
||||
[[ "$output" == *"-96.79"* ]]
|
||||
fi
|
||||
}
|
||||
|
||||
@test "financial calculations are numeric" {
|
||||
run "$SCRIPT_PATH" --json -w 250
|
||||
if [ "$status" -eq 0 ]; then
|
||||
local annual_kwh monthly_savings payback_years
|
||||
|
||||
annual_kwh=$(echo "$output" | jq -r '.production.annual_kwh')
|
||||
monthly_savings=$(echo "$output" | jq -r '.financials.monthly_savings')
|
||||
payback_years=$(echo "$output" | jq -r '.financials.payback_years')
|
||||
|
||||
[[ "$annual_kwh" =~ ^[0-9]+$ ]]
|
||||
[[ "$monthly_savings" =~ ^[0-9]+\.[0-9]+$ ]]
|
||||
[[ "$payback_years" =~ ^[0-9]+\.[0-9]+$ ]]
|
||||
fi
|
||||
}
|
||||
623
v2-siter-solar-plan.md
Normal file
@@ -0,0 +1,623 @@
|
||||
# SITER Solar Installation Project
|
||||
## 2026 Renewable Energy Initiative - v2 Analysis
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document outlines a solar energy installation project for SITER, designed to offset a portion of monthly electricity costs through solar generation. The system will utilize 16 solar panels across 8 custom-built ground-mount racks, paired with a Sol-Ark 5K hybrid inverter that integrates with the existing Base Power battery backup system.
|
||||
|
||||
**Total Project Investment:** $4,100.00
|
||||
**Estimated ROI Period:** 9.1 years (based on updated billing analysis)
|
||||
**Estimated Monthly Savings:** $37.61 (14.2% bill offset)
|
||||
|
||||
**Existing Infrastructure:** SITER currently operates with a Base Power battery backup system and automatic transfer switch (ATS), installed in 2025, providing resilience and a fixed energy rate of **$0.085/kWh** through a 3-year contract (Oct 2025 - Oct 2028).
|
||||
|
||||
**Updated Billing Data (15 months):** Average monthly consumption of 1,885 kWh (~22,600 kWh/year) at average effective rate of $0.140/kWh.
|
||||
|
||||
**NREL PVWatts Analysis:** Based on location data for SITER, a 16-panel system (4.0 kW DC with 250W panels) is projected to produce ~6,000 kWh/year, offsetting approximately 14% of current consumption.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Project Overview](#project-overview)
|
||||
2. [Technical Specifications](#technical-specifications)
|
||||
3. [Detailed Budget](#detailed-budget)
|
||||
4. [Project Timeline](#project-timeline)
|
||||
5. [Risk Analysis](#risk-analysis)
|
||||
6. [Return on Investment Analysis](#return-on-investment-analysis)
|
||||
7. [Recommendations](#recommendations)
|
||||
8. [Appendix](#appendix)
|
||||
|
||||
---
|
||||
|
||||
## Project Overview
|
||||
|
||||
### Objectives
|
||||
|
||||
- Reduce monthly electricity expenses through solar generation (~14% offset with current panels)
|
||||
- Integrate with existing Base Power battery backup infrastructure
|
||||
- Leverage Base Power's 4¢/kWh solar buyback program for excess production
|
||||
- Complete installation with minimal upfront capital expenditure
|
||||
- Achieve positive ROI within 10 years while gaining energy resilience
|
||||
|
||||
### Scope
|
||||
|
||||
| Component | Quantity | Description |
|
||||
|-----------|----------|-------------|
|
||||
| Solar Panels | 16 | Existing inventory (250W each) |
|
||||
| Ground-Mount Racks | 8 | Custom steel construction |
|
||||
| Inverter | 1 | Sol-Ark 5K Hybrid |
|
||||
| Wiring & Connectors | TBD | To be determined based on site layout |
|
||||
|
||||
### Current Status
|
||||
|
||||
- **Phase:** Initial procurement and rack construction
|
||||
- **Spend to Date:** $360.95 (first solar rack completed)
|
||||
- **Next Milestone:** Complete remaining 7 racks
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
SITER has the following energy infrastructure already in place:
|
||||
|
||||
| Component | Details | Install Date |
|
||||
|-----------|---------|--------------|
|
||||
| Base Power Battery System | Battery backup with automatic transfer switch | 2024 |
|
||||
| Automatic Transfer Switch (ATS) | Seamless grid/battery switching | 2024 |
|
||||
| Base Power Contract | 3-year fixed rate at $0.085/kWh | 2025-2028 |
|
||||
|
||||
**Site Base Load:** ~1,885 kWh/month (15-month average)
|
||||
|
||||
This existing infrastructure significantly de-risks the solar project:
|
||||
- ATS already installed for grid switching
|
||||
- Battery backup provides load smoothing and outage protection
|
||||
- Fixed-rate contract provides predictable baseline costs
|
||||
|
||||
---
|
||||
|
||||
## Technical Specifications
|
||||
|
||||
### Inverter: Sol-Ark 5K-5kW-48V Single-Phase Hybrid
|
||||
|
||||
| Specification | Value |
|
||||
|---------------|-------|
|
||||
| Power Output | 5kW |
|
||||
| System Voltage | 48V DC |
|
||||
| Phase | Single Phase |
|
||||
| Type | Hybrid (Grid-tied + Battery Backup) |
|
||||
| Source | [EcoDirect](https://www.ecodirect.com/Sol-Ark-5K-5kW-48V-Single-Phase-Hybrid-Inverter-p/sol-ark-5k-1p-n.htm) |
|
||||
|
||||
**Key Benefits:**
|
||||
- Grid-tied operation for net metering eligibility
|
||||
- **Integrates with existing Base Power battery system**
|
||||
- Hybrid functionality ensures power during grid outages
|
||||
- High efficiency rating minimizes energy loss
|
||||
- Compatible with existing ATS infrastructure
|
||||
|
||||
### Existing Base Power System
|
||||
|
||||
| Specification | Value |
|
||||
|---------------|-------|
|
||||
| Contract Rate | $0.085/kWh (fixed through Oct 2028) |
|
||||
| Solar Buyback Rate | $0.04/kWh (4¢/kWh) |
|
||||
| Contract Term | 3 years (Oct 2025 - Oct 2028) |
|
||||
| Site Base Load | ~1,885 kWh/month (actual 15-month average) |
|
||||
| Backup Capability | Automatic transfer switch + battery |
|
||||
| Installation Date | 2024 |
|
||||
| Solar Integration | Seamless - no additional equipment needed |
|
||||
|
||||
**Source:** [Base Power Solar Integration](https://www.basepowercompany.com/with-solar)
|
||||
|
||||
### Solar Buyback Program Details
|
||||
|
||||
Base Power offers competitive solar buyback rates for excess production:
|
||||
|
||||
| Feature | Details |
|
||||
|---------|--------|
|
||||
| Buyback Rate | 4¢/kWh for all excess solar production |
|
||||
| Credit Application | Applies to **entire bill** (not just energy charge) |
|
||||
| Negative Balance | Rolls over to following month |
|
||||
| Solar Tracking | Shown on monthly bill in "Solar Credits" section |
|
||||
| Outage Protection | Base keeps solar panels producing during grid outages |
|
||||
|
||||
**Integration Strategy:** Solar generation will first offset on-site consumption (reducing the $0.085/kWh charge), with excess production credited at $0.04/kWh. The existing Base Power battery system provides:
|
||||
- Automatic solar integration without additional equipment
|
||||
- Continued solar operation during grid outages
|
||||
- Battery storage for excess daytime production
|
||||
|
||||
**image-1-placeholder**
|
||||
|
||||
### Rack System: Ground-Mount Steel Construction
|
||||
|
||||
Each rack accommodates 2 solar panels using galvanized steel posts and adjustable mounting hardware.
|
||||
|
||||
**Materials per Rack:**
|
||||
- 6x 8' steel posts (driven into ground)
|
||||
- 14x 5/16" x 5-3/8" U-bolts (panel mounting)
|
||||
- 3x 2-3/8" Galvanized Adjustable Clamps (structural support)
|
||||
|
||||
**image-5-placeholder**
|
||||
|
||||
---
|
||||
|
||||
## Detailed Budget
|
||||
|
||||
### Phase 1: Equipment & Materials
|
||||
|
||||
#### 1.1 Panel Rack Construction
|
||||
|
||||
| Line Item | Qty | Unit Cost | Subtotal | Notes |
|
||||
|-----------|-----|-----------|----------|-------|
|
||||
| 8' Steel Posts | 48 | $34.97 | $1,678.56 | 6 posts per rack × 8 racks |
|
||||
| 5/16" x 5-3/8" U-bolts | 112 | $2.33 | $260.96 | 14 per rack × 8 racks |
|
||||
| 2-3/8" Galv Adj. Clamps | 24 | $3.77 | $90.48 | 3 per rack × 8 racks |
|
||||
| **Rack Materials Subtotal** | | | **$2,030.00** | |
|
||||
|
||||
#### 1.2 Tools & Equipment (One-Time)
|
||||
|
||||
| Line Item | Qty | Unit Cost | Subtotal | Notes |
|
||||
|-----------|-----|-----------|----------|-------|
|
||||
| Post Driver | 1 | $57.75 | $57.75 | Reusable for future projects |
|
||||
| **Tools Subtotal** | | | **$57.75** | |
|
||||
|
||||
#### 1.3 Electrical Components
|
||||
|
||||
| Line Item | Est. Cost | Notes |
|
||||
|-----------|-----------|-------|
|
||||
| Romex Wiring | $200.00 | Gauge/length TBD based on site survey |
|
||||
| Connectors & Junction Boxes | $112.25 | MC4 connectors, conduit, etc. |
|
||||
| **Electrical Subtotal** | **$312.25** | Conservative estimate |
|
||||
|
||||
#### 1.4 Inverter
|
||||
|
||||
| Line Item | Qty | Unit Cost | Subtotal | Notes |
|
||||
|-----------|-----|-----------|----------|-------|
|
||||
| Sol-Ark 5K Hybrid Inverter | 1 | $1,500.00 | $1,500.00 | Includes shipping |
|
||||
| **Inverter Subtotal** | | | **$1,500.00** | |
|
||||
|
||||
---
|
||||
|
||||
### Budget Summary
|
||||
|
||||
| Category | Amount | % of Total |
|
||||
|----------|--------|------------|
|
||||
| Rack Materials | $2,030.00 | 49.5% |
|
||||
| Tools (One-Time) | $57.75 | 1.4% |
|
||||
| Electrical Components | $312.25 | 7.6% |
|
||||
| Inverter | $1,500.00 | 36.6% |
|
||||
| **Contingency Reserve (10%)** | **$200.00** | **4.9%** |
|
||||
| **TOTAL PROJECT BUDGET** | **$4,100.00** | **100%** |
|
||||
|
||||
### Spend to Date
|
||||
|
||||
| Date | Item | Amount | Running Total |
|
||||
|------|------|--------|---------------|
|
||||
| 2026 | First Solar Rack (materials) | $360.95 | $360.95 |
|
||||
| | *Remaining Budget* | *$3,739.05* | |
|
||||
|
||||
---
|
||||
|
||||
### Cost Comparison: Per-Rack Analysis
|
||||
|
||||
| Cost Element | Per Rack | 8 Racks Total |
|
||||
|--------------|----------|---------------|
|
||||
| Steel Posts | $209.82 | $1,678.56 |
|
||||
| U-bolts | $45.92 | $367.36 |
|
||||
| Adjustable Clamps | $11.31 | $90.48 |
|
||||
| **Base Rack Cost** | **$267.05** | **$2,136.40** |
|
||||
| Wiring/Connectors (est.) | $32.95 | $263.60 |
|
||||
| **All-In Rack Cost** | **$301.08** | **$2,400.00** |
|
||||
|
||||
---
|
||||
|
||||
## Project Timeline
|
||||
|
||||
### Phase Overview
|
||||
|
||||
```
|
||||
Week 1-2: [████████] Procurement & Site Prep
|
||||
Week 3-6: [████████████████████████████████] Rack Construction
|
||||
Week 7-8: [████████████] Inverter Installation
|
||||
Week 9: [████] Electrical Wiring
|
||||
Week 10: [████] Testing & Commissioning
|
||||
```
|
||||
|
||||
### Detailed Schedule
|
||||
|
||||
| Phase | Tasks | Duration | Dependencies |
|
||||
|-------|-------|----------|--------------|
|
||||
| **1. Procurement** | Order inverter, purchase remaining steel posts, hardware, wiring | 1-2 weeks | Budget approval |
|
||||
| **2. Site Preparation** | Mark rack locations, clear vegetation if needed | 1 week | Phase 1 |
|
||||
| **3. Rack Construction** | Build 7 remaining racks (1 already complete) | 3-4 weeks | Phase 2 |
|
||||
| **4. Panel Mounting** | Install panels on completed racks | 1 week | Phase 3 |
|
||||
| **5. Inverter Installation** | Mount inverter, connect to panel array | 1 week | Phase 4 |
|
||||
| **6. Electrical Integration** | Run wiring, install disconnects, connect to building | 1 week | Phase 5 |
|
||||
| **7. Testing & Commissioning** | System testing, safety inspection, energize | 1 week | Phase 6 |
|
||||
|
||||
**Estimated Total Duration:** 8-10 weeks
|
||||
|
||||
### Milestones
|
||||
|
||||
- [ ] **M1:** All materials procured
|
||||
- [ ] **M2:** Site preparation complete
|
||||
- [ ] **M3:** All 8 racks constructed
|
||||
- [ ] **M4:** Panels mounted and secured
|
||||
- [ ] **M5:** Inverter installed and connected
|
||||
- [ ] **M6:** System commissioned and operational
|
||||
|
||||
---
|
||||
|
||||
## Risk Analysis
|
||||
|
||||
### Risk Register
|
||||
|
||||
| ID | Risk Description | Probability | Impact | Risk Score | Mitigation Strategy |
|
||||
|----|------------------|-------------|--------|------------|---------------------|
|
||||
| R1 | Wiring costs exceed estimates | Medium | Medium | 6 | Obtain quotes from 2-3 suppliers; consider DIY installation |
|
||||
| R2 | Weather delays rack construction | Medium | Low | 4 | Build buffer into timeline; work during favorable seasons |
|
||||
| R3 | Inverter supply chain delays | Low | High | 5 | Order early; confirm stock before project start |
|
||||
| R4 | Structural issues with racks | Low | Medium | 3 | Follow engineering best practices; inspect posts regularly |
|
||||
| R5 | Utility interconnection delays | Low | Medium | 3 | Research local requirements early; submit paperwork promptly |
|
||||
| R6 | Permit requirements | Medium | Medium | 6 | Verify local building codes before construction |
|
||||
| R7 | Panel compatibility issues | Low | Low | 2 | Confirm panel specs match inverter requirements |
|
||||
| R8 | ROI takes longer than projected | Low | Medium | 3 | Conservative savings estimates; monitor actual output |
|
||||
|
||||
### Risk Matrix
|
||||
|
||||
```
|
||||
IMPACT
|
||||
Low Medium High
|
||||
┌─────────┬─────────┬─────────┐
|
||||
High │ │ R6 │ │
|
||||
PROB ├─────────┼─────────┼─────────┤
|
||||
Medium│ R2 │ R1,R8 │ │
|
||||
├─────────┼─────────┼─────────┤
|
||||
Low│ R7 │ R4,R5 │ R3 │
|
||||
└─────────┴─────────┴─────────┘
|
||||
```
|
||||
|
||||
### Key Risk Mitigations
|
||||
|
||||
#### R1: Wiring Cost Overrun
|
||||
- **Current Status:** Only rough estimates completed
|
||||
- **Action Items:**
|
||||
- [ ] Conduct site survey to determine exact wire runs
|
||||
- [ ] Obtain quotes from multiple electrical suppliers
|
||||
- [ ] Consider aluminum conductors for long runs (cost savings)
|
||||
- [ ] Allocate $200 contingency specifically for electrical
|
||||
|
||||
#### R6: Permit Requirements
|
||||
- **Current Status:** Unknown
|
||||
- **Action Items:**
|
||||
- [ ] Contact local building department
|
||||
- [ ] Research setback requirements for ground-mount systems
|
||||
- [ ] Verify utility interconnection requirements
|
||||
- [ ] Factor permit fees into budget if required
|
||||
|
||||
---
|
||||
|
||||
## Return on Investment Analysis
|
||||
|
||||
### Updated Billing Analysis (15 Months of Data)
|
||||
|
||||
Based on actual Base Power billing data from October 2024 through January 2026:
|
||||
|
||||
| Service Period | Consumption (kWh) | Bill Amount | Effective Rate |
|
||||
|----------------|-------------------|-------------|----------------|
|
||||
| Oct 18 - Nov 19, 2024 | 1,368 | $34.61 | $0.025/kWh* |
|
||||
| Nov 19 - Dec 18, 2024 | 863 | $132.37 | $0.153/kWh |
|
||||
| Dec 18, 2024 - Jan 17, 2025 | 1,092 | $160.33 | $0.147/kWh |
|
||||
| Jan 17 - Feb 19, 2025 | 1,434 | $210.73 | $0.147/kWh |
|
||||
| Feb 19 - Mar 21, 2025 | 1,398 | $199.35 | $0.143/kWh |
|
||||
| Mar 21 - Apr 22, 2025 | 1,472 | $209.71 | $0.143/kWh |
|
||||
| Apr 22 - May 21, 2025 | 1,936 | $277.13 | $0.143/kWh |
|
||||
| May 21 - Jun 20, 2025 | 2,713 | $385.08 | $0.142/kWh |
|
||||
| Jun 20 - Jul 22, 2025 | 2,995 | $423.56 | $0.141/kWh |
|
||||
| Jul 22 - Aug 20, 2025 | 3,015 | $428.76 | $0.142/kWh |
|
||||
| Aug 20 - Sep 19, 2025 | 2,924 | $440.35 | $0.151/kWh |
|
||||
| Sep 19 - Oct 20, 2025 | 2,486 | $372.89 | $0.150/kWh |
|
||||
| Oct 20 - Nov 18, 2025 | 1,553 | $235.81 | $0.152/kWh |
|
||||
| Nov 18 - Dec 18, 2025 | 1,469 | $240.12 | $0.163/kWh |
|
||||
| Dec 18, 2025 - Jan 20, 2026 | 1,549 | $216.25 | $0.140/kWh |
|
||||
| **15-Month Average** | **1,885** | **$264.47** | **$0.140/kWh** |
|
||||
|
||||
*Note: Oct-Nov 2024 bill had credit applied from previous provider transition
|
||||
|
||||
### Seasonal Consumption Patterns
|
||||
|
||||
| Season | Months | Avg Consumption | Avg Bill |
|
||||
|--------|--------|-----------------|----------|
|
||||
| **Summer Peak** | Jun - Sep | 2,912 kWh | $415.89 |
|
||||
| **Winter Low** | Nov - Feb | 1,333 kWh | $175.56 |
|
||||
| **Shoulder** | Mar - May, Oct | 1,777 kWh | $261.55 |
|
||||
|
||||
**Key Insight:** Summer consumption is 2.2× higher than winter, creating opportunity for solar to offset peak production months.
|
||||
|
||||
### NREL PVWatts Production Estimate
|
||||
|
||||
Based on NREL PVWatts analysis for SITER:
|
||||
|
||||
| Parameter | Value |
|
||||
|-----------|-------|
|
||||
| System Capacity | 4.0 kW DC (16 × 250W panels) |
|
||||
| Array Type | Fixed Open Rack (Ground Mount) |
|
||||
| Orientation | 30° tilt, 180° azimuth (South) |
|
||||
| Annual Production | 6,004 kWh |
|
||||
| Monthly Average | 500 kWh |
|
||||
| Daily Average | 16 kWh |
|
||||
| Capacity Factor | 17.1% |
|
||||
| Avg Solar Radiation | 5.52 kWh/m²/day |
|
||||
|
||||
### Ground Mount Advantages at SITER
|
||||
|
||||
The ground-mount configuration at this site has significant advantages:
|
||||
|
||||
| Advantage | Impact |
|
||||
|-----------|--------|
|
||||
| **Optimal Tilt (30°)** | Matches latitude for maximum annual production |
|
||||
| **South-Facing (180°)** | Optimal azimuth for Texas |
|
||||
| **No Trees/Shading** | Minimal shading losses (typical: 3-5%, this site: <1%) |
|
||||
| **Open Rack** | Better airflow = cooler panels = higher efficiency |
|
||||
| **Adjustable Orientation** | Can fine-tune tilt seasonally if desired |
|
||||
|
||||
**Optimized Production Estimate (8% losses vs 14% standard):**
|
||||
|
||||
| Scenario | Losses | Annual kWh | Monthly $ | Payback |
|
||||
|----------|--------|------------|-----------|---------|
|
||||
| Conservative (NREL default) | 14% | 6,004 | $37.61 | 9.1 yrs |
|
||||
| **Optimized (no shade)** | **8%** | **~6,500** | **~$41** | **~8.3 yrs** |
|
||||
|
||||
The site's open exposure and ground-mount flexibility make it an ideal solar location.
|
||||
|
||||
### Monthly Production Profile (NREL)
|
||||
|
||||
| Month | AC Output (kWh) | Daily Avg | Solar Rad (kWh/m²/day) |
|
||||
|-------|-----------------|-----------|------------------------|
|
||||
| Jan | 450 | 15 | 4.58 |
|
||||
| Feb | 453 | 15 | 5.15 |
|
||||
| Mar | 492 | 16 | 5.23 |
|
||||
| Apr | 508 | 17 | 5.61 |
|
||||
| May | 534 | 18 | 5.87 |
|
||||
| Jun | 514 | 17 | 5.94 |
|
||||
| Jul | 555 | 18 | 6.31 |
|
||||
| Aug | 571 | 19 | 6.57 |
|
||||
| Sep | 514 | 17 | 6.01 |
|
||||
| Oct | 516 | 17 | 5.62 |
|
||||
| Nov | 469 | 15 | 4.99 |
|
||||
| Dec | 429 | 14 | 4.39 |
|
||||
| **Annual** | **6,004** | **16** | **5.52** |
|
||||
|
||||
**image-2-placeholder**
|
||||
|
||||
### Investment Summary (Updated)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total Project Cost | $4,100.00 |
|
||||
| Average Monthly Bill (15-mo actual) | $264.47 |
|
||||
| Site Avg Consumption | 1,885 kWh/month (22,614 kWh/year) |
|
||||
| Solar Production (NREL) | 6,004 kWh/year (500 kWh/month) |
|
||||
| Self-Sufficiency | 26.6% |
|
||||
| Base Power Energy Rate | $0.085/kWh |
|
||||
| Base Power Export Rate | $0.04/kWh |
|
||||
|
||||
### Financial Analysis (Updated)
|
||||
|
||||
| Category | kWh/year | Rate | Annual Value |
|
||||
|----------|----------|------|--------------|
|
||||
| Self-Consumed (60%) | 3,602 | $0.085/kWh | $306.18 |
|
||||
| Exported to Grid (40%) | 2,401 | $0.04/kWh | $96.06 |
|
||||
| **Total Annual Value** | **6,004** | | **$402.24** |
|
||||
| **Monthly Savings** | | | **$33.52** |
|
||||
|
||||
### ROI Summary (Updated)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Monthly Savings | $33.52 |
|
||||
| Bill Offset | 12.7% (of $264.47 avg bill) |
|
||||
| Payback Period | 10.2 years (122 months) |
|
||||
|
||||
### System Size Scenarios (NREL Analysis)
|
||||
|
||||
| System | kW | kWh/yr | $/mo | Offset | Payback |
|
||||
|--------|-----|--------|------|--------|---------|
|
||||
| 16 × 250W (current) | 4.0 | 6,004 | $33.52 | 12.7% | 10.2 yrs |
|
||||
| 16 × 300W | 4.8 | 7,204 | $40.22 | 15.2% | 8.5 yrs |
|
||||
| 16 × 350W | 5.6 | 8,405 | $46.93 | 17.8% | 7.3 yrs |
|
||||
| 16 × 400W | 6.4 | 9,606 | $53.63 | 20.3% | 6.4 yrs |
|
||||
| 16 × 450W | 7.2 | 10,806 | $60.34 | 22.8% | 5.7 yrs |
|
||||
| 20 × 400W (expanded) | 8.0 | 12,007 | $67.04 | 25.4% | 5.1 yrs |
|
||||
| 24 × 400W | 9.6 | 14,409 | $80.45 | 30.4% | 4.2 yrs |
|
||||
|
||||
**image-4-placeholder**
|
||||
|
||||
**Note:** To achieve 100% bill offset would require ~32 kW (80 panels @ 400W each).
|
||||
|
||||
### 10-Year Financial Projection (Current System: 16 × 250W)
|
||||
|
||||
| Year | Cumulative Savings | Net Position |
|
||||
|------|-------------------|--------------|
|
||||
| 0 | $0 | -$4,100.00 |
|
||||
| 1 | $402.24 | -$3,697.76 |
|
||||
| 2 | $804.48 | -$3,295.52 |
|
||||
| 3 | $1,206.72 | -$2,893.28 |
|
||||
| 4 | $1,608.97 | -$2,491.03 |
|
||||
| 5 | $2,011.21 | -$2,088.79 |
|
||||
| 6 | $2,413.45 | -$1,686.55 |
|
||||
| 7 | $2,815.69 | -$1,284.31 |
|
||||
| 8 | $3,217.93 | -$882.07 |
|
||||
| 9 | $3,620.17 | -$479.83 |
|
||||
| 10 | $4,022.41 | -$77.59 |
|
||||
| 11 | $4,424.65 | +$324.65 |
|
||||
|
||||
**image-3-placeholder**
|
||||
|
||||
### Assumptions
|
||||
|
||||
- NREL PVWatts production estimates based on TMY (Typical Meteorological Year) data
|
||||
- 60% self-consumption / 40% export split (conservative estimate)
|
||||
- System losses of 14% (inverter, wiring, soiling, etc.)
|
||||
- Electricity rates remain constant (conservative - rates typically increase 2-3% annually)
|
||||
- No major maintenance required in first 10 years
|
||||
- Base Power contract at $0.085/kWh remains in effect through Oct 2028
|
||||
- Base Power solar buyback at $0.04/kWh continues
|
||||
- Solar integration with Base Power system is technically feasible
|
||||
|
||||
### Key Considerations
|
||||
|
||||
**Production vs. Consumption Gap:**
|
||||
- Current consumption: ~22,600 kWh/year
|
||||
- Solar production: ~6,000 kWh/year
|
||||
- Gap: ~16,600 kWh/year (73% still from grid/Base Power)
|
||||
|
||||
**Options to Improve Economics:**
|
||||
1. Upgrade to higher-wattage panels (400W+ panels reduce payback to ~6 years)
|
||||
2. Expand system size (additional panels/racks)
|
||||
3. Reduce consumption through efficiency measures
|
||||
4. Add more panels when costs decrease
|
||||
|
||||
### Value Adds Not Quantified
|
||||
|
||||
- **Energy Independence:** Already partially achieved via Base Power; solar adds generation capability
|
||||
- **Property Value:** Solar installations typically increase property value
|
||||
- **Environmental Impact:** Reduced carbon footprint
|
||||
- **Existing Battery Storage:** Already in place via Base Power system
|
||||
- **Rate Hedge:** Protection against future electricity rate increases post-2028
|
||||
- **Redundancy:** Solar + Base Power + Grid provides triple-redundant power architecture
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Immediate Actions
|
||||
|
||||
1. **Approve Budget:** Authorize $4,100 project expenditure
|
||||
2. **Order Inverter:** Confirm Sol-Ark 5K availability and place order
|
||||
3. **Complete Site Survey:** Determine exact wiring requirements and costs
|
||||
4. **Verify Permits:** Contact local building department to confirm requirements
|
||||
|
||||
### Panel Upgrade Consideration
|
||||
|
||||
Based on the updated analysis, upgrading from 250W to 400W panels would:
|
||||
- Increase production from 6,004 to 9,606 kWh/year (+60%)
|
||||
- Increase monthly savings from $33.52 to $53.63
|
||||
- Reduce payback from 10.2 years to 6.4 years
|
||||
- Increase bill offset from 12.7% to 20.3%
|
||||
|
||||
**Recommendation:** If budget allows, consider sourcing 400W panels instead of using existing 250W inventory.
|
||||
|
||||
### Project Execution
|
||||
|
||||
1. Continue rack construction using proven design from Rack #1
|
||||
2. Procure all remaining materials in single order for cost efficiency
|
||||
3. Consider hiring licensed electrician for final grid connection
|
||||
4. Document entire process for future reference/expansion
|
||||
|
||||
### Future Considerations
|
||||
|
||||
- **Base Power Contract Renewal (2028):** Evaluate contract terms vs. solar-only operation
|
||||
- **Panel Expansion:** Inverter can handle additional capacity if needed
|
||||
- **Monitoring:** Add production monitoring system for performance tracking
|
||||
- **Grid Export:** Explore net metering options for excess generation
|
||||
|
||||
---
|
||||
|
||||
## Appendix
|
||||
|
||||
### A. Vendor Information
|
||||
|
||||
**Inverter Supplier:**
|
||||
- EcoDirect
|
||||
- Product: Sol-Ark 5K-5kW-48V Single-Phase Hybrid Inverter
|
||||
- URL: https://www.ecodirect.com/Sol-Ark-5K-5kW-48V-Single-Phase-Hybrid-Inverter-p/sol-ark-5k-1p-n.htm
|
||||
- Price: $1,500.00
|
||||
|
||||
### B. Bill of Materials
|
||||
|
||||
| Item | Specification | Qty Needed | Unit Cost | Total |
|
||||
|------|--------------|------------|-----------|-------|
|
||||
| Steel Posts | 8' galvanized | 48 | $34.97 | $1,678.56 |
|
||||
| U-bolts | 5/16" x 5-3/8" | 112 | $2.33 | $260.96 |
|
||||
| Adjustable Clamps | 2-3/8" galvanized | 24 | $3.77 | $90.48 |
|
||||
| Post Driver | Manual | 1 | $57.75 | $57.75 |
|
||||
| Hybrid Inverter | Sol-Ark 5K | 1 | $1,500.00 | $1,500.00 |
|
||||
| Wiring/Connectors | TBD | - | - | $312.25 |
|
||||
| **TOTAL** | | | | **$3,900.00** |
|
||||
|
||||
*Note: $200 contingency brings total to $4,100.00*
|
||||
|
||||
### C. Rack Construction Details
|
||||
|
||||
**Standard Rack Assembly (Per Rack):**
|
||||
1. Drive 6 steel posts into ground in 2 rows of 3
|
||||
2. Connect posts with horizontal rails using adjustable clamps
|
||||
3. Secure panel mounting rails with U-bolts
|
||||
4. Mount 2 solar panels per rack
|
||||
5. Route wiring through protective conduit
|
||||
|
||||
**Completed:** 1 of 8 racks ($360.95 invested)
|
||||
|
||||
### D. Base Power Billing Details
|
||||
|
||||
**Contract Information:**
|
||||
- Contract #: SITER (renewed Oct 2025)
|
||||
- Contract ID: SITER
|
||||
- Provider: Base Power Company (PUCT License #10338)
|
||||
- Address: 1606 Headway Cir, Ste 9333, Austin, TX 78754
|
||||
- Support: 1-866-479-POWR (7697)
|
||||
|
||||
**Rate Structure:**
|
||||
- Energy Charge: $0.085/kWh (Oct 2025 - Oct 2028)
|
||||
- Previous Rate: $0.090/kWh (Oct 2024 - Oct 2025)
|
||||
- Utility Delivery (Oncor): Pass-through charges (~$0.056/kWh equivalent)
|
||||
- Base Subscription Fee: $10.00/month
|
||||
- Taxes: ~3.7% (SITER rates)
|
||||
|
||||
**Meter Data:**
|
||||
- Consumption Meter #: SITER
|
||||
- Generation Meter #: SITER (dual-purpose)
|
||||
- Reads: Actual (not estimated)
|
||||
- Tracks both Generation (solar/battery) and Consumption
|
||||
|
||||
### E. Base Power Integration Notes
|
||||
|
||||
**Technical Considerations:**
|
||||
- Verify Sol-Ark 5K compatibility with existing Base Power ATS
|
||||
- Confirm DC voltage alignment between solar array and Base Power battery system
|
||||
- Determine optimal AC coupling configuration
|
||||
- Review interconnection requirements with Base Power
|
||||
|
||||
**Contract Considerations:**
|
||||
- Review Base Power contract for solar integration provisions
|
||||
- Confirm no penalties for reduced consumption
|
||||
- Plan for contract renewal negotiations in Oct 2028
|
||||
|
||||
### Base Power Solar Economics
|
||||
|
||||
**Consumption Offset vs. Export Credit:**
|
||||
|
||||
| Scenario | Rate | Benefit |
|
||||
|----------|------|--------|
|
||||
| Solar consumed on-site | $0.085/kWh avoided | Higher value - reduces energy charge |
|
||||
| Solar exported to grid | $0.04/kWh credit | Lower value - but credits apply to entire bill |
|
||||
|
||||
**Optimal Strategy:** Maximize self-consumption during peak production hours (run high-load appliances when solar is generating) to capture the $0.085/kWh value rather than exporting at $0.04/kWh.
|
||||
|
||||
**Key Advantage:** Unlike many providers that only credit against energy charges, Base applies solar credits to the **entire bill** including delivery fees, taxes, and the $10/month subscription fee.
|
||||
|
||||
### F. Document History
|
||||
|
||||
| Version | Date | Author | Changes |
|
||||
|---------|------|--------|---------|
|
||||
| 1.0 | February 2026 | SITER | Initial project documentation |
|
||||
| 1.1 | February 2026 | SITER | Added Base Power infrastructure details |
|
||||
| 1.2 | February 2026 | SITER | Updated with actual billing data (5 months) |
|
||||
| 2.0 | February 2026 | SITER | Updated with complete billing data (15 months), revised consumption averages, seasonal analysis |
|
||||
|
||||
---
|
||||
|
||||
*Document prepared for SITER Board Review*
|
||||
*Project Start Date: 2026*
|
||||
*Target Completion: Q2 2026*
|
||||