mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
integrating solar api to router
This commit is contained in:
parent
9781b08478
commit
01c50eb5cb
6 changed files with 51 additions and 127 deletions
2
.idea/Model.iml
generated
2
.idea/Model.iml
generated
|
|
@ -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
2
.idea/misc.xml
generated
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue