98 lines
4.2 KiB
Python
98 lines
4.2 KiB
Python
"""Explicit synthetic-data portal for local acceptance. No Oracle transport or credentials."""
|
|
import argparse
|
|
import hashlib
|
|
from contextlib import contextmanager
|
|
from datetime import date
|
|
import os
|
|
from pathlib import Path
|
|
import secrets
|
|
import signal
|
|
import tempfile
|
|
from types import SimpleNamespace
|
|
|
|
from arr_processing.policy import load_processor_policy
|
|
from arr_web.arr_data_executor import DirectARRExecutor
|
|
from arr_web.local_replay import ReplayRuntime
|
|
from arr_web.local_replay_database import ReplayDatabase
|
|
from arr_web.local_xml_replay import LocalReplayPortal, document
|
|
from arr_web.server import serve
|
|
from integrations.ohip.arr_data import ARRDataSource
|
|
from integrations.ohip.capture_job import atomic_json, fingerprint, job_lock, private_directory
|
|
from tests.test_ohip_arr_data import SimulatedOHIP, DAY, HOTEL, FIXTURE
|
|
|
|
PROJECT = Path(__file__).resolve().parents[1]
|
|
|
|
class DirectDataPortal(LocalReplayPortal):
|
|
allow_xml_upload = True
|
|
cookie_name = "arr_direct_data_simulation_session"
|
|
environment = "local-direct-data-simulation"
|
|
source_kind = "synthetic_direct_data"
|
|
filename_prefix = "LOCAL-DATA-"
|
|
banner_title = "本机接口数据模拟 · ARR 全流程"
|
|
banner_action = "直接处理 6 笔模拟订单,使用独立测试数据库。"
|
|
|
|
|
|
def identity(root, policy):
|
|
return {"version": "arr-local-direct-data/v1", "root": str(root), "hotel_id": HOTEL,
|
|
"report_date": DAY, "source_records": 6, "oracle_connected": False,
|
|
"fixture_sha256": hashlib.sha256(FIXTURE.read_bytes()).hexdigest(), "processor_version": policy.processor_version,
|
|
"rule_set_sha256": policy.rule_set_sha256}
|
|
|
|
|
|
def create(parent):
|
|
if parent.resolve().is_relative_to(PROJECT):
|
|
raise ValueError("local_data_must_be_outside_repository")
|
|
root = Path(tempfile.mkdtemp(prefix="arr-direct-data-", dir=parent)).resolve()
|
|
atomic_json(root / "instance.json", identity(root, load_processor_policy(PROJECT)), replace=False)
|
|
atomic_json(root / "login.json", {"username": "arr-data", "password": secrets.token_urlsafe(24)}, replace=False)
|
|
return root
|
|
|
|
|
|
@contextmanager
|
|
def open_portal(root, port):
|
|
if root.is_symlink() or root.resolve().is_relative_to(PROJECT) or not 1024 <= port <= 65535:
|
|
raise ValueError("invalid_local_instance")
|
|
root = root.resolve(); private_directory(root)
|
|
policy = load_processor_policy(PROJECT)
|
|
if document(root / "instance.json") != identity(root, policy):
|
|
raise ValueError("local_instance_source_or_rules_changed")
|
|
snapshot = SimpleNamespace(day=date.fromisoformat(DAY), manifest={"source_records": 6})
|
|
with job_lock(root):
|
|
database = ReplayDatabase(root, schema_version=19)
|
|
try:
|
|
database.start()
|
|
def factory(*, root, snapshot, **dependencies):
|
|
source = ARRDataSource(root / "source", HOTEL, transport_factory=lambda: SimulatedOHIP(6))
|
|
return DirectARRExecutor(root=root / "processing", source=source, **dependencies)
|
|
runtime = ReplayRuntime(root, database, snapshot, policy, port,
|
|
executor_factory=factory, portal_type=DirectDataPortal, enable_upload=True)
|
|
try:
|
|
yield runtime
|
|
finally:
|
|
runtime.close()
|
|
finally:
|
|
database.close()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("action", choices=["init", "serve"])
|
|
parser.add_argument("--root", type=Path)
|
|
parser.add_argument("--parent", type=Path, default=Path(tempfile.gettempdir()))
|
|
parser.add_argument("--port", type=int, default=8875)
|
|
args = parser.parse_args(); os.umask(0o077)
|
|
if args.action == "init":
|
|
print(create(args.parent)); return
|
|
if args.root is None: parser.error("serve requires --root")
|
|
def stop(*_): raise KeyboardInterrupt
|
|
signal.signal(signal.SIGTERM, stop)
|
|
try:
|
|
with open_portal(args.root, args.port) as runtime:
|
|
runtime.start_monthly_worker()
|
|
print(f"Local direct-data simulation: http://127.0.0.1:{args.port}/", flush=True)
|
|
serve(runtime.app, "127.0.0.1", args.port)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
if __name__ == "__main__": main()
|