"""Orchestration and privacy-minimized results for monthly channel reports.""" from __future__ import annotations import json import os import uuid from dataclasses import dataclass from datetime import date from pathlib import Path from typing import Any, Dict, Mapping, Optional, Protocol, Tuple from monthly_reports.contracts import ( RESULT_SCHEMA_VERSION, ErrorCode, MonthlyReport, MonthlyReportError, ) from monthly_reports.core import build_monthly_report, validate_as_of from monthly_reports.publishing import ( BuildError, BuiltWorkbook, PublicationError, PublicationOutcome, private_staging_directory, ) from monthly_reports.repository import ReportRepository, RepositoryError, ReservedReport class WorkbookBuilder(Protocol): def build(self, report: MonthlyReport, work_dir: Path) -> BuiltWorkbook: ... class ReportPublisher(Protocol): def publish( self, report: MonthlyReport, reservation: ReservedReport, built: BuiltWorkbook, repository: ReportRepository, work_dir: Path, ) -> PublicationOutcome: ... @dataclass(frozen=True) class RunRequest: report_year: int report_month: int as_of_date: date def validate(self) -> None: validate_as_of(self.report_year, self.report_month, self.as_of_date) @dataclass(frozen=True) class RunResult: request: RunRequest status: str row_count: int = 0 channel_manifest: Tuple[Tuple[str, int, int], ...] = tuple() report_version_id: Optional[int] = None version_no: Optional[int] = None artifact: Optional[Mapping[str, Any]] = None error_code: Optional[str] = None error_stage: Optional[str] = None @property def exit_code(self) -> int: if self.status == "success": return 0 if self.error_code in { ErrorCode.SOURCE_INVALID, ErrorCode.SOURCE_SNAPSHOT_STALE, }: return 2 return 4 def to_dict(self) -> Dict[str, Any]: payload: Dict[str, Any] = { "schema_version": RESULT_SCHEMA_VERSION, "status": self.status, "report_year": self.request.report_year, "report_month": self.request.report_month, "as_of_date": self.request.as_of_date.isoformat(), "row_count": self.row_count, "channel_manifest": [ { "worksheet": worksheet, "worksheet_order": order, "row_count": row_count, } for worksheet, order, row_count in self.channel_manifest ], "warnings": [], "errors": [], } if self.report_version_id is not None: payload["report_version_id"] = self.report_version_id if self.version_no is not None: payload["version_no"] = self.version_no if self.artifact is not None: payload["artifact"] = dict(self.artifact) if self.error_code is not None: payload["errors"] = [ { "code": self.error_code, "stage": self.error_stage or "internal", } ] return payload class MonthlyReportService: def __init__( self, repository: ReportRepository, builder: WorkbookBuilder, publisher: ReportPublisher, staging_root: Path, ) -> None: self._repository = repository self._builder = builder self._publisher = publisher self._staging_root = staging_root @staticmethod def _failed( request: RunRequest, code: str, stage: str, report: Optional[MonthlyReport] = None, ) -> RunResult: return RunResult( request=request, status="failed", row_count=report.row_count if report else 0, channel_manifest=report.channel_manifest if report else tuple(), error_code=code, error_stage=stage, ) def run(self, request: RunRequest) -> RunResult: request.validate() try: snapshot = self._repository.load_snapshot( request.report_year, request.report_month, request.as_of_date, ) except RepositoryError as error: return self._failed(request, error.code, "source") try: report = build_monthly_report( request.report_year, request.report_month, request.as_of_date, snapshot, ) except MonthlyReportError as error: return self._failed(request, error.code, "source") reservation: Optional[ReservedReport] = None try: reservation = self._repository.reserve_report(report) with private_staging_directory(self._staging_root) as temp_dir: work_dir = Path(temp_dir) built = self._builder.build(report, work_dir) outcome = self._publisher.publish( report, reservation, built, self._repository, work_dir, ) return RunResult( request=request, status="success", row_count=report.row_count, channel_manifest=report.channel_manifest, report_version_id=reservation.report_version_id, version_no=reservation.version_no, artifact={ "filename": report.filename, "storage_key": outcome.artifact.storage_key, "sha256": outcome.artifact.sha256, "semantic_sha256": str(built.summary.get("semantic_sha256", "")), }, ) except BuildError as error: if reservation is not None: try: self._repository.mark_failed(reservation, error.code, error.safe_message) except Exception: pass return self._failed(request, error.code, "xlsx", report) except (PublicationError, RepositoryError) as error: return self._failed(request, error.code, "publish", report) except Exception: if reservation is not None: try: self._repository.mark_failed( reservation, ErrorCode.INTERNAL_ERROR, "monthly report processing failed", ) except Exception: pass return self._failed(request, ErrorCode.INTERNAL_ERROR, "internal", report) def write_run_result(path: Path, result: RunResult) -> None: path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) temporary = path.parent / f".{path.name}.tmp-{uuid.uuid4().hex}" descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) try: with os.fdopen(descriptor, "w", encoding="utf-8") as handle: json.dump(result.to_dict(), handle, ensure_ascii=False, sort_keys=True, indent=2) handle.write("\n") handle.flush() os.fsync(handle.fileno()) os.replace(temporary, path) os.chmod(path, 0o600) finally: temporary.unlink(missing_ok=True)