integrating solar api to router

This commit is contained in:
Khalim Conn-Kowlessar 2024-06-24 14:57:01 +01:00
parent 9781b08478
commit 01c50eb5cb
6 changed files with 51 additions and 127 deletions

2
.idea/Model.iml generated
View file

@ -7,7 +7,7 @@
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (model_data)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.10 (backend)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyNamespacePackagesService">

2
.idea/misc.xml generated
View file

@ -3,7 +3,7 @@
<component name="Black">
<option name="sdkName" value="Python 3.10 (backend)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (model_data)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (backend)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>

View file

@ -1,136 +1,19 @@
import pandas as pd
import numpy as np
from recommendations.Costs import MCS_SOLAR_PV_COST_DATA
from backend.Property import Property
from backend.SearchEpc import SearchEpc
from etl.epc.Record import EPCRecord
from dotenv import load_dotenv
from utils.s3 import read_dataframe_from_s3_parquet, read_from_s3
import os
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
import requests
import msgpack
from functools import lru_cache
import time
load_dotenv(dotenv_path="backend/.env")
EPC_AUTH_TOKEN = os.getenv("EPC_AUTH_TOKEN")
# This is for 6 Laura Close, Tintagel, PL34 0EB (same property that Cotswolrd energy used)
uprn = 100040099104
# This is for 353A, Hermitage Lane, ME16 9NT (one of the e.on properties)
uprn = 200000964454
# This is for 14 Victoria Road, Cross Hills, KEIGHLEY, North Yorkshire, ENGLAND, BD20 8SY
uprn = 100050346517
cleaning_data = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet",
)
searcher = SearchEpc(address1="", postcode="", uprn=uprn, auth_token=EPC_AUTH_TOKEN, os_api_key="")
searcher.find_property(skip_os=True)
epc_records = {
'original_epc': searcher.newest_epc.copy(),
'full_sap_epc': searcher.full_sap_epc.copy(),
'old_data': searcher.older_epcs.copy(),
}
epc = EPCRecord(
epc_records=epc_records,
run_mode="newdata",
cleaning_data=cleaning_data
)
uprn_filenames = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev", file_key="spatial/filename_meta.parquet"
)
p = Property(
id=0,
address=searcher.address_clean,
postcode=searcher.postcode_clean,
epc_record=epc,
already_installed={},
non_invasive_recommendations={},
)
p.get_spatial_data(uprn_filenames)
cleaned = read_from_s3(
s3_file_name="cleaned_epc_data/cleaned.bson",
bucket_name="retrofit-data-dev"
)
cleaned = msgpack.unpackb(cleaned, raw=False)
from etl.solar.SolarPhotoSupply import SolarPhotoSupply
photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev")
p.get_components(
cleaned=cleaned,
photo_supply_lookup=photo_supply_lookup,
floor_area_decile_thresholds=floor_area_decile_thresholds
)
p.hot_water_energy_source
p.heating_energy_source
longitude = p.spatial["longitude"]
latitude = p.spatial["latitude"]
api_key = "AIzaSyCIz8Psu5h-1txuDX0rQpUTgkvdj8yohqU"
url = 'https://solar.googleapis.com/v1/solarPotential'
params = {
'location.latitude': f'{latitude:.5f}',
'location.longitude': f'{longitude:.5f}',
'requiredQuality': "MEDIUM",
'key': api_key
}
insights_url = 'https://solar.googleapis.com/v1/buildingInsights:findClosest'
# Make the GET request to the Solar API
insights_response = requests.get(insights_url, params=params)
insights_data = insights_response.json()
solar_potential = insights_data["solarPotential"]
from pprint import pprint
pprint(solar_potential)
# This is the maximum number of panels that can be installed
solar_potential["maxArrayPanelsCount"]
# This is the size of the panels used in the calculation - 400 watt
solar_potential["panelCapacityWatts"]
# Height of the panels used
solar_potential["panelHeightMeters"]
# Width of the panels used
solar_potential["panelWidthMeters"]
# This is the maximum area that can be covered by the panels
solar_potential["maxArrayAreaMeters2"]
# This is the area of the roof
solar_potential["wholeRoofStats"]["areaMeters2"]
# This is the area of the floor
solar_potential["wholeRoofStats"]["groundAreaMeters2"]
solar_potential["solarPanelConfigs"][0]
solar_potential["solarPanelConfigs"][1]
self = GoogleSolarApi(api_key=api_key)
class GoogleSolarApi:
NORTH_FACING_AZIMUTH_RANGE = (-30, 30)
# Conservative estimate of the proportion of electricity that will be consumed, whereas the rest will
# be exported
SOLAR_CONSUMPTION_PROPORTION = 0.5
def __init__(self, api_key, max_retries=5):
"""
Initialize the GoogleSolarApi class with the provided API key and maximum retries.
@ -150,6 +33,8 @@ class GoogleSolarApi:
self.roof_area = None
self.roof_segment_indexes = None
self.panel_area = None
self.panel_wattage = None
self.panel_performance = None
def get_building_insights(self, longitude, latitude, required_quality="MEDIUM", max_retries=None):
"""
@ -198,7 +83,6 @@ class GoogleSolarApi:
:return: The JSON response containing the building insights data.
"""
# TODO - can we make a request which includes the 30cm buffer from the edge of the roof?
self.insights_data = self.get_building_insights(longitude, latitude, required_quality)
# Extract key data from the insights response
@ -209,6 +93,7 @@ class GoogleSolarApi:
self.insights_data["solarPotential"]["panelHeightMeters"] *
self.insights_data["solarPotential"]["panelWidthMeters"]
)
self.panel_wattage = self.insights_data["solarPotential"]["panelCapacityWatts"]
# Automatically exclude north-facing segments
self.exclude_north_facing_segments()
@ -246,7 +131,8 @@ class GoogleSolarApi:
"generatedEnergy": generated_energy,
"ratio": ratio,
"n_panels": segment["panelsCount"],
"cost": cost
"cost": cost,
"panneled_roof_area": self.panel_area * int(segment["panelsCount"])
}
)
@ -263,12 +149,43 @@ class GoogleSolarApi:
"n_panels": roi_summary["n_panels"].sum(),
"total_energy": total_energy,
"total_cost": total_cost,
"weighted_ratio": weighted_ratio
"weighted_ratio": weighted_ratio,
"panneled_roof_area": roi_summary["panneled_roof_area"].sum(),
"array_warrage": roi_summary["n_panels"].sum() * self.panel_wattage
}
)
panel_performance = pd.DataFrame(panel_performance)
# We can have duplicate configurations
panel_performance = panel_performance.drop_duplicates()
# Ensure more than 4 panels
panel_performance = panel_performance[panel_performance["n_panels"] >= 4]
# Remove anything where the total energy is less than half of the array wattage
panel_performance = panel_performance[
(panel_performance["total_energy"] / panel_performance["array_warrage"]) >= 0.5
]
# This first bracket is the value of the energy bill savings
panel_performance["bill_savings"] = (
self.SOLAR_CONSUMPTION_PROPORTION *
panel_performance["total_energy"] *
AnnualBillSavings.ELECTRICITY_PRICE_CAP
)
# This is the amount of energy exported
panel_performance["export_value"] = (
(1 - self.SOLAR_CONSUMPTION_PROPORTION) *
panel_performance["total_energy"] *
AnnualBillSavings.ELECTRICITY_EXPORT_PAYMENT
)
panel_performance["energy_value"] = panel_performance["bill_savings"] + panel_performance["export_value"]
panel_performance["payback_years"] = panel_performance["total_cost"] / panel_performance["energy_value"]
panel_performance = panel_performance.sort_values("weighted_ratio", ascending=False)
# TODO: Finish this!!
panel_performance["roof_area_percentage"] = panel_performance["panneled_roof_area"] / self.roof_area
self.panel_performance = panel_performance
def exclude_north_facing_segments(self):
"""

View file

@ -14,6 +14,7 @@ class Settings(BaseSettings):
PLAN_TRIGGER_BUCKET: str
EPC_AUTH_TOKEN: str
ORDNANCE_SURVEY_API_KEY: str
GOOGLE_SOLAR_API_KEY: str
DB_HOST: str
DB_PASSWORD: str
DB_USERNAME: str

View file

@ -29,6 +29,7 @@ from backend.app.utils import epc_to_sap_lower_bound, sap_to_epc
from backend.ml_models.api import ModelApi
from backend.Property import Property
from backend.apis.GoogleSolarApi import GoogleSolarApi
from etl.solar.SolarPhotoSupply import SolarPhotoSupply
from recommendations.optimiser.CostOptimiser import CostOptimiser
@ -347,10 +348,13 @@ async def trigger_plan(body: PlanTriggerRequest):
bucket_name=get_settings().DATA_BUCKET, file_key="spatial/filename_meta.parquet"
)
photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket=get_settings().DATA_BUCKET)
solar_api_client = GoogleSolarApi(api_key=get_settings().GOOGLE_SOLAR_API_KEY)
logger.info("Getting spatial data")
for p in input_properties:
p.get_spatial_data(uprn_filenames)
# Call Google Solar API
solar_api_client.get(longitude=p.spatial["longitude"], latitude=p.spatial["latitude"])
logger.info("Getting components and epc recommendations")
recommendations = {}

View file

@ -14,6 +14,8 @@ class AnnualBillSavings:
# https://www.ofgem.gov.uk/publications/new-energy-price-cap-level-april-june-2024-starts-today
ELECTRICITY_PRICE_CAP = 0.245
GAS_PRICE_CAP = 0.0604
# This is the most recent export payment figure, at 12p per kwh
ELECTRICITY_EXPORT_PAYMENT = 0.12
# This is a weighted mean of the price caps, using the consumption figures above as weights
PRICE_FACTOR = 0.09549999999999999