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

+106
View File
@@ -0,0 +1,106 @@
"""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()