Files
ARR-2.0-0918/arr_web/local_api_simulation.py

107 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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()