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$/open_uprn" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
</content> </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" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="PyNamespacePackagesService"> <component name="PyNamespacePackagesService">

2
.idea/misc.xml generated
View file

@ -3,7 +3,7 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.10 (backend)" /> <option name="sdkName" value="Python 3.10 (backend)" />
</component> </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"> <component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" /> <option name="version" value="3" />
</component> </component>

View file

@ -1,136 +1,19 @@
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from recommendations.Costs import MCS_SOLAR_PV_COST_DATA from recommendations.Costs import MCS_SOLAR_PV_COST_DATA
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
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
import requests import requests
import msgpack
from functools import lru_cache from functools import lru_cache
import time 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: class GoogleSolarApi:
NORTH_FACING_AZIMUTH_RANGE = (-30, 30) 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): def __init__(self, api_key, max_retries=5):
""" """
Initialize the GoogleSolarApi class with the provided API key and maximum retries. Initialize the GoogleSolarApi class with the provided API key and maximum retries.
@ -150,6 +33,8 @@ class GoogleSolarApi:
self.roof_area = None self.roof_area = None
self.roof_segment_indexes = None self.roof_segment_indexes = None
self.panel_area = 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): 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. :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) self.insights_data = self.get_building_insights(longitude, latitude, required_quality)
# Extract key data from the insights response # Extract key data from the insights response
@ -209,6 +93,7 @@ class GoogleSolarApi:
self.insights_data["solarPotential"]["panelHeightMeters"] * self.insights_data["solarPotential"]["panelHeightMeters"] *
self.insights_data["solarPotential"]["panelWidthMeters"] self.insights_data["solarPotential"]["panelWidthMeters"]
) )
self.panel_wattage = self.insights_data["solarPotential"]["panelCapacityWatts"]
# Automatically exclude north-facing segments # Automatically exclude north-facing segments
self.exclude_north_facing_segments() self.exclude_north_facing_segments()
@ -246,7 +131,8 @@ class GoogleSolarApi:
"generatedEnergy": generated_energy, "generatedEnergy": generated_energy,
"ratio": ratio, "ratio": ratio,
"n_panels": segment["panelsCount"], "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(), "n_panels": roi_summary["n_panels"].sum(),
"total_energy": total_energy, "total_energy": total_energy,
"total_cost": total_cost, "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) 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) 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): def exclude_north_facing_segments(self):
""" """

View file

@ -14,6 +14,7 @@ class Settings(BaseSettings):
PLAN_TRIGGER_BUCKET: str PLAN_TRIGGER_BUCKET: str
EPC_AUTH_TOKEN: str EPC_AUTH_TOKEN: str
ORDNANCE_SURVEY_API_KEY: str ORDNANCE_SURVEY_API_KEY: str
GOOGLE_SOLAR_API_KEY: str
DB_HOST: str DB_HOST: str
DB_PASSWORD: str DB_PASSWORD: str
DB_USERNAME: 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.ml_models.api import ModelApi
from backend.Property import Property from backend.Property import Property
from backend.apis.GoogleSolarApi import GoogleSolarApi
from etl.solar.SolarPhotoSupply import SolarPhotoSupply from etl.solar.SolarPhotoSupply import SolarPhotoSupply
from recommendations.optimiser.CostOptimiser import CostOptimiser 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" 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) 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") logger.info("Getting spatial data")
for p in input_properties: for p in input_properties:
p.get_spatial_data(uprn_filenames) 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") logger.info("Getting components and epc recommendations")
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 # https://www.ofgem.gov.uk/publications/new-energy-price-cap-level-april-june-2024-starts-today
ELECTRICITY_PRICE_CAP = 0.245 ELECTRICITY_PRICE_CAP = 0.245
GAS_PRICE_CAP = 0.0604 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 # This is a weighted mean of the price caps, using the consumption figures above as weights
PRICE_FACTOR = 0.09549999999999999 PRICE_FACTOR = 0.09549999999999999