"""Local entry point. Production injects private OSS and processing adapters.""" from __future__ import annotations import argparse from pathlib import Path from typing import Any, Callable, Optional, Sequence from arr_database import controlled_connect from arr_web.app import PortalApplication, RuntimeHealth from arr_web.auth import LoginCredentials from arr_web.booking_uploads import ProgramBookingSourceCoordinator from arr_web.company_jobs import ( PersistentCompanyReportCoordinator, ProgramCompanyReportExecutor, ) from arr_web.downloads import ( ControlledProjectArtifactReader, ManagedObjectArtifactReader, RoutedArtifactReader, ) from arr_web.repository import PostgresPortalRepository, UnavailablePortalRepository from arr_web.processing_runtime import ( ProcessingInputRuntime, compose_programmatic_processing, ) from arr_web.server import serve from arr_web.services import ProgramMonthlyCoordinator from monthly_reports.publishing import ArtifactToolBuilder, AtomicReportPublisher from monthly_reports.repository import DatabaseConfig, PostgresReportRepository from monthly_reports.service import MonthlyReportService from company_reports.publishing import ( ArtifactToolBuilder as CompanyArtifactToolBuilder, AtomicReportPublisher as AtomicCompanyReportPublisher, ) from company_reports.repository import ( DatabaseConfig as CompanyDatabaseConfig, PostgresReportRepository as PostgresCompanyReportRepository, ) from company_reports.service import CompanyReportService from booking_ingestion.excel_postgres import ( DatabaseConfig as BookingExcelDatabaseConfig, ) from booking_ingestion.excel_review_postgres import PostgresBookingReviewRepository PROJECT_ROOT = Path(__file__).resolve().parents[1] def _parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="arr-web") parser.add_argument("--host", default="127.0.0.1") parser.add_argument("--port", type=int, default=8765) parser.add_argument( "--db-config", type=Path, help="local controlled ARR_DB_* env file; values are never logged", ) parser.add_argument( "--driver-path", type=Path, help="optional local directory containing the PostgreSQL driver", ) parser.add_argument( "--enable-monthly-generation", action="store_true", help="enable the database monthly-report mutation endpoint", ) parser.add_argument( "--enable-company-reports", action="store_true", help="enable Bangkok-gated five-company channel-detail jobs", ) parser.add_argument( "--enable-processing", action="store_true", help="enable ARR-owned deterministic XML processing and ingestion", ) parser.add_argument( "--secure-cookies", action="store_true", help="mark browser session cookies Secure for an HTTPS deployment", ) parser.add_argument("--node-binary", type=Path) parser.add_argument("--artifact-tool-module", type=Path) return parser def main(argv: Optional[Sequence[str]] = None) -> int: args = _parser().parse_args(argv) try: login_credentials = LoginCredentials.from_environment() except ValueError as error: raise SystemExit(str(error)) from None connect: Optional[Callable[[str], Any]] = None try: if args.db_config: connect = controlled_connect(args.db_config, args.driver_path) repository = PostgresPortalRepository( "controlled", connect=connect, ) else: repository = PostgresPortalRepository.from_environment() repository.list_months() database_ready = True except Exception: repository = UnavailablePortalRepository() database_ready = False monthly = None monthly_ready = False if args.enable_monthly_generation and database_ready: try: report_repository = PostgresReportRepository( DatabaseConfig("controlled"), connect=connect, ) if connect is not None else PostgresReportRepository(DatabaseConfig.from_environment()) builder = ArtifactToolBuilder( PROJECT_ROOT / "monthly_reports" / "xlsx" / "build_workbook.mjs", node_binary=str(args.node_binary) if args.node_binary else None, artifact_tool_module=args.artifact_tool_module, ) output_root = PROJECT_ROOT / "outputs" / "monthly_reports" monthly = ProgramMonthlyCoordinator( MonthlyReportService( report_repository, builder, AtomicReportPublisher(PROJECT_ROOT, output_root), output_root / ".staging", ) ) monthly_ready = True except Exception: monthly = None monthly_ready = False company_reports = None company_reports_ready = False if args.enable_company_reports and database_ready: try: company_repository = ( PostgresCompanyReportRepository( CompanyDatabaseConfig("controlled"), connect=connect, ) if connect is not None else PostgresCompanyReportRepository( CompanyDatabaseConfig.from_environment() ) ) company_builder = CompanyArtifactToolBuilder( PROJECT_ROOT / "company_reports" / "xlsx" / "build_workbook.mjs", node_binary=str(args.node_binary) if args.node_binary else None, artifact_tool_module=args.artifact_tool_module, ) company_output_root = PROJECT_ROOT / "outputs" / "company_reports" company_service = CompanyReportService( company_repository, company_builder, AtomicCompanyReportPublisher(PROJECT_ROOT, company_output_root), company_output_root / ".staging", ) company_reports = PersistentCompanyReportCoordinator( PROJECT_ROOT, company_output_root, ProgramCompanyReportExecutor(company_service), ) company_reports_ready = True except Exception: company_reports = None company_reports_ready = False processing_input: Optional[ProcessingInputRuntime] = None processing_ready = False if args.enable_processing and database_ready: try: processing_input = compose_programmatic_processing( project_root=PROJECT_ROOT, connect=connect, ) processing_ready = True except Exception: processing_input = None processing_ready = False booking_sources = None company_source_upload_ready = False if args.enable_company_reports and database_ready and processing_input is not None: try: booking_repository = PostgresBookingReviewRepository( BookingExcelDatabaseConfig("controlled"), connect=connect, ) if connect is not None else PostgresBookingReviewRepository( BookingExcelDatabaseConfig.from_environment() ) booking_sources = ProgramBookingSourceCoordinator( processing_input.object_store, booking_repository, ) booking_sources.current() booking_sources.draft() company_source_upload_ready = True except Exception: booking_sources = None company_source_upload_ready = False application = PortalApplication( repository=repository, upload=( processing_input.coordinator if processing_input is not None else None ), monthly=monthly, company_reports=company_reports, booking_sources=booking_sources, artifact_reader=( RoutedArtifactReader( daily_reader=( ManagedObjectArtifactReader(processing_input.object_store) if processing_input is not None else None ), local_reader=ControlledProjectArtifactReader(PROJECT_ROOT), ) if database_ready else None ), health=RuntimeHealth( database_ready=database_ready, upload_ready=processing_ready, monthly_ready=monthly_ready, download_ready=database_ready, company_reports_ready=company_reports_ready, company_source_upload_ready=company_source_upload_ready, ), credentials=login_credentials, secure_cookies=args.secure_cookies, ) try: serve(application, args.host, args.port) finally: if company_reports is not None: company_reports.close() if processing_input is not None: processing_input.close() return 0 if __name__ == "__main__": raise SystemExit(main())