CoolFace
Apppublic

AbishKamran/Solar-Rooftop-Analysis-Tool

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
app.py468 linesDownload Raw Back to root
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()