"""The Download Package plan (ADR-0060). Pure logic: given the documents resolved for a set of properties, decide what goes where in the ZIP — one folder per property, the latest file of each Document Type — and which rows are skipped and reported. No I/O: the orchestrator does the S3/DB work; this module only lays out the archive. """ from __future__ import annotations import posixpath from dataclasses import dataclass from datetime import datetime from typing import Optional, Sequence @dataclass(frozen=True) class ResolvedDocument: """One `uploaded_files` row resolved for packaging: its property identity (matched by `landlord_property_id`, ADR-0060), the display address enriched from the hubspot deals data, its Document Type (`file_type`, nullable), the S3 location to read it from, and when it was uploaded (for latest-per-type). """ landlord_property_id: str address: str document_type: Optional[str] s3_bucket: str s3_key: str uploaded_at: datetime @dataclass(frozen=True) class PackageEntry: """One file to place in the Download Package: where it lands in the ZIP and the S3 object to stream in from.""" zip_path: str s3_bucket: str s3_key: str @dataclass(frozen=True) class SkippedDocument: """A resolved row left out of the package, with the reason (surfaced in the run's `sub_task.outputs`, ADR-0060).""" landlord_property_id: str s3_key: str reason: str @dataclass(frozen=True) class DownloadPackagePlan: """The laid-out archive: the files to include and the rows skipped.""" entries: tuple[PackageEntry, ...] skipped: tuple[SkippedDocument, ...] def _extension_of(s3_key: str) -> str: """The file extension of an S3 key (``.pdf``, ``.zip``, …), empty when the key's basename has none. Used to keep the packaged file openable while naming it by Document Type.""" _root, ext = posixpath.splitext(posixpath.basename(s3_key)) return ext def plan_download_package( documents: Sequence[ResolvedDocument], ) -> DownloadPackagePlan: """Lay out a Download Package from resolved documents (ADR-0060): one folder per property, the latest file of each Document Type, null-Document-Type rows skipped and reported.""" latest_by_group: dict[tuple[str, str], ResolvedDocument] = {} skipped: list[SkippedDocument] = [] for document in documents: if document.document_type is None: skipped.append( SkippedDocument( landlord_property_id=document.landlord_property_id, s3_key=document.s3_key, reason="null_document_type", ) ) continue group = (document.landlord_property_id, document.document_type) incumbent = latest_by_group.get(group) if incumbent is None or document.uploaded_at > incumbent.uploaded_at: latest_by_group[group] = document entries: list[PackageEntry] = [ PackageEntry( zip_path=f"{document.address} ({document.landlord_property_id})/" f"{document.document_type}{_extension_of(document.s3_key)}", s3_bucket=document.s3_bucket, s3_key=document.s3_key, ) for document in latest_by_group.values() ] return DownloadPackagePlan(entries=tuple(entries), skipped=tuple(skipped))