mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Merge pull request #1370 from Hestia-Homes/feature/ventilation-audit-changes
Ventilation audit changes: directly populate Total Effective Area for trickle vents from form data, rather than calculating it
This commit is contained in:
commit
a8821c9cdb
9 changed files with 161 additions and 15 deletions
Binary file not shown.
|
|
@ -0,0 +1,11 @@
|
|||
version: "3.9"
|
||||
|
||||
services:
|
||||
audit-generator-lambda:
|
||||
build:
|
||||
context: ../../../
|
||||
dockerfile: applications/audit_generator/handler/Dockerfile
|
||||
ports:
|
||||
- "9001:8080"
|
||||
env_file:
|
||||
- ../../../.env
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env python3
|
||||
import json
|
||||
import requests
|
||||
|
||||
HOST = "localhost"
|
||||
PORT = "9001"
|
||||
|
||||
LAMBDA_URL = f"http://{HOST}:{PORT}/2015-03-31/functions/function/invocations"
|
||||
|
||||
payload = {
|
||||
"Records": [
|
||||
{
|
||||
"messageId": "test-message-id",
|
||||
"body": json.dumps(
|
||||
{
|
||||
"task_id": "00000000-0000-0000-0000-000000000001",
|
||||
"sub_task_id": "00000000-0000-0000-0000-000000000002",
|
||||
"hubspot_deal_id": "500311510234",
|
||||
}
|
||||
),
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
response = requests.post(LAMBDA_URL, json=payload)
|
||||
|
||||
print("Status code:", response.status_code)
|
||||
print("Response:")
|
||||
print(response.text)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Run AuditGeneratorOrchestrator directly, bypassing @subtask_handler.
|
||||
|
||||
Loads credentials from the repo-root .env file so no DB task/subtask rows
|
||||
are needed.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import boto3
|
||||
from dotenv import load_dotenv
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
load_dotenv(REPO_ROOT / ".env")
|
||||
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from utilities.logger import setup_logger
|
||||
|
||||
setup_logger()
|
||||
|
||||
from infrastructure.postgres.config import PostgresConfig
|
||||
from infrastructure.postgres.engine import make_engine, make_session
|
||||
from infrastructure.s3.s3_client import S3Client
|
||||
from orchestration.audit_generator_orchestrator import AuditGeneratorOrchestrator
|
||||
from orchestration.audit_generator_unit_of_work import AuditGeneratorUnitOfWork
|
||||
|
||||
HUBSPOT_DEAL_ID = "505712829658"
|
||||
|
||||
boto3_client: Any = boto3.client # type: ignore[attr-defined]
|
||||
s3_client = S3Client(
|
||||
boto_s3_client=boto3_client("s3"),
|
||||
# bucket=os.environ["S3_BUCKET_NAME"],
|
||||
bucket="retrofit-energy-assessments-dev",
|
||||
)
|
||||
|
||||
engine = make_engine(PostgresConfig.from_env(os.environ))
|
||||
|
||||
|
||||
def session_factory() -> Any:
|
||||
return make_session(engine)
|
||||
|
||||
|
||||
def uow_factory() -> AuditGeneratorUnitOfWork:
|
||||
return AuditGeneratorUnitOfWork(session_factory)
|
||||
|
||||
|
||||
print(f"Running AuditGeneratorOrchestrator for deal: {HUBSPOT_DEAL_ID!r}")
|
||||
AuditGeneratorOrchestrator(
|
||||
hubspot_deal_id=HUBSPOT_DEAL_ID,
|
||||
s3_client=s3_client,
|
||||
uow_factory=uow_factory,
|
||||
).run()
|
||||
print("Done")
|
||||
|
|
@ -99,8 +99,17 @@ def _map_window_ventilation(
|
|||
opening_type=_try_get_str_value("Opening Type"),
|
||||
num_openings=_try_get_int_value("Number of Openings (In Same Window)"),
|
||||
pct_openable=_try_get_int_value("% of Window Openable"),
|
||||
trickle_vent_area_mm2=_try_get_int_value(
|
||||
"Trickle Vent Effective Area (mm2) (No Code Then Width x Height)"
|
||||
trickle_vent_area_mm2=next(
|
||||
(
|
||||
v
|
||||
for label in [
|
||||
"Total Trickle Vent Effective Area (mm2) (No Code Then Width x Height)",
|
||||
"Individual Trickle Vent Effective Area (mm2) (No Code Then Width x Height)",
|
||||
"Trickle Vent Effective Area (mm2) (No Code Then Width x Height)",
|
||||
]
|
||||
if (v := _try_get_int_value(label)) is not None
|
||||
),
|
||||
None,
|
||||
),
|
||||
num_trickle_vents=_try_get_int_value("No. of Trickle Vents"),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from domain.magicplan.models import Door, Plan, Room, Window
|
|||
|
||||
_DATA_START_ROW = 6
|
||||
_MAX_ROWS = 50
|
||||
_DOOR_AREA_COL = "U"
|
||||
_DOOR_AREA_COL = "T"
|
||||
_DOOR_AREA_CF_RANGE = f"{_DOOR_AREA_COL}{_DATA_START_ROW}:{_DOOR_AREA_COL}{_DATA_START_ROW + _MAX_ROWS - 1}"
|
||||
_Y_THRESHOLD = 7600
|
||||
_DOOR_AREA_HEADER = CellRichText(
|
||||
|
|
@ -94,9 +94,9 @@ def populate_sheet(sheet: Any, plan: Plan) -> None:
|
|||
for i, (room_name, door) in enumerate(doors):
|
||||
row = _DATA_START_ROW + i
|
||||
vent = door.ventilation
|
||||
_write_cell(sheet, row, "R", room_name)
|
||||
_write_cell(sheet, row, "S", door.width_mm)
|
||||
_write_cell(sheet, row, "T", vent.undercut_mm if vent else 0)
|
||||
# U = formula =S*T — do not write
|
||||
_write_cell(sheet, row, "Q", room_name)
|
||||
_write_cell(sheet, row, "R", door.width_mm)
|
||||
_write_cell(sheet, row, "S", vent.undercut_mm if vent else 0)
|
||||
# T = formula =R*S — do not write
|
||||
|
||||
_apply_column_y_formatting(sheet)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import domain.magicplan.api.response as api
|
|||
from domain.magicplan.api.response import MagicPlanPlan, Symbol, Vec3, WallItem
|
||||
from domain.magicplan.mapper import (
|
||||
_map_window, # pyright: ignore[reportPrivateUsage]
|
||||
_map_window_ventilation, # pyright: ignore[reportPrivateUsage]
|
||||
map_address,
|
||||
map_plan,
|
||||
)
|
||||
|
|
@ -229,3 +230,42 @@ def test_map_address_city_absent_is_omitted() -> None:
|
|||
|
||||
def test_map_address_none_returns_none() -> None:
|
||||
assert map_address(None) is None
|
||||
|
||||
|
||||
def _make_survey_field(label: str, value: str) -> api.SurveyField:
|
||||
return api.SurveyField(
|
||||
id="test",
|
||||
type=5,
|
||||
type_as_string="float",
|
||||
is_required=False,
|
||||
label=label,
|
||||
value=api.FieldValue(index=-1, has_value=True, is_array=False, value=value),
|
||||
)
|
||||
|
||||
|
||||
def test_trickle_vent_area_read_from_legacy_field_label() -> None:
|
||||
# Arrange — old MagicPlan label, not yet renamed in the API response
|
||||
fields = [_make_survey_field(
|
||||
"Trickle Vent Effective Area (mm2) (No Code Then Width x Height)", "1700"
|
||||
)]
|
||||
|
||||
# Act
|
||||
vent = _map_window_ventilation(fields)
|
||||
|
||||
# Assert
|
||||
assert vent is not None
|
||||
assert vent.trickle_vent_area_mm2 == 1700
|
||||
|
||||
|
||||
def test_trickle_vent_area_read_from_individual_field_label() -> None:
|
||||
# Arrange — "Individual" variant used in some MagicPlan templates
|
||||
fields = [_make_survey_field(
|
||||
"Individual Trickle Vent Effective Area (mm2) (No Code Then Width x Height)", "900"
|
||||
)]
|
||||
|
||||
# Act
|
||||
vent = _map_window_ventilation(fields)
|
||||
|
||||
# Assert
|
||||
assert vent is not None
|
||||
assert vent.trickle_vent_area_mm2 == 900
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ def test_writes_trickle_vent_data_to_columns_N_and_O() -> None:
|
|||
assert sheet["O6"].value == 2
|
||||
|
||||
|
||||
def test_writes_door_room_name_to_column_R() -> None:
|
||||
def test_writes_door_room_name_to_column_Q() -> None:
|
||||
# Arrange
|
||||
plan = _make_plan(num_rooms=1, num_windows_per_room=0, num_doors_per_room=1)
|
||||
sheet = _blank_sheet()
|
||||
|
|
@ -157,10 +157,10 @@ def test_writes_door_room_name_to_column_R() -> None:
|
|||
populate_sheet(sheet, plan)
|
||||
|
||||
# Assert
|
||||
assert sheet["R6"].value == "Room 0"
|
||||
assert sheet["Q6"].value == "Room 0"
|
||||
|
||||
|
||||
def test_writes_door_dimensions_to_columns_S_and_T() -> None:
|
||||
def test_writes_door_dimensions_to_columns_R_and_S() -> None:
|
||||
# Arrange
|
||||
plan = _make_plan(num_rooms=1, num_windows_per_room=0, num_doors_per_room=1)
|
||||
sheet = _blank_sheet()
|
||||
|
|
@ -169,8 +169,8 @@ def test_writes_door_dimensions_to_columns_S_and_T() -> None:
|
|||
populate_sheet(sheet, plan)
|
||||
|
||||
# Assert
|
||||
assert sheet["S6"].value == 800.0
|
||||
assert sheet["T6"].value == 10.0
|
||||
assert sheet["R6"].value == 800.0
|
||||
assert sheet["S6"].value == 10.0
|
||||
|
||||
|
||||
def test_writes_zeros_when_window_has_no_ventilation() -> None:
|
||||
|
|
@ -200,7 +200,7 @@ def test_writes_zeros_when_door_has_no_ventilation() -> None:
|
|||
populate_sheet(sheet, plan)
|
||||
|
||||
# Assert
|
||||
assert sheet["T6"].value == 0
|
||||
assert sheet["S6"].value == 0
|
||||
|
||||
|
||||
_TEMPLATE_PATH = (
|
||||
|
|
@ -220,4 +220,4 @@ def test_real_template_survives_18_doors() -> None:
|
|||
|
||||
populate_sheet(sheet, plan)
|
||||
|
||||
assert sheet["R23"].value == "Room 17"
|
||||
assert sheet["Q23"].value == "Room 17"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue