mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
define db methods
This commit is contained in:
parent
e0857ab7a2
commit
598a612b40
2 changed files with 175 additions and 102 deletions
|
|
@ -3,15 +3,29 @@ from sqlalchemy import insert, delete
|
|||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from backend.app.db.models.recommendations import (
|
||||
Plan, Recommendation, RecommendationMaterials, PlanRecommendations, Scenario
|
||||
Plan,
|
||||
Recommendation,
|
||||
RecommendationMaterials,
|
||||
PlanRecommendations,
|
||||
Scenario,
|
||||
)
|
||||
from backend.app.db.models.portfolio import PropertyModel
|
||||
from backend.app.db.connection import db_session, db_read_session
|
||||
|
||||
|
||||
def prepare_plan_data(
|
||||
p, body, scenario_id, eco_packages, valuations, new_sap_points, new_epc, default_recommendations,
|
||||
rebaselining_carbon=0, rebaselining_heat_demand=0, rebaselining_kwh=0, rebaselining_bills=0,
|
||||
p,
|
||||
body,
|
||||
scenario_id,
|
||||
eco_packages,
|
||||
valuations,
|
||||
new_sap_points,
|
||||
new_epc,
|
||||
default_recommendations,
|
||||
rebaselining_carbon=0,
|
||||
rebaselining_heat_demand=0,
|
||||
rebaselining_kwh=0,
|
||||
rebaselining_bills=0,
|
||||
):
|
||||
"""
|
||||
Utility function to prepare the data that goes into the production of a plan. Is a fairly rough and unstructured
|
||||
|
|
@ -32,21 +46,37 @@ def prepare_plan_data(
|
|||
"""
|
||||
# Plan carbon savings
|
||||
co2_savings = sum(
|
||||
[r["co2_equivalent_savings"] for r in default_recommendations if not r.get("already_installed", False)]
|
||||
[
|
||||
r["co2_equivalent_savings"]
|
||||
for r in default_recommendations
|
||||
if not r.get("already_installed", False)
|
||||
]
|
||||
)
|
||||
post_co2_emissions = p.energy["co2_emissions"] - rebaselining_carbon - co2_savings
|
||||
|
||||
# Plan bill savings
|
||||
energy_bill_savings = sum(
|
||||
[r["energy_cost_savings"] for r in default_recommendations if not r.get("already_installed", False)]
|
||||
[
|
||||
r["energy_cost_savings"]
|
||||
for r in default_recommendations
|
||||
if not r.get("already_installed", False)
|
||||
]
|
||||
)
|
||||
post_energy_bill = (
|
||||
sum(p.current_energy_bill.values()) - rebaselining_bills - energy_bill_savings
|
||||
)
|
||||
post_energy_bill = sum(p.current_energy_bill.values()) - rebaselining_bills - energy_bill_savings
|
||||
|
||||
# energy consumption
|
||||
energy_consumption_savings = sum(
|
||||
[r["kwh_savings"] for r in default_recommendations if not r.get("already_installed", False)]
|
||||
[
|
||||
r["kwh_savings"]
|
||||
for r in default_recommendations
|
||||
if not r.get("already_installed", False)
|
||||
]
|
||||
)
|
||||
post_energy_consumption = (
|
||||
p.current_energy_consumption - rebaselining_kwh - energy_consumption_savings
|
||||
)
|
||||
post_energy_consumption = p.current_energy_consumption - rebaselining_kwh - energy_consumption_savings
|
||||
|
||||
valuation_post_retrofit, valuation_increase = None, None
|
||||
if valuations["current_value"]:
|
||||
|
|
@ -54,9 +84,19 @@ def prepare_plan_data(
|
|||
valuation_post_retrofit = valuations["average_increased_value"]
|
||||
|
||||
# plan costing data
|
||||
cost_of_works = sum([r["total"] for r in default_recommendations if not r.get("already_installed", False)])
|
||||
cost_of_works = sum(
|
||||
[
|
||||
r["total"]
|
||||
for r in default_recommendations
|
||||
if not r.get("already_installed", False)
|
||||
]
|
||||
)
|
||||
contingency_cost = sum(
|
||||
[r.get("contingency", 0) for r in default_recommendations if not r.get("already_installed", False)]
|
||||
[
|
||||
r.get("contingency", 0)
|
||||
for r in default_recommendations
|
||||
if not r.get("already_installed", False)
|
||||
]
|
||||
)
|
||||
|
||||
return {
|
||||
|
|
@ -86,7 +126,7 @@ def prepare_plan_data(
|
|||
"valuation_increase": valuation_increase,
|
||||
"cost_of_works": float(cost_of_works),
|
||||
"contingency_cost": float(contingency_cost),
|
||||
"plan_type": eco_packages.get(p.id, (None, None, None))[2]
|
||||
"plan_type": eco_packages.get(p.id, (None, None, None))[2],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -119,11 +159,7 @@ def bulk_create_plans(session: Session, plans_to_create: list[dict]) -> dict[int
|
|||
for p in plans_to_create
|
||||
]
|
||||
|
||||
stmt = (
|
||||
insert(Plan)
|
||||
.values(payload)
|
||||
.returning(Plan.id, Plan.property_id)
|
||||
)
|
||||
stmt = insert(Plan).values(payload).returning(Plan.id, Plan.property_id)
|
||||
|
||||
result = session.execute(stmt).all()
|
||||
|
||||
|
|
@ -133,9 +169,7 @@ def bulk_create_plans(session: Session, plans_to_create: list[dict]) -> dict[int
|
|||
|
||||
def create_scenario(session: Session, scenario: dict) -> int:
|
||||
existing_scenario = (
|
||||
session.query(Scenario)
|
||||
.filter_by(portfolio_id=scenario["portfolio_id"])
|
||||
.first()
|
||||
session.query(Scenario).filter_by(portfolio_id=scenario["portfolio_id"]).first()
|
||||
)
|
||||
|
||||
scenario["is_default"] = not bool(existing_scenario)
|
||||
|
|
@ -167,7 +201,9 @@ def create_recommendation(session: Session, recommendation):
|
|||
raise e
|
||||
|
||||
|
||||
def create_recommendation_material(session: Session, recommendation_id, material_id, depth):
|
||||
def create_recommendation_material(
|
||||
session: Session, recommendation_id, material_id, depth
|
||||
):
|
||||
"""
|
||||
This function will create a record for the recommendation_material in the database if it does not exist.
|
||||
:param session: The databse session
|
||||
|
|
@ -177,9 +213,7 @@ def create_recommendation_material(session: Session, recommendation_id, material
|
|||
"""
|
||||
|
||||
new_recommendation_material = RecommendationMaterials(
|
||||
recommendation_id=recommendation_id,
|
||||
material_id=material_id,
|
||||
depth=depth
|
||||
recommendation_id=recommendation_id, material_id=material_id, depth=depth
|
||||
)
|
||||
session.add(new_recommendation_material)
|
||||
session.flush()
|
||||
|
|
@ -196,13 +230,17 @@ def create_plan_recommendations(session: Session, plan_id, recommendation_ids):
|
|||
"""
|
||||
|
||||
# Prepare a list of dictionaries for bulk insert
|
||||
data = [{"plan_id": plan_id, "recommendation_id": rid} for rid in recommendation_ids]
|
||||
data = [
|
||||
{"plan_id": plan_id, "recommendation_id": rid} for rid in recommendation_ids
|
||||
]
|
||||
|
||||
# Bulk insert using SQLAlchemy's core API
|
||||
session.execute(insert(PlanRecommendations).values(data))
|
||||
|
||||
|
||||
def upload_recommendations(session: Session, recommendations_to_upload, property_id, new_plan_id):
|
||||
def upload_recommendations(
|
||||
session: Session, recommendations_to_upload, property_id, new_plan_id
|
||||
):
|
||||
try:
|
||||
# Prepare data for bulk insert for Recommendation
|
||||
recommendations_data = [
|
||||
|
|
@ -213,8 +251,14 @@ def upload_recommendations(session: Session, recommendations_to_upload, property
|
|||
"description": rec["description"],
|
||||
"estimated_cost": float(rec["total"]),
|
||||
"default": rec["default"],
|
||||
"starting_u_value": float(rec.get("starting_u_value")) if rec.get("starting_u_value") else None,
|
||||
"new_u_value": float(rec.get("new_u_value")) if rec.get("new_u_value") else None,
|
||||
"starting_u_value": (
|
||||
float(rec.get("starting_u_value"))
|
||||
if rec.get("starting_u_value")
|
||||
else None
|
||||
),
|
||||
"new_u_value": (
|
||||
float(rec.get("new_u_value")) if rec.get("new_u_value") else None
|
||||
),
|
||||
"sap_points": float(rec["sap_points"]),
|
||||
"energy_savings": float(rec["heat_demand"]),
|
||||
"kwh_savings": float(rec["kwh_savings"]),
|
||||
|
|
@ -223,13 +267,17 @@ def upload_recommendations(session: Session, recommendations_to_upload, property
|
|||
"energy_cost_savings": float(rec["energy_cost_savings"]),
|
||||
"labour_days": float(rec["labour_days"]),
|
||||
"already_installed": rec["already_installed"],
|
||||
"heat_demand": float(rec["heat_demand"])
|
||||
"heat_demand": float(rec["heat_demand"]),
|
||||
}
|
||||
for rec in recommendations_to_upload
|
||||
]
|
||||
|
||||
# Insert the recommendations, get back the IDs
|
||||
stmt = insert(Recommendation).returning(Recommendation.id).values(recommendations_data)
|
||||
stmt = (
|
||||
insert(Recommendation)
|
||||
.returning(Recommendation.id)
|
||||
.values(recommendations_data)
|
||||
)
|
||||
result = session.execute(stmt)
|
||||
uploaded_recommendation_ids = [row[0] for row in result]
|
||||
|
||||
|
|
@ -243,11 +291,15 @@ def upload_recommendations(session: Session, recommendations_to_upload, property
|
|||
"quantity_unit": part.get("quantity_unit", None),
|
||||
"estimated_cost": float(part.get("total", part.get("total_cost"))),
|
||||
}
|
||||
for rec, recommendation_id in zip(recommendations_to_upload, uploaded_recommendation_ids)
|
||||
for rec, recommendation_id in zip(
|
||||
recommendations_to_upload, uploaded_recommendation_ids
|
||||
)
|
||||
for part in rec["parts"]
|
||||
]
|
||||
|
||||
session.bulk_insert_mappings(RecommendationMaterials, recommendation_materials_data)
|
||||
session.bulk_insert_mappings(
|
||||
RecommendationMaterials, recommendation_materials_data
|
||||
)
|
||||
|
||||
# flush the changes to get the newly created IDs
|
||||
session.flush()
|
||||
|
|
@ -283,25 +335,27 @@ def bulk_upload_recommendations_and_materials(
|
|||
plan_ids_by_index = []
|
||||
|
||||
for rec in recommendation_payload:
|
||||
recommendation_rows.append({
|
||||
"property_id": rec["property_id"],
|
||||
"type": rec["type"],
|
||||
"measure_type": rec["measure_type"],
|
||||
"description": rec["description"],
|
||||
"estimated_cost": rec["estimated_cost"],
|
||||
"default": rec["default"],
|
||||
"starting_u_value": rec["starting_u_value"],
|
||||
"new_u_value": rec["new_u_value"],
|
||||
"sap_points": rec["sap_points"],
|
||||
"heat_demand": rec["heat_demand"],
|
||||
"kwh_savings": rec["kwh_savings"],
|
||||
"co2_equivalent_savings": rec["co2_equivalent_savings"],
|
||||
"energy_savings": rec["energy_savings"],
|
||||
"energy_cost_savings": rec["energy_cost_savings"],
|
||||
"total_work_hours": rec["total_work_hours"],
|
||||
"labour_days": rec["labour_days"],
|
||||
"already_installed": rec["already_installed"],
|
||||
})
|
||||
recommendation_rows.append(
|
||||
{
|
||||
"property_id": rec["property_id"],
|
||||
"type": rec["type"],
|
||||
"measure_type": rec["measure_type"],
|
||||
"description": rec["description"],
|
||||
"estimated_cost": rec["estimated_cost"],
|
||||
"default": rec["default"],
|
||||
"starting_u_value": rec["starting_u_value"],
|
||||
"new_u_value": rec["new_u_value"],
|
||||
"sap_points": rec["sap_points"],
|
||||
"heat_demand": rec["heat_demand"],
|
||||
"kwh_savings": rec["kwh_savings"],
|
||||
"co2_equivalent_savings": rec["co2_equivalent_savings"],
|
||||
"energy_savings": rec["energy_savings"],
|
||||
"energy_cost_savings": rec["energy_cost_savings"],
|
||||
"total_work_hours": rec["total_work_hours"],
|
||||
"labour_days": rec["labour_days"],
|
||||
"already_installed": rec["already_installed"],
|
||||
}
|
||||
)
|
||||
|
||||
parts_by_index.append(rec["parts"])
|
||||
plan_ids_by_index.append(rec["plan_id"])
|
||||
|
|
@ -310,9 +364,7 @@ def bulk_upload_recommendations_and_materials(
|
|||
# 2. Insert recommendations and get IDs
|
||||
# ---------------------------------------------------------
|
||||
result = session.execute(
|
||||
insert(Recommendation)
|
||||
.values(recommendation_rows)
|
||||
.returning(Recommendation.id)
|
||||
insert(Recommendation).values(recommendation_rows).returning(Recommendation.id)
|
||||
)
|
||||
|
||||
recommendation_ids = [row[0] for row in result]
|
||||
|
|
@ -324,19 +376,19 @@ def bulk_upload_recommendations_and_materials(
|
|||
|
||||
for recommendation_id, parts in zip(recommendation_ids, parts_by_index):
|
||||
for part in parts:
|
||||
materials_rows.append({
|
||||
"recommendation_id": recommendation_id,
|
||||
"material_id": part["material_id"],
|
||||
"depth": part["depth"],
|
||||
"quantity": part["quantity"],
|
||||
"quantity_unit": part["quantity_unit"],
|
||||
"estimated_cost": part["estimated_cost"],
|
||||
})
|
||||
materials_rows.append(
|
||||
{
|
||||
"recommendation_id": recommendation_id,
|
||||
"material_id": part["material_id"],
|
||||
"depth": part["depth"],
|
||||
"quantity": part["quantity"],
|
||||
"quantity_unit": part["quantity_unit"],
|
||||
"estimated_cost": part["estimated_cost"],
|
||||
}
|
||||
)
|
||||
|
||||
if materials_rows:
|
||||
session.execute(
|
||||
insert(RecommendationMaterials).values(materials_rows)
|
||||
)
|
||||
session.execute(insert(RecommendationMaterials).values(materials_rows))
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 4. Insert plan ↔ recommendation links
|
||||
|
|
@ -346,26 +398,22 @@ def bulk_upload_recommendations_and_materials(
|
|||
"plan_id": plan_id,
|
||||
"recommendation_id": recommendation_id,
|
||||
}
|
||||
for plan_id, recommendation_id in zip(
|
||||
plan_ids_by_index, recommendation_ids
|
||||
)
|
||||
for plan_id, recommendation_id in zip(plan_ids_by_index, recommendation_ids)
|
||||
]
|
||||
|
||||
session.execute(
|
||||
insert(PlanRecommendations).values(plan_recommendation_rows)
|
||||
)
|
||||
session.execute(insert(PlanRecommendations).values(plan_recommendation_rows))
|
||||
|
||||
|
||||
def chunked(iterable, size=100):
|
||||
for i in range(0, len(iterable), size):
|
||||
yield iterable[i:i + size]
|
||||
yield iterable[i : i + size]
|
||||
|
||||
|
||||
def get_property_ids(portfolio_id: int) -> list[int]:
|
||||
with db_read_session() as session:
|
||||
return [
|
||||
pid for (pid,) in
|
||||
session.query(PropertyModel.id)
|
||||
pid
|
||||
for (pid,) in session.query(PropertyModel.id)
|
||||
.filter(PropertyModel.portfolio_id == portfolio_id)
|
||||
.all()
|
||||
]
|
||||
|
|
@ -381,12 +429,14 @@ def delete_property_batch(session: Session, property_ids: list[int]):
|
|||
# recommendation_materials (via recommendation)
|
||||
# --------------------------------------------------
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM recommendation_materials rm
|
||||
USING recommendation r
|
||||
WHERE rm.recommendation_id = r.id
|
||||
AND r.property_id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
|
|
@ -394,12 +444,14 @@ def delete_property_batch(session: Session, property_ids: list[int]):
|
|||
# plan_recommendations (via plan)
|
||||
# --------------------------------------------------
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM plan_recommendations pr
|
||||
USING plan p
|
||||
WHERE pr.plan_id = p.id
|
||||
AND p.property_id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
|
|
@ -407,13 +459,15 @@ def delete_property_batch(session: Session, property_ids: list[int]):
|
|||
# funding_package_measures
|
||||
# --------------------------------------------------
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM funding_package_measures fpm
|
||||
USING funding_package fp, plan p
|
||||
WHERE fpm.funding_package_id = fp.id
|
||||
AND fp.plan_id = p.id
|
||||
AND p.property_id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
|
|
@ -421,10 +475,12 @@ def delete_property_batch(session: Session, property_ids: list[int]):
|
|||
# inspections (direct)
|
||||
# --------------------------------------------------
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM inspections
|
||||
WHERE property_id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
|
|
@ -432,12 +488,14 @@ def delete_property_batch(session: Session, property_ids: list[int]):
|
|||
# funding_package
|
||||
# --------------------------------------------------
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM funding_package fp
|
||||
USING plan p
|
||||
WHERE fp.plan_id = p.id
|
||||
AND p.property_id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
|
|
@ -445,10 +503,12 @@ def delete_property_batch(session: Session, property_ids: list[int]):
|
|||
# recommendation (direct — CRITICAL FIX)
|
||||
# --------------------------------------------------
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM recommendation
|
||||
WHERE property_id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
|
|
@ -456,10 +516,12 @@ def delete_property_batch(session: Session, property_ids: list[int]):
|
|||
# plan (direct)
|
||||
# --------------------------------------------------
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM plan
|
||||
WHERE property_id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
|
|
@ -467,18 +529,22 @@ def delete_property_batch(session: Session, property_ids: list[int]):
|
|||
# property-scoped tables
|
||||
# --------------------------------------------------
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM property_details_epc
|
||||
WHERE property_id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM property_targets
|
||||
WHERE property_id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
|
|
@ -486,10 +552,12 @@ def delete_property_batch(session: Session, property_ids: list[int]):
|
|||
# properties LAST
|
||||
# --------------------------------------------------
|
||||
session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
DELETE FROM property
|
||||
WHERE id = ANY(:property_ids)
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
|
||||
|
|
@ -509,10 +577,7 @@ def delete_portfolio_scenarios_if_empty(portfolio_id: int):
|
|||
return
|
||||
|
||||
with db_session() as session:
|
||||
session.execute(
|
||||
delete(Scenario)
|
||||
.where(Scenario.portfolio_id == portfolio_id)
|
||||
)
|
||||
session.execute(delete(Scenario).where(Scenario.portfolio_id == portfolio_id))
|
||||
|
||||
print("Deleted scenarios for empty portfolio")
|
||||
|
||||
|
|
@ -530,6 +595,7 @@ def clear_portfolio_in_batches(
|
|||
|
||||
total = (len(property_ids) + property_batch_size - 1) // property_batch_size
|
||||
import time
|
||||
|
||||
for i, batch in enumerate(chunked(property_ids, property_batch_size), start=1):
|
||||
print(f"Deleting batch {i}/{total} ({len(batch)} properties)")
|
||||
start_time = time.time()
|
||||
|
|
@ -542,3 +608,15 @@ def clear_portfolio_in_batches(
|
|||
delete_portfolio_scenarios_if_empty(portfolio_id)
|
||||
|
||||
print("Portfolio cleared in batches.")
|
||||
|
||||
|
||||
def get_plans_by_portfolio_id(portfolio_id: int) -> list[Plan]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def get_scenario(scenario_id: int) -> list[Scenario]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def set_plan_default(plan_id: int, is_default: bool) -> bool:
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
from backend.app.db.connection import db_session
|
||||
|
||||
|
||||
class CategorisationPostgres:
|
||||
pass
|
||||
Loading…
Add table
Reference in a new issue