feat: add daily manual price review workflow

This commit is contained in:
Wyndham ARR
2026-08-06 22:40:18 +08:00
parent aae3d8e1db
commit ca3e8e18fa
77 changed files with 7750 additions and 602 deletions

View File

@@ -16,6 +16,7 @@ from openpyxl import load_workbook
from arr_ingestion.contracts import IngestionError
from arr_ingestion.validation import DeliveryValidator, ProcessorPolicy
from tests.test_arr_opera_daily_ingest import (
manual_override_manifest,
reservation,
run_processor,
xml_document,
@@ -55,9 +56,22 @@ def artifact_ref(key: str, filename: str, value: bytes, mime_type: str) -> dict[
}
def build_delivery(xml_text: str, root: Path) -> tuple[bytes, MemoryStore, dict[str, bytes]]:
exit_code, xml_path, output_dir, result, _structured = run_processor(xml_text, root)
status = "success" if exit_code == 0 else "failed"
def build_delivery(
xml_text: str,
root: Path,
*,
manual_override: dict[str, object] | None = None,
review_job_id: str | None = None,
review_case_id: str | None = None,
) -> tuple[bytes, MemoryStore, dict[str, bytes]]:
_exit_code, xml_path, output_dir, result, structured = run_processor(
xml_text,
root,
manual_override=manual_override,
review_job_id=review_job_id,
review_case_id=review_case_id,
)
status = result["status"]
values = {
"source_xml": xml_path.read_bytes(),
"result_json": (output_dir / "result.json").read_bytes(),
@@ -65,8 +79,10 @@ def build_delivery(xml_text: str, root: Path) -> tuple[bytes, MemoryStore, dict[
}
if status == "success":
values["daily_report"] = (output_dir / result["outputs"]["daily_report"]).read_bytes()
else:
elif status == "failed":
values["exception_report"] = (output_dir / result["outputs"]["exception_report"]).read_bytes()
if structured["artifacts"]["manual_override_json"] is not None:
values["manual_override_json"] = (root / "manual-override.json").read_bytes()
keys = {role: f"arr/jobs/job-001/{role}/{role}" for role in values}
objects = {keys[role]: value for role, value in values.items()}
xlsx_mime = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
@@ -78,7 +94,7 @@ def build_delivery(xml_text: str, root: Path) -> tuple[bytes, MemoryStore, dict[
"status": status,
"processor_version": processor.PROCESSOR_VERSION,
"rule_set_sha256": processor.rule_set_sha256(),
"result_schema_version": "3.0",
"result_schema_version": processor.RESULT_VERSION,
"business_date": result["business_date"],
"artifacts": {
"source_xml": artifact_ref(keys["source_xml"], xml_path.name, values["source_xml"], "application/xml"),
@@ -111,6 +127,16 @@ def build_delivery(xml_text: str, root: Path) -> tuple[bytes, MemoryStore, dict[
if status == "failed"
else None
),
"manual_override_json": (
artifact_ref(
keys["manual_override_json"],
"manual-override.json",
values["manual_override_json"],
"application/json",
)
if "manual_override_json" in values
else None
),
},
}
return json.dumps(envelope, ensure_ascii=False).encode("utf-8"), MemoryStore(objects), values
@@ -186,7 +212,7 @@ class DeliveryValidationTests(unittest.TestCase):
def test_failed_delivery_is_retained_but_never_declared_activation_eligible(self):
failure_xml = xml_document(
reservation(1, rate_code="GRPA4", rate_amount="1800", res_comment="SYN-FAIL")
reservation(1, departure="2026-07-26", res_comment="SYN-FAIL")
)
with tempfile.TemporaryDirectory() as temp_dir:
raw, store, _values = build_delivery(failure_xml, Path(temp_dir))
@@ -206,6 +232,53 @@ class DeliveryValidationTests(unittest.TestCase):
with self.assertRaisesRegex(IngestionError, "identity is invalid"):
DeliveryValidator(store, policy()).validate(json.dumps(envelope).encode("utf-8"))
def test_review_delivery_is_replayed_without_daily_or_finance_artifacts(self):
review_xml = xml_document(
reservation(1, rate_code="GRPA1", rate_amount="900"),
reservation(2, rate_code="GRPA1", rate_amount="1800"),
)
with tempfile.TemporaryDirectory() as temp_dir:
raw, store, _values = build_delivery(review_xml, Path(temp_dir))
verified = DeliveryValidator(store, policy()).validate(raw)
self.assertEqual(verified.envelope.status, "review_required")
self.assertEqual(verified.structured_payload["candidate_rows"], 1)
self.assertEqual(verified.structured_payload["review_required_rows"], 1)
self.assertEqual(verified.structured_payload["review_issue_count"], 1)
self.assertIsNone(verified.envelope.artifacts["daily_report"])
self.assertIsNone(verified.envelope.artifacts["exception_report"])
self.assertIsNone(verified.envelope.artifacts["manual_override_json"])
self.assertEqual(store.fetch_count, 3)
def test_final_manual_delivery_replays_frozen_manifest(self):
source = xml_document(
reservation(1, rate_code="GRPA1", rate_amount="900"),
reservation(2, rate_code="GRPA1", rate_amount="1800"),
)
review_case_id = "dailyreview-0123456789abcdef0123456789abcdef"
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
review = run_processor(source, root / "review")
manifest = manual_override_manifest(
review[1],
review[4]["review_issues"],
job_id="job-001",
review_case_id=review_case_id,
real_price="0.00",
)
raw, store, _values = build_delivery(
source,
root / "final",
manual_override=manifest,
review_job_id="job-001",
review_case_id=review_case_id,
)
verified = DeliveryValidator(store, policy()).validate(raw)
self.assertEqual(verified.envelope.status, "success")
self.assertEqual(verified.structured_payload["review_case_id"], review_case_id)
self.assertEqual(verified.structured_payload["manually_priced_rows"], 1)
self.assertIsNotNone(verified.envelope.artifacts["manual_override_json"])
self.assertEqual(store.fetch_count, 5)
def test_duplicate_json_keys_are_rejected(self):
raw = b'{"delivery_schema_version":"1.0","delivery_schema_version":"1.0"}'
with tempfile.TemporaryDirectory() as temp_dir: