mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-19 17:03:02 +00:00
A live test returned 'no documents could be packaged' for 91 correctly-resolved properties. Root cause: no upload source populates uploaded_files.landlord_property_id (pashub/magic-plan/audit set hubspot_deal_id; ECMK sets hubspot_listing_id), so matching on landlord_property_id found zero rows. Match fix: the worker now joins uploaded_files -> hubspot_deal_data (on deal_id) -> landlord_property_id, taking the property identity from the bridge. The repository returns a small PropertyDocument read-model instead of the infra ORM row, so the orchestrator no longer names infrastructure.postgres.* (resolves the leak dancafc flagged) and the s3_upload_timestamp cast is gone. Coverage is limited to deal-id-linked sources; listing_id/uprn-only files are a noted follow-up. Observability: the empty-selection failure now carries stage counts (selected/matched/planned/skipped) in both the message (-> the worker WARNING log) and details (-> sub_task.outputs), and the run logs those counts. The next failure says 'matched 0 documents' instead of failing opaquely. ADR-0060 + CONTEXT.md updated (matching decision + considered options). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
2.7 KiB
Python
66 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Optional, Sequence
|
|
|
|
from sqlalchemy import select, text
|
|
from sqlmodel import Session, col
|
|
|
|
from domain.bulk_document_download.property_document import PropertyDocument
|
|
from infrastructure.postgres.uploaded_file_table import FileTypeEnum, UploadedFile
|
|
|
|
|
|
class UploadedFilePostgresRepository:
|
|
def __init__(self, session: Session) -> None:
|
|
self._session = session
|
|
|
|
def get_latest_by_hubspot_deal_id(
|
|
self, hubspot_deal_id: str, file_type: FileTypeEnum
|
|
) -> Optional[UploadedFile]:
|
|
stmt = (
|
|
select(UploadedFile)
|
|
.where(col(UploadedFile.hubspot_deal_id) == hubspot_deal_id)
|
|
.where(col(UploadedFile.file_type) == file_type.value)
|
|
.order_by(col(UploadedFile.s3_upload_timestamp).desc())
|
|
.limit(1)
|
|
)
|
|
return self._session.execute(stmt).scalars().one_or_none() # pyright: ignore[reportDeprecated]
|
|
|
|
def by_landlord_property_ids(
|
|
self, landlord_property_ids: Sequence[str]
|
|
) -> list[PropertyDocument]:
|
|
"""Every uploaded file held for the given properties (ADR-0060), across
|
|
all Document Types, as a small domain read-model.
|
|
|
|
Files are matched by **`hubspot_deal_id`**, bridged to the property
|
|
through `hubspot_deal_data` — `uploaded_files.landlord_property_id` is
|
|
unpopulated in practice (no upload source sets it), so the property
|
|
identity comes from the deal bridge, not the file row. Coverage is
|
|
therefore limited to deal-id-linked sources (pashub, magic plan, audit
|
|
generator); files keyed only by `hubspot_listing_id` or `uprn` are not
|
|
reached — see the ADR-0060 matching decision.
|
|
|
|
Latest-per-Document-Type selection and null-type skipping are the
|
|
Download Package plan's job, not the query's."""
|
|
rows = self._session.connection().execute(
|
|
text(
|
|
"SELECT h.landlord_property_id, u.file_type,"
|
|
" u.s3_file_bucket, u.s3_file_key, u.s3_upload_timestamp"
|
|
" FROM uploaded_files u"
|
|
" JOIN hubspot_deal_data h ON h.deal_id = u.hubspot_deal_id"
|
|
" WHERE h.landlord_property_id = ANY(:lpids)"
|
|
),
|
|
{"lpids": list(landlord_property_ids)},
|
|
)
|
|
return [
|
|
PropertyDocument(
|
|
landlord_property_id=row[0],
|
|
document_type=row[1],
|
|
s3_bucket=row[2],
|
|
s3_key=row[3],
|
|
uploaded_at=row[4],
|
|
)
|
|
for row in rows.all()
|
|
]
|
|
|
|
def insert(self, uploaded_file: UploadedFile) -> None:
|
|
self._session.add(uploaded_file)
|