{ "cells": [ { "cell_type": "markdown", "id": "5ced98fd", "metadata": {}, "source": [ "#### 新的更改在最后cell 用的rf " ] }, { "cell_type": "code", "execution_count": null, "id": "80d003ef", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "🚀 Launching Home Retrofit Calculator...\n", "* Running on local URL: http://127.0.0.1:7866\n", "* Running on public URL: https://7814baa6974f53f8b9.gradio.live\n", "\n", "This share link expires in 1 week. For free permanent hosting and GPU upgrades, run `gradio deploy` from the terminal in the working directory to deploy to Hugging Face Spaces (https://huggingface.co/spaces)\n" ] }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Starting analysis with inputs: floor_area=60, epc=50\n", "Roof type determined: pitched, insulation: uninsulated\n", "Energy prediction completed: 17457.51824094143 kWh, 71058.28296304637 kWh\n", "Areas calculated: wall=19.4, glazing=3.9, roof=60.0\n", "🔍 Generating optimization strategies...\n", "📊 Analyzing 240 combinations...\n", " Progress: 0/240\n", " Progress: 20/240\n", " Progress: 40/240\n", " Progress: 60/240\n", " Progress: 80/240\n", " Progress: 100/240\n", " Progress: 120/240\n", " Progress: 140/240\n", " Progress: 160/240\n", " Progress: 180/240\n", " Progress: 200/240\n", " Progress: 220/240\n", "✅ Found 46 viable strategies\n", "Creating optimization chart...\n", "Chart data: 9 strategies\n", "Chart created successfully\n", "Chart will be displayed\n", "Options available: wall=3, roof=2, glazing=4, heating=5, fuel=2\n", "Chart visible: True\n", "Starting analysis with inputs: floor_area=60, epc=50\n", "Roof type determined: pitched, insulation: uninsulated\n", "Energy prediction completed: 17457.51824094143 kWh, 71058.28296304637 kWh\n", "Areas calculated: wall=19.4, glazing=3.9, roof=60.0\n", "🔍 Generating optimization strategies...\n", "📊 Analyzing 320 combinations...\n", " Progress: 0/320\n", " Progress: 20/320\n", " Progress: 40/320\n", " Progress: 60/320\n", " Progress: 80/320\n", " Progress: 100/320\n", " Progress: 120/320\n", " Progress: 140/320\n", " Progress: 160/320\n", " Progress: 180/320\n", " Progress: 200/320\n", " Progress: 220/320\n", " Progress: 240/320\n", " Progress: 260/320\n", " Progress: 280/320\n", " Progress: 300/320\n", "✅ Found 62 viable strategies\n", "Creating optimization chart...\n", "Chart data: 9 strategies\n", "Chart created successfully\n", "Chart will be displayed\n", "Options available: wall=4, roof=2, glazing=4, heating=5, fuel=2\n", "Chart visible: True\n", "Starting analysis with inputs: floor_area=60, epc=50\n", "Roof type determined: pitched, insulation: uninsulated\n", "Energy prediction completed: 17457.51824094143 kWh, 71058.28296304637 kWh\n", "Areas calculated: wall=19.4, glazing=3.9, roof=60.0\n", "🔍 Generating optimization strategies...\n", "📊 Analyzing 240 combinations...\n", " Progress: 0/240\n", " Progress: 20/240\n", " Progress: 40/240\n", " Progress: 60/240\n", " Progress: 80/240\n", " Progress: 100/240\n", " Progress: 120/240\n", " Progress: 140/240\n", " Progress: 160/240\n", " Progress: 180/240\n", " Progress: 200/240\n", " Progress: 220/240\n", "✅ Found 46 viable strategies\n", "Creating optimization chart...\n", "Chart data: 9 strategies\n", "Chart created successfully\n", "Chart will be displayed\n", "Options available: wall=3, roof=2, glazing=4, heating=5, fuel=2\n", "Chart visible: True\n", "Starting renovation analysis...\n", "Renovations selected: wall=internal, roof=no_change, glazing=secondary_glazing, heating=no_change, fuel=no_change\n", "Original consumption: 17457.51824094143 kWh\n", "Renovated consumption: 14816.756481546063 kWh\n", "Analysis complete: savings=2640.7617593953673 kWh, cost_savings=£193, carbon_savings=481 kg CO2e, payback=9.5 years\n" ] } ], "source": [ "import gradio as gr\n", "import pandas as pd\n", "import numpy as np\n", "import math\n", "import joblib\n", "import traceback\n", "import matplotlib.pyplot as plt\n", "import matplotlib\n", "matplotlib.use('Agg')\n", "from itertools import product\n", "import plotly.graph_objects as go\n", "import plotly.express as px\n", "from plotly.subplots import make_subplots\n", "\n", "# Load model and feature list with comprehensive error handling\n", "model = joblib.load('rf_model.joblib')\n", "feature_cols = joblib.load('rf_features.joblib')\n", "\n", "# Carbon emission factors (kg CO2e per kWh) - UK BEIS 2024 data\n", "CARBON_FACTORS = {\n", " 'electricity': 0.193, # kg CO2e/kWh - Grid electricity (2024)\n", " 'gas': 0.182, # kg CO2e/kWh - Natural gas (2024)\n", " 'mixed': 0.187 # Average for mixed systems\n", "}\n", "\n", "# Renovation costs (UK market rates 2024)\n", "RENOVATION_COSTS = {\n", " 'wall_insulation': {\n", " 'internal': 65, # £55-75/m²\n", " 'external': 120, # £100-140/m²\n", " 'cavity_fill': 22 # £18-26/m² - Cavity wall insulation fill\n", " },\n", " 'roof_insulation': {\n", " 'loft': 18, # £15-22/m²\n", " 'flat_roof': 80 # £70-90/m²\n", " },\n", " 'glazing': {\n", " 'double_glazing': 320, # £280-360/m²\n", " 'triple_glazing': 450, # £400-500/m²\n", " 'secondary_glazing': 150 # £130-170/m²\n", " },\n", " 'heating_system': {\n", " # These costs are AFTER BUS grant (already deducted)\n", " 'air_source_heat_pump': 6500, # £14,000 - £7,500 BUS grant\n", " 'ground_source_heat_pump': 12000, # £18,000 - £6,000 BUS grant\n", " 'gas_boiler_upgrade': 2400, # Standard cost\n", " 'electric_boiler': 1800 # Standard cost\n", " }\n", "}\n", "\n", "# Energy costs from Ofgem Price Cap (October 2024)\n", "ENERGY_COSTS = {\n", " 'electricity': 0.285, # £/kWh \n", " 'gas': 0.073, # £/kWh\n", " 'mixed': 0.18 # Average for mixed systems\n", "}\n", "\n", "# Government grants information\n", "GOVERNMENT_GRANTS = {\n", " 'BUS': {\n", " 'air_source_heat_pump': 7500,\n", " 'ground_source_heat_pump': 6000,\n", " 'description': 'Boiler Upgrade Scheme (BUS) grants already deducted from heat pump costs above'\n", " },\n", " 'ECO4': {\n", " 'max_amount': 10000,\n", " 'description': 'Energy Company Obligation - for eligible low-income households'\n", " }\n", "}\n", "\n", "def safe_float_conversion(value, default=0.0):\n", " \"\"\"Safely convert value to float\"\"\"\n", " try:\n", " if value is None:\n", " return default\n", " return float(value)\n", " except (ValueError, TypeError):\n", " print(f\"Warning: Could not convert {value} to float, using default {default}\")\n", " return default\n", "\n", "def safe_int_conversion(value, default=1):\n", " \"\"\"Safely convert value to int\"\"\"\n", " try:\n", " if value is None:\n", " return default\n", " return int(float(value)) # Convert through float first to handle \"2.0\" strings\n", " except (ValueError, TypeError):\n", " print(f\"Warning: Could not convert {value} to int, using default {default}\")\n", " return default\n", "\n", "def get_energy_cost_per_kwh(fuel_type):\n", " \"\"\"Get energy cost per kWh with comprehensive error handling\"\"\"\n", " try:\n", " if fuel_type is None:\n", " return ENERGY_COSTS['mixed']\n", " \n", " fuel_type_str = str(fuel_type).lower().strip()\n", " \n", " if 'electric' in fuel_type_str:\n", " return ENERGY_COSTS['electricity']\n", " elif 'gas' in fuel_type_str:\n", " return ENERGY_COSTS['gas']\n", " else:\n", " return ENERGY_COSTS['mixed']\n", " except Exception as e:\n", " print(f\"Warning: Error getting energy cost for {fuel_type}: {e}\")\n", " return ENERGY_COSTS['mixed']\n", "\n", "def get_carbon_factor(fuel_type):\n", " \"\"\"Get carbon emission factor per kWh\"\"\"\n", " try:\n", " if fuel_type is None:\n", " return CARBON_FACTORS['mixed']\n", " \n", " fuel_type_str = str(fuel_type).lower().strip()\n", " \n", " if 'electric' in fuel_type_str:\n", " return CARBON_FACTORS['electricity']\n", " elif 'gas' in fuel_type_str:\n", " return CARBON_FACTORS['gas']\n", " else:\n", " return CARBON_FACTORS['mixed']\n", " except Exception as e:\n", " print(f\"Warning: Error getting carbon factor for {fuel_type}: {e}\")\n", " return CARBON_FACTORS['mixed']\n", "\n", "def calculate_actual_areas(total_floor_area, building_type='flat'):\n", " \"\"\"Calculate actual renovation areas with comprehensive validation\"\"\"\n", " try:\n", " # Input validation and conversion\n", " floor_area = safe_float_conversion(total_floor_area, 60.0)\n", " \n", " if floor_area <= 0:\n", " print(f\"Warning: Invalid floor area {floor_area}, using default 60\")\n", " floor_area = 60.0\n", " \n", " if floor_area > 2000: # Sanity check\n", " print(f\"Warning: Very large floor area: {floor_area} m²\")\n", "\n", " if building_type == 'flat':\n", " # Conservative calculation for flats\n", " wall_perimeter = math.sqrt(floor_area) * 2.5\n", " wall_area = wall_perimeter * 2.5 * 0.4 # Only 40% is external wall\n", " glazing_area = wall_area * 0.2 # 20% of wall area\n", " roof_area = floor_area\n", " else: # house\n", " wall_area = floor_area * 1.8\n", " glazing_area = floor_area * 0.15\n", " roof_area = floor_area / math.cos(math.radians(30))\n", " \n", " # Ensure all areas are positive\n", " wall_area = max(wall_area, 1.0)\n", " glazing_area = max(glazing_area, 1.0)\n", " roof_area = max(roof_area, 1.0)\n", " \n", " return wall_area, glazing_area, roof_area\n", " \n", " except Exception as e:\n", " print(f\"Error in calculate_actual_areas: {e}\")\n", " # Return safe default values\n", " default_area = safe_float_conversion(total_floor_area, 60.0)\n", " return default_area * 0.3, default_area * 0.06, default_area\n", "\n", "def determine_roof_type_from_location(floor_location, roof_type):\n", " \"\"\"Determine final roof type based on floor location\"\"\"\n", " try:\n", " if floor_location == 'top floor':\n", " return str(roof_type) if roof_type else 'pitched'\n", " else:\n", " return 'another dwelling above'\n", " except Exception as e:\n", " print(f\"Error in determine_roof_type_from_location: {e}\")\n", " return 'another dwelling above'\n", "\n", "def get_energy_prediction(total_floor_area, estimated_floor_count, epc_score,\n", " wall_insulation, roof_type, roof_insulation, glazing_type,\n", " built_form, main_heat_type, main_fuel_type, lookup_age_band, wall_type):\n", " \"\"\"Energy prediction function with comprehensive error handling\"\"\"\n", " try:\n", " # Input validation and conversion\n", " floor_area = safe_float_conversion(total_floor_area, 60.0)\n", " floor_count = safe_float_conversion(estimated_floor_count, 2.0)\n", " epc = safe_float_conversion(epc_score, 50.0)\n", " \n", " # Validate ranges\n", " floor_area = max(10.0, min(1000.0, floor_area))\n", " floor_count = max(1.0, min(10.0, floor_count))\n", " epc = max(1.0, min(100.0, epc))\n", " \n", " # Ensure all string inputs are valid\n", " wall_insulation = str(wall_insulation) if wall_insulation else 'uninsulated'\n", " roof_type = str(roof_type) if roof_type else 'pitched'\n", " roof_insulation = str(roof_insulation) if roof_insulation else 'uninsulated'\n", " glazing_type = str(glazing_type) if glazing_type else 'single/partial'\n", " built_form = str(built_form) if built_form else 'mid-terrace'\n", " main_heat_type = str(main_heat_type) if main_heat_type else 'boiler'\n", " main_fuel_type = str(main_fuel_type) if main_fuel_type else 'mains gas'\n", " lookup_age_band = str(lookup_age_band) if lookup_age_band else '1950-1966'\n", " wall_type = str(wall_type) if wall_type else 'solid'\n", " \n", " # U-value calculations with error handling\n", " U_wall = 0.37 if wall_insulation == 'insulated' else 1.7\n", " \n", " if roof_insulation == 'insulated':\n", " U_roof = 0.25\n", " elif roof_type == 'flat':\n", " U_roof = 0.28\n", " else:\n", " U_roof = 2.3\n", " \n", " U_floor = 0.25\n", " \n", " if glazing_type == 'double/triple':\n", " U_glazing = 2.4\n", " elif glazing_type == 'secondary':\n", " U_glazing = 2.82\n", " else:\n", " U_glazing = 5.75\n", " \n", " # Area calculations\n", " if roof_type == 'pitched':\n", " roof_area = floor_area / math.cos(math.radians(30))\n", " elif roof_type == 'flat':\n", " roof_area = floor_area\n", " else:\n", " roof_area = 0\n", " \n", " wall_area = floor_area * 2.1\n", " floor_area_calc = floor_area\n", " glazing_area = floor_area * 0.18\n", " delta_T = 18\n", "\n", " Q_total = (U_wall * wall_area + U_roof * roof_area + U_floor * floor_area_calc + U_glazing * glazing_area) * delta_T\n", "\n", " # Create input data with safe conversions\n", " rowdict = {\n", " 'epc_score': epc,\n", " 'estimated_floor_count': floor_count,\n", " 'wall_area': wall_area,\n", " 'roof_area': roof_area,\n", " 'floor_area': floor_area_calc,\n", " 'glazing_area': glazing_area,\n", " 'u_value_wall': U_wall,\n", " 'u_value_roof': U_roof,\n", " 'u_value_floor': U_floor,\n", " 'u_value_glazing': U_glazing,\n", " 'Q_total': Q_total,\n", " 'wall_type': wall_type,\n", " 'wall_insulation': wall_insulation,\n", " 'roof_type': roof_type,\n", " 'roof_insulation': roof_insulation,\n", " 'glazing_type': glazing_type,\n", " 'built_form': built_form,\n", " 'main_heat_type': main_heat_type,\n", " 'main_fuel_type': main_fuel_type,\n", " 'lookup_age_band': lookup_age_band\n", " }\n", " \n", " # Create DataFrame and make prediction\n", " df_input = pd.DataFrame([rowdict])\n", " df_input = pd.get_dummies(df_input)\n", " \n", " # Ensure all required feature columns exist\n", " for col in feature_cols:\n", " if col not in df_input.columns:\n", " df_input[col] = 0\n", " \n", " # Select features in correct order\n", " df_input = df_input[feature_cols]\n", " \n", " # Make prediction\n", " pred = model.predict(df_input)[0]\n", " \n", " # Validate prediction result\n", " if pred < 0:\n", " print(f\"Warning: Negative prediction {pred}, setting to 0\")\n", " pred = 0\n", " elif pred > 100000:\n", " print(f\"Warning: Very high prediction {pred}\")\n", " \n", " # Convert Q_total from W to kWh (assuming full year operation)\n", " # Q_total (W) * 8760 hours / 1000 = kWh per year\n", " q_total_kwh = (Q_total * 8760) / 1000\n", " \n", " return float(pred), float(q_total_kwh)\n", " \n", " except Exception as e:\n", " print(f\"Error in get_energy_prediction: {e}\")\n", " print(f\"Traceback: {traceback.format_exc()}\")\n", " # Return reasonable default values\n", " default_area = safe_float_conversion(total_floor_area, 60.0)\n", " default_consumption = default_area * 150 # 150 kWh/m² typical\n", " default_q_total = default_area * 50 * 8760 / 1000 # Convert to kWh\n", " return float(default_consumption), float(default_q_total)\n", "\n", "def generate_optimization_strategies(\n", " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n", " floor_location, roof_type, wall_type, wall_insulation, roof_insulation, \n", " glazing_type, main_heat_type, main_fuel_type\n", "):\n", " \"\"\"Generate all possible renovation combinations and find optimal strategies\"\"\"\n", " try:\n", " print(\"🔍 Generating optimization strategies...\")\n", " \n", " # Determine available options\n", " final_roof_type = determine_roof_type_from_location(floor_location, roof_type)\n", " final_roof_insulation = roof_insulation if floor_location == 'top floor' else 'another dwelling above'\n", " \n", " # Get original performance\n", " original_consumption, original_q_total = get_energy_prediction(\n", " total_floor_area, estimated_floor_count, epc_score,\n", " wall_insulation, final_roof_type, final_roof_insulation, glazing_type,\n", " built_form, main_heat_type, main_fuel_type, lookup_age_band, wall_type\n", " )\n", " \n", " # Calculate renovation areas\n", " wall_area, glazing_area, roof_area = calculate_actual_areas(total_floor_area, 'flat')\n", " \n", " # Define available options based on current state\n", " wall_options = ['no_change']\n", " if wall_insulation == 'uninsulated':\n", " if wall_type == 'solid':\n", " wall_options.extend(['internal', 'external'])\n", " elif wall_type == 'cavity':\n", " wall_options.extend(['cavity_fill', 'internal', 'external'])\n", " \n", " roof_options = ['no_change']\n", " if floor_location == 'top floor' and final_roof_insulation == 'uninsulated':\n", " if final_roof_type == 'pitched':\n", " roof_options.append('loft')\n", " elif final_roof_type == 'flat':\n", " roof_options.append('flat_roof')\n", " \n", " glazing_options = ['no_change']\n", " if glazing_type == 'single/partial':\n", " glazing_options.extend(['double_glazing', 'triple_glazing', 'secondary_glazing'])\n", " elif glazing_type == 'secondary':\n", " glazing_options.extend(['double_glazing', 'triple_glazing'])\n", " \n", " heating_options = ['no_change']\n", " if main_heat_type != 'heat pump':\n", " heating_options.extend(['air_source_heat_pump', 'ground_source_heat_pump'])\n", " if main_heat_type == 'boiler' and lookup_age_band in ['pre-1920', '1930-1949', '1950-1966', '1967-1982']:\n", " heating_options.append('gas_boiler_upgrade')\n", " if main_heat_type != 'boiler' or main_fuel_type != 'electricity':\n", " heating_options.append('electric_boiler')\n", " \n", " fuel_options = ['no_change']\n", " if main_fuel_type != 'mains gas':\n", " fuel_options.append('mains gas')\n", " if main_fuel_type != 'electricity':\n", " fuel_options.append('electricity')\n", " \n", " # Generate all combinations\n", " all_combinations = list(product(wall_options, roof_options, glazing_options, heating_options, fuel_options))\n", " \n", " strategies = []\n", " print(f\"📊 Analyzing {len(all_combinations)} combinations...\")\n", " \n", " for i, (wall_ren, roof_ren, glazing_ren, heating_ren, fuel_ren) in enumerate(all_combinations):\n", " if i % 20 == 0:\n", " print(f\" Progress: {i}/{len(all_combinations)}\")\n", " \n", " # Skip no-change combination\n", " if all(opt == 'no_change' for opt in [wall_ren, roof_ren, glazing_ren, heating_ren, fuel_ren]):\n", " continue\n", " \n", " # Calculate costs and savings for this combination\n", " try:\n", " result = calculate_single_strategy(\n", " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n", " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n", " glazing_type, main_heat_type, main_fuel_type,\n", " wall_ren, roof_ren, glazing_ren, heating_ren, fuel_ren,\n", " original_consumption, original_q_total, wall_area, glazing_area, roof_area\n", " )\n", " \n", " if result and result['annual_cost_savings'] > 0:\n", " strategies.append(result)\n", " \n", " except Exception as e:\n", " continue\n", " \n", " print(f\"✅ Found {len(strategies)} viable strategies\")\n", " \n", " # Sort strategies\n", " strategies_by_payback = sorted([s for s in strategies if s['payback_years'] < float('inf')], \n", " key=lambda x: x['payback_years'])[:5]\n", " strategies_by_savings = sorted(strategies, key=lambda x: x['annual_cost_savings'], reverse=True)[:5]\n", " \n", " return strategies_by_payback, strategies_by_savings, original_consumption, original_q_total\n", " \n", " except Exception as e:\n", " print(f\"Error in generate_optimization_strategies: {e}\")\n", " return [], [], 0, 0\n", "\n", "def calculate_single_strategy(\n", " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n", " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n", " glazing_type, main_heat_type, main_fuel_type,\n", " wall_renovation, roof_renovation, glazing_renovation, heating_renovation, fuel_change,\n", " original_consumption, original_q_total, wall_area, glazing_area, roof_area\n", "):\n", " \"\"\"Calculate costs and savings for a single strategy\"\"\"\n", " try:\n", " final_roof_type = determine_roof_type_from_location(floor_location, roof_type)\n", " final_roof_insulation = roof_insulation if floor_location == 'top floor' else 'another dwelling above'\n", " \n", " # Apply renovations\n", " new_wall_insulation = 'insulated' if wall_renovation != 'no_change' else wall_insulation\n", " new_roof_insulation = 'insulated' if roof_renovation != 'no_change' else final_roof_insulation\n", " \n", " # Glazing upgrades\n", " new_glazing_type = glazing_type\n", " if glazing_renovation in ['double_glazing', 'triple_glazing']:\n", " new_glazing_type = 'double/triple'\n", " elif glazing_renovation == 'secondary_glazing':\n", " new_glazing_type = 'secondary'\n", " \n", " # Heating system upgrades\n", " new_heat_type = main_heat_type\n", " new_fuel_type = main_fuel_type\n", " \n", " if heating_renovation in ['air_source_heat_pump', 'ground_source_heat_pump']:\n", " new_heat_type = 'heat pump'\n", " new_fuel_type = 'electricity'\n", " elif heating_renovation == 'gas_boiler_upgrade':\n", " new_heat_type = 'boiler'\n", " new_fuel_type = 'mains gas'\n", " elif heating_renovation == 'electric_boiler':\n", " new_heat_type = 'boiler'\n", " new_fuel_type = 'electricity'\n", " \n", " # Fuel change override\n", " if fuel_change != 'no_change':\n", " new_fuel_type = fuel_change\n", " \n", " # Get renovated performance\n", " renovated_consumption, renovated_q_total = get_energy_prediction(\n", " total_floor_area, estimated_floor_count, epc_score,\n", " new_wall_insulation, final_roof_type, new_roof_insulation, new_glazing_type,\n", " built_form, new_heat_type, new_fuel_type, lookup_age_band, wall_type\n", " )\n", " \n", " # Calculate costs\n", " total_cost = 0\n", " cost_breakdown = []\n", " \n", " # Wall insulation cost\n", " if wall_renovation != 'no_change' and wall_renovation in RENOVATION_COSTS['wall_insulation']:\n", " cost = wall_area * RENOVATION_COSTS['wall_insulation'][wall_renovation]\n", " total_cost += cost\n", " cost_breakdown.append(f'Wall ({wall_renovation})')\n", " \n", " # Roof insulation cost\n", " if (roof_renovation != 'no_change' and \n", " floor_location == 'top floor' and \n", " roof_renovation in RENOVATION_COSTS['roof_insulation']):\n", " cost = roof_area * RENOVATION_COSTS['roof_insulation'][roof_renovation]\n", " total_cost += cost\n", " cost_breakdown.append(f'Roof ({roof_renovation})')\n", " \n", " # Glazing cost\n", " if glazing_renovation != 'no_change' and glazing_renovation in RENOVATION_COSTS['glazing']:\n", " cost = glazing_area * RENOVATION_COSTS['glazing'][glazing_renovation]\n", " total_cost += cost\n", " cost_breakdown.append(f'Glazing ({glazing_renovation})')\n", " \n", " # Heating system cost\n", " if heating_renovation != 'no_change' and heating_renovation in RENOVATION_COSTS['heating_system']:\n", " cost = RENOVATION_COSTS['heating_system'][heating_renovation]\n", " total_cost += cost\n", " cost_breakdown.append(f'Heating ({heating_renovation})')\n", " \n", " # Calculate savings\n", " annual_savings = max(0, original_consumption - renovated_consumption)\n", " original_energy_cost = get_energy_cost_per_kwh(main_fuel_type)\n", " new_energy_cost = get_energy_cost_per_kwh(new_fuel_type)\n", " \n", " original_annual_cost = original_consumption * original_energy_cost\n", " new_annual_cost = renovated_consumption * new_energy_cost\n", " annual_cost_savings = original_annual_cost - new_annual_cost\n", " \n", " # Calculate carbon savings\n", " original_carbon_factor = get_carbon_factor(main_fuel_type)\n", " new_carbon_factor = get_carbon_factor(new_fuel_type)\n", " \n", " original_annual_carbon = original_consumption * original_carbon_factor\n", " new_annual_carbon = renovated_consumption * new_carbon_factor\n", " annual_carbon_savings = original_annual_carbon - new_annual_carbon\n", " \n", " # Calculate payback period\n", " if annual_cost_savings > 0:\n", " payback_years = total_cost / annual_cost_savings\n", " else:\n", " payback_years = float('inf')\n", " \n", " return {\n", " 'combination': ' + '.join(cost_breakdown),\n", " 'total_cost': total_cost,\n", " 'annual_cost_savings': annual_cost_savings,\n", " 'annual_carbon_savings': annual_carbon_savings,\n", " 'payback_years': payback_years,\n", " 'energy_savings': annual_savings,\n", " 'renovated_consumption': renovated_consumption,\n", " 'renovated_q_total': renovated_q_total,\n", " 'details': {\n", " 'wall': wall_renovation,\n", " 'roof': roof_renovation,\n", " 'glazing': glazing_renovation,\n", " 'heating': heating_renovation,\n", " 'fuel': fuel_change\n", " }\n", " }\n", " \n", " except Exception as e:\n", " return None\n", "\n", "def create_optimization_chart(strategies_by_payback, strategies_by_savings):\n", " \"\"\"Create optimization scatter plot using Plotly\"\"\"\n", " try:\n", " print(\"Creating optimization chart...\")\n", " \n", " # 合并并去重策略\n", " all_strategies = {}\n", " \n", " # 添加最快回本策略\n", " for i, strategy in enumerate(strategies_by_payback[:5]):\n", " key = strategy['combination']\n", " if key not in all_strategies:\n", " all_strategies[key] = {\n", " 'combination': strategy['combination'],\n", " 'payback_years': strategy['payback_years'],\n", " 'annual_savings': strategy['annual_cost_savings'],\n", " 'annual_carbon_savings': strategy['annual_carbon_savings'],\n", " 'total_cost': strategy['total_cost'],\n", " 'category': 'Fastest Payback',\n", " 'rank_payback': i + 1,\n", " 'rank_savings': None\n", " }\n", " \n", " # 添加最高节省策略\n", " for i, strategy in enumerate(strategies_by_savings[:5]):\n", " key = strategy['combination']\n", " if key in all_strategies:\n", " all_strategies[key]['rank_savings'] = i + 1\n", " if all_strategies[key]['category'] == 'Fastest Payback':\n", " all_strategies[key]['category'] = 'Both Top 5'\n", " else:\n", " all_strategies[key] = {\n", " 'combination': strategy['combination'],\n", " 'payback_years': strategy['payback_years'],\n", " 'annual_savings': strategy['annual_cost_savings'],\n", " 'annual_carbon_savings': strategy['annual_carbon_savings'],\n", " 'total_cost': strategy['total_cost'],\n", " 'category': 'Highest Savings',\n", " 'rank_payback': None,\n", " 'rank_savings': i + 1\n", " }\n", " \n", " # 转换为列表\n", " plot_data = list(all_strategies.values())\n", " \n", " if not plot_data:\n", " print(\"No data for chart\")\n", " return None\n", " \n", " print(f\"Chart data: {len(plot_data)} strategies\")\n", " \n", " # 创建散点图\n", " fig = go.Figure()\n", " \n", " # 定义颜色和标记\n", " colors = {\n", " 'Fastest Payback': '#FF6B6B', # 红色\n", " 'Highest Savings': '#4ECDC4', # 青色\n", " 'Both Top 5': '#45B7D1' # 蓝色\n", " }\n", " \n", " symbols = {\n", " 'Fastest Payback': 'circle',\n", " 'Highest Savings': 'square',\n", " 'Both Top 5': 'star'\n", " }\n", " \n", " # 按类别分组绘制\n", " for category in ['Fastest Payback', 'Highest Savings', 'Both Top 5']:\n", " category_data = [s for s in plot_data if s['category'] == category]\n", " \n", " if not category_data:\n", " continue\n", " \n", " # 创建简化标签\n", " labels = []\n", " for s in category_data:\n", " if s['rank_payback'] and s['rank_savings']:\n", " labels.append(f\"#{s['rank_payback']}/{s['rank_savings']}\")\n", " elif s['rank_payback']:\n", " labels.append(f\"#{s['rank_payback']}\")\n", " elif s['rank_savings']:\n", " labels.append(f\"#{s['rank_savings']}\")\n", " else:\n", " labels.append(\"\")\n", " \n", " fig.add_trace(go.Scatter(\n", " x=[s['payback_years'] for s in category_data],\n", " y=[s['annual_savings'] for s in category_data],\n", " mode='markers+text',\n", " marker=dict(\n", " size=[max(15, min(30, 15 + (s['total_cost'] / 500))) for s in category_data],\n", " color=colors[category],\n", " symbol=symbols[category],\n", " line=dict(width=2, color='white'),\n", " opacity=0.8\n", " ),\n", " text=labels,\n", " textposition=\"top center\",\n", " textfont=dict(size=12, color='black', family=\"Arial Black\"),\n", " name=category,\n", " hovertemplate=(\n", " '%{customdata[0]}
' +\n", " 'Payback: %{x:.1f} years
' +\n", " 'Annual Savings: £%{y:,.0f}
' +\n", " 'Carbon Savings: %{customdata[2]:,.0f} kg CO2e/year
' +\n", " 'Investment: £%{customdata[1]:,.0f}
' +\n", " ''\n", " ),\n", " customdata=[[s['combination'][:40] + \"...\" if len(s['combination']) > 40 else s['combination'], \n", " s['total_cost'], s['annual_carbon_savings']] for s in category_data]\n", " ))\n", " \n", " # 添加理想区域\n", " max_savings = max([s['annual_savings'] for s in plot_data])\n", " fig.add_shape(\n", " type=\"rect\",\n", " x0=0, x1=10, y0=max_savings * 0.5, y1=max_savings * 1.1,\n", " fillcolor=\"lightgreen\",\n", " opacity=0.1,\n", " line=dict(width=0),\n", " )\n", " \n", " fig.add_annotation(\n", " x=5, y=max_savings * 0.8,\n", " text=\"🎯 Sweet Spot\",\n", " showarrow=False,\n", " font=dict(size=14, color=\"green\"),\n", " bgcolor=\"white\",\n", " bordercolor=\"green\",\n", " borderwidth=1\n", " )\n", " \n", " # 更新布局\n", " fig.update_layout(\n", " title={\n", " 'text': '🏠 Retrofit Investment Strategy Analysis',\n", " 'x': 0.5,\n", " 'font': {'size': 20, 'family': 'Arial'}\n", " },\n", " xaxis=dict(\n", " title='⏱️ Payback Period (Years)',\n", " gridcolor='lightgray',\n", " showgrid=True,\n", " range=[0, max([s['payback_years'] for s in plot_data]) * 1.1],\n", " title_font=dict(size=14)\n", " ),\n", " yaxis=dict(\n", " title='💰 Annual Savings (£)',\n", " gridcolor='lightgray',\n", " showgrid=True,\n", " range=[0, max_savings * 1.1],\n", " title_font=dict(size=14)\n", " ),\n", " plot_bgcolor='white',\n", " height=500,\n", " showlegend=True,\n", " legend=dict(\n", " yanchor=\"top\",\n", " y=0.99,\n", " xanchor=\"left\",\n", " x=0.01,\n", " bgcolor=\"rgba(255,255,255,0.9)\",\n", " bordercolor=\"gray\",\n", " borderwidth=1,\n", " font=dict(size=12)\n", " ),\n", " hovermode='closest',\n", " margin=dict(l=60, r=60, t=80, b=60)\n", " )\n", " \n", " print(\"Chart created successfully\")\n", " return fig\n", " \n", " except Exception as e:\n", " print(f\"Error creating scatter plot: {e}\")\n", " print(f\"Traceback: {traceback.format_exc()}\")\n", " return None\n", "\n", "def predict_current_energy_and_show_options(\n", " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n", " floor_location, roof_type, wall_type, wall_insulation, roof_insulation, \n", " glazing_type, main_heat_type, main_fuel_type\n", "):\n", " \"\"\"Predict current energy consumption and show available renovation options\"\"\"\n", " try:\n", " print(f\"Starting analysis with inputs: floor_area={total_floor_area}, epc={epc_score}\")\n", " \n", " # Input validation - ensure all required parameters exist\n", " required_params = [\n", " ('lookup_age_band', lookup_age_band),\n", " ('built_form', built_form),\n", " ('wall_type', wall_type),\n", " ('wall_insulation', wall_insulation),\n", " ('glazing_type', glazing_type),\n", " ('main_heat_type', main_heat_type),\n", " ('main_fuel_type', main_fuel_type),\n", " ('floor_location', floor_location)\n", " ]\n", " \n", " for param_name, param_value in required_params:\n", " if not param_value:\n", " raise ValueError(f\"Missing required parameter: {param_name}\")\n", " \n", " # Determine final roof parameters\n", " final_roof_type = determine_roof_type_from_location(floor_location, roof_type)\n", " final_roof_insulation = roof_insulation if floor_location == 'top floor' else 'another dwelling above'\n", " \n", " print(f\"Roof type determined: {final_roof_type}, insulation: {final_roof_insulation}\")\n", " \n", " # Get energy prediction\n", " consumption, q_total = get_energy_prediction(\n", " total_floor_area, estimated_floor_count, epc_score,\n", " wall_insulation, final_roof_type, final_roof_insulation, glazing_type,\n", " built_form, main_heat_type, main_fuel_type, lookup_age_band, wall_type\n", " )\n", " \n", " print(f\"Energy prediction completed: {consumption} kWh, {q_total} kWh\")\n", " \n", " # Calculate costs and carbon emissions\n", " energy_cost = get_energy_cost_per_kwh(main_fuel_type)\n", " annual_cost = consumption * energy_cost\n", " \n", " carbon_factor = get_carbon_factor(main_fuel_type)\n", " annual_carbon = consumption * carbon_factor\n", " \n", " # Calculate renovation areas\n", " wall_area, glazing_area, roof_area = calculate_actual_areas(total_floor_area, 'flat')\n", " \n", " print(f\"Areas calculated: wall={wall_area:.1f}, glazing={glazing_area:.1f}, roof={roof_area:.1f}\")\n", " \n", " # Performance result display - 包含碳排放\n", " performance_result = f\"\"\"\n", "## 🏠 Current Building Performance\n", "\n", "| **Metric** | **Value** |\n", "|------------|-----------|\n", "| **Annual Energy Consumption** | {consumption:,.0f} kWh |\n", "| **Annual Energy Bills** | £{annual_cost:,.0f} |\n", "| **Annual Carbon Emissions** | {annual_carbon:,.0f} kg CO2e |\n", "| **Total Heating Demand** | {q_total:,.0f} kWh/year |\n", "| **Floor Area** | {safe_float_conversion(total_floor_area):.0f} m² |\n", "| **EPC Score** | {safe_float_conversion(epc_score):.0f} |\n", "\n", "**Current Systems:** {wall_type} walls ({wall_insulation}) • {final_roof_type} roof ({final_roof_insulation}) • {glazing_type} glazing • {main_heat_type} ({main_fuel_type})\n", "\n", "**🌱 Carbon Impact:** Your property produces {annual_carbon/1000:.1f} tonnes of CO2 equivalent per year.\n", " \"\"\"\n", " \n", " # Generate optimization strategies\n", " strategies_by_payback, strategies_by_savings, orig_consumption, orig_q_total = generate_optimization_strategies(\n", " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n", " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n", " glazing_type, main_heat_type, main_fuel_type\n", " )\n", " \n", " # Create optimization scatter plot\n", " optimization_chart = None\n", " chart_visible = False\n", " if strategies_by_payback or strategies_by_savings:\n", " optimization_chart = create_optimization_chart(strategies_by_payback, strategies_by_savings)\n", " if optimization_chart:\n", " chart_visible = True\n", " print(\"Chart will be displayed\")\n", " else:\n", " print(\"Chart creation failed\")\n", " \n", " # Determine available renovation options\n", " wall_options = ['no_change']\n", " if wall_insulation == 'uninsulated':\n", " if wall_type == 'solid':\n", " wall_options.extend(['internal', 'external'])\n", " elif wall_type == 'cavity':\n", " wall_options.extend(['cavity_fill', 'internal', 'external'])\n", " \n", " roof_options = ['no_change']\n", " if floor_location == 'top floor' and final_roof_insulation == 'uninsulated':\n", " if final_roof_type == 'pitched':\n", " roof_options.append('loft')\n", " elif final_roof_type == 'flat':\n", " roof_options.append('flat_roof')\n", " \n", " glazing_options = ['no_change']\n", " if glazing_type == 'single/partial':\n", " glazing_options.extend(['double_glazing', 'triple_glazing', 'secondary_glazing'])\n", " elif glazing_type == 'secondary':\n", " glazing_options.extend(['double_glazing', 'triple_glazing'])\n", " \n", " heating_options = ['no_change']\n", " if main_heat_type != 'heat pump':\n", " heating_options.extend(['air_source_heat_pump', 'ground_source_heat_pump'])\n", " if main_heat_type == 'boiler' and lookup_age_band in ['pre-1920', '1930-1949', '1950-1966', '1967-1982']:\n", " heating_options.append('gas_boiler_upgrade')\n", " if main_heat_type != 'boiler' or main_fuel_type != 'electricity':\n", " heating_options.append('electric_boiler')\n", " \n", " fuel_options = ['no_change']\n", " if main_fuel_type != 'mains gas':\n", " fuel_options.append('mains gas')\n", " if main_fuel_type != 'electricity':\n", " fuel_options.append('electricity')\n", " \n", " # Check if any renovation options are available\n", " show_options = any([\n", " len(wall_options) > 1,\n", " len(roof_options) > 1,\n", " len(glazing_options) > 1,\n", " len(heating_options) > 1,\n", " len(fuel_options) > 1\n", " ])\n", " \n", " print(f\"Options available: wall={len(wall_options)}, roof={len(roof_options)}, glazing={len(glazing_options)}, heating={len(heating_options)}, fuel={len(fuel_options)}\")\n", " print(f\"Chart visible: {chart_visible}\")\n", " \n", " return (\n", " performance_result,\n", " gr.Radio(choices=wall_options, value='no_change', visible=(len(wall_options) > 1)),\n", " gr.Radio(choices=roof_options, value='no_change', visible=(len(roof_options) > 1)),\n", " gr.Radio(choices=glazing_options, value='no_change', visible=(len(glazing_options) > 1)),\n", " gr.Radio(choices=heating_options, value='no_change', visible=(len(heating_options) > 1)),\n", " gr.Radio(choices=fuel_options, value='no_change', visible=(len(fuel_options) > 1)),\n", " gr.Markdown(\"## 🔧 Available Renovation Options\", visible=show_options),\n", " gr.Button(\"💰 Calculate Renovation Analysis\", visible=show_options),\n", " gr.Plot(value=optimization_chart, visible=chart_visible)\n", " )\n", " \n", " except Exception as e:\n", " error_msg = f\"❌ Error in building analysis: {str(e)}\\n\\nPlease check all building parameters and try again.\"\n", " print(f\"Error in predict_current_energy_and_show_options: {e}\")\n", " print(f\"Traceback: {traceback.format_exc()}\")\n", " \n", " return (\n", " error_msg,\n", " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n", " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n", " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n", " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n", " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n", " gr.Markdown(visible=False),\n", " gr.Button(visible=False),\n", " gr.Plot(visible=False)\n", " )\n", "\n", "def calculate_renovation_analysis(\n", " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n", " floor_location, roof_type, wall_type, wall_insulation, roof_insulation, \n", " glazing_type, main_heat_type, main_fuel_type,\n", " wall_renovation, roof_renovation, glazing_renovation, heating_renovation, fuel_change\n", "):\n", " \"\"\"Calculate renovation costs and energy savings\"\"\"\n", " try:\n", " print(f\"Starting renovation analysis...\")\n", " \n", " # Handle None values from hidden radio buttons\n", " wall_renovation = wall_renovation or 'no_change'\n", " roof_renovation = roof_renovation or 'no_change'\n", " glazing_renovation = glazing_renovation or 'no_change'\n", " heating_renovation = heating_renovation or 'no_change'\n", " fuel_change = fuel_change or 'no_change'\n", " \n", " print(f\"Renovations selected: wall={wall_renovation}, roof={roof_renovation}, glazing={glazing_renovation}, heating={heating_renovation}, fuel={fuel_change}\")\n", " \n", " # Determine final roof parameters\n", " final_roof_type = determine_roof_type_from_location(floor_location, roof_type)\n", " final_roof_insulation = roof_insulation if floor_location == 'top floor' else 'another dwelling above'\n", " \n", " # Check if any renovations are selected\n", " no_renovations = all([\n", " wall_renovation == 'no_change',\n", " roof_renovation == 'no_change',\n", " glazing_renovation == 'no_change',\n", " heating_renovation == 'no_change',\n", " fuel_change == 'no_change'\n", " ])\n", " \n", " if no_renovations:\n", " return \"⚠️ Please select at least one renovation option to see the analysis.\"\n", " \n", " # Calculate renovation areas\n", " wall_area, glazing_area, roof_area = calculate_actual_areas(total_floor_area, 'flat')\n", " \n", " # Get original performance\n", " original_consumption, original_q_total = get_energy_prediction(\n", " total_floor_area, estimated_floor_count, epc_score,\n", " wall_insulation, final_roof_type, final_roof_insulation, glazing_type,\n", " built_form, main_heat_type, main_fuel_type, lookup_age_band, wall_type\n", " )\n", " \n", " print(f\"Original consumption: {original_consumption} kWh\")\n", " \n", " # Apply renovations\n", " new_wall_insulation = 'insulated' if wall_renovation != 'no_change' else wall_insulation\n", " new_roof_insulation = 'insulated' if roof_renovation != 'no_change' else final_roof_insulation\n", " \n", " # Glazing upgrades\n", " new_glazing_type = glazing_type\n", " if glazing_renovation in ['double_glazing', 'triple_glazing']:\n", " new_glazing_type = 'double/triple'\n", " elif glazing_renovation == 'secondary_glazing':\n", " new_glazing_type = 'secondary'\n", " \n", " # Heating system upgrades\n", " new_heat_type = main_heat_type\n", " new_fuel_type = main_fuel_type\n", " \n", " if heating_renovation in ['air_source_heat_pump', 'ground_source_heat_pump']:\n", " new_heat_type = 'heat pump'\n", " new_fuel_type = 'electricity'\n", " elif heating_renovation == 'gas_boiler_upgrade':\n", " new_heat_type = 'boiler'\n", " new_fuel_type = 'mains gas'\n", " elif heating_renovation == 'electric_boiler':\n", " new_heat_type = 'boiler'\n", " new_fuel_type = 'electricity'\n", " \n", " # Fuel change override\n", " if fuel_change != 'no_change':\n", " new_fuel_type = fuel_change\n", " \n", " # Get renovated performance\n", " renovated_consumption, renovated_q_total = get_energy_prediction(\n", " total_floor_area, estimated_floor_count, epc_score,\n", " new_wall_insulation, final_roof_type, new_roof_insulation, new_glazing_type,\n", " built_form, new_heat_type, new_fuel_type, lookup_age_band, wall_type\n", " )\n", " \n", " print(f\"Renovated consumption: {renovated_consumption} kWh\")\n", " \n", " # Calculate costs\n", " total_cost = 0\n", " cost_breakdown = {}\n", " \n", " # Wall insulation cost\n", " if wall_renovation != 'no_change' and wall_renovation in RENOVATION_COSTS['wall_insulation']:\n", " cost = wall_area * RENOVATION_COSTS['wall_insulation'][wall_renovation]\n", " total_cost += cost\n", " cost_breakdown[f'Wall Insulation ({wall_renovation})'] = cost\n", " \n", " # Roof insulation cost\n", " if (roof_renovation != 'no_change' and \n", " floor_location == 'top floor' and \n", " roof_renovation in RENOVATION_COSTS['roof_insulation']):\n", " cost = roof_area * RENOVATION_COSTS['roof_insulation'][roof_renovation]\n", " total_cost += cost\n", " cost_breakdown[f'Roof Insulation ({roof_renovation})'] = cost\n", " \n", " # Glazing cost\n", " if glazing_renovation != 'no_change' and glazing_renovation in RENOVATION_COSTS['glazing']:\n", " cost = glazing_area * RENOVATION_COSTS['glazing'][glazing_renovation]\n", " total_cost += cost\n", " cost_breakdown[f'Glazing ({glazing_renovation})'] = cost\n", " \n", " # Heating system cost\n", " if heating_renovation != 'no_change' and heating_renovation in RENOVATION_COSTS['heating_system']:\n", " cost = RENOVATION_COSTS['heating_system'][heating_renovation]\n", " total_cost += cost\n", " cost_breakdown[f'Heating ({heating_renovation})'] = cost\n", " \n", " # Calculate savings\n", " annual_savings = max(0, original_consumption - renovated_consumption)\n", " original_energy_cost = get_energy_cost_per_kwh(main_fuel_type)\n", " new_energy_cost = get_energy_cost_per_kwh(new_fuel_type)\n", " \n", " original_annual_cost = original_consumption * original_energy_cost\n", " new_annual_cost = renovated_consumption * new_energy_cost\n", " annual_cost_savings = original_annual_cost - new_annual_cost\n", " \n", " # Calculate carbon savings\n", " original_carbon_factor = get_carbon_factor(main_fuel_type)\n", " new_carbon_factor = get_carbon_factor(new_fuel_type)\n", " \n", " original_annual_carbon = original_consumption * original_carbon_factor\n", " new_annual_carbon = renovated_consumption * new_carbon_factor\n", " annual_carbon_savings = original_annual_carbon - new_annual_carbon\n", " \n", " # Calculate payback period\n", " if annual_cost_savings > 0:\n", " payback_years = total_cost / annual_cost_savings\n", " else:\n", " payback_years = float('inf')\n", " \n", " # Calculate efficiency improvement\n", " if original_consumption > 0:\n", " efficiency_improvement = (annual_savings / original_consumption) * 100\n", " else:\n", " efficiency_improvement = 0\n", " \n", " print(f\"Analysis complete: savings={annual_savings} kWh, cost_savings=£{annual_cost_savings:.0f}, carbon_savings={annual_carbon_savings:.0f} kg CO2e, payback={payback_years:.1f} years\")\n", " \n", " # Generate results\n", " result_text = f\"\"\"\n", "# 🏠 Renovation Analysis Results\n", "\n", "## 📊 Performance Impact Summary\n", "| **Metric** | **Current** | **After Renovation** | **Improvement** |\n", "|------------|-------------|---------------------|-----------------|\n", "| **Energy Consumption** | {original_consumption:,.0f} kWh | {renovated_consumption:,.0f} kWh | **{annual_savings:,.0f} kWh** ({efficiency_improvement:.1f}% ↓) |\n", "| **Annual Energy Bills** | £{original_annual_cost:,.0f} | £{new_annual_cost:,.0f} | **£{annual_cost_savings:,.0f}** saved/year |\n", "| **Carbon Emissions** | {original_annual_carbon:,.0f} kg CO2e | {new_annual_carbon:,.0f} kg CO2e | **{annual_carbon_savings:,.0f} kg CO2e** saved/year |\n", "| **Total Heating Demand** | {original_q_total:,.0f} kWh/year | {renovated_q_total:,.0f} kWh/year | **{original_q_total - renovated_q_total:,.0f} kWh/year** ↓ |\n", "\n", "## 💰 Investment Analysis\n", "- **Total Renovation Investment**: £{total_cost:,.0f} *(one-time upfront cost)*\n", "- **Annual Bill Savings**: £{annual_cost_savings:,.0f} *(recurring yearly savings)*\n", "- **Simple Payback Period**: {payback_years:.1f} years\n", "- **25-Year Total Savings**: £{annual_cost_savings * 25:,.0f}\n", "\n", "## 🌱 Carbon Impact\n", "- **Annual Carbon Reduction**: {annual_carbon_savings:,.0f} kg CO2e/year ({annual_carbon_savings/1000:.1f} tonnes/year)\n", "- **25-Year Carbon Saved**: {annual_carbon_savings * 25 / 1000:.1f} tonnes CO2e\n", "- **Equivalent to**: {annual_carbon_savings / 4600:.1f} cars removed from roads for a year\n", "\n", "## 🔧 Selected Renovations & Costs\n", "\"\"\"\n", " \n", " if cost_breakdown:\n", " for item, cost in cost_breakdown.items():\n", " percentage = (cost / total_cost * 100) if total_cost > 0 else 0\n", " result_text += f\"- **{item}**: £{cost:,.0f} ({percentage:.1f}%)\\n\"\n", " else:\n", " result_text += \"- No renovation costs calculated (free fuel switch only)\\n\"\n", " \n", " result_text += f\"\"\"\n", "\n", "## 📏 Area Calculations Used:\n", "- **External Wall Area**: {wall_area:.1f} m² (only exterior walls of your unit)\n", "- **Window/Glazing Area**: {glazing_area:.1f} m² (20% of external wall area)\n", "- **Roof Area**: {roof_area:.1f} m² (only if top floor)\n", "\n", "## 🏛️ Government Support Information:\n", "- **BUS Grant**: {GOVERNMENT_GRANTS['BUS']['description']}\n", "- **ECO4**: Up to £{GOVERNMENT_GRANTS['ECO4']['max_amount']:,} for eligible households\n", "- **Local Grants**: Check your council for additional support\n", "\"\"\"\n", " \n", " # Show fuel cost impact if fuel type changes\n", " if new_fuel_type != main_fuel_type:\n", " result_text += f\"\"\"\n", "## 💡 Fuel Cost Impact:\n", "**Note**: Switching from {main_fuel_type} (£{original_energy_cost:.3f}/kWh) to {new_fuel_type} (£{new_energy_cost:.3f}/kWh)\n", "\"\"\"\n", " \n", " # Investment recommendation\n", " if annual_cost_savings > 0:\n", " if payback_years <= 7:\n", " result_text += \"\\n## ✅ **Investment Recommendation: HIGHLY RECOMMENDED**\\n**Excellent ROI** - Short payback period with strong long-term savings and carbon benefits.\"\n", " elif payback_years <= 15:\n", " result_text += \"\\n## ⚖️ **Investment Recommendation: RECOMMENDED**\\n**Good ROI** - Reasonable payback period with solid financial and environmental benefits.\"\n", " else:\n", " result_text += \"\\n## ⚠️ **Investment Recommendation: CONSIDER CAREFULLY**\\n**Extended Payback** - Prioritize highest-impact measures first for better ROI.\"\n", " else:\n", " result_text += \"\\n## ❌ **Investment Recommendation: NOT RECOMMENDED**\\n**Negative savings** - This combination increases annual costs. Consider different options.\"\n", " \n", " return result_text\n", " \n", " except Exception as e:\n", " error_msg = f\"❌ Error in renovation analysis: {str(e)}\\n\\nPlease check your selections and try again.\"\n", " print(f\"Error in calculate_renovation_analysis: {e}\")\n", " print(f\"Traceback: {traceback.format_exc()}\")\n", " return error_msg\n", "\n", "def update_roof_type_visibility(floor_location):\n", " \"\"\"Update roof type options visibility based on floor location\"\"\"\n", " try:\n", " if floor_location == 'top floor':\n", " return gr.Radio(\n", " choices=['pitched', 'flat', 'room in roof'],\n", " value='pitched',\n", " visible=True\n", " )\n", " else:\n", " return gr.Radio(\n", " choices=['another dwelling above'],\n", " value='another dwelling above',\n", " visible=False\n", " )\n", " except Exception as e:\n", " print(f\"Error in update_roof_type_visibility: {e}\")\n", " return gr.Radio(\n", " choices=['pitched'],\n", " value='pitched',\n", " visible=True\n", " )\n", "\n", "def update_roof_insulation_visibility(floor_location):\n", " \"\"\"Update roof insulation options visibility based on floor location\"\"\"\n", " try:\n", " if floor_location == 'top floor':\n", " return gr.Radio(\n", " choices=['insulated', 'uninsulated'],\n", " value='uninsulated',\n", " visible=True\n", " )\n", " else:\n", " return gr.Radio(\n", " choices=['another dwelling above'],\n", " value='another dwelling above',\n", " visible=False\n", " )\n", " except Exception as e:\n", " print(f\"Error in update_roof_insulation_visibility: {e}\")\n", " return gr.Radio(\n", " choices=['uninsulated'],\n", " value='uninsulated',\n", " visible=True\n", " )\n", "\n", "# Create interface with comprehensive error handling\n", "try:\n", " with gr.Blocks(theme=gr.themes.Soft(), title=\"🏠 Home Retrofit Calculator\") as demo:\n", " gr.Markdown(\"# 🏠 Home Retrofit Calculator\")\n", " gr.Markdown(\"**Find the best energy-saving upgrades for your property with real UK costs, payback analysis & carbon impact**\")\n", " \n", " with gr.Row():\n", " # Left column - Building inputs\n", " with gr.Column(scale=1):\n", " gr.Markdown(\"## 🏢 Building Information\")\n", " \n", " lookup_age_band = gr.Radio(\n", " choices=['pre-1920', '1930-1949', '1950-1966', '1967-1982', '1983-1995', '1996-2011', '2012-onwards'],\n", " label=\"📅 Construction Age Band\",\n", " value=\"1950-1966\"\n", " )\n", " \n", " total_floor_area = gr.Number(\n", " label=\"🏠 Total Floor Area (m²)\",\n", " value=60,\n", " minimum=10,\n", " maximum=1000\n", " )\n", " \n", " estimated_floor_count = gr.Number(\n", " label=\"🏢 Above-ground Floors\",\n", " value=2,\n", " minimum=1,\n", " maximum=10\n", " )\n", " \n", " epc_score = gr.Number(\n", " label=\"📊 EPC Score\",\n", " value=50,\n", " minimum=1,\n", " maximum=100\n", " )\n", " \n", " built_form = gr.Radio(\n", " choices=['end-terrace', 'mid-terrace'],\n", " label=\"🏘️ Built Form\",\n", " value=\"mid-terrace\"\n", " )\n", " \n", " # Floor location and roof type\n", " floor_location = gr.Radio(\n", " choices=['top floor', 'other floor'],\n", " label=\"🏢 Floor Location\",\n", " value=\"top floor\"\n", " )\n", " \n", " roof_type = gr.Radio(\n", " choices=['pitched', 'flat', 'room in roof'],\n", " label=\"🏠 Roof Type\",\n", " value=\"pitched\"\n", " )\n", " \n", " # Wall information\n", " with gr.Row():\n", " wall_type = gr.Radio(\n", " choices=['solid', 'cavity'],\n", " label=\"🧱 Wall Type\",\n", " value=\"solid\"\n", " )\n", " wall_insulation = gr.Radio(\n", " choices=['insulated', 'uninsulated'],\n", " label=\"🧱 Wall Insulation\",\n", " value=\"uninsulated\"\n", " )\n", " \n", " # Roof insulation\n", " roof_insulation = gr.Radio(\n", " choices=['insulated', 'uninsulated'],\n", " label=\"🏠 Roof Insulation\",\n", " value=\"uninsulated\"\n", " )\n", " \n", " # Other systems\n", " glazing_type = gr.Radio(\n", " choices=['single/partial', 'double/triple', 'secondary'],\n", " label=\"🪟 Glazing Type\",\n", " value=\"single/partial\"\n", " )\n", " \n", " with gr.Row():\n", " main_heat_type = gr.Radio(\n", " choices=['boiler', 'communal', 'room/storage heaters', 'heat pump', 'other', 'no heating system'],\n", " label=\"🔥 Main Heating\",\n", " value=\"boiler\"\n", " )\n", " main_fuel_type = gr.Radio(\n", " choices=['mains gas', 'electricity', 'other', 'no heating system'],\n", " label=\"⚡ Main Fuel\",\n", " value=\"mains gas\"\n", " )\n", " \n", " analyze_btn = gr.Button(\n", " \"📊 Analyze Building Performance\",\n", " variant=\"primary\",\n", " size=\"lg\"\n", " )\n", " \n", " # Right column - Results and options\n", " with gr.Column(scale=1):\n", " current_performance = gr.Markdown()\n", " \n", " # Optimization strategies chart\n", " optimization_chart = gr.Plot(label=\"📊 Retrofit Strategy Analysis\", visible=False)\n", " \n", " # Renovation options (initially hidden)\n", " options_title = gr.Markdown(\"## 🔧 Available Renovation Options\", visible=False)\n", " wall_renovation = gr.Radio(label=\"🧱 Wall Insulation Upgrade\", visible=False)\n", " roof_renovation = gr.Radio(label=\"🏠 Roof Insulation Upgrade\", visible=False)\n", " glazing_renovation = gr.Radio(label=\"🪟 Glazing Upgrade\", visible=False)\n", " heating_renovation = gr.Radio(label=\"🔥 Heating System Upgrade\", visible=False)\n", " fuel_change = gr.Radio(label=\"⚡ Fuel Type Change\", visible=False)\n", " \n", " calculate_btn = gr.Button(\n", " \"💰 Calculate Renovation Analysis\",\n", " variant=\"secondary\",\n", " size=\"lg\",\n", " visible=False\n", " )\n", " \n", " # Results area\n", " renovation_results = gr.Markdown()\n", " \n", " # Event handlers with error handling\n", " def safe_update_roof_type(floor_location):\n", " try:\n", " return update_roof_type_visibility(floor_location)\n", " except Exception as e:\n", " print(f\"Error updating roof type: {e}\")\n", " return gr.Radio(choices=['pitched'], value='pitched', visible=True)\n", " \n", " def safe_update_roof_insulation(floor_location):\n", " try:\n", " return update_roof_insulation_visibility(floor_location)\n", " except Exception as e:\n", " print(f\"Error updating roof insulation: {e}\")\n", " return gr.Radio(choices=['uninsulated'], value='uninsulated', visible=True)\n", " \n", " # Connect event handlers\n", " floor_location.change(\n", " fn=safe_update_roof_type,\n", " inputs=[floor_location],\n", " outputs=[roof_type]\n", " )\n", " \n", " floor_location.change(\n", " fn=safe_update_roof_insulation,\n", " inputs=[floor_location],\n", " outputs=[roof_insulation]\n", " )\n", " \n", " analyze_btn.click(\n", " fn=predict_current_energy_and_show_options,\n", " inputs=[lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n", " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n", " glazing_type, main_heat_type, main_fuel_type],\n", " outputs=[current_performance, wall_renovation, roof_renovation, glazing_renovation,\n", " heating_renovation, fuel_change, options_title, calculate_btn, optimization_chart]\n", " )\n", " \n", " calculate_btn.click(\n", " fn=calculate_renovation_analysis,\n", " inputs=[lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n", " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n", " glazing_type, main_heat_type, main_fuel_type,\n", " wall_renovation, roof_renovation, glazing_renovation, heating_renovation, fuel_change],\n", " outputs=[renovation_results]\n", " )\n", "\n", " # Launch application with proper conditions\n", " if __name__ == \"__main__\":\n", " print(\"🚀 Launching Home Retrofit Calculator...\")\n", " demo.launch(share=True, debug=False) # Set debug=False for production\n", " else:\n", " print(\"✅ Application ready for launch\")\n", "\n", "except Exception as e:\n", " print(f\"❌ Critical error in interface setup: {e}\")\n", " print(f\"Traceback: {traceback.format_exc()}\")\n", " raise" ] } ], "metadata": { "kernelspec": { "display_name": "Python (base)", "language": "python", "name": "base" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.9" } }, "nbformat": 4, "nbformat_minor": 5 }