107 lines
5.2 KiB
Python
107 lines
5.2 KiB
Python
"""Isolated local HTTP acquisition → XML → Finance → monthly integration portal."""
|
||
|
||
import argparse
|
||
from contextlib import contextmanager
|
||
import os
|
||
from pathlib import Path
|
||
import signal
|
||
|
||
from arr_web.arr_download_executor import CapturedARRExecutor
|
||
from arr_web.local_api_fixture import HOTEL, VERSION, LocalAPIAdapter, NativeBaselineValidator, build_fixture
|
||
from arr_web.local_api_server import serve_fixture
|
||
from arr_web.local_replay import create_instance, open_instance
|
||
from arr_web.local_xml_replay import LocalReplayPortal, NativeXMLSnapshot, document
|
||
from arr_web.server import serve
|
||
from integrations.ohip import collect_arr_source as source, rate_info
|
||
from integrations.ohip.audit_arr_capture import protected_read
|
||
from integrations.ohip.capture_job import atomic_json, fingerprint
|
||
|
||
|
||
class LocalSimulationPortal(LocalReplayPortal):
|
||
cookie_name = "arr_api_simulation_session"
|
||
environment = "local-api-simulation"
|
||
source_kind = "local_api_simulation"
|
||
filename_prefix = "LOCAL-API-SIMULATION-"
|
||
banner_title = "本机接口模拟 · ARR 全流程联调"
|
||
banner_action = "点击下载会查询本机模拟接口、分页采集、生成 ARR,并进入独立测试数据库。"
|
||
|
||
|
||
def simulation_manifest(snapshot, fixture):
|
||
return {"version": VERSION, "source_sha256": snapshot.manifest["source_sha256"],
|
||
"fixture_sha256": fingerprint(fixture), "hotel_id": HOTEL, "report_date": snapshot.day.isoformat(),
|
||
"records": len(fixture["records"]), "oracle_connected": False,
|
||
"synthetic_fields": ["hotelId", "Reservation ID", "reservationStatus", "lastModifyDateTime"],
|
||
"local_report_fields": ["full_name", "company_name", "room_no", "block_code", "products",
|
||
"room_category_label", "source_sequence"],
|
||
"traces_selected": False, "oracle_report_equivalence_verified": False}
|
||
|
||
|
||
def create_simulation(parent, source_path, expected_sha256, report_date):
|
||
root = create_instance(parent, source_path, expected_sha256, report_date, prefix="arr-api-simulation-")
|
||
snapshot = NativeXMLSnapshot.load(root / "fixture")
|
||
fixture = build_fixture(snapshot)
|
||
source.require(len(fixture["records"]) <= 2000, "local_simulation_record_limit")
|
||
atomic_json(root / "simulation-data.json", fixture, replace=False)
|
||
atomic_json(root / "simulation.json", simulation_manifest(snapshot, fixture), replace=False)
|
||
return root
|
||
|
||
|
||
@contextmanager
|
||
def open_simulation(root, port):
|
||
snapshot = NativeXMLSnapshot.load(root / "fixture")
|
||
marker = document(root / "simulation.json")
|
||
fixture = source.strict_json(protected_read(root / "simulation-data.json", 32 * 1024 * 1024))
|
||
expected_fixture = build_fixture(snapshot)
|
||
source.require(fingerprint(marker) == fingerprint(simulation_manifest(snapshot, expected_fixture))
|
||
and fingerprint(fixture) == fingerprint(expected_fixture) and len(fixture["records"]) <= 2000,
|
||
"local_simulation_identity_mismatch")
|
||
with serve_fixture(fixture) as api:
|
||
def factory(*, root, snapshot, **kwargs):
|
||
def readers(archive, hotel):
|
||
return (source.Reader(archive, hotel, api.transport, key=api.key),
|
||
rate_info.RateInfoReader(archive, hotel, api.transport, key=api.key))
|
||
return CapturedARRExecutor(root=root / "acquisition", hotel_id=HOTEL,
|
||
adapter_contract="local-api-simulation/v1-" + marker["fixture_sha256"],
|
||
adapter=LocalAPIAdapter(marker["source_sha256"], marker["records"]),
|
||
mapping_validator=NativeBaselineValidator(snapshot), reader_factory=readers,
|
||
page_size=20, max_pages=100, max_records=2000, **kwargs)
|
||
try:
|
||
with open_instance(root, port, executor_factory=factory, portal_type=LocalSimulationPortal) as runtime:
|
||
runtime.local_api = api
|
||
yield runtime
|
||
finally:
|
||
atomic_json(root / "http-counts-last-run.json", {"environment": "local-api-simulation",
|
||
"operations": dict(api.counts), "oracle_calls": 0}, replace=True)
|
||
|
||
|
||
def main(argv=None):
|
||
parser = argparse.ArgumentParser(description=__doc__)
|
||
actions = parser.add_subparsers(dest="action", required=True)
|
||
init = actions.add_parser("init")
|
||
init.add_argument("--parent", type=Path, required=True)
|
||
init.add_argument("--xml", type=Path, required=True)
|
||
init.add_argument("--expected-sha256", required=True)
|
||
init.add_argument("--report-date", required=True)
|
||
run = actions.add_parser("serve")
|
||
run.add_argument("--root", type=Path, required=True)
|
||
run.add_argument("--port", type=int, default=8874)
|
||
args = parser.parse_args(argv)
|
||
os.umask(0o077)
|
||
if args.action == "init":
|
||
print(create_simulation(args.parent, args.xml, args.expected_sha256, args.report_date))
|
||
return
|
||
def stop(_signal, _frame):
|
||
raise KeyboardInterrupt
|
||
signal.signal(signal.SIGTERM, stop)
|
||
try:
|
||
with open_simulation(args.root, args.port) as runtime:
|
||
runtime.start_monthly_worker()
|
||
print(f"Local API simulation only: 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()
|