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

@@ -1,8 +1,11 @@
from __future__ import annotations
import json
import os
import tempfile
import unittest
from datetime import date
from decimal import Decimal
from pathlib import Path
from unittest.mock import patch
@@ -10,6 +13,8 @@ from arr_ingestion.contracts import ArtifactRef, IngestionError
from arr_ingestion.postgres import (
DatabaseConfig,
PostgresIngestionRepository,
_manual_price,
_review_price_text,
)
from arr_ingestion.repository import JobRegistration
from tests.test_arr_opera_daily_ingest import run_processor, success_xml
@@ -99,9 +104,15 @@ class RetryConnection:
class LifecycleCursor:
def __init__(self, run_status: str = "queued", attempt_status: str = "queued") -> None:
def __init__(
self,
run_status: str = "queued",
attempt_status: str = "queued",
active_review: tuple[int, int] | None = None,
) -> None:
self.run_status = run_status
self.attempt_status = attempt_status
self.active_review = active_review
self.last_query = ""
self.executed: list[tuple[str, tuple[object, ...]]] = []
@@ -113,9 +124,61 @@ class LifecycleCursor:
def fetchone(self):
if self.last_query.startswith("SELECT run.id, run.run_status"):
return (12, self.run_status, 13, self.attempt_status)
if "FROM ingestion.daily_review_cases" in self.last_query:
return self.active_review
return None
class ReviewGenerationCursor:
def __init__(self) -> None:
self.last_query = ""
self.executed: list[tuple[str, tuple[object, ...]]] = []
self.case_key = "dailyreview-0123456789abcdef0123456789abcdef"
self.source_sha256 = "c" * 64
def execute(self, query: str, params: tuple[object, ...]) -> None:
InsertCursor.assert_placeholder_count(query, params)
self.last_query = " ".join(query.split())
self.executed.append((self.last_query, params))
def fetchone(self):
if self.last_query.startswith("SELECT review.id, review.case_key"):
return (
33,
self.case_key,
"open",
1,
date(2026, 7, 27),
"4.0.0",
"b" * 64,
self.source_sha256,
None,
None,
None,
44,
"awaiting_review",
"4.0.0",
"b" * 64,
)
if self.last_query.startswith("SELECT COALESCE(max(attempt_no), 0) + 1"):
return (3,)
if "FROM ingestion.processing_runs AS run" in self.last_query:
return (
"opera_xml",
"arr/jobs/job-001/attempts/0001/committed/source_xml/source.xml",
"source.xml",
self.source_sha256,
321,
"application/xml",
)
return None
def fetchall(self):
if "FROM ingestion.daily_review_items" in self.last_query:
return [("QBD", "GRPA1", Decimal("1800.00"), Decimal("0.00"))]
return []
class RegistrationCursor:
def __init__(self) -> None:
self.last_query = ""
@@ -166,6 +229,16 @@ class PostgresIngestionTests(unittest.TestCase):
"postgresql://synthetic",
)
def test_manual_review_accepts_integer_input_and_keeps_exact_decimal_storage(self):
self.assertEqual(_manual_price("0"), Decimal("0.00"))
self.assertEqual(_manual_price("2300"), Decimal("2300.00"))
self.assertEqual(_review_price_text(Decimal("2300.00")), "2300")
for invalid in ("0.00", "01", "-1", "1.5", ""):
with self.subTest(invalid=invalid), self.assertRaisesRegex(
IngestionError, "非负整数"
):
_manual_price(invalid)
def test_record_insert_maps_duplicate_lineage_and_booking_source_status(self):
with tempfile.TemporaryDirectory() as temp_dir:
exit_code, _xml, _output, _result, payload = run_processor(
@@ -382,6 +455,86 @@ class PostgresIngestionTests(unittest.TestCase):
self.assertIn("PROCESSOR_TIMEOUT", rendered_params)
self.assertNotIn("password", rendered_params.lower())
def test_deterministic_review_runtime_failure_closes_case_with_audit_event(self):
cursor = LifecycleCursor(
run_status="running",
attempt_status="running",
active_review=(81, 4),
)
repository = PostgresIngestionRepository(DatabaseConfig("postgresql://synthetic"))
repository._record_runtime_failure(
cursor,
"arrjob-programmatic-003",
2,
"PROCESSOR_RESULT_INVALID",
)
statements = "\n".join(query for query, _params in cursor.executed)
self.assertIn("case_status = 'failed'", statements)
self.assertIn("PRICE_REVIEW_FAILED", repr(cursor.executed))
self.assertIn("system:runtime", repr(cursor.executed))
def test_finalization_plan_reloads_registered_source_artifact(self):
cursor = ReviewGenerationCursor()
repository = PostgresIngestionRepository(DatabaseConfig("postgresql://synthetic"))
plan = repository._begin_price_review_generation(
cursor,
"job-001",
cursor.case_key,
1,
"operator@example.test",
"4.0.0",
"b" * 64,
"d" * 64,
)
self.assertEqual(plan.status, "ready")
self.assertEqual(plan.attempt_no, 3)
self.assertIsNotNone(plan.source)
assert plan.source is not None
self.assertEqual(plan.source.role, "source_xml")
self.assertEqual(plan.source.sha256, cursor.source_sha256)
self.assertEqual(plan.source.object_key.rsplit("/", 1)[-1], "source.xml")
self.assertIsNotNone(plan.manual_override_bytes)
manifest = json.loads(plan.manual_override_bytes or b"{}")
self.assertEqual(manifest["source_sha256"], cursor.source_sha256)
self.assertEqual(manifest["overrides"][0]["real_price"], "0.00")
statements = "\n".join(query for query, _params in cursor.executed)
self.assertIn("FOR SHARE OF artifact", statements)
self.assertIn("PRICE_REVIEW_FINALIZED", repr(cursor.executed))
def test_review_receipt_is_separate_from_finance_and_outbox_writes(self):
source = PostgresIngestionRepository._record_review.__code__.co_consts
rendered = "\n".join(value for value in source if isinstance(value, str))
self.assertIn("delivery_status = 'recorded_review'", rendered)
self.assertNotIn("finance.daily_versions", rendered)
self.assertNotIn("ingestion.outbox_events", rendered)
def test_delivery_insert_binds_manual_override_artifact_column(self):
source = PostgresIngestionRepository._commit.__code__.co_consts
delivery_insert = next(
value
for value in source
if isinstance(value, str)
and "INSERT INTO ingestion.processing_deliveries" in value
)
self.assertIn("manual_override_artifact_id", delivery_insert)
self.assertEqual(delivery_insert.count("%s"), 16)
def test_finance_version_insert_matches_bound_finalization_values(self):
source = PostgresIngestionRepository._commit_success.__code__.co_consts
version_insert = next(
value
for value in source
if isinstance(value, str)
and "INSERT INTO finance.daily_versions" in value
)
self.assertEqual(version_insert.count("%s"), 20)
class Migration008ContractTests(unittest.TestCase):
def test_rebuild_targets_v3_and_all_source_record_storage(self):