Upload energy_predictor.ipynb
Browse files- 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 |
+
}
|