From e7181b7d1fb84e674b1dfd458d8a32776cba2ab8 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 8 Jul 2026 09:31:49 +0000 Subject: [PATCH] =?UTF-8?q?Build,=20upload=20and=20email=20a=20Download=20?= =?UTF-8?q?Package=20for=20a=20property=20set=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .../bulk_document_download_orchestrator.py | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/orchestration/bulk_document_download_orchestrator.py b/orchestration/bulk_document_download_orchestrator.py index 01d65a44a..54de823bb 100644 --- a/orchestration/bulk_document_download_orchestrator.py +++ b/orchestration/bulk_document_download_orchestrator.py @@ -21,7 +21,6 @@ from domain.bulk_document_download.package_plan import ( SkippedDocument, plan_download_package, ) -from domain.tasks.subtasks import SubTaskFailure from infrastructure.postgres.uploaded_file_table import UploadedFile from infrastructure.s3.s3_client import S3Client from repositories.email.email_sender import EmailSender @@ -78,4 +77,46 @@ class BulkDocumentDownloadOrchestrator: recipient_email: str, package_name: str, ) -> DownloadPackageResult: - raise NotImplementedError + files = self._uploaded_files.by_landlord_property_ids(landlord_property_ids) + resolved = [self._resolve(file) for file in files] + plan = plan_download_package([r for r in resolved if r is not None]) + + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as archive: + for entry in plan.entries: + archive.writestr( + entry.zip_path, self._documents.read(entry.s3_bucket, entry.s3_key) + ) + + key = f"{self._package_key_prefix}/{package_name}.zip" + self._packages.put_object(key, buffer.getvalue()) + url = self._packages.generate_presigned_url(key, self._url_ttl_seconds) + + self._email.send( + to=recipient_email, + subject="Your document download is ready", + body=( + f"Your document download is ready. It contains " + f"{len(plan.entries)} document(s).\n\n" + f"Download it here (link valid for " + f"{self._url_ttl_seconds // 60} minutes):\n{url}" + ), + ) + return DownloadPackageResult( + presigned_url=url, + package_s3_key=key, + included=len(plan.entries), + skipped=plan.skipped, + ) + + def _resolve(self, file: UploadedFile) -> Optional[ResolvedDocument]: + if file.landlord_property_id is None: + return None + return ResolvedDocument( + landlord_property_id=file.landlord_property_id, + address=self._addresses.address_for(file.landlord_property_id), + document_type=file.file_type, + s3_bucket=file.s3_file_bucket, + s3_key=file.s3_file_key, + uploaded_at=cast(datetime, file.s3_upload_timestamp), + )