cladyles commited on
Commit
9b1461d
·
verified ·
1 Parent(s): 49577c0

Upload energy_predictor.ipynb

Browse files
Files changed (1) hide show
  1. energy_predictor.ipynb +1465 -0
energy_predictor.ipynb ADDED
@@ -0,0 +1,1465 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "5ced98fd",
6
+ "metadata": {},
7
+ "source": [
8
+ "#### 新的更改在最后cell 用的rf "
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "id": "80d003ef",
15
+ "metadata": {},
16
+ "outputs": [
17
+ {
18
+ "name": "stdout",
19
+ "output_type": "stream",
20
+ "text": [
21
+ "🚀 Launching Home Retrofit Calculator...\n",
22
+ "* Running on local URL: http://127.0.0.1:7866\n",
23
+ "* Running on public URL: https://7814baa6974f53f8b9.gradio.live\n",
24
+ "\n",
25
+ "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"
26
+ ]
27
+ },
28
+ {
29
+ "data": {
30
+ "text/html": [
31
+ "<div><iframe src=\"https://7814baa6974f53f8b9.gradio.live\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
32
+ ],
33
+ "text/plain": [
34
+ "<IPython.core.display.HTML object>"
35
+ ]
36
+ },
37
+ "metadata": {},
38
+ "output_type": "display_data"
39
+ },
40
+ {
41
+ "name": "stdout",
42
+ "output_type": "stream",
43
+ "text": [
44
+ "Starting analysis with inputs: floor_area=60, epc=50\n",
45
+ "Roof type determined: pitched, insulation: uninsulated\n",
46
+ "Energy prediction completed: 17457.51824094143 kWh, 71058.28296304637 kWh\n",
47
+ "Areas calculated: wall=19.4, glazing=3.9, roof=60.0\n",
48
+ "🔍 Generating optimization strategies...\n",
49
+ "📊 Analyzing 240 combinations...\n",
50
+ " Progress: 0/240\n",
51
+ " Progress: 20/240\n",
52
+ " Progress: 40/240\n",
53
+ " Progress: 60/240\n",
54
+ " Progress: 80/240\n",
55
+ " Progress: 100/240\n",
56
+ " Progress: 120/240\n",
57
+ " Progress: 140/240\n",
58
+ " Progress: 160/240\n",
59
+ " Progress: 180/240\n",
60
+ " Progress: 200/240\n",
61
+ " Progress: 220/240\n",
62
+ "✅ Found 46 viable strategies\n",
63
+ "Creating optimization chart...\n",
64
+ "Chart data: 9 strategies\n",
65
+ "Chart created successfully\n",
66
+ "Chart will be displayed\n",
67
+ "Options available: wall=3, roof=2, glazing=4, heating=5, fuel=2\n",
68
+ "Chart visible: True\n",
69
+ "Starting analysis with inputs: floor_area=60, epc=50\n",
70
+ "Roof type determined: pitched, insulation: uninsulated\n",
71
+ "Energy prediction completed: 17457.51824094143 kWh, 71058.28296304637 kWh\n",
72
+ "Areas calculated: wall=19.4, glazing=3.9, roof=60.0\n",
73
+ "🔍 Generating optimization strategies...\n",
74
+ "📊 Analyzing 320 combinations...\n",
75
+ " Progress: 0/320\n",
76
+ " Progress: 20/320\n",
77
+ " Progress: 40/320\n",
78
+ " Progress: 60/320\n",
79
+ " Progress: 80/320\n",
80
+ " Progress: 100/320\n",
81
+ " Progress: 120/320\n",
82
+ " Progress: 140/320\n",
83
+ " Progress: 160/320\n",
84
+ " Progress: 180/320\n",
85
+ " Progress: 200/320\n",
86
+ " Progress: 220/320\n",
87
+ " Progress: 240/320\n",
88
+ " Progress: 260/320\n",
89
+ " Progress: 280/320\n",
90
+ " Progress: 300/320\n",
91
+ "✅ Found 62 viable strategies\n",
92
+ "Creating optimization chart...\n",
93
+ "Chart data: 9 strategies\n",
94
+ "Chart created successfully\n",
95
+ "Chart will be displayed\n",
96
+ "Options available: wall=4, roof=2, glazing=4, heating=5, fuel=2\n",
97
+ "Chart visible: True\n",
98
+ "Starting analysis with inputs: floor_area=60, epc=50\n",
99
+ "Roof type determined: pitched, insulation: uninsulated\n",
100
+ "Energy prediction completed: 17457.51824094143 kWh, 71058.28296304637 kWh\n",
101
+ "Areas calculated: wall=19.4, glazing=3.9, roof=60.0\n",
102
+ "🔍 Generating optimization strategies...\n",
103
+ "📊 Analyzing 240 combinations...\n",
104
+ " Progress: 0/240\n",
105
+ " Progress: 20/240\n",
106
+ " Progress: 40/240\n",
107
+ " Progress: 60/240\n",
108
+ " Progress: 80/240\n",
109
+ " Progress: 100/240\n",
110
+ " Progress: 120/240\n",
111
+ " Progress: 140/240\n",
112
+ " Progress: 160/240\n",
113
+ " Progress: 180/240\n",
114
+ " Progress: 200/240\n",
115
+ " Progress: 220/240\n",
116
+ "✅ Found 46 viable strategies\n",
117
+ "Creating optimization chart...\n",
118
+ "Chart data: 9 strategies\n",
119
+ "Chart created successfully\n",
120
+ "Chart will be displayed\n",
121
+ "Options available: wall=3, roof=2, glazing=4, heating=5, fuel=2\n",
122
+ "Chart visible: True\n",
123
+ "Starting renovation analysis...\n",
124
+ "Renovations selected: wall=internal, roof=no_change, glazing=secondary_glazing, heating=no_change, fuel=no_change\n",
125
+ "Original consumption: 17457.51824094143 kWh\n",
126
+ "Renovated consumption: 14816.756481546063 kWh\n",
127
+ "Analysis complete: savings=2640.7617593953673 kWh, cost_savings=£193, carbon_savings=481 kg CO2e, payback=9.5 years\n"
128
+ ]
129
+ }
130
+ ],
131
+ "source": [
132
+ "import gradio as gr\n",
133
+ "import pandas as pd\n",
134
+ "import numpy as np\n",
135
+ "import math\n",
136
+ "import joblib\n",
137
+ "import traceback\n",
138
+ "import matplotlib.pyplot as plt\n",
139
+ "import matplotlib\n",
140
+ "matplotlib.use('Agg')\n",
141
+ "from itertools import product\n",
142
+ "import plotly.graph_objects as go\n",
143
+ "import plotly.express as px\n",
144
+ "from plotly.subplots import make_subplots\n",
145
+ "\n",
146
+ "# Load model and feature list with comprehensive error handling\n",
147
+ "model = joblib.load('rf_model.joblib')\n",
148
+ "feature_cols = joblib.load('rf_features.joblib')\n",
149
+ "\n",
150
+ "# Carbon emission factors (kg CO2e per kWh) - UK BEIS 2024 data\n",
151
+ "CARBON_FACTORS = {\n",
152
+ " 'electricity': 0.193, # kg CO2e/kWh - Grid electricity (2024)\n",
153
+ " 'gas': 0.182, # kg CO2e/kWh - Natural gas (2024)\n",
154
+ " 'mixed': 0.187 # Average for mixed systems\n",
155
+ "}\n",
156
+ "\n",
157
+ "# Renovation costs (UK market rates 2024)\n",
158
+ "RENOVATION_COSTS = {\n",
159
+ " 'wall_insulation': {\n",
160
+ " 'internal': 65, # £55-75/m²\n",
161
+ " 'external': 120, # £100-140/m²\n",
162
+ " 'cavity_fill': 22 # £18-26/m² - Cavity wall insulation fill\n",
163
+ " },\n",
164
+ " 'roof_insulation': {\n",
165
+ " 'loft': 18, # £15-22/m²\n",
166
+ " 'flat_roof': 80 # £70-90/m²\n",
167
+ " },\n",
168
+ " 'glazing': {\n",
169
+ " 'double_glazing': 320, # £280-360/m²\n",
170
+ " 'triple_glazing': 450, # £400-500/m²\n",
171
+ " 'secondary_glazing': 150 # £130-170/m²\n",
172
+ " },\n",
173
+ " 'heating_system': {\n",
174
+ " # These costs are AFTER BUS grant (already deducted)\n",
175
+ " 'air_source_heat_pump': 6500, # £14,000 - £7,500 BUS grant\n",
176
+ " 'ground_source_heat_pump': 12000, # £18,000 - £6,000 BUS grant\n",
177
+ " 'gas_boiler_upgrade': 2400, # Standard cost\n",
178
+ " 'electric_boiler': 1800 # Standard cost\n",
179
+ " }\n",
180
+ "}\n",
181
+ "\n",
182
+ "# Energy costs from Ofgem Price Cap (October 2024)\n",
183
+ "ENERGY_COSTS = {\n",
184
+ " 'electricity': 0.285, # £/kWh \n",
185
+ " 'gas': 0.073, # £/kWh\n",
186
+ " 'mixed': 0.18 # Average for mixed systems\n",
187
+ "}\n",
188
+ "\n",
189
+ "# Government grants information\n",
190
+ "GOVERNMENT_GRANTS = {\n",
191
+ " 'BUS': {\n",
192
+ " 'air_source_heat_pump': 7500,\n",
193
+ " 'ground_source_heat_pump': 6000,\n",
194
+ " 'description': 'Boiler Upgrade Scheme (BUS) grants already deducted from heat pump costs above'\n",
195
+ " },\n",
196
+ " 'ECO4': {\n",
197
+ " 'max_amount': 10000,\n",
198
+ " 'description': 'Energy Company Obligation - for eligible low-income households'\n",
199
+ " }\n",
200
+ "}\n",
201
+ "\n",
202
+ "def safe_float_conversion(value, default=0.0):\n",
203
+ " \"\"\"Safely convert value to float\"\"\"\n",
204
+ " try:\n",
205
+ " if value is None:\n",
206
+ " return default\n",
207
+ " return float(value)\n",
208
+ " except (ValueError, TypeError):\n",
209
+ " print(f\"Warning: Could not convert {value} to float, using default {default}\")\n",
210
+ " return default\n",
211
+ "\n",
212
+ "def safe_int_conversion(value, default=1):\n",
213
+ " \"\"\"Safely convert value to int\"\"\"\n",
214
+ " try:\n",
215
+ " if value is None:\n",
216
+ " return default\n",
217
+ " return int(float(value)) # Convert through float first to handle \"2.0\" strings\n",
218
+ " except (ValueError, TypeError):\n",
219
+ " print(f\"Warning: Could not convert {value} to int, using default {default}\")\n",
220
+ " return default\n",
221
+ "\n",
222
+ "def get_energy_cost_per_kwh(fuel_type):\n",
223
+ " \"\"\"Get energy cost per kWh with comprehensive error handling\"\"\"\n",
224
+ " try:\n",
225
+ " if fuel_type is None:\n",
226
+ " return ENERGY_COSTS['mixed']\n",
227
+ " \n",
228
+ " fuel_type_str = str(fuel_type).lower().strip()\n",
229
+ " \n",
230
+ " if 'electric' in fuel_type_str:\n",
231
+ " return ENERGY_COSTS['electricity']\n",
232
+ " elif 'gas' in fuel_type_str:\n",
233
+ " return ENERGY_COSTS['gas']\n",
234
+ " else:\n",
235
+ " return ENERGY_COSTS['mixed']\n",
236
+ " except Exception as e:\n",
237
+ " print(f\"Warning: Error getting energy cost for {fuel_type}: {e}\")\n",
238
+ " return ENERGY_COSTS['mixed']\n",
239
+ "\n",
240
+ "def get_carbon_factor(fuel_type):\n",
241
+ " \"\"\"Get carbon emission factor per kWh\"\"\"\n",
242
+ " try:\n",
243
+ " if fuel_type is None:\n",
244
+ " return CARBON_FACTORS['mixed']\n",
245
+ " \n",
246
+ " fuel_type_str = str(fuel_type).lower().strip()\n",
247
+ " \n",
248
+ " if 'electric' in fuel_type_str:\n",
249
+ " return CARBON_FACTORS['electricity']\n",
250
+ " elif 'gas' in fuel_type_str:\n",
251
+ " return CARBON_FACTORS['gas']\n",
252
+ " else:\n",
253
+ " return CARBON_FACTORS['mixed']\n",
254
+ " except Exception as e:\n",
255
+ " print(f\"Warning: Error getting carbon factor for {fuel_type}: {e}\")\n",
256
+ " return CARBON_FACTORS['mixed']\n",
257
+ "\n",
258
+ "def calculate_actual_areas(total_floor_area, building_type='flat'):\n",
259
+ " \"\"\"Calculate actual renovation areas with comprehensive validation\"\"\"\n",
260
+ " try:\n",
261
+ " # Input validation and conversion\n",
262
+ " floor_area = safe_float_conversion(total_floor_area, 60.0)\n",
263
+ " \n",
264
+ " if floor_area <= 0:\n",
265
+ " print(f\"Warning: Invalid floor area {floor_area}, using default 60\")\n",
266
+ " floor_area = 60.0\n",
267
+ " \n",
268
+ " if floor_area > 2000: # Sanity check\n",
269
+ " print(f\"Warning: Very large floor area: {floor_area} m²\")\n",
270
+ "\n",
271
+ " if building_type == 'flat':\n",
272
+ " # Conservative calculation for flats\n",
273
+ " wall_perimeter = math.sqrt(floor_area) * 2.5\n",
274
+ " wall_area = wall_perimeter * 2.5 * 0.4 # Only 40% is external wall\n",
275
+ " glazing_area = wall_area * 0.2 # 20% of wall area\n",
276
+ " roof_area = floor_area\n",
277
+ " else: # house\n",
278
+ " wall_area = floor_area * 1.8\n",
279
+ " glazing_area = floor_area * 0.15\n",
280
+ " roof_area = floor_area / math.cos(math.radians(30))\n",
281
+ " \n",
282
+ " # Ensure all areas are positive\n",
283
+ " wall_area = max(wall_area, 1.0)\n",
284
+ " glazing_area = max(glazing_area, 1.0)\n",
285
+ " roof_area = max(roof_area, 1.0)\n",
286
+ " \n",
287
+ " return wall_area, glazing_area, roof_area\n",
288
+ " \n",
289
+ " except Exception as e:\n",
290
+ " print(f\"Error in calculate_actual_areas: {e}\")\n",
291
+ " # Return safe default values\n",
292
+ " default_area = safe_float_conversion(total_floor_area, 60.0)\n",
293
+ " return default_area * 0.3, default_area * 0.06, default_area\n",
294
+ "\n",
295
+ "def determine_roof_type_from_location(floor_location, roof_type):\n",
296
+ " \"\"\"Determine final roof type based on floor location\"\"\"\n",
297
+ " try:\n",
298
+ " if floor_location == 'top floor':\n",
299
+ " return str(roof_type) if roof_type else 'pitched'\n",
300
+ " else:\n",
301
+ " return 'another dwelling above'\n",
302
+ " except Exception as e:\n",
303
+ " print(f\"Error in determine_roof_type_from_location: {e}\")\n",
304
+ " return 'another dwelling above'\n",
305
+ "\n",
306
+ "def get_energy_prediction(total_floor_area, estimated_floor_count, epc_score,\n",
307
+ " wall_insulation, roof_type, roof_insulation, glazing_type,\n",
308
+ " built_form, main_heat_type, main_fuel_type, lookup_age_band, wall_type):\n",
309
+ " \"\"\"Energy prediction function with comprehensive error handling\"\"\"\n",
310
+ " try:\n",
311
+ " # Input validation and conversion\n",
312
+ " floor_area = safe_float_conversion(total_floor_area, 60.0)\n",
313
+ " floor_count = safe_float_conversion(estimated_floor_count, 2.0)\n",
314
+ " epc = safe_float_conversion(epc_score, 50.0)\n",
315
+ " \n",
316
+ " # Validate ranges\n",
317
+ " floor_area = max(10.0, min(1000.0, floor_area))\n",
318
+ " floor_count = max(1.0, min(10.0, floor_count))\n",
319
+ " epc = max(1.0, min(100.0, epc))\n",
320
+ " \n",
321
+ " # Ensure all string inputs are valid\n",
322
+ " wall_insulation = str(wall_insulation) if wall_insulation else 'uninsulated'\n",
323
+ " roof_type = str(roof_type) if roof_type else 'pitched'\n",
324
+ " roof_insulation = str(roof_insulation) if roof_insulation else 'uninsulated'\n",
325
+ " glazing_type = str(glazing_type) if glazing_type else 'single/partial'\n",
326
+ " built_form = str(built_form) if built_form else 'mid-terrace'\n",
327
+ " main_heat_type = str(main_heat_type) if main_heat_type else 'boiler'\n",
328
+ " main_fuel_type = str(main_fuel_type) if main_fuel_type else 'mains gas'\n",
329
+ " lookup_age_band = str(lookup_age_band) if lookup_age_band else '1950-1966'\n",
330
+ " wall_type = str(wall_type) if wall_type else 'solid'\n",
331
+ " \n",
332
+ " # U-value calculations with error handling\n",
333
+ " U_wall = 0.37 if wall_insulation == 'insulated' else 1.7\n",
334
+ " \n",
335
+ " if roof_insulation == 'insulated':\n",
336
+ " U_roof = 0.25\n",
337
+ " elif roof_type == 'flat':\n",
338
+ " U_roof = 0.28\n",
339
+ " else:\n",
340
+ " U_roof = 2.3\n",
341
+ " \n",
342
+ " U_floor = 0.25\n",
343
+ " \n",
344
+ " if glazing_type == 'double/triple':\n",
345
+ " U_glazing = 2.4\n",
346
+ " elif glazing_type == 'secondary':\n",
347
+ " U_glazing = 2.82\n",
348
+ " else:\n",
349
+ " U_glazing = 5.75\n",
350
+ " \n",
351
+ " # Area calculations\n",
352
+ " if roof_type == 'pitched':\n",
353
+ " roof_area = floor_area / math.cos(math.radians(30))\n",
354
+ " elif roof_type == 'flat':\n",
355
+ " roof_area = floor_area\n",
356
+ " else:\n",
357
+ " roof_area = 0\n",
358
+ " \n",
359
+ " wall_area = floor_area * 2.1\n",
360
+ " floor_area_calc = floor_area\n",
361
+ " glazing_area = floor_area * 0.18\n",
362
+ " delta_T = 18\n",
363
+ "\n",
364
+ " Q_total = (U_wall * wall_area + U_roof * roof_area + U_floor * floor_area_calc + U_glazing * glazing_area) * delta_T\n",
365
+ "\n",
366
+ " # Create input data with safe conversions\n",
367
+ " rowdict = {\n",
368
+ " 'epc_score': epc,\n",
369
+ " 'estimated_floor_count': floor_count,\n",
370
+ " 'wall_area': wall_area,\n",
371
+ " 'roof_area': roof_area,\n",
372
+ " 'floor_area': floor_area_calc,\n",
373
+ " 'glazing_area': glazing_area,\n",
374
+ " 'u_value_wall': U_wall,\n",
375
+ " 'u_value_roof': U_roof,\n",
376
+ " 'u_value_floor': U_floor,\n",
377
+ " 'u_value_glazing': U_glazing,\n",
378
+ " 'Q_total': Q_total,\n",
379
+ " 'wall_type': wall_type,\n",
380
+ " 'wall_insulation': wall_insulation,\n",
381
+ " 'roof_type': roof_type,\n",
382
+ " 'roof_insulation': roof_insulation,\n",
383
+ " 'glazing_type': glazing_type,\n",
384
+ " 'built_form': built_form,\n",
385
+ " 'main_heat_type': main_heat_type,\n",
386
+ " 'main_fuel_type': main_fuel_type,\n",
387
+ " 'lookup_age_band': lookup_age_band\n",
388
+ " }\n",
389
+ " \n",
390
+ " # Create DataFrame and make prediction\n",
391
+ " df_input = pd.DataFrame([rowdict])\n",
392
+ " df_input = pd.get_dummies(df_input)\n",
393
+ " \n",
394
+ " # Ensure all required feature columns exist\n",
395
+ " for col in feature_cols:\n",
396
+ " if col not in df_input.columns:\n",
397
+ " df_input[col] = 0\n",
398
+ " \n",
399
+ " # Select features in correct order\n",
400
+ " df_input = df_input[feature_cols]\n",
401
+ " \n",
402
+ " # Make prediction\n",
403
+ " pred = model.predict(df_input)[0]\n",
404
+ " \n",
405
+ " # Validate prediction result\n",
406
+ " if pred < 0:\n",
407
+ " print(f\"Warning: Negative prediction {pred}, setting to 0\")\n",
408
+ " pred = 0\n",
409
+ " elif pred > 100000:\n",
410
+ " print(f\"Warning: Very high prediction {pred}\")\n",
411
+ " \n",
412
+ " # Convert Q_total from W to kWh (assuming full year operation)\n",
413
+ " # Q_total (W) * 8760 hours / 1000 = kWh per year\n",
414
+ " q_total_kwh = (Q_total * 8760) / 1000\n",
415
+ " \n",
416
+ " return float(pred), float(q_total_kwh)\n",
417
+ " \n",
418
+ " except Exception as e:\n",
419
+ " print(f\"Error in get_energy_prediction: {e}\")\n",
420
+ " print(f\"Traceback: {traceback.format_exc()}\")\n",
421
+ " # Return reasonable default values\n",
422
+ " default_area = safe_float_conversion(total_floor_area, 60.0)\n",
423
+ " default_consumption = default_area * 150 # 150 kWh/m² typical\n",
424
+ " default_q_total = default_area * 50 * 8760 / 1000 # Convert to kWh\n",
425
+ " return float(default_consumption), float(default_q_total)\n",
426
+ "\n",
427
+ "def generate_optimization_strategies(\n",
428
+ " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n",
429
+ " floor_location, roof_type, wall_type, wall_insulation, roof_insulation, \n",
430
+ " glazing_type, main_heat_type, main_fuel_type\n",
431
+ "):\n",
432
+ " \"\"\"Generate all possible renovation combinations and find optimal strategies\"\"\"\n",
433
+ " try:\n",
434
+ " print(\"🔍 Generating optimization strategies...\")\n",
435
+ " \n",
436
+ " # Determine available options\n",
437
+ " final_roof_type = determine_roof_type_from_location(floor_location, roof_type)\n",
438
+ " final_roof_insulation = roof_insulation if floor_location == 'top floor' else 'another dwelling above'\n",
439
+ " \n",
440
+ " # Get original performance\n",
441
+ " original_consumption, original_q_total = get_energy_prediction(\n",
442
+ " total_floor_area, estimated_floor_count, epc_score,\n",
443
+ " wall_insulation, final_roof_type, final_roof_insulation, glazing_type,\n",
444
+ " built_form, main_heat_type, main_fuel_type, lookup_age_band, wall_type\n",
445
+ " )\n",
446
+ " \n",
447
+ " # Calculate renovation areas\n",
448
+ " wall_area, glazing_area, roof_area = calculate_actual_areas(total_floor_area, 'flat')\n",
449
+ " \n",
450
+ " # Define available options based on current state\n",
451
+ " wall_options = ['no_change']\n",
452
+ " if wall_insulation == 'uninsulated':\n",
453
+ " if wall_type == 'solid':\n",
454
+ " wall_options.extend(['internal', 'external'])\n",
455
+ " elif wall_type == 'cavity':\n",
456
+ " wall_options.extend(['cavity_fill', 'internal', 'external'])\n",
457
+ " \n",
458
+ " roof_options = ['no_change']\n",
459
+ " if floor_location == 'top floor' and final_roof_insulation == 'uninsulated':\n",
460
+ " if final_roof_type == 'pitched':\n",
461
+ " roof_options.append('loft')\n",
462
+ " elif final_roof_type == 'flat':\n",
463
+ " roof_options.append('flat_roof')\n",
464
+ " \n",
465
+ " glazing_options = ['no_change']\n",
466
+ " if glazing_type == 'single/partial':\n",
467
+ " glazing_options.extend(['double_glazing', 'triple_glazing', 'secondary_glazing'])\n",
468
+ " elif glazing_type == 'secondary':\n",
469
+ " glazing_options.extend(['double_glazing', 'triple_glazing'])\n",
470
+ " \n",
471
+ " heating_options = ['no_change']\n",
472
+ " if main_heat_type != 'heat pump':\n",
473
+ " heating_options.extend(['air_source_heat_pump', 'ground_source_heat_pump'])\n",
474
+ " if main_heat_type == 'boiler' and lookup_age_band in ['pre-1920', '1930-1949', '1950-1966', '1967-1982']:\n",
475
+ " heating_options.append('gas_boiler_upgrade')\n",
476
+ " if main_heat_type != 'boiler' or main_fuel_type != 'electricity':\n",
477
+ " heating_options.append('electric_boiler')\n",
478
+ " \n",
479
+ " fuel_options = ['no_change']\n",
480
+ " if main_fuel_type != 'mains gas':\n",
481
+ " fuel_options.append('mains gas')\n",
482
+ " if main_fuel_type != 'electricity':\n",
483
+ " fuel_options.append('electricity')\n",
484
+ " \n",
485
+ " # Generate all combinations\n",
486
+ " all_combinations = list(product(wall_options, roof_options, glazing_options, heating_options, fuel_options))\n",
487
+ " \n",
488
+ " strategies = []\n",
489
+ " print(f\"📊 Analyzing {len(all_combinations)} combinations...\")\n",
490
+ " \n",
491
+ " for i, (wall_ren, roof_ren, glazing_ren, heating_ren, fuel_ren) in enumerate(all_combinations):\n",
492
+ " if i % 20 == 0:\n",
493
+ " print(f\" Progress: {i}/{len(all_combinations)}\")\n",
494
+ " \n",
495
+ " # Skip no-change combination\n",
496
+ " if all(opt == 'no_change' for opt in [wall_ren, roof_ren, glazing_ren, heating_ren, fuel_ren]):\n",
497
+ " continue\n",
498
+ " \n",
499
+ " # Calculate costs and savings for this combination\n",
500
+ " try:\n",
501
+ " result = calculate_single_strategy(\n",
502
+ " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n",
503
+ " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n",
504
+ " glazing_type, main_heat_type, main_fuel_type,\n",
505
+ " wall_ren, roof_ren, glazing_ren, heating_ren, fuel_ren,\n",
506
+ " original_consumption, original_q_total, wall_area, glazing_area, roof_area\n",
507
+ " )\n",
508
+ " \n",
509
+ " if result and result['annual_cost_savings'] > 0:\n",
510
+ " strategies.append(result)\n",
511
+ " \n",
512
+ " except Exception as e:\n",
513
+ " continue\n",
514
+ " \n",
515
+ " print(f\"✅ Found {len(strategies)} viable strategies\")\n",
516
+ " \n",
517
+ " # Sort strategies\n",
518
+ " strategies_by_payback = sorted([s for s in strategies if s['payback_years'] < float('inf')], \n",
519
+ " key=lambda x: x['payback_years'])[:5]\n",
520
+ " strategies_by_savings = sorted(strategies, key=lambda x: x['annual_cost_savings'], reverse=True)[:5]\n",
521
+ " \n",
522
+ " return strategies_by_payback, strategies_by_savings, original_consumption, original_q_total\n",
523
+ " \n",
524
+ " except Exception as e:\n",
525
+ " print(f\"Error in generate_optimization_strategies: {e}\")\n",
526
+ " return [], [], 0, 0\n",
527
+ "\n",
528
+ "def calculate_single_strategy(\n",
529
+ " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n",
530
+ " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n",
531
+ " glazing_type, main_heat_type, main_fuel_type,\n",
532
+ " wall_renovation, roof_renovation, glazing_renovation, heating_renovation, fuel_change,\n",
533
+ " original_consumption, original_q_total, wall_area, glazing_area, roof_area\n",
534
+ "):\n",
535
+ " \"\"\"Calculate costs and savings for a single strategy\"\"\"\n",
536
+ " try:\n",
537
+ " final_roof_type = determine_roof_type_from_location(floor_location, roof_type)\n",
538
+ " final_roof_insulation = roof_insulation if floor_location == 'top floor' else 'another dwelling above'\n",
539
+ " \n",
540
+ " # Apply renovations\n",
541
+ " new_wall_insulation = 'insulated' if wall_renovation != 'no_change' else wall_insulation\n",
542
+ " new_roof_insulation = 'insulated' if roof_renovation != 'no_change' else final_roof_insulation\n",
543
+ " \n",
544
+ " # Glazing upgrades\n",
545
+ " new_glazing_type = glazing_type\n",
546
+ " if glazing_renovation in ['double_glazing', 'triple_glazing']:\n",
547
+ " new_glazing_type = 'double/triple'\n",
548
+ " elif glazing_renovation == 'secondary_glazing':\n",
549
+ " new_glazing_type = 'secondary'\n",
550
+ " \n",
551
+ " # Heating system upgrades\n",
552
+ " new_heat_type = main_heat_type\n",
553
+ " new_fuel_type = main_fuel_type\n",
554
+ " \n",
555
+ " if heating_renovation in ['air_source_heat_pump', 'ground_source_heat_pump']:\n",
556
+ " new_heat_type = 'heat pump'\n",
557
+ " new_fuel_type = 'electricity'\n",
558
+ " elif heating_renovation == 'gas_boiler_upgrade':\n",
559
+ " new_heat_type = 'boiler'\n",
560
+ " new_fuel_type = 'mains gas'\n",
561
+ " elif heating_renovation == 'electric_boiler':\n",
562
+ " new_heat_type = 'boiler'\n",
563
+ " new_fuel_type = 'electricity'\n",
564
+ " \n",
565
+ " # Fuel change override\n",
566
+ " if fuel_change != 'no_change':\n",
567
+ " new_fuel_type = fuel_change\n",
568
+ " \n",
569
+ " # Get renovated performance\n",
570
+ " renovated_consumption, renovated_q_total = get_energy_prediction(\n",
571
+ " total_floor_area, estimated_floor_count, epc_score,\n",
572
+ " new_wall_insulation, final_roof_type, new_roof_insulation, new_glazing_type,\n",
573
+ " built_form, new_heat_type, new_fuel_type, lookup_age_band, wall_type\n",
574
+ " )\n",
575
+ " \n",
576
+ " # Calculate costs\n",
577
+ " total_cost = 0\n",
578
+ " cost_breakdown = []\n",
579
+ " \n",
580
+ " # Wall insulation cost\n",
581
+ " if wall_renovation != 'no_change' and wall_renovation in RENOVATION_COSTS['wall_insulation']:\n",
582
+ " cost = wall_area * RENOVATION_COSTS['wall_insulation'][wall_renovation]\n",
583
+ " total_cost += cost\n",
584
+ " cost_breakdown.append(f'Wall ({wall_renovation})')\n",
585
+ " \n",
586
+ " # Roof insulation cost\n",
587
+ " if (roof_renovation != 'no_change' and \n",
588
+ " floor_location == 'top floor' and \n",
589
+ " roof_renovation in RENOVATION_COSTS['roof_insulation']):\n",
590
+ " cost = roof_area * RENOVATION_COSTS['roof_insulation'][roof_renovation]\n",
591
+ " total_cost += cost\n",
592
+ " cost_breakdown.append(f'Roof ({roof_renovation})')\n",
593
+ " \n",
594
+ " # Glazing cost\n",
595
+ " if glazing_renovation != 'no_change' and glazing_renovation in RENOVATION_COSTS['glazing']:\n",
596
+ " cost = glazing_area * RENOVATION_COSTS['glazing'][glazing_renovation]\n",
597
+ " total_cost += cost\n",
598
+ " cost_breakdown.append(f'Glazing ({glazing_renovation})')\n",
599
+ " \n",
600
+ " # Heating system cost\n",
601
+ " if heating_renovation != 'no_change' and heating_renovation in RENOVATION_COSTS['heating_system']:\n",
602
+ " cost = RENOVATION_COSTS['heating_system'][heating_renovation]\n",
603
+ " total_cost += cost\n",
604
+ " cost_breakdown.append(f'Heating ({heating_renovation})')\n",
605
+ " \n",
606
+ " # Calculate savings\n",
607
+ " annual_savings = max(0, original_consumption - renovated_consumption)\n",
608
+ " original_energy_cost = get_energy_cost_per_kwh(main_fuel_type)\n",
609
+ " new_energy_cost = get_energy_cost_per_kwh(new_fuel_type)\n",
610
+ " \n",
611
+ " original_annual_cost = original_consumption * original_energy_cost\n",
612
+ " new_annual_cost = renovated_consumption * new_energy_cost\n",
613
+ " annual_cost_savings = original_annual_cost - new_annual_cost\n",
614
+ " \n",
615
+ " # Calculate carbon savings\n",
616
+ " original_carbon_factor = get_carbon_factor(main_fuel_type)\n",
617
+ " new_carbon_factor = get_carbon_factor(new_fuel_type)\n",
618
+ " \n",
619
+ " original_annual_carbon = original_consumption * original_carbon_factor\n",
620
+ " new_annual_carbon = renovated_consumption * new_carbon_factor\n",
621
+ " annual_carbon_savings = original_annual_carbon - new_annual_carbon\n",
622
+ " \n",
623
+ " # Calculate payback period\n",
624
+ " if annual_cost_savings > 0:\n",
625
+ " payback_years = total_cost / annual_cost_savings\n",
626
+ " else:\n",
627
+ " payback_years = float('inf')\n",
628
+ " \n",
629
+ " return {\n",
630
+ " 'combination': ' + '.join(cost_breakdown),\n",
631
+ " 'total_cost': total_cost,\n",
632
+ " 'annual_cost_savings': annual_cost_savings,\n",
633
+ " 'annual_carbon_savings': annual_carbon_savings,\n",
634
+ " 'payback_years': payback_years,\n",
635
+ " 'energy_savings': annual_savings,\n",
636
+ " 'renovated_consumption': renovated_consumption,\n",
637
+ " 'renovated_q_total': renovated_q_total,\n",
638
+ " 'details': {\n",
639
+ " 'wall': wall_renovation,\n",
640
+ " 'roof': roof_renovation,\n",
641
+ " 'glazing': glazing_renovation,\n",
642
+ " 'heating': heating_renovation,\n",
643
+ " 'fuel': fuel_change\n",
644
+ " }\n",
645
+ " }\n",
646
+ " \n",
647
+ " except Exception as e:\n",
648
+ " return None\n",
649
+ "\n",
650
+ "def create_optimization_chart(strategies_by_payback, strategies_by_savings):\n",
651
+ " \"\"\"Create optimization scatter plot using Plotly\"\"\"\n",
652
+ " try:\n",
653
+ " print(\"Creating optimization chart...\")\n",
654
+ " \n",
655
+ " # 合并并去重策略\n",
656
+ " all_strategies = {}\n",
657
+ " \n",
658
+ " # 添加最快回本策略\n",
659
+ " for i, strategy in enumerate(strategies_by_payback[:5]):\n",
660
+ " key = strategy['combination']\n",
661
+ " if key not in all_strategies:\n",
662
+ " all_strategies[key] = {\n",
663
+ " 'combination': strategy['combination'],\n",
664
+ " 'payback_years': strategy['payback_years'],\n",
665
+ " 'annual_savings': strategy['annual_cost_savings'],\n",
666
+ " 'annual_carbon_savings': strategy['annual_carbon_savings'],\n",
667
+ " 'total_cost': strategy['total_cost'],\n",
668
+ " 'category': 'Fastest Payback',\n",
669
+ " 'rank_payback': i + 1,\n",
670
+ " 'rank_savings': None\n",
671
+ " }\n",
672
+ " \n",
673
+ " # 添加最高节省策略\n",
674
+ " for i, strategy in enumerate(strategies_by_savings[:5]):\n",
675
+ " key = strategy['combination']\n",
676
+ " if key in all_strategies:\n",
677
+ " all_strategies[key]['rank_savings'] = i + 1\n",
678
+ " if all_strategies[key]['category'] == 'Fastest Payback':\n",
679
+ " all_strategies[key]['category'] = 'Both Top 5'\n",
680
+ " else:\n",
681
+ " all_strategies[key] = {\n",
682
+ " 'combination': strategy['combination'],\n",
683
+ " 'payback_years': strategy['payback_years'],\n",
684
+ " 'annual_savings': strategy['annual_cost_savings'],\n",
685
+ " 'annual_carbon_savings': strategy['annual_carbon_savings'],\n",
686
+ " 'total_cost': strategy['total_cost'],\n",
687
+ " 'category': 'Highest Savings',\n",
688
+ " 'rank_payback': None,\n",
689
+ " 'rank_savings': i + 1\n",
690
+ " }\n",
691
+ " \n",
692
+ " # 转换为列表\n",
693
+ " plot_data = list(all_strategies.values())\n",
694
+ " \n",
695
+ " if not plot_data:\n",
696
+ " print(\"No data for chart\")\n",
697
+ " return None\n",
698
+ " \n",
699
+ " print(f\"Chart data: {len(plot_data)} strategies\")\n",
700
+ " \n",
701
+ " # 创建散点图\n",
702
+ " fig = go.Figure()\n",
703
+ " \n",
704
+ " # 定义颜色和标记\n",
705
+ " colors = {\n",
706
+ " 'Fastest Payback': '#FF6B6B', # 红色\n",
707
+ " 'Highest Savings': '#4ECDC4', # 青色\n",
708
+ " 'Both Top 5': '#45B7D1' # 蓝色\n",
709
+ " }\n",
710
+ " \n",
711
+ " symbols = {\n",
712
+ " 'Fastest Payback': 'circle',\n",
713
+ " 'Highest Savings': 'square',\n",
714
+ " 'Both Top 5': 'star'\n",
715
+ " }\n",
716
+ " \n",
717
+ " # 按类别分组绘制\n",
718
+ " for category in ['Fastest Payback', 'Highest Savings', 'Both Top 5']:\n",
719
+ " category_data = [s for s in plot_data if s['category'] == category]\n",
720
+ " \n",
721
+ " if not category_data:\n",
722
+ " continue\n",
723
+ " \n",
724
+ " # 创建简化标签\n",
725
+ " labels = []\n",
726
+ " for s in category_data:\n",
727
+ " if s['rank_payback'] and s['rank_savings']:\n",
728
+ " labels.append(f\"#{s['rank_payback']}/{s['rank_savings']}\")\n",
729
+ " elif s['rank_payback']:\n",
730
+ " labels.append(f\"#{s['rank_payback']}\")\n",
731
+ " elif s['rank_savings']:\n",
732
+ " labels.append(f\"#{s['rank_savings']}\")\n",
733
+ " else:\n",
734
+ " labels.append(\"\")\n",
735
+ " \n",
736
+ " fig.add_trace(go.Scatter(\n",
737
+ " x=[s['payback_years'] for s in category_data],\n",
738
+ " y=[s['annual_savings'] for s in category_data],\n",
739
+ " mode='markers+text',\n",
740
+ " marker=dict(\n",
741
+ " size=[max(15, min(30, 15 + (s['total_cost'] / 500))) for s in category_data],\n",
742
+ " color=colors[category],\n",
743
+ " symbol=symbols[category],\n",
744
+ " line=dict(width=2, color='white'),\n",
745
+ " opacity=0.8\n",
746
+ " ),\n",
747
+ " text=labels,\n",
748
+ " textposition=\"top center\",\n",
749
+ " textfont=dict(size=12, color='black', family=\"Arial Black\"),\n",
750
+ " name=category,\n",
751
+ " hovertemplate=(\n",
752
+ " '<b>%{customdata[0]}</b><br>' +\n",
753
+ " 'Payback: %{x:.1f} years<br>' +\n",
754
+ " 'Annual Savings: £%{y:,.0f}<br>' +\n",
755
+ " 'Carbon Savings: %{customdata[2]:,.0f} kg CO2e/year<br>' +\n",
756
+ " 'Investment: £%{customdata[1]:,.0f}<br>' +\n",
757
+ " '<extra></extra>'\n",
758
+ " ),\n",
759
+ " customdata=[[s['combination'][:40] + \"...\" if len(s['combination']) > 40 else s['combination'], \n",
760
+ " s['total_cost'], s['annual_carbon_savings']] for s in category_data]\n",
761
+ " ))\n",
762
+ " \n",
763
+ " # 添加理想区域\n",
764
+ " max_savings = max([s['annual_savings'] for s in plot_data])\n",
765
+ " fig.add_shape(\n",
766
+ " type=\"rect\",\n",
767
+ " x0=0, x1=10, y0=max_savings * 0.5, y1=max_savings * 1.1,\n",
768
+ " fillcolor=\"lightgreen\",\n",
769
+ " opacity=0.1,\n",
770
+ " line=dict(width=0),\n",
771
+ " )\n",
772
+ " \n",
773
+ " fig.add_annotation(\n",
774
+ " x=5, y=max_savings * 0.8,\n",
775
+ " text=\"🎯 Sweet Spot\",\n",
776
+ " showarrow=False,\n",
777
+ " font=dict(size=14, color=\"green\"),\n",
778
+ " bgcolor=\"white\",\n",
779
+ " bordercolor=\"green\",\n",
780
+ " borderwidth=1\n",
781
+ " )\n",
782
+ " \n",
783
+ " # 更新布局\n",
784
+ " fig.update_layout(\n",
785
+ " title={\n",
786
+ " 'text': '🏠 Retrofit Investment Strategy Analysis',\n",
787
+ " 'x': 0.5,\n",
788
+ " 'font': {'size': 20, 'family': 'Arial'}\n",
789
+ " },\n",
790
+ " xaxis=dict(\n",
791
+ " title='⏱️ Payback Period (Years)',\n",
792
+ " gridcolor='lightgray',\n",
793
+ " showgrid=True,\n",
794
+ " range=[0, max([s['payback_years'] for s in plot_data]) * 1.1],\n",
795
+ " title_font=dict(size=14)\n",
796
+ " ),\n",
797
+ " yaxis=dict(\n",
798
+ " title='💰 Annual Savings (£)',\n",
799
+ " gridcolor='lightgray',\n",
800
+ " showgrid=True,\n",
801
+ " range=[0, max_savings * 1.1],\n",
802
+ " title_font=dict(size=14)\n",
803
+ " ),\n",
804
+ " plot_bgcolor='white',\n",
805
+ " height=500,\n",
806
+ " showlegend=True,\n",
807
+ " legend=dict(\n",
808
+ " yanchor=\"top\",\n",
809
+ " y=0.99,\n",
810
+ " xanchor=\"left\",\n",
811
+ " x=0.01,\n",
812
+ " bgcolor=\"rgba(255,255,255,0.9)\",\n",
813
+ " bordercolor=\"gray\",\n",
814
+ " borderwidth=1,\n",
815
+ " font=dict(size=12)\n",
816
+ " ),\n",
817
+ " hovermode='closest',\n",
818
+ " margin=dict(l=60, r=60, t=80, b=60)\n",
819
+ " )\n",
820
+ " \n",
821
+ " print(\"Chart created successfully\")\n",
822
+ " return fig\n",
823
+ " \n",
824
+ " except Exception as e:\n",
825
+ " print(f\"Error creating scatter plot: {e}\")\n",
826
+ " print(f\"Traceback: {traceback.format_exc()}\")\n",
827
+ " return None\n",
828
+ "\n",
829
+ "def predict_current_energy_and_show_options(\n",
830
+ " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n",
831
+ " floor_location, roof_type, wall_type, wall_insulation, roof_insulation, \n",
832
+ " glazing_type, main_heat_type, main_fuel_type\n",
833
+ "):\n",
834
+ " \"\"\"Predict current energy consumption and show available renovation options\"\"\"\n",
835
+ " try:\n",
836
+ " print(f\"Starting analysis with inputs: floor_area={total_floor_area}, epc={epc_score}\")\n",
837
+ " \n",
838
+ " # Input validation - ensure all required parameters exist\n",
839
+ " required_params = [\n",
840
+ " ('lookup_age_band', lookup_age_band),\n",
841
+ " ('built_form', built_form),\n",
842
+ " ('wall_type', wall_type),\n",
843
+ " ('wall_insulation', wall_insulation),\n",
844
+ " ('glazing_type', glazing_type),\n",
845
+ " ('main_heat_type', main_heat_type),\n",
846
+ " ('main_fuel_type', main_fuel_type),\n",
847
+ " ('floor_location', floor_location)\n",
848
+ " ]\n",
849
+ " \n",
850
+ " for param_name, param_value in required_params:\n",
851
+ " if not param_value:\n",
852
+ " raise ValueError(f\"Missing required parameter: {param_name}\")\n",
853
+ " \n",
854
+ " # Determine final roof parameters\n",
855
+ " final_roof_type = determine_roof_type_from_location(floor_location, roof_type)\n",
856
+ " final_roof_insulation = roof_insulation if floor_location == 'top floor' else 'another dwelling above'\n",
857
+ " \n",
858
+ " print(f\"Roof type determined: {final_roof_type}, insulation: {final_roof_insulation}\")\n",
859
+ " \n",
860
+ " # Get energy prediction\n",
861
+ " consumption, q_total = get_energy_prediction(\n",
862
+ " total_floor_area, estimated_floor_count, epc_score,\n",
863
+ " wall_insulation, final_roof_type, final_roof_insulation, glazing_type,\n",
864
+ " built_form, main_heat_type, main_fuel_type, lookup_age_band, wall_type\n",
865
+ " )\n",
866
+ " \n",
867
+ " print(f\"Energy prediction completed: {consumption} kWh, {q_total} kWh\")\n",
868
+ " \n",
869
+ " # Calculate costs and carbon emissions\n",
870
+ " energy_cost = get_energy_cost_per_kwh(main_fuel_type)\n",
871
+ " annual_cost = consumption * energy_cost\n",
872
+ " \n",
873
+ " carbon_factor = get_carbon_factor(main_fuel_type)\n",
874
+ " annual_carbon = consumption * carbon_factor\n",
875
+ " \n",
876
+ " # Calculate renovation areas\n",
877
+ " wall_area, glazing_area, roof_area = calculate_actual_areas(total_floor_area, 'flat')\n",
878
+ " \n",
879
+ " print(f\"Areas calculated: wall={wall_area:.1f}, glazing={glazing_area:.1f}, roof={roof_area:.1f}\")\n",
880
+ " \n",
881
+ " # Performance result display - 包含碳排放\n",
882
+ " performance_result = f\"\"\"\n",
883
+ "## 🏠 Current Building Performance\n",
884
+ "\n",
885
+ "| **Metric** | **Value** |\n",
886
+ "|------------|-----------|\n",
887
+ "| **Annual Energy Consumption** | {consumption:,.0f} kWh |\n",
888
+ "| **Annual Energy Bills** | £{annual_cost:,.0f} |\n",
889
+ "| **Annual Carbon Emissions** | {annual_carbon:,.0f} kg CO2e |\n",
890
+ "| **Total Heating Demand** | {q_total:,.0f} kWh/year |\n",
891
+ "| **Floor Area** | {safe_float_conversion(total_floor_area):.0f} m² |\n",
892
+ "| **EPC Score** | {safe_float_conversion(epc_score):.0f} |\n",
893
+ "\n",
894
+ "**Current Systems:** {wall_type} walls ({wall_insulation}) • {final_roof_type} roof ({final_roof_insulation}) • {glazing_type} glazing • {main_heat_type} ({main_fuel_type})\n",
895
+ "\n",
896
+ "**🌱 Carbon Impact:** Your property produces {annual_carbon/1000:.1f} tonnes of CO2 equivalent per year.\n",
897
+ " \"\"\"\n",
898
+ " \n",
899
+ " # Generate optimization strategies\n",
900
+ " strategies_by_payback, strategies_by_savings, orig_consumption, orig_q_total = generate_optimization_strategies(\n",
901
+ " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n",
902
+ " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n",
903
+ " glazing_type, main_heat_type, main_fuel_type\n",
904
+ " )\n",
905
+ " \n",
906
+ " # Create optimization scatter plot\n",
907
+ " optimization_chart = None\n",
908
+ " chart_visible = False\n",
909
+ " if strategies_by_payback or strategies_by_savings:\n",
910
+ " optimization_chart = create_optimization_chart(strategies_by_payback, strategies_by_savings)\n",
911
+ " if optimization_chart:\n",
912
+ " chart_visible = True\n",
913
+ " print(\"Chart will be displayed\")\n",
914
+ " else:\n",
915
+ " print(\"Chart creation failed\")\n",
916
+ " \n",
917
+ " # Determine available renovation options\n",
918
+ " wall_options = ['no_change']\n",
919
+ " if wall_insulation == 'uninsulated':\n",
920
+ " if wall_type == 'solid':\n",
921
+ " wall_options.extend(['internal', 'external'])\n",
922
+ " elif wall_type == 'cavity':\n",
923
+ " wall_options.extend(['cavity_fill', 'internal', 'external'])\n",
924
+ " \n",
925
+ " roof_options = ['no_change']\n",
926
+ " if floor_location == 'top floor' and final_roof_insulation == 'uninsulated':\n",
927
+ " if final_roof_type == 'pitched':\n",
928
+ " roof_options.append('loft')\n",
929
+ " elif final_roof_type == 'flat':\n",
930
+ " roof_options.append('flat_roof')\n",
931
+ " \n",
932
+ " glazing_options = ['no_change']\n",
933
+ " if glazing_type == 'single/partial':\n",
934
+ " glazing_options.extend(['double_glazing', 'triple_glazing', 'secondary_glazing'])\n",
935
+ " elif glazing_type == 'secondary':\n",
936
+ " glazing_options.extend(['double_glazing', 'triple_glazing'])\n",
937
+ " \n",
938
+ " heating_options = ['no_change']\n",
939
+ " if main_heat_type != 'heat pump':\n",
940
+ " heating_options.extend(['air_source_heat_pump', 'ground_source_heat_pump'])\n",
941
+ " if main_heat_type == 'boiler' and lookup_age_band in ['pre-1920', '1930-1949', '1950-1966', '1967-1982']:\n",
942
+ " heating_options.append('gas_boiler_upgrade')\n",
943
+ " if main_heat_type != 'boiler' or main_fuel_type != 'electricity':\n",
944
+ " heating_options.append('electric_boiler')\n",
945
+ " \n",
946
+ " fuel_options = ['no_change']\n",
947
+ " if main_fuel_type != 'mains gas':\n",
948
+ " fuel_options.append('mains gas')\n",
949
+ " if main_fuel_type != 'electricity':\n",
950
+ " fuel_options.append('electricity')\n",
951
+ " \n",
952
+ " # Check if any renovation options are available\n",
953
+ " show_options = any([\n",
954
+ " len(wall_options) > 1,\n",
955
+ " len(roof_options) > 1,\n",
956
+ " len(glazing_options) > 1,\n",
957
+ " len(heating_options) > 1,\n",
958
+ " len(fuel_options) > 1\n",
959
+ " ])\n",
960
+ " \n",
961
+ " 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",
962
+ " print(f\"Chart visible: {chart_visible}\")\n",
963
+ " \n",
964
+ " return (\n",
965
+ " performance_result,\n",
966
+ " gr.Radio(choices=wall_options, value='no_change', visible=(len(wall_options) > 1)),\n",
967
+ " gr.Radio(choices=roof_options, value='no_change', visible=(len(roof_options) > 1)),\n",
968
+ " gr.Radio(choices=glazing_options, value='no_change', visible=(len(glazing_options) > 1)),\n",
969
+ " gr.Radio(choices=heating_options, value='no_change', visible=(len(heating_options) > 1)),\n",
970
+ " gr.Radio(choices=fuel_options, value='no_change', visible=(len(fuel_options) > 1)),\n",
971
+ " gr.Markdown(\"## 🔧 Available Renovation Options\", visible=show_options),\n",
972
+ " gr.Button(\"💰 Calculate Renovation Analysis\", visible=show_options),\n",
973
+ " gr.Plot(value=optimization_chart, visible=chart_visible)\n",
974
+ " )\n",
975
+ " \n",
976
+ " except Exception as e:\n",
977
+ " error_msg = f\"❌ Error in building analysis: {str(e)}\\n\\nPlease check all building parameters and try again.\"\n",
978
+ " print(f\"Error in predict_current_energy_and_show_options: {e}\")\n",
979
+ " print(f\"Traceback: {traceback.format_exc()}\")\n",
980
+ " \n",
981
+ " return (\n",
982
+ " error_msg,\n",
983
+ " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n",
984
+ " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n",
985
+ " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n",
986
+ " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n",
987
+ " gr.Radio(choices=['no_change'], value='no_change', visible=False),\n",
988
+ " gr.Markdown(visible=False),\n",
989
+ " gr.Button(visible=False),\n",
990
+ " gr.Plot(visible=False)\n",
991
+ " )\n",
992
+ "\n",
993
+ "def calculate_renovation_analysis(\n",
994
+ " lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n",
995
+ " floor_location, roof_type, wall_type, wall_insulation, roof_insulation, \n",
996
+ " glazing_type, main_heat_type, main_fuel_type,\n",
997
+ " wall_renovation, roof_renovation, glazing_renovation, heating_renovation, fuel_change\n",
998
+ "):\n",
999
+ " \"\"\"Calculate renovation costs and energy savings\"\"\"\n",
1000
+ " try:\n",
1001
+ " print(f\"Starting renovation analysis...\")\n",
1002
+ " \n",
1003
+ " # Handle None values from hidden radio buttons\n",
1004
+ " wall_renovation = wall_renovation or 'no_change'\n",
1005
+ " roof_renovation = roof_renovation or 'no_change'\n",
1006
+ " glazing_renovation = glazing_renovation or 'no_change'\n",
1007
+ " heating_renovation = heating_renovation or 'no_change'\n",
1008
+ " fuel_change = fuel_change or 'no_change'\n",
1009
+ " \n",
1010
+ " print(f\"Renovations selected: wall={wall_renovation}, roof={roof_renovation}, glazing={glazing_renovation}, heating={heating_renovation}, fuel={fuel_change}\")\n",
1011
+ " \n",
1012
+ " # Determine final roof parameters\n",
1013
+ " final_roof_type = determine_roof_type_from_location(floor_location, roof_type)\n",
1014
+ " final_roof_insulation = roof_insulation if floor_location == 'top floor' else 'another dwelling above'\n",
1015
+ " \n",
1016
+ " # Check if any renovations are selected\n",
1017
+ " no_renovations = all([\n",
1018
+ " wall_renovation == 'no_change',\n",
1019
+ " roof_renovation == 'no_change',\n",
1020
+ " glazing_renovation == 'no_change',\n",
1021
+ " heating_renovation == 'no_change',\n",
1022
+ " fuel_change == 'no_change'\n",
1023
+ " ])\n",
1024
+ " \n",
1025
+ " if no_renovations:\n",
1026
+ " return \"⚠️ Please select at least one renovation option to see the analysis.\"\n",
1027
+ " \n",
1028
+ " # Calculate renovation areas\n",
1029
+ " wall_area, glazing_area, roof_area = calculate_actual_areas(total_floor_area, 'flat')\n",
1030
+ " \n",
1031
+ " # Get original performance\n",
1032
+ " original_consumption, original_q_total = get_energy_prediction(\n",
1033
+ " total_floor_area, estimated_floor_count, epc_score,\n",
1034
+ " wall_insulation, final_roof_type, final_roof_insulation, glazing_type,\n",
1035
+ " built_form, main_heat_type, main_fuel_type, lookup_age_band, wall_type\n",
1036
+ " )\n",
1037
+ " \n",
1038
+ " print(f\"Original consumption: {original_consumption} kWh\")\n",
1039
+ " \n",
1040
+ " # Apply renovations\n",
1041
+ " new_wall_insulation = 'insulated' if wall_renovation != 'no_change' else wall_insulation\n",
1042
+ " new_roof_insulation = 'insulated' if roof_renovation != 'no_change' else final_roof_insulation\n",
1043
+ " \n",
1044
+ " # Glazing upgrades\n",
1045
+ " new_glazing_type = glazing_type\n",
1046
+ " if glazing_renovation in ['double_glazing', 'triple_glazing']:\n",
1047
+ " new_glazing_type = 'double/triple'\n",
1048
+ " elif glazing_renovation == 'secondary_glazing':\n",
1049
+ " new_glazing_type = 'secondary'\n",
1050
+ " \n",
1051
+ " # Heating system upgrades\n",
1052
+ " new_heat_type = main_heat_type\n",
1053
+ " new_fuel_type = main_fuel_type\n",
1054
+ " \n",
1055
+ " if heating_renovation in ['air_source_heat_pump', 'ground_source_heat_pump']:\n",
1056
+ " new_heat_type = 'heat pump'\n",
1057
+ " new_fuel_type = 'electricity'\n",
1058
+ " elif heating_renovation == 'gas_boiler_upgrade':\n",
1059
+ " new_heat_type = 'boiler'\n",
1060
+ " new_fuel_type = 'mains gas'\n",
1061
+ " elif heating_renovation == 'electric_boiler':\n",
1062
+ " new_heat_type = 'boiler'\n",
1063
+ " new_fuel_type = 'electricity'\n",
1064
+ " \n",
1065
+ " # Fuel change override\n",
1066
+ " if fuel_change != 'no_change':\n",
1067
+ " new_fuel_type = fuel_change\n",
1068
+ " \n",
1069
+ " # Get renovated performance\n",
1070
+ " renovated_consumption, renovated_q_total = get_energy_prediction(\n",
1071
+ " total_floor_area, estimated_floor_count, epc_score,\n",
1072
+ " new_wall_insulation, final_roof_type, new_roof_insulation, new_glazing_type,\n",
1073
+ " built_form, new_heat_type, new_fuel_type, lookup_age_band, wall_type\n",
1074
+ " )\n",
1075
+ " \n",
1076
+ " print(f\"Renovated consumption: {renovated_consumption} kWh\")\n",
1077
+ " \n",
1078
+ " # Calculate costs\n",
1079
+ " total_cost = 0\n",
1080
+ " cost_breakdown = {}\n",
1081
+ " \n",
1082
+ " # Wall insulation cost\n",
1083
+ " if wall_renovation != 'no_change' and wall_renovation in RENOVATION_COSTS['wall_insulation']:\n",
1084
+ " cost = wall_area * RENOVATION_COSTS['wall_insulation'][wall_renovation]\n",
1085
+ " total_cost += cost\n",
1086
+ " cost_breakdown[f'Wall Insulation ({wall_renovation})'] = cost\n",
1087
+ " \n",
1088
+ " # Roof insulation cost\n",
1089
+ " if (roof_renovation != 'no_change' and \n",
1090
+ " floor_location == 'top floor' and \n",
1091
+ " roof_renovation in RENOVATION_COSTS['roof_insulation']):\n",
1092
+ " cost = roof_area * RENOVATION_COSTS['roof_insulation'][roof_renovation]\n",
1093
+ " total_cost += cost\n",
1094
+ " cost_breakdown[f'Roof Insulation ({roof_renovation})'] = cost\n",
1095
+ " \n",
1096
+ " # Glazing cost\n",
1097
+ " if glazing_renovation != 'no_change' and glazing_renovation in RENOVATION_COSTS['glazing']:\n",
1098
+ " cost = glazing_area * RENOVATION_COSTS['glazing'][glazing_renovation]\n",
1099
+ " total_cost += cost\n",
1100
+ " cost_breakdown[f'Glazing ({glazing_renovation})'] = cost\n",
1101
+ " \n",
1102
+ " # Heating system cost\n",
1103
+ " if heating_renovation != 'no_change' and heating_renovation in RENOVATION_COSTS['heating_system']:\n",
1104
+ " cost = RENOVATION_COSTS['heating_system'][heating_renovation]\n",
1105
+ " total_cost += cost\n",
1106
+ " cost_breakdown[f'Heating ({heating_renovation})'] = cost\n",
1107
+ " \n",
1108
+ " # Calculate savings\n",
1109
+ " annual_savings = max(0, original_consumption - renovated_consumption)\n",
1110
+ " original_energy_cost = get_energy_cost_per_kwh(main_fuel_type)\n",
1111
+ " new_energy_cost = get_energy_cost_per_kwh(new_fuel_type)\n",
1112
+ " \n",
1113
+ " original_annual_cost = original_consumption * original_energy_cost\n",
1114
+ " new_annual_cost = renovated_consumption * new_energy_cost\n",
1115
+ " annual_cost_savings = original_annual_cost - new_annual_cost\n",
1116
+ " \n",
1117
+ " # Calculate carbon savings\n",
1118
+ " original_carbon_factor = get_carbon_factor(main_fuel_type)\n",
1119
+ " new_carbon_factor = get_carbon_factor(new_fuel_type)\n",
1120
+ " \n",
1121
+ " original_annual_carbon = original_consumption * original_carbon_factor\n",
1122
+ " new_annual_carbon = renovated_consumption * new_carbon_factor\n",
1123
+ " annual_carbon_savings = original_annual_carbon - new_annual_carbon\n",
1124
+ " \n",
1125
+ " # Calculate payback period\n",
1126
+ " if annual_cost_savings > 0:\n",
1127
+ " payback_years = total_cost / annual_cost_savings\n",
1128
+ " else:\n",
1129
+ " payback_years = float('inf')\n",
1130
+ " \n",
1131
+ " # Calculate efficiency improvement\n",
1132
+ " if original_consumption > 0:\n",
1133
+ " efficiency_improvement = (annual_savings / original_consumption) * 100\n",
1134
+ " else:\n",
1135
+ " efficiency_improvement = 0\n",
1136
+ " \n",
1137
+ " 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",
1138
+ " \n",
1139
+ " # Generate results\n",
1140
+ " result_text = f\"\"\"\n",
1141
+ "# 🏠 Renovation Analysis Results\n",
1142
+ "\n",
1143
+ "## 📊 Performance Impact Summary\n",
1144
+ "| **Metric** | **Current** | **After Renovation** | **Improvement** |\n",
1145
+ "|------------|-------------|---------------------|-----------------|\n",
1146
+ "| **Energy Consumption** | {original_consumption:,.0f} kWh | {renovated_consumption:,.0f} kWh | **{annual_savings:,.0f} kWh** ({efficiency_improvement:.1f}% ↓) |\n",
1147
+ "| **Annual Energy Bills** | £{original_annual_cost:,.0f} | £{new_annual_cost:,.0f} | **£{annual_cost_savings:,.0f}** saved/year |\n",
1148
+ "| **Carbon Emissions** | {original_annual_carbon:,.0f} kg CO2e | {new_annual_carbon:,.0f} kg CO2e | **{annual_carbon_savings:,.0f} kg CO2e** saved/year |\n",
1149
+ "| **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",
1150
+ "\n",
1151
+ "## 💰 Investment Analysis\n",
1152
+ "- **Total Renovation Investment**: £{total_cost:,.0f} *(one-time upfront cost)*\n",
1153
+ "- **Annual Bill Savings**: £{annual_cost_savings:,.0f} *(recurring yearly savings)*\n",
1154
+ "- **Simple Payback Period**: {payback_years:.1f} years\n",
1155
+ "- **25-Year Total Savings**: £{annual_cost_savings * 25:,.0f}\n",
1156
+ "\n",
1157
+ "## 🌱 Carbon Impact\n",
1158
+ "- **Annual Carbon Reduction**: {annual_carbon_savings:,.0f} kg CO2e/year ({annual_carbon_savings/1000:.1f} tonnes/year)\n",
1159
+ "- **25-Year Carbon Saved**: {annual_carbon_savings * 25 / 1000:.1f} tonnes CO2e\n",
1160
+ "- **Equivalent to**: {annual_carbon_savings / 4600:.1f} cars removed from roads for a year\n",
1161
+ "\n",
1162
+ "## 🔧 Selected Renovations & Costs\n",
1163
+ "\"\"\"\n",
1164
+ " \n",
1165
+ " if cost_breakdown:\n",
1166
+ " for item, cost in cost_breakdown.items():\n",
1167
+ " percentage = (cost / total_cost * 100) if total_cost > 0 else 0\n",
1168
+ " result_text += f\"- **{item}**: £{cost:,.0f} ({percentage:.1f}%)\\n\"\n",
1169
+ " else:\n",
1170
+ " result_text += \"- No renovation costs calculated (free fuel switch only)\\n\"\n",
1171
+ " \n",
1172
+ " result_text += f\"\"\"\n",
1173
+ "\n",
1174
+ "## 📏 Area Calculations Used:\n",
1175
+ "- **External Wall Area**: {wall_area:.1f} m² (only exterior walls of your unit)\n",
1176
+ "- **Window/Glazing Area**: {glazing_area:.1f} m² (20% of external wall area)\n",
1177
+ "- **Roof Area**: {roof_area:.1f} m² (only if top floor)\n",
1178
+ "\n",
1179
+ "## 🏛️ Government Support Information:\n",
1180
+ "- **BUS Grant**: {GOVERNMENT_GRANTS['BUS']['description']}\n",
1181
+ "- **ECO4**: Up to £{GOVERNMENT_GRANTS['ECO4']['max_amount']:,} for eligible households\n",
1182
+ "- **Local Grants**: Check your council for additional support\n",
1183
+ "\"\"\"\n",
1184
+ " \n",
1185
+ " # Show fuel cost impact if fuel type changes\n",
1186
+ " if new_fuel_type != main_fuel_type:\n",
1187
+ " result_text += f\"\"\"\n",
1188
+ "## 💡 Fuel Cost Impact:\n",
1189
+ "**Note**: Switching from {main_fuel_type} (£{original_energy_cost:.3f}/kWh) to {new_fuel_type} (£{new_energy_cost:.3f}/kWh)\n",
1190
+ "\"\"\"\n",
1191
+ " \n",
1192
+ " # Investment recommendation\n",
1193
+ " if annual_cost_savings > 0:\n",
1194
+ " if payback_years <= 7:\n",
1195
+ " result_text += \"\\n## ✅ **Investment Recommendation: HIGHLY RECOMMENDED**\\n**Excellent ROI** - Short payback period with strong long-term savings and carbon benefits.\"\n",
1196
+ " elif payback_years <= 15:\n",
1197
+ " result_text += \"\\n## ⚖️ **Investment Recommendation: RECOMMENDED**\\n**Good ROI** - Reasonable payback period with solid financial and environmental benefits.\"\n",
1198
+ " else:\n",
1199
+ " result_text += \"\\n## ⚠️ **Investment Recommendation: CONSIDER CAREFULLY**\\n**Extended Payback** - Prioritize highest-impact measures first for better ROI.\"\n",
1200
+ " else:\n",
1201
+ " result_text += \"\\n## ❌ **Investment Recommendation: NOT RECOMMENDED**\\n**Negative savings** - This combination increases annual costs. Consider different options.\"\n",
1202
+ " \n",
1203
+ " return result_text\n",
1204
+ " \n",
1205
+ " except Exception as e:\n",
1206
+ " error_msg = f\"❌ Error in renovation analysis: {str(e)}\\n\\nPlease check your selections and try again.\"\n",
1207
+ " print(f\"Error in calculate_renovation_analysis: {e}\")\n",
1208
+ " print(f\"Traceback: {traceback.format_exc()}\")\n",
1209
+ " return error_msg\n",
1210
+ "\n",
1211
+ "def update_roof_type_visibility(floor_location):\n",
1212
+ " \"\"\"Update roof type options visibility based on floor location\"\"\"\n",
1213
+ " try:\n",
1214
+ " if floor_location == 'top floor':\n",
1215
+ " return gr.Radio(\n",
1216
+ " choices=['pitched', 'flat', 'room in roof'],\n",
1217
+ " value='pitched',\n",
1218
+ " visible=True\n",
1219
+ " )\n",
1220
+ " else:\n",
1221
+ " return gr.Radio(\n",
1222
+ " choices=['another dwelling above'],\n",
1223
+ " value='another dwelling above',\n",
1224
+ " visible=False\n",
1225
+ " )\n",
1226
+ " except Exception as e:\n",
1227
+ " print(f\"Error in update_roof_type_visibility: {e}\")\n",
1228
+ " return gr.Radio(\n",
1229
+ " choices=['pitched'],\n",
1230
+ " value='pitched',\n",
1231
+ " visible=True\n",
1232
+ " )\n",
1233
+ "\n",
1234
+ "def update_roof_insulation_visibility(floor_location):\n",
1235
+ " \"\"\"Update roof insulation options visibility based on floor location\"\"\"\n",
1236
+ " try:\n",
1237
+ " if floor_location == 'top floor':\n",
1238
+ " return gr.Radio(\n",
1239
+ " choices=['insulated', 'uninsulated'],\n",
1240
+ " value='uninsulated',\n",
1241
+ " visible=True\n",
1242
+ " )\n",
1243
+ " else:\n",
1244
+ " return gr.Radio(\n",
1245
+ " choices=['another dwelling above'],\n",
1246
+ " value='another dwelling above',\n",
1247
+ " visible=False\n",
1248
+ " )\n",
1249
+ " except Exception as e:\n",
1250
+ " print(f\"Error in update_roof_insulation_visibility: {e}\")\n",
1251
+ " return gr.Radio(\n",
1252
+ " choices=['uninsulated'],\n",
1253
+ " value='uninsulated',\n",
1254
+ " visible=True\n",
1255
+ " )\n",
1256
+ "\n",
1257
+ "# Create interface with comprehensive error handling\n",
1258
+ "try:\n",
1259
+ " with gr.Blocks(theme=gr.themes.Soft(), title=\"🏠 Home Retrofit Calculator\") as demo:\n",
1260
+ " gr.Markdown(\"# 🏠 Home Retrofit Calculator\")\n",
1261
+ " gr.Markdown(\"**Find the best energy-saving upgrades for your property with real UK costs, payback analysis & carbon impact**\")\n",
1262
+ " \n",
1263
+ " with gr.Row():\n",
1264
+ " # Left column - Building inputs\n",
1265
+ " with gr.Column(scale=1):\n",
1266
+ " gr.Markdown(\"## 🏢 Building Information\")\n",
1267
+ " \n",
1268
+ " lookup_age_band = gr.Radio(\n",
1269
+ " choices=['pre-1920', '1930-1949', '1950-1966', '1967-1982', '1983-1995', '1996-2011', '2012-onwards'],\n",
1270
+ " label=\"📅 Construction Age Band\",\n",
1271
+ " value=\"1950-1966\"\n",
1272
+ " )\n",
1273
+ " \n",
1274
+ " total_floor_area = gr.Number(\n",
1275
+ " label=\"🏠 Total Floor Area (m²)\",\n",
1276
+ " value=60,\n",
1277
+ " minimum=10,\n",
1278
+ " maximum=1000\n",
1279
+ " )\n",
1280
+ " \n",
1281
+ " estimated_floor_count = gr.Number(\n",
1282
+ " label=\"🏢 Above-ground Floors\",\n",
1283
+ " value=2,\n",
1284
+ " minimum=1,\n",
1285
+ " maximum=10\n",
1286
+ " )\n",
1287
+ " \n",
1288
+ " epc_score = gr.Number(\n",
1289
+ " label=\"📊 EPC Score\",\n",
1290
+ " value=50,\n",
1291
+ " minimum=1,\n",
1292
+ " maximum=100\n",
1293
+ " )\n",
1294
+ " \n",
1295
+ " built_form = gr.Radio(\n",
1296
+ " choices=['end-terrace', 'mid-terrace'],\n",
1297
+ " label=\"🏘️ Built Form\",\n",
1298
+ " value=\"mid-terrace\"\n",
1299
+ " )\n",
1300
+ " \n",
1301
+ " # Floor location and roof type\n",
1302
+ " floor_location = gr.Radio(\n",
1303
+ " choices=['top floor', 'other floor'],\n",
1304
+ " label=\"🏢 Floor Location\",\n",
1305
+ " value=\"top floor\"\n",
1306
+ " )\n",
1307
+ " \n",
1308
+ " roof_type = gr.Radio(\n",
1309
+ " choices=['pitched', 'flat', 'room in roof'],\n",
1310
+ " label=\"🏠 Roof Type\",\n",
1311
+ " value=\"pitched\"\n",
1312
+ " )\n",
1313
+ " \n",
1314
+ " # Wall information\n",
1315
+ " with gr.Row():\n",
1316
+ " wall_type = gr.Radio(\n",
1317
+ " choices=['solid', 'cavity'],\n",
1318
+ " label=\"🧱 Wall Type\",\n",
1319
+ " value=\"solid\"\n",
1320
+ " )\n",
1321
+ " wall_insulation = gr.Radio(\n",
1322
+ " choices=['insulated', 'uninsulated'],\n",
1323
+ " label=\"🧱 Wall Insulation\",\n",
1324
+ " value=\"uninsulated\"\n",
1325
+ " )\n",
1326
+ " \n",
1327
+ " # Roof insulation\n",
1328
+ " roof_insulation = gr.Radio(\n",
1329
+ " choices=['insulated', 'uninsulated'],\n",
1330
+ " label=\"🏠 Roof Insulation\",\n",
1331
+ " value=\"uninsulated\"\n",
1332
+ " )\n",
1333
+ " \n",
1334
+ " # Other systems\n",
1335
+ " glazing_type = gr.Radio(\n",
1336
+ " choices=['single/partial', 'double/triple', 'secondary'],\n",
1337
+ " label=\"🪟 Glazing Type\",\n",
1338
+ " value=\"single/partial\"\n",
1339
+ " )\n",
1340
+ " \n",
1341
+ " with gr.Row():\n",
1342
+ " main_heat_type = gr.Radio(\n",
1343
+ " choices=['boiler', 'communal', 'room/storage heaters', 'heat pump', 'other', 'no heating system'],\n",
1344
+ " label=\"🔥 Main Heating\",\n",
1345
+ " value=\"boiler\"\n",
1346
+ " )\n",
1347
+ " main_fuel_type = gr.Radio(\n",
1348
+ " choices=['mains gas', 'electricity', 'other', 'no heating system'],\n",
1349
+ " label=\"⚡ Main Fuel\",\n",
1350
+ " value=\"mains gas\"\n",
1351
+ " )\n",
1352
+ " \n",
1353
+ " analyze_btn = gr.Button(\n",
1354
+ " \"📊 Analyze Building Performance\",\n",
1355
+ " variant=\"primary\",\n",
1356
+ " size=\"lg\"\n",
1357
+ " )\n",
1358
+ " \n",
1359
+ " # Right column - Results and options\n",
1360
+ " with gr.Column(scale=1):\n",
1361
+ " current_performance = gr.Markdown()\n",
1362
+ " \n",
1363
+ " # Optimization strategies chart\n",
1364
+ " optimization_chart = gr.Plot(label=\"📊 Retrofit Strategy Analysis\", visible=False)\n",
1365
+ " \n",
1366
+ " # Renovation options (initially hidden)\n",
1367
+ " options_title = gr.Markdown(\"## 🔧 Available Renovation Options\", visible=False)\n",
1368
+ " wall_renovation = gr.Radio(label=\"🧱 Wall Insulation Upgrade\", visible=False)\n",
1369
+ " roof_renovation = gr.Radio(label=\"🏠 Roof Insulation Upgrade\", visible=False)\n",
1370
+ " glazing_renovation = gr.Radio(label=\"🪟 Glazing Upgrade\", visible=False)\n",
1371
+ " heating_renovation = gr.Radio(label=\"🔥 Heating System Upgrade\", visible=False)\n",
1372
+ " fuel_change = gr.Radio(label=\"⚡ Fuel Type Change\", visible=False)\n",
1373
+ " \n",
1374
+ " calculate_btn = gr.Button(\n",
1375
+ " \"💰 Calculate Renovation Analysis\",\n",
1376
+ " variant=\"secondary\",\n",
1377
+ " size=\"lg\",\n",
1378
+ " visible=False\n",
1379
+ " )\n",
1380
+ " \n",
1381
+ " # Results area\n",
1382
+ " renovation_results = gr.Markdown()\n",
1383
+ " \n",
1384
+ " # Event handlers with error handling\n",
1385
+ " def safe_update_roof_type(floor_location):\n",
1386
+ " try:\n",
1387
+ " return update_roof_type_visibility(floor_location)\n",
1388
+ " except Exception as e:\n",
1389
+ " print(f\"Error updating roof type: {e}\")\n",
1390
+ " return gr.Radio(choices=['pitched'], value='pitched', visible=True)\n",
1391
+ " \n",
1392
+ " def safe_update_roof_insulation(floor_location):\n",
1393
+ " try:\n",
1394
+ " return update_roof_insulation_visibility(floor_location)\n",
1395
+ " except Exception as e:\n",
1396
+ " print(f\"Error updating roof insulation: {e}\")\n",
1397
+ " return gr.Radio(choices=['uninsulated'], value='uninsulated', visible=True)\n",
1398
+ " \n",
1399
+ " # Connect event handlers\n",
1400
+ " floor_location.change(\n",
1401
+ " fn=safe_update_roof_type,\n",
1402
+ " inputs=[floor_location],\n",
1403
+ " outputs=[roof_type]\n",
1404
+ " )\n",
1405
+ " \n",
1406
+ " floor_location.change(\n",
1407
+ " fn=safe_update_roof_insulation,\n",
1408
+ " inputs=[floor_location],\n",
1409
+ " outputs=[roof_insulation]\n",
1410
+ " )\n",
1411
+ " \n",
1412
+ " analyze_btn.click(\n",
1413
+ " fn=predict_current_energy_and_show_options,\n",
1414
+ " inputs=[lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n",
1415
+ " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n",
1416
+ " glazing_type, main_heat_type, main_fuel_type],\n",
1417
+ " outputs=[current_performance, wall_renovation, roof_renovation, glazing_renovation,\n",
1418
+ " heating_renovation, fuel_change, options_title, calculate_btn, optimization_chart]\n",
1419
+ " )\n",
1420
+ " \n",
1421
+ " calculate_btn.click(\n",
1422
+ " fn=calculate_renovation_analysis,\n",
1423
+ " inputs=[lookup_age_band, total_floor_area, estimated_floor_count, epc_score, built_form,\n",
1424
+ " floor_location, roof_type, wall_type, wall_insulation, roof_insulation,\n",
1425
+ " glazing_type, main_heat_type, main_fuel_type,\n",
1426
+ " wall_renovation, roof_renovation, glazing_renovation, heating_renovation, fuel_change],\n",
1427
+ " outputs=[renovation_results]\n",
1428
+ " )\n",
1429
+ "\n",
1430
+ " # Launch application with proper conditions\n",
1431
+ " if __name__ == \"__main__\":\n",
1432
+ " print(\"🚀 Launching Home Retrofit Calculator...\")\n",
1433
+ " demo.launch(share=True, debug=False) # Set debug=False for production\n",
1434
+ " else:\n",
1435
+ " print(\"✅ Application ready for launch\")\n",
1436
+ "\n",
1437
+ "except Exception as e:\n",
1438
+ " print(f\"❌ Critical error in interface setup: {e}\")\n",
1439
+ " print(f\"Traceback: {traceback.format_exc()}\")\n",
1440
+ " raise"
1441
+ ]
1442
+ }
1443
+ ],
1444
+ "metadata": {
1445
+ "kernelspec": {
1446
+ "display_name": "Python (base)",
1447
+ "language": "python",
1448
+ "name": "base"
1449
+ },
1450
+ "language_info": {
1451
+ "codemirror_mode": {
1452
+ "name": "ipython",
1453
+ "version": 3
1454
+ },
1455
+ "file_extension": ".py",
1456
+ "mimetype": "text/x-python",
1457
+ "name": "python",
1458
+ "nbconvert_exporter": "python",
1459
+ "pygments_lexer": "ipython3",
1460
+ "version": "3.11.9"
1461
+ }
1462
+ },
1463
+ "nbformat": 4,
1464
+ "nbformat_minor": 5
1465
+ }