mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
270 lines
10 KiB
Python
270 lines
10 KiB
Python
import os
|
|
import requests
|
|
import PyPDF2
|
|
from string import Template
|
|
|
|
import pandas as pd
|
|
|
|
from survey_report.extraction.detect_report_type import detect_report_type
|
|
from survey_report.extraction.quidos import SiteNotesExtractor, EPRExtractor
|
|
|
|
|
|
def generate_html_report(template_path, output_path, data):
|
|
"""
|
|
Reads an HTML template file, injects dynamic values, and generates a final HTML report.
|
|
|
|
Args:
|
|
- template_path (str): Path to the HTML template file.
|
|
- output_path (str): Path to save the generated HTML file.
|
|
- data (dict): Dictionary containing dynamic values for the report.
|
|
"""
|
|
# Read the template file
|
|
with open(template_path, "r", encoding="utf-8") as f:
|
|
html_template = Template(f.read()) # Use Template from string module
|
|
|
|
# Replace placeholders with actual data
|
|
final_html = html_template.safe_substitute(data) # Use safe_substitute to prevent missing key errors
|
|
|
|
# Save the generated HTML file
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
f.write(final_html)
|
|
|
|
print(f"HTML report generated successfully: {output_path}")
|
|
|
|
|
|
def stringify_number(num: int, rounding: bool = True) -> str:
|
|
if num < 100000: # 5 figures or fewer
|
|
rounded_num = ((num + 99) // 100) * 100 if rounding else num
|
|
return f"{rounded_num:,}"
|
|
else: # More than 5 figures
|
|
rounded_num = ((num + 999) // 1000) * 1000 if rounding else num
|
|
return f"{rounded_num // 1000}k"
|
|
|
|
|
|
class PlacidApi:
|
|
# Errors as defined by docs: https://placid.app/docs/2.0/rest/errors
|
|
ERROR_CODES = {
|
|
400: "Bad request",
|
|
401: "Unauthorized",
|
|
404: "Template Not found",
|
|
422: "Validation error",
|
|
429: "Rate limit exceeded",
|
|
500: "Internal server error",
|
|
}
|
|
|
|
def __init__(self, api_key):
|
|
self.api_key = api_key
|
|
|
|
self.headers = {
|
|
"Authorization": f"Bearer {self.api_key}",
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
}
|
|
|
|
def create_pdf(
|
|
self,
|
|
template_uuid: str,
|
|
current_epc_rating: str,
|
|
current_epc_rating_colour: str,
|
|
post_retrofit_epc_rating: str,
|
|
post_retrofit_epc_rating_colour: str,
|
|
):
|
|
url = "https://api.placid.app/api/rest/pdfs"
|
|
|
|
body = {
|
|
"webhook_success": None,
|
|
"passthrough": None,
|
|
"pages": [
|
|
{
|
|
"template_uuid": template_uuid,
|
|
"layers": {
|
|
"current_epc_rating": {
|
|
"text": current_epc_rating,
|
|
"text_color": current_epc_rating_colour,
|
|
},
|
|
"post_retrofit_epc_rating": {
|
|
"text": post_retrofit_epc_rating,
|
|
"text_color": post_retrofit_epc_rating_colour,
|
|
}
|
|
},
|
|
},
|
|
]
|
|
}
|
|
|
|
response = requests.post(
|
|
url,
|
|
headers=self.headers,
|
|
json=body
|
|
)
|
|
|
|
response_body = response.json()
|
|
|
|
return response_body
|
|
|
|
def get_pdf(self, pdf_id: str):
|
|
"""
|
|
Poll the API every 5 seconds until the PDF is ready
|
|
"""
|
|
url = f"https://api.placid.app/api/rest/pdfs/{pdf_id}"
|
|
|
|
response = requests.get(
|
|
url,
|
|
headers=self.headers
|
|
)
|
|
response_body = response.json()
|
|
|
|
url = response_body["pdf_url"]
|
|
# Download the PDF form this uurl
|
|
pdf_download = requests.get(url)
|
|
with open("survey_report/example_data/output.pdf", "wb") as f:
|
|
f.write(pdf_download.content)
|
|
|
|
|
|
def handler():
|
|
"""
|
|
Performs the data extraction process for the survey report
|
|
:return:
|
|
"""
|
|
|
|
PLACID_API_KEY = "placid-mpkwidzer2mens9h-hifa3dmbxpfeghpa"
|
|
TEMPLATE_UUID = "5bst9mh1q9lk9"
|
|
placid_api = PlacidApi(PLACID_API_KEY)
|
|
|
|
current_property_value = 250000 # Needs to be an input
|
|
|
|
EPC_COLOURS = {
|
|
"A": "#117d58",
|
|
"B": "#2da55c",
|
|
"C": "#8dbd40",
|
|
"D": "#f7cd14",
|
|
"E": "#f3a96a",
|
|
"F": "#ef8026",
|
|
"G": "#e41e3b",
|
|
}
|
|
|
|
folders = [
|
|
{
|
|
"site_notes": "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/example_data/Flat 1/3 "
|
|
"WILLIS ROAD FLAT 1 PRE EPR SITE NOTES.pdf",
|
|
"epr": "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/example_data/Flat 1/3 WILLIS "
|
|
"ROAD FLAT 1 PRE EPR PDF.pdf",
|
|
"scenario_site_notes": "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/example_data"
|
|
"/Flat 1/3 WILLIS ROAD FLAT 1 POST EPR SITE NOTES.pdf"
|
|
},
|
|
{
|
|
"site_notes": "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/example_data/Flat 2/3 "
|
|
"WILLIS ROAD FLAT 2 PRE EPR SITE NOTES.pdf",
|
|
"epr": "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/example_data/Flat 2/3 WILLIS "
|
|
"ROAD FLAT 2 PRE EPR PDF.pdf",
|
|
"scenario_site_notes": "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/example_data"
|
|
"/Flat 2/3 WILLIS ROAD FLAT 2 POST EPR SITE NOTES.pdf"
|
|
},
|
|
{
|
|
"site_notes": "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/example_data/Flat 3/3 "
|
|
"WILLIS ROAD FLAT 3 PRE EPR SITE NOTES.pdf",
|
|
"epr": "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/example_data/Flat 3/3 WILLIS "
|
|
"ROAD FLAT 3 PRE EPR PDF.pdf",
|
|
"scenario_site_notes": "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/example_data"
|
|
"/Flat 3/3 WILLIS ROAD FLAT 3 POST EPR SITE NOTES.pdf"
|
|
},
|
|
]
|
|
|
|
data = []
|
|
for data_config in folders:
|
|
|
|
file_mapping = {}
|
|
for filename, filepath in data_config.items():
|
|
with (open(filepath, "rb") as f):
|
|
pdf = PyPDF2.PdfReader(f)
|
|
first_page = pdf.pages[0].extract_text()
|
|
text = ""
|
|
for page in pdf.pages:
|
|
text += page.extract_text()
|
|
|
|
# Check the report type
|
|
report_type = detect_report_type(first_page)
|
|
if report_type is not None:
|
|
file_mapping[filename] = text
|
|
|
|
# This is only set up to work with quido site notes so we must have it
|
|
site_notes_extractor = SiteNotesExtractor(file_mapping["site_notes"])
|
|
site_notes = site_notes_extractor.extract_all()
|
|
|
|
# We also must have an EPR
|
|
epr_extractor = EPRExtractor(file_mapping["epr"])
|
|
epr = epr_extractor.extract_all()
|
|
|
|
# Valuation simulation
|
|
scenario_site_notes_extractor = SiteNotesExtractor(file_mapping["scenario_site_notes"])
|
|
scenario_site_notes = scenario_site_notes_extractor.extract_all()
|
|
|
|
from backend.ml_models.Valuation import PropertyValuation
|
|
valuation_uplift = PropertyValuation.estimate_valuation_improvement(
|
|
current_value=current_property_value,
|
|
current_epc=site_notes["Current EPC Band"],
|
|
target_epc=scenario_site_notes["Current EPC Band"],
|
|
)
|
|
# TODO - should convert this, when it's more than 5 figures and we should certainly stringify this
|
|
|
|
valuation_difference = round(valuation_uplift["average_increased_value"] - current_property_value)
|
|
|
|
# Prepare the data for output
|
|
bill_savings = round(
|
|
site_notes['Estimated Annual Energy Cost (£)'] - scenario_site_notes['Estimated Annual Energy Cost (£)']
|
|
)
|
|
|
|
carbon_savings = round(
|
|
site_notes["Current Carbon Emissions (TCO2)"] - scenario_site_notes["Current Carbon Emissions (TCO2)"],
|
|
2
|
|
)
|
|
|
|
payback_period = None
|
|
if payback_period is None:
|
|
raise NotImplementedError("Implement me")
|
|
|
|
# We extract the measures from the site notes
|
|
|
|
report_data = {
|
|
"current_epc_rating": site_notes["Current EPC Band"],
|
|
"current_epc_rating_colour": EPC_COLOURS[site_notes["Current EPC Band"]],
|
|
"post_retrofit_epc_rating": scenario_site_notes["Current EPC Band"],
|
|
"post_retrofit_epc_rating_colour": EPC_COLOURS[scenario_site_notes["Current EPC Band"]],
|
|
"bill_savings": stringify_number(bill_savings),
|
|
"valuation_improvement": stringify_number(valuation_difference),
|
|
"carbon_savings": carbon_savings,
|
|
|
|
}
|
|
|
|
# We now produce the combined data sheet which is the starting figure:
|
|
# data_sheet = {**epr, **site_notes}
|
|
# del data_sheet['Building Dimensions']
|
|
# # We unnest the Total Building Dimensions
|
|
# data_sheet["Total Building Floor Area (m2)"] = data_sheet["Total Building Dimensions"]["floor_area"]
|
|
# data_sheet["Total Building Heat Loss Area (m2)"] = data_sheet["Total Building Dimensions"]["heat_loss_area"]
|
|
# del data_sheet["Total Building Dimensions"]
|
|
|
|
create_pdf_response = placid_api.create_pdf(
|
|
template_uuid=TEMPLATE_UUID, **report_data
|
|
)
|
|
# {'id': 769832, 'type': 'pdf', 'status': 'queued', 'pdf_url': None, 'transfer_url': None, 'passthrough': None}
|
|
# Download locally
|
|
placid_api.get_pdf(create_pdf_response["id"])
|
|
|
|
data = pd.DataFrame(data)
|
|
|
|
# Generate the HTML report
|
|
# Placeholder locations
|
|
template_path = "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/template.html"
|
|
output_path = "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/output/report.html"
|
|
logo_path = "/Users/khalimconn-kowlessar/Documents/hestia/Model/survey_report/assets/logo.png"
|
|
generate_html_report(
|
|
template_path, output_path,
|
|
data={
|
|
"address": data_sheet["Address"],
|
|
"logo_path": logo_path,
|
|
"current_epc": data_sheet["Current EPC Band"],
|
|
"current_sap": data_sheet["Current SAP Rating"],
|
|
"potential_epc": "A", # TODO PLACEHOLDER
|
|
"potential_sap": 91, # TODO PLACEHOLDER
|
|
}
|
|
)
|