feat: add date-based OHIP data reporting alongside XML

This commit is contained in:
Wyndham ARR committed 2026-09-18 15:29:27 +08:00
1 parent 5ecd571e3b
commit ad2c59256f
259 files changed
+37326 -126

No files matched your search

+43 -10
View File
@@ -3,12 +3,16 @@
from __future__ import annotations
import argparse
from contextlib import ExitStack
from pathlib import Path
import signal
import threading
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.arr_download_runtime import CapturedARRSource, DirectARRSource, compose_arr_downloads
from arr_web.booking_uploads import ProgramBookingSourceCoordinator
from arr_web.company_jobs import (
PersistentCompanyReportCoordinator,
@@ -83,15 +87,38 @@ def _parser() -> argparse.ArgumentParser:
action="store_true",
help="mark browser session cookies Secure for an HTTPS deployment",
)
parser.add_argument("--ohip-hotel-id", help="Hotel used by the direct ARR data entry")
parser.add_argument("--ohip-credential-file", type=Path, help="Private OHIP credential file, read only on download")
parser.add_argument("--ohip-state-root", type=Path, help="Private persistent OHIP task directory outside the repository")
return parser
def main(argv: Optional[Sequence[str]] = None) -> int:
def main(argv: Optional[Sequence[str]] = None, *, arr_source: CapturedARRSource | DirectARRSource | None = None) -> int:
args = _parser().parse_args(argv)
direct_values = (args.ohip_hotel_id, args.ohip_credential_file, args.ohip_state_root)
if any(value is not None for value in direct_values):
if arr_source is not None or not all(value is not None for value in direct_values):
raise SystemExit("Provide all three OHIP options without an injected source")
arr_source = DirectARRSource(args.ohip_state_root, args.ohip_hotel_id, args.ohip_credential_file)
if arr_source is not None and not args.enable_processing:
raise SystemExit("ARR downloads require --enable-processing")
try:
login_credentials = LoginCredentials.from_environment()
except ValueError as error:
raise SystemExit(str(error)) from None
with ExitStack() as resources:
if arr_source is not None and threading.current_thread() is threading.main_thread():
previous = signal.signal(signal.SIGTERM, _terminate)
resources.callback(signal.signal, signal.SIGTERM, previous)
return _run(args, login_credentials, resources, arr_source=arr_source)
def _terminate(signum, _frame):
# Let ExitStack drain the active ARR task before shared clients close.
raise SystemExit(128 + signum)
def _run(args, login_credentials, resources: ExitStack, *, arr_source: CapturedARRSource | DirectARRSource | None) -> int:
connect: Optional[Callable[[str], Any]] = None
try:
if args.db_config:
@@ -116,6 +143,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
project_root=PROJECT_ROOT,
connect=connect,
)
resources.callback(processing_input.close)
processing_ready = True
except Exception:
processing_input = None
@@ -130,6 +158,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
):
try:
report_storage_runtime = compose_object_store()
resources.callback(report_storage_runtime.close)
report_object_store = report_storage_runtime.object_store
except Exception:
report_storage_runtime = None
@@ -191,6 +220,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
ProgramCompanyReportExecutor(company_service),
object_store=report_object_store,
)
resources.callback(company_reports.close)
company_reports_ready = True
except Exception:
company_reports = None
@@ -215,8 +245,19 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
except Exception:
booking_sources = None
company_source_upload_ready = False
arr_downloads = None
if arr_source is not None:
if processing_input is None:
raise SystemExit("ARR download processing dependencies are unavailable")
try:
arr_downloads = compose_arr_downloads(arr_source, processing_input)
except Exception:
raise SystemExit("ARR download source initialization failed") from None
# Last registered, first closed: a running delivery still needs OSS/SQL.
resources.callback(arr_downloads.close, wait=True)
application = PortalApplication(
repository=repository,
arr_downloads=arr_downloads,
upload=(
processing_input.coordinator if processing_input is not None else None
),
@@ -254,15 +295,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
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()
if report_storage_runtime is not None:
report_storage_runtime.close()
serve(application, args.host, args.port)
return 0