AbishKamran/Solar-Rooftop-Analysis-Tool
0
1#!/usr/bin/env python32"""3Solar Rooftop Analysis Tool - Simplified Version4"""5 6import gradio as gr7import requests8import json9import base6410from PIL import Image, ImageDraw11import io12import math13import pandas as pd14from datetime import datetime, timedelta15import plotly.graph_objects as go16import plotly.express as px17from typing import Dict, List, Tuple, Optional, Union, Any18import numpy as np19import re20import os21 22# Configuration23OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")24OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1/chat/completions"25 26# Solar Industry Constants (same as before)27SOLAR_PANEL_SPECS = {28 "residential": {29 "monocrystalline": {30 "efficiency": 0.22,31 "power_rating": 400,32 "dimensions": (2.0, 1.0),33 "cost_per_watt": 2.50,34 "warranty_years": 25,35 "degradation_rate": 0.00536 },37 "polycrystalline": {38 "efficiency": 0.18,39 "power_rating": 320,40 "dimensions": (2.0, 1.0),41 "cost_per_watt": 2.20,42 "warranty_years": 25,43 "degradation_rate": 0.00744 },45 "thin_film": {46 "efficiency": 0.12,47 "power_rating": 200,48 "dimensions": (2.0, 1.0),49 "cost_per_watt": 1.80,50 "warranty_years": 20,51 "degradation_rate": 0.00852 }53 }54}55 56SYSTEM_COSTS = {57 "inverter_cost_per_watt": 0.40,58 "mounting_cost_per_watt": 0.30,59 "electrical_cost_per_watt": 0.25,60 "labor_cost_per_watt": 0.60,61 "permit_inspection": 1500,62 "design_engineering": 80063}64 65INCENTIVES = {66 "federal_tax_credit": 0.30,67 "state_rebate_per_watt": 0.50,68 "srec_annual_value": 30069}70 71class SolarAnalysisEngine:72 def __init__(self, api_key: str):73 self.api_key = api_key74 self.headers = {75 "Authorization": f"Bearer {api_key}",76 "Content-Type": "application/json"77 } if api_key else {}78 79 def encode_image(self, image: Image.Image) -> str:80 try:81 if image.mode != 'RGB':82 image = image.convert('RGB')83 84 buffer = io.BytesIO()85 image.save(buffer, format="JPEG", quality=85)86 image_data = buffer.getvalue()87 return base64.b64encode(image_data).decode('utf-8')88 except Exception as e:89 print(f"Error encoding image: {str(e)}")90 raise91 92 def extract_json_from_text(self, text: str) -> Dict[str, Any]:93 if not text or not text.strip():94 raise ValueError("Empty response received")95 96 json_pattern = r'\{.*\}'97 matches = re.findall(json_pattern, text, re.DOTALL)98 99 for match in matches:100 try:101 return json.loads(match)102 except json.JSONDecodeError:103 continue104 105 json_block_pattern = r'```json\s*(.*?)\s*```'106 matches = re.findall(json_block_pattern, text, re.DOTALL | re.IGNORECASE)107 108 for match in matches:109 try:110 return json.loads(match)111 except json.JSONDecodeError:112 continue113 114 try:115 start = text.find('{')116 end = text.rfind('}') + 1117 if start != -1 and end > start:118 json_str = text[start:end]119 return json.loads(json_str)120 except (json.JSONDecodeError, ValueError):121 pass122 123 raise ValueError(f"Could not extract valid JSON from response: {text[:200]}...")124 125 def analyze_rooftop_image(self, image: Image.Image) -> Dict[str, Any]:126 if not self.api_key:127 print("No API key provided, using fallback analysis")128 return self._get_fallback_analysis()129 130 try:131 base64_image = self.encode_image(image)132 except Exception as e:133 print(f"Error encoding image: {str(e)}")134 return self._get_fallback_analysis()135 136 prompt = """137 You are an expert solar installer analyzing a rooftop satellite image for solar panel installation potential.138 139 Analyze this rooftop image and provide a detailed assessment in the following JSON format.140 IMPORTANT: Respond ONLY with valid JSON, no additional text or formatting.141 142 {143 "rooftop_analysis": {144 "total_roof_area_sqm": 150,145 "usable_area_sqm": 120,146 "roof_orientation": "South",147 "roof_tilt_degrees": 30,148 "shading_assessment": {149 "trees": "Minimal",150 "buildings": "None",151 "other_obstacles": "Standard vents and chimney"152 },153 "roof_condition": "Good",154 "access_difficulty": "Moderate",155 "structural_concerns": "None observed"156 },157 "solar_suitability": {158 "overall_score": 8,159 "primary_factors": ["Good south-facing orientation", "Minimal shading", "Adequate roof area"],160 "recommended_panel_layout": "Array on main south-facing section",161 "estimated_panel_capacity": 60162 },163 "additional_notes": "Analysis based on visible rooftop features"164 }165 166 Replace the example values with your actual analysis. Use realistic estimates for the uploaded image.167 Consider standard residential solar panel size of 2m x 1m when estimating capacity.168 """169 170 payload = {171 "model": "openai/gpt-4o",172 "messages": [173 {174 "role": "user",175 "content": [176 {"type": "text", "text": prompt},177 {178 "type": "image_url",179 "image_url": {180 "url": f"data:image/jpeg;base64,{base64_image}"181 }182 }183 ]184 }185 ],186 "max_tokens": 1500,187 "temperature": 0.3188 }189 190 try:191 response = requests.post(192 OPENROUTER_BASE_URL, 193 headers=self.headers, 194 json=payload,195 timeout=30196 )197 response.raise_for_status()198 199 result = response.json()200 201 if 'choices' not in result or not result['choices']:202 raise ValueError("Invalid API response structure")203 204 content = result['choices'][0]['message']['content']205 206 if not content:207 raise ValueError("Empty content in API response")208 209 return self.extract_json_from_text(content)210 211 except requests.exceptions.RequestException as e:212 print(f"API request failed: {str(e)}")213 return self._get_fallback_analysis()214 215 except json.JSONDecodeError as e:216 print(f"Failed to parse AI response as JSON: {str(e)}")217 return self._get_fallback_analysis()218 219 except Exception as e:220 print(f"Unexpected error during analysis: {str(e)}")221 return self._get_fallback_analysis()222 223 def _get_fallback_analysis(self) -> Dict[str, Any]:224 return {225 "rooftop_analysis": {226 "total_roof_area_sqm": 150,227 "usable_area_sqm": 120,228 "roof_orientation": "South",229 "roof_tilt_degrees": 30,230 "shading_assessment": {231 "trees": "Minimal",232 "buildings": "None",233 "other_obstacles": "Standard vents and chimney"234 },235 "roof_condition": "Good",236 "access_difficulty": "Moderate",237 "structural_concerns": "None observed"238 },239 "solar_suitability": {240 "overall_score": 7,241 "primary_factors": ["Good south-facing orientation", "Minimal shading", "Adequate roof area"],242 "recommended_panel_layout": "Array on main south-facing section",243 "estimated_panel_capacity": 60244 },245 "additional_notes": "Fallback analysis - upload an image and configure API key for AI-powered assessment"246 }247 248class SolarCalculator:249 @staticmethod250 def calculate_system_capacity(panel_count: int, panel_type: str = "monocrystalline") -> float:251 panel_power = SOLAR_PANEL_SPECS["residential"][panel_type]["power_rating"]252 return (panel_count * panel_power) / 1000253 254 @staticmethod255 def estimate_annual_production(system_capacity_kw: float, location_factor: float = 1400) -> float:256 return system_capacity_kw * location_factor257 258 @staticmethod259 def calculate_system_cost(system_capacity_kw: float, panel_type: str = "monocrystalline") -> Dict[str, float]:260 capacity_watts = system_capacity_kw * 1000261 262 panel_specs = SOLAR_PANEL_SPECS["residential"][panel_type]263 264 costs = {265 "panels": capacity_watts * panel_specs["cost_per_watt"],266 "inverter": capacity_watts * SYSTEM_COSTS["inverter_cost_per_watt"],267 "mounting": capacity_watts * SYSTEM_COSTS["mounting_cost_per_watt"],268 "electrical": capacity_watts * SYSTEM_COSTS["electrical_cost_per_watt"],269 "labor": capacity_watts * SYSTEM_COSTS["labor_cost_per_watt"],270 "permits": SYSTEM_COSTS["permit_inspection"],271 "design": SYSTEM_COSTS["design_engineering"]272 }273 274 costs["subtotal"] = sum(costs.values())275 costs["contingency"] = costs["subtotal"] * 0.10276 costs["total"] = costs["subtotal"] + costs["contingency"]277 278 return costs279 280 @staticmethod281 def calculate_incentives(system_cost: float, system_capacity_kw: float) -> Dict[str, float]:282 capacity_watts = system_capacity_kw * 1000283 284 incentives = {285 "federal_tax_credit": system_cost * INCENTIVES["federal_tax_credit"],286 "state_rebate": capacity_watts * INCENTIVES["state_rebate_per_watt"],287 "srec_10_year": INCENTIVES["srec_annual_value"] * 10288 }289 290 incentives["total"] = sum(incentives.values())291 return incentives292 293 @staticmethod294 def calculate_roi_analysis(system_cost: float, annual_production: float, 295 electricity_rate: float = 0.12) -> Dict[str, float]:296 annual_savings = annual_production * electricity_rate297 298 total_savings_25_years = 0299 current_production = annual_production300 current_rate = electricity_rate301 302 for year in range(1, 26):303 total_savings_25_years += current_production * current_rate304 current_production *= 0.995305 current_rate *= 1.02306 307 simple_payback = system_cost / annual_savings if annual_savings > 0 else float('inf')308 309 return {310 "annual_savings": annual_savings,311 "simple_payback_years": simple_payback,312 "total_25_year_savings": total_savings_25_years,313 "net_25_year_benefit": total_savings_25_years - system_cost,314 "roi_percentage": ((total_savings_25_years - system_cost) / system_cost * 100) if system_cost > 0 else 0315 }316 317def analyze_solar_potential(image, api_key, electricity_rate, panel_type, location_factor):318 """Main analysis function - simplified version"""319 320 try:321 if image is None:322 return "Please upload a rooftop image to analyze."323 324 # Initialize analyzer325 analyzer = SolarAnalysisEngine(api_key if api_key else OPENROUTER_API_KEY)326 327 # Analyze the image328 analysis = analyzer.analyze_rooftop_image(image)329 330 # Extract analysis data331 roof_data = analysis['rooftop_analysis']332 solar_data = analysis['solar_suitability']333 334 # Calculate system specifications335 panel_count = solar_data['estimated_panel_capacity']336 system_capacity = SolarCalculator.calculate_system_capacity(panel_count, panel_type)337 annual_production = SolarCalculator.estimate_annual_production(system_capacity, location_factor)338 339 # Calculate costs and ROI340 system_costs = SolarCalculator.calculate_system_cost(system_capacity, panel_type)341 incentives = SolarCalculator.calculate_incentives(system_costs['total'], system_capacity)342 net_cost = system_costs['total'] - incentives['total']343 roi_analysis = SolarCalculator.calculate_roi_analysis(net_cost, annual_production, electricity_rate)344 345 # Create summary text346 summary = f"""## Solar Analysis Results347 348### Key Metrics349- **Suitability Score:** {solar_data['overall_score']}/10350- **Usable Roof Area:** {roof_data['usable_area_sqm']} m²351- **Estimated Panels:** {panel_count} panels352- **System Capacity:** {system_capacity:.1f} kW353- **Annual Production:** {annual_production:,.0f} kWh354 355### Roof Assessment356- **Orientation:** {roof_data['roof_orientation']}357- **Tilt:** {roof_data['roof_tilt_degrees']}°358- **Condition:** {roof_data['roof_condition']}359- **Shading - Trees:** {roof_data['shading_assessment']['trees']}360- **Shading - Buildings:** {roof_data['shading_assessment']['buildings']}361 362### Financial Summary363- **Total System Cost:** ${system_costs['total']:,.0f}364- **Federal Tax Credit:** -${incentives['federal_tax_credit']:,.0f}365- **Net Cost:** ${net_cost:,.0f}366- **Annual Savings:** ${roi_analysis['annual_savings']:,.0f}367- **Payback Period:** {roi_analysis['simple_payback_years']:.1f} years368- **25-Year ROI:** {roi_analysis['roi_percentage']:.0f}%369 370### Recommendations"""371 372 # Add recommendations373 if solar_data['overall_score'] >= 8:374 summary += "\n✅ **Highly Recommended**: Excellent solar potential with strong ROI"375 elif solar_data['overall_score'] >= 6:376 summary += "\n✅ **Recommended**: Good solar potential with reasonable payback"377 else:378 summary += "\n⚠️ **Consider Carefully**: Limited solar potential, evaluate alternatives"379 380 if roof_data['roof_orientation'] in ['South', 'Southeast', 'Southwest']:381 summary += "\n✅ **Optimal Orientation**: Excellent sun exposure throughout the day"382 383 if roi_analysis['simple_payback_years'] < 8:384 summary += "\n💰 **Fast Payback**: System will pay for itself quickly"385 386 summary += f"\n🔧 **Recommended Setup**: {solar_data['recommended_panel_layout']}"387 388 return summary389 390 except Exception as e:391 error_message = f"Error during analysis: {str(e)}"392 print(error_message)393 return f"❌ **Analysis Error**: {error_message}\n\nPlease try again or check your inputs."394 395# Simplified Gradio interface396def create_interface():397 with gr.Blocks(title="Solar Rooftop Analysis Tool") as demo:398 gr.Markdown("""399 # ☀️ AI-Powered Solar Rooftop Analysis Tool400 *Professional solar installation potential assessment using satellite imagery and AI*401 402 Upload a clear aerial or satellite image of a rooftop to get a comprehensive solar analysis.403 """)404 405 with gr.Row():406 with gr.Column():407 image_input = gr.Image(label="Rooftop Image", type="pil")408 409 api_key_input = gr.Textbox(410 label="OpenRouter API Key (Optional)",411 type="password",412 placeholder="sk-or-v1-...",413 value=""414 )415 416 electricity_rate = gr.Slider(417 minimum=0.08,418 maximum=0.30,419 value=0.12,420 step=0.01,421 label="Electricity Rate ($/kWh)"422 )423 424 panel_type = gr.Radio(425 choices=["monocrystalline", "polycrystalline", "thin_film"],426 value="monocrystalline",427 label="Panel Type"428 )429 430 location_factor = gr.Slider(431 minimum=1000,432 maximum=2000,433 value=1400,434 step=50,435 label="Location Solar Factor"436 )437 438 analyze_btn = gr.Button("🔍 Analyze Solar Potential", variant="primary")439 440 with gr.Column():441 analysis_output = gr.Markdown(value="Upload an image and click analyze to see results...")442 443 analyze_btn.click(444 fn=analyze_solar_potential,445 inputs=[image_input, api_key_input, electricity_rate, panel_type, location_factor],446 outputs=[analysis_output]447 )448 449 gr.Markdown("""450 ### 🚀 Next Steps451 452 **For Homeowners:**453 1. Get quotes from 3-5 local solar installers454 2. Verify roof structural integrity with engineer 455 3. Check local permitting requirements456 4. Review utility interconnection policies457 5. Schedule site assessment with chosen installer458 459 ---460 *This tool provides estimates based on AI analysis and industry standards.*461 """)462 463 return demo464 465# Launch the demo466if __name__ == "__main__":467 demo = create_interface()468 demo.launch()