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

@@ -10,7 +10,13 @@ from typing import Any, Dict, Mapping, Optional
DELIVERY_SCHEMA_VERSION = "1.0"
RESULT_SCHEMA_VERSION = "3.0"
# v4 is the active fixed-processor contract. v3 remains parseable only for
# historical/direct-MCP compatibility and must never acquire review behavior.
LEGACY_RESULT_SCHEMA_VERSION = "3.0"
RESULT_SCHEMA_VERSION = "4.0"
SUPPORTED_RESULT_SCHEMA_VERSIONS = frozenset(
{LEGACY_RESULT_SCHEMA_VERSION, RESULT_SCHEMA_VERSION}
)
XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
OPAQUE_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$")
@@ -41,7 +47,9 @@ ARTIFACT_ROLES = {
"result_json",
"structured_result_json",
"exception_report",
"manual_override_json",
}
LEGACY_ARTIFACT_ROLES = ARTIFACT_ROLES - {"manual_override_json"}
ROLE_CONTRACTS = {
"source_xml": ("opera_xml", ".xml", "application/xml"),
"booking_source": ("booking_excel", ".xlsx", XLSX_MIME),
@@ -55,6 +63,11 @@ ROLE_CONTRACTS = {
"application/json",
),
"exception_report": ("exception_xlsx", ".xlsx", XLSX_MIME),
"manual_override_json": (
"manual_override_json",
".json",
"application/json",
),
}
ARTIFACT_SIZE_LIMITS = {
"source_xml": 100 * 1024 * 1024,
@@ -65,9 +78,18 @@ ARTIFACT_SIZE_LIMITS = {
"result_json": 5 * 1024 * 1024,
"structured_result_json": 50 * 1024 * 1024,
"exception_report": 20 * 1024 * 1024,
"manual_override_json": 5 * 1024 * 1024,
}
def artifact_roles_for_schema(result_schema_version: str) -> set[str]:
if result_schema_version == LEGACY_RESULT_SCHEMA_VERSION:
return set(LEGACY_ARTIFACT_ROLES)
if result_schema_version == RESULT_SCHEMA_VERSION:
return set(ARTIFACT_ROLES)
_fail("DELIVERY_INVALID", "result schema version is unsupported")
class IngestionError(RuntimeError):
"""A stable, non-sensitive validation or persistence error."""
@@ -206,8 +228,6 @@ class DeliveryEnvelope:
_fail("DELIVERY_INVALID", "job identifier is invalid")
if not isinstance(attempt_no, int) or isinstance(attempt_no, bool) or attempt_no < 1:
_fail("DELIVERY_INVALID", "attempt number is invalid")
if status not in {"success", "failed"}:
_fail("DELIVERY_INVALID", "delivery status is invalid")
if (
not isinstance(processor_version, str)
or not PROCESSOR_VERSION_RE.fullmatch(processor_version)
@@ -215,8 +235,13 @@ class DeliveryEnvelope:
_fail("DELIVERY_INVALID", "processor version is invalid")
if not isinstance(rule_set_sha256, str) or not SHA256_RE.fullmatch(rule_set_sha256):
_fail("DELIVERY_INVALID", "rule set identity is invalid")
if result_schema_version != RESULT_SCHEMA_VERSION:
if result_schema_version not in SUPPORTED_RESULT_SCHEMA_VERSIONS:
_fail("DELIVERY_INVALID", "result schema version is unsupported")
allowed_statuses = {"success", "failed"}
if result_schema_version == RESULT_SCHEMA_VERSION:
allowed_statuses.add("review_required")
if status not in allowed_statuses:
_fail("DELIVERY_INVALID", "delivery status is invalid")
raw_business_date = values.get("business_date")
parsed_date: Optional[date]
@@ -230,9 +255,10 @@ class DeliveryEnvelope:
else:
_fail("DELIVERY_INVALID", "business date is invalid")
raw_artifacts = _strict_mapping(values.get("artifacts"), ARTIFACT_ROLES, "artifact")
artifact_roles = artifact_roles_for_schema(str(result_schema_version))
raw_artifacts = _strict_mapping(values.get("artifacts"), artifact_roles, "artifact")
artifacts: Dict[str, Optional[ArtifactRef]] = {}
for role in sorted(ARTIFACT_ROLES):
for role in sorted(artifact_roles):
raw_artifact = raw_artifacts.get(role)
artifacts[role] = (
None if raw_artifact is None else ArtifactRef.from_dict(role, raw_artifact)
@@ -240,6 +266,7 @@ class DeliveryEnvelope:
always_required = ("source_xml", "result_json", "structured_result_json")
if any(artifacts[role] is None for role in always_required):
_fail("DELIVERY_INVALID", "required delivery artifact is missing")
manual_override = artifacts.get("manual_override_json")
if status == "success":
if (
parsed_date is None
@@ -247,8 +274,18 @@ class DeliveryEnvelope:
or artifacts["exception_report"] is not None
):
_fail("DELIVERY_INVALID", "success delivery artifact shape is invalid")
elif status == "review_required":
if (
parsed_date is None
or artifacts["daily_report"] is not None
or artifacts["exception_report"] is not None
or manual_override is not None
):
_fail("DELIVERY_INVALID", "review delivery artifact shape is invalid")
elif artifacts["daily_report"] is not None or artifacts["exception_report"] is None:
_fail("DELIVERY_INVALID", "failed delivery artifact shape is invalid")
elif manual_override is not None:
_fail("DELIVERY_INVALID", "failed delivery artifact shape is invalid")
return cls(
delivery_id=delivery_id,
job_id=job_id,