147 lines
8.9 KiB
Python
147 lines
8.9 KiB
Python
from datetime import datetime, timedelta, timezone
|
|
|
|
from app.authoritative_operational_adapter import decide_authoritative_operation
|
|
from app.canonical_operations import canonicalize_operations
|
|
|
|
|
|
NOW = datetime(2026, 8, 16, tzinfo=timezone.utc)
|
|
|
|
|
|
def projection(state="AWAITING_CUSTOMER", action=None, **extra):
|
|
return {"opportunity_id": "opp", "canonical_opportunity_id": "opp",
|
|
"business_state": state, "business_next_action": action,
|
|
"confidence": "high", **extra}
|
|
|
|
|
|
def task(code, due=NOW, **extra):
|
|
return {"id": code, "status": "pending", "action_code": code, "due_at": due, **extra}
|
|
|
|
|
|
def test_business_baselines_and_terminal():
|
|
for state, action in [("PROFORMA_REQUIRED", "CREATE_PROFORMA"),
|
|
("INVOICE_REQUIRED", "CREATE_INVOICE"),
|
|
("ODOO_ORDER_REQUIRED", "PREPARE_ORDER"),
|
|
("ODOO_ORDER_CREATED", "VALIDATE_ODOO_ORDER")]:
|
|
got = decide_authoritative_operation(projection(state, action), now=NOW)
|
|
assert (got.effective_action, got.queue) == (action, "do_now")
|
|
assert decide_authoritative_operation(projection("COMPLETED"), now=NOW).queue == "not_current"
|
|
|
|
|
|
def test_terminal_state_ignores_leftover_pending_obligation():
|
|
got = decide_authoritative_operation(projection("COMPLETED"),
|
|
obligations=[task("REVIEW_RECONSTRUCTED_PROCESS")], now=NOW)
|
|
assert (got.effective_action, got.queue, got.reason_code) == (None, "not_current", "TERMINAL_BUSINESS_STATE")
|
|
|
|
|
|
def test_specific_reconstructed_review_overrides_generic_business_review():
|
|
got = decide_authoritative_operation(projection("REVIEW_REQUIRED", "REVIEW_REQUIRED"),
|
|
obligations=[task("REVIEW_RECONSTRUCTED_PROCESS")], now=NOW)
|
|
assert (got.business_next_action, got.effective_action, got.queue) == (
|
|
"REVIEW_REQUIRED", "REVIEW_RECONSTRUCTED_PROCESS", "review")
|
|
|
|
|
|
def test_missing_projection_fails_closed_without_v1():
|
|
got = decide_authoritative_operation(None, now=NOW)
|
|
assert (got.effective_action, got.queue, got.reason_code) == ("REVIEW_REQUIRED", "review", "MISSING_V2_PROJECTION")
|
|
|
|
|
|
def test_due_and_future_customer_followup_semantics():
|
|
due = decide_authoritative_operation(projection(), obligations=[task("FOLLOW_UP_CUSTOMER_REVIEW")], now=NOW)
|
|
assert (due.effective_action, due.queue) == ("FOLLOW_UP_CUSTOMER_REVIEW", "do_now")
|
|
future = decide_authoritative_operation(projection(), obligations=[task("FOLLOW_UP_CUSTOMER_REVIEW", NOW + timedelta(days=1))], now=NOW)
|
|
assert (future.effective_action, future.queue) == (None, "waiting")
|
|
future_item = canonicalize_operations([{
|
|
"source": "flow_v2", "id": "opp", "opportunity_id": "opp",
|
|
"status": "projected", "action_code": "WAIT_CUSTOMER", "created_at": NOW,
|
|
}], {"opp": future.to_dict()})["items"]
|
|
assert len(future_item) == 1 and future_item[0]["operational_queue"] == "waiting"
|
|
assert decide_authoritative_operation(projection(), now=NOW).queue == "waiting"
|
|
|
|
|
|
def test_unanswered_send_info_remains_valid_on_awaiting_customer():
|
|
inbound = NOW - timedelta(hours=2)
|
|
obligation = task("SEND_INFO", latest_public_inbound=inbound, latest_public_outbound=None)
|
|
got = decide_authoritative_operation(projection(), obligations=[obligation], now=NOW)
|
|
assert (got.effective_action, got.queue, got.reason_code) == (
|
|
"SEND_INFO", "do_now", "ACTIVE_RESPONSE_OBLIGATION")
|
|
|
|
|
|
def test_send_info_satisfied_by_later_outbound_is_not_current():
|
|
inbound = NOW - timedelta(hours=2)
|
|
obligation = task("SEND_INFO", latest_public_inbound=inbound,
|
|
latest_public_outbound=inbound + timedelta(minutes=10))
|
|
got = decide_authoritative_operation(projection(), obligations=[obligation], now=NOW)
|
|
assert (got.effective_action, got.queue) == (None, "waiting")
|
|
|
|
|
|
def test_only_active_non_superseded_tasks_override_and_precedence():
|
|
stale = task("SUPPORT", resolved_at=NOW)
|
|
assert decide_authoritative_operation(projection("PROFORMA_REQUIRED", "CREATE_PROFORMA"), obligations=[stale], now=NOW).effective_action == "CREATE_PROFORMA"
|
|
obligations = [task("CALL_CUSTOMER"), task("SEND_INFO"), task("SUPPORT"), task("REVIEW_MANUALLY")]
|
|
assert decide_authoritative_operation(projection(), obligations=obligations, now=NOW).effective_action == "REVIEW_MANUALLY"
|
|
assert decide_authoritative_operation(projection(), obligations=[task("SUPPORT")], now=NOW).effective_action == "SUPPORT"
|
|
assert decide_authoritative_operation(projection("INQUIRY", "SEND_INFO"), obligations=[task("SEND_INFO")], now=NOW).effective_action == "SEND_INFO"
|
|
assert decide_authoritative_operation(projection(), obligations=[task("SEND_INFO")], now=NOW).effective_action is None
|
|
assert decide_authoritative_operation(projection(), obligations=[task("CALL_CUSTOMER")], now=NOW).effective_action == "CALL_CUSTOMER"
|
|
|
|
|
|
def test_fiscal_and_reconciliation_only_block_formal_action():
|
|
inquiry = decide_authoritative_operation(projection("INQUIRY", "SEND_INFO"), fiscal_complete=False, reconciliation_blocking=True, now=NOW)
|
|
assert inquiry.effective_action == "SEND_INFO"
|
|
formal = decide_authoritative_operation(projection("PROFORMA_REQUIRED", "CREATE_PROFORMA"), fiscal_complete=False, now=NOW)
|
|
assert (formal.effective_action, formal.blocking_action_code) == ("VALIDATE_FISCAL_CUSTOMER", "VALIDATE_FISCAL_CUSTOMER")
|
|
|
|
|
|
def test_duplicate_is_suppressed_even_with_pending_legacy_task():
|
|
decision = decide_authoritative_operation(projection("REVIEW_REQUIRED", "REVIEW_REQUIRED",
|
|
is_duplicate_representation=True,
|
|
canonical_opportunity_id="canonical"),
|
|
obligations=[task("REVIEW_MANUALLY")], now=NOW).to_dict()
|
|
source = {"source": "task", "id": "t", "status": "pending", "action_code": "REVIEW_MANUALLY",
|
|
"opportunity_id": "opp", "created_at": NOW}
|
|
assert canonicalize_operations([source], {"opp": decision})["items"] == []
|
|
|
|
|
|
def test_xmat_duplicate_suppresses_diagnostic_review_business_action():
|
|
got = decide_authoritative_operation({
|
|
**projection("REVIEW_REQUIRED", "REVIEW_REQUIRED"),
|
|
"opportunity_id": "1816a06e-9a69-4a9b-9279-1263156892d3",
|
|
"canonical_opportunity_id": "dc89a466-db24-401b-bfe9-d47644b2d0c8",
|
|
"is_duplicate_representation": True,
|
|
}, now=NOW).to_dict()
|
|
assert got["business_next_action"] == "REVIEW_REQUIRED"
|
|
assert got["effective_action"] is None and got["suppress_current_card"] is True
|
|
assert got["canonical_opportunity_id"] == "dc89a466-db24-401b-bfe9-d47644b2d0c8"
|
|
|
|
|
|
def test_authoritative_queue_and_action_survive_canonical_boundary():
|
|
decision = decide_authoritative_operation(projection(), obligations=[task("FOLLOW_UP_CUSTOMER_REVIEW")], now=NOW).to_dict()
|
|
source = {"source": "task", "id": "old", "status": "pending", "action_code": "SEND_INVOICE",
|
|
"opportunity_id": "opp", "created_at": NOW}
|
|
item = canonicalize_operations([source], {"opp": decision})["items"][0]
|
|
assert (item["action_code"], item["operational_queue"]) == ("FOLLOW_UP_CUSTOMER_REVIEW", "do_now")
|
|
|
|
|
|
def test_named_case_acceptance_decisions():
|
|
cases = {
|
|
"5c33db95-fab8-477a-bddd-0b9cc8f91302": ("PROFORMA_REQUIRED", "CREATE_PROFORMA", [], "CREATE_PROFORMA", "do_now"),
|
|
"fd79b9a1-07e6-4f61-95e8-09eab89c155e": ("AWAITING_CUSTOMER", None, [task("FOLLOW_UP_CUSTOMER_REVIEW")], "FOLLOW_UP_CUSTOMER_REVIEW", "do_now"),
|
|
"61f1c955-a372-4ea7-b9b0-b8528d74a141": ("ODOO_ORDER_REQUIRED", "PREPARE_ORDER", [], "PREPARE_ORDER", "do_now"),
|
|
"e3b23ac5-84db-4763-8a31-a684e873032c": ("ODOO_ORDER_REQUIRED", "PREPARE_ORDER", [], "PREPARE_ORDER", "do_now"),
|
|
"dc89a466-db24-401b-bfe9-d47644b2d0c8": ("COMPLETED", None, [], None, "not_current"),
|
|
"fd221608-e007-4043-a23d-07e0c119a345": ("REVIEW_REQUIRED", "REVIEW_REQUIRED", [], "REVIEW_REQUIRED", "review"),
|
|
}
|
|
for oid, (state, action, obligations, effective, queue) in cases.items():
|
|
got = decide_authoritative_operation({**projection(state, action), "opportunity_id": oid,
|
|
"canonical_opportunity_id": oid},
|
|
obligations=obligations, now=NOW)
|
|
assert (got.effective_action, got.queue) == (effective, queue)
|
|
for duplicate, canonical in [
|
|
("1816a06e-9a69-4a9b-9279-1263156892d3", "dc89a466-db24-401b-bfe9-d47644b2d0c8"),
|
|
("434124fb-ac19-4d78-909a-55761d7e8daa", "fd221608-e007-4043-a23d-07e0c119a345"),
|
|
]:
|
|
got = decide_authoritative_operation({**projection("REVIEW_REQUIRED", "REVIEW_REQUIRED"),
|
|
"opportunity_id": duplicate, "canonical_opportunity_id": canonical,
|
|
"is_duplicate_representation": True}, now=NOW)
|
|
assert (got.effective_action, got.queue, got.canonical_opportunity_id) == (None, "not_current", canonical)
|