mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Persist resolved UPRN on PasHub site-notes rows (#1537)
Thread the UPRN the PashubService has already resolved for a job into the PasHub site-notes mapper so the EpcPropertyData aggregate is born with its uprn set, and the existing save_epc_property_data path persists it. - EpcPropertyDataMapper.from_site_notes gains uprn: Optional[int] = None and sets it unconditionally (site notes never carry a UPRN natively). - parse_site_notes_pdf / _parse_pashub forward the uprn; the Elmhurst branch is untouched. - PashubService coerces uprn str -> int in one place, carries it on the internal upload record, and passes it into parse_site_notes_pdf. - No new lookups; jobs with no known UPRN still persist null, unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
086294fe36
commit
b5c05166db
4 changed files with 46 additions and 8 deletions
|
|
@ -1,4 +1,4 @@
|
|||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
|
|
@ -8,13 +8,15 @@ from backend.documents_parser.extractor import PasHubRdSapSiteNotesExtractor
|
|||
from backend.documents_parser.pdf import pdf_to_pages, pdf_to_text_list
|
||||
|
||||
|
||||
def parse_site_notes_pdf(file_path: str) -> EpcPropertyData:
|
||||
def parse_site_notes_pdf(
|
||||
file_path: str, uprn: Optional[int] = None
|
||||
) -> EpcPropertyData:
|
||||
with open(file_path, "rb") as f:
|
||||
pdf_bytes = f.read()
|
||||
pages = pdf_to_pages(pdf_bytes)
|
||||
if "Elmhurst Energy Systems" in "\n".join(pages):
|
||||
return _parse_elmhurst(pages)
|
||||
return _parse_pashub(pdf_bytes)
|
||||
return _parse_pashub(pdf_bytes, uprn=uprn)
|
||||
|
||||
|
||||
def _parse_elmhurst(pages: List[str]) -> EpcPropertyData:
|
||||
|
|
@ -22,7 +24,9 @@ def _parse_elmhurst(pages: List[str]) -> EpcPropertyData:
|
|||
return EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
|
||||
|
||||
def _parse_pashub(pdf_bytes: bytes) -> EpcPropertyData:
|
||||
def _parse_pashub(
|
||||
pdf_bytes: bytes, uprn: Optional[int] = None
|
||||
) -> EpcPropertyData:
|
||||
tokens = pdf_to_text_list(pdf_bytes)
|
||||
site_notes = PasHubRdSapSiteNotesExtractor(tokens).extract()
|
||||
return EpcPropertyDataMapper.from_site_notes(site_notes)
|
||||
return EpcPropertyDataMapper.from_site_notes(site_notes, uprn=uprn)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class _FileUploadRecord(NamedTuple):
|
|||
file_path: str
|
||||
file_type: Optional[str]
|
||||
uploaded_file_id: int
|
||||
uprn: Optional[int]
|
||||
|
||||
|
||||
class PashubService:
|
||||
|
|
@ -149,6 +150,10 @@ class PashubService:
|
|||
else f"documents/hubspot_deal_id/{hubspot_deal_id}"
|
||||
)
|
||||
|
||||
# Coerce the service-held UPRN string to an int in exactly one place so
|
||||
# the site-notes row's UPRN cannot diverge from the UploadedFile row's.
|
||||
uprn_int: Optional[int] = int(uprn) if uprn else None
|
||||
|
||||
file_paths: List[str] = []
|
||||
uploaded_files: List[UploadedFile] = []
|
||||
|
||||
|
|
@ -162,7 +167,7 @@ class PashubService:
|
|||
s3_file_bucket=self._s3_bucket,
|
||||
s3_file_key=file_key,
|
||||
s3_upload_timestamp=datetime.now(timezone.utc),
|
||||
uprn=int(uprn) if uprn else None,
|
||||
uprn=uprn_int,
|
||||
hubspot_deal_id=hubspot_deal_id,
|
||||
file_source=file_source.value,
|
||||
file_type=get_file_type_string(filename, df.evidence_category) or default_file_type,
|
||||
|
|
@ -178,6 +183,7 @@ class PashubService:
|
|||
file_path=fp,
|
||||
file_type=cast(Optional[str], uf.file_type),
|
||||
uploaded_file_id=cast(int, uf.id),
|
||||
uprn=uprn_int,
|
||||
)
|
||||
for fp, uf in zip(file_paths, uploaded_files)
|
||||
]
|
||||
|
|
@ -192,7 +198,9 @@ class PashubService:
|
|||
):
|
||||
continue
|
||||
try:
|
||||
epc_data: EpcPropertyData = parse_site_notes_pdf(record.file_path)
|
||||
epc_data: EpcPropertyData = parse_site_notes_pdf(
|
||||
record.file_path, uprn=record.uprn
|
||||
)
|
||||
with db_session() as session:
|
||||
save_epc_property_data(
|
||||
session, epc_data, uploaded_file_id=record.uploaded_file_id
|
||||
|
|
|
|||
|
|
@ -280,7 +280,9 @@ def _map_schema_21_pv(
|
|||
class EpcPropertyDataMapper:
|
||||
|
||||
@staticmethod
|
||||
def from_site_notes(survey: PasHubRdSapSiteNotes) -> EpcPropertyData:
|
||||
def from_site_notes(
|
||||
survey: PasHubRdSapSiteNotes, uprn: Optional[int] = None
|
||||
) -> EpcPropertyData:
|
||||
general = survey.general
|
||||
metadata = survey.inspection_metadata
|
||||
address_parts = [p.strip() for p in metadata.property_address.split(", ")]
|
||||
|
|
@ -336,6 +338,7 @@ class EpcPropertyDataMapper:
|
|||
) # TODO: verify that is the correct approach
|
||||
|
||||
return EpcPropertyData(
|
||||
uprn=uprn,
|
||||
dwelling_type=f"{general.detachment_type} {general.property_type.lower()}",
|
||||
inspection_date=general.inspection_date,
|
||||
tenure=general.tenure,
|
||||
|
|
|
|||
|
|
@ -723,6 +723,29 @@ class TestUnmeasurableWallThickness:
|
|||
assert result.sap_building_parts[0].wall_thickness_mm is None
|
||||
|
||||
|
||||
class TestFromSiteNotesUprnInjection:
|
||||
"""Site notes never carry a UPRN natively, but the PasHub path can inject a
|
||||
UPRN the service has already resolved. A supplied UPRN must land on the
|
||||
resulting EpcPropertyData; the default (none supplied) must yield no UPRN.
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def survey(self) -> PasHubRdSapSiteNotes:
|
||||
return from_dict(
|
||||
PasHubRdSapSiteNotes, load("pashub_rdsap_site_notes_example1.json")
|
||||
)
|
||||
|
||||
def test_supplied_uprn_lands_on_result(
|
||||
self, survey: PasHubRdSapSiteNotes
|
||||
) -> None:
|
||||
result = EpcPropertyDataMapper.from_site_notes(survey, uprn=100010344955)
|
||||
assert result.uprn == 100010344955
|
||||
|
||||
def test_default_yields_no_uprn(self, survey: PasHubRdSapSiteNotes) -> None:
|
||||
result = EpcPropertyDataMapper.from_site_notes(survey)
|
||||
assert result.uprn is None
|
||||
|
||||
|
||||
class TestElmhurstSecondaryFuelFromSapCode:
|
||||
"""`_elmhurst_secondary_fuel_from_sap_code` must RAISE on an unmapped
|
||||
fuel-fired Table 4a Category-10 room-heater code rather than return
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue