feat: add shadow BLIF Flow v2 projection
This commit is contained in:
244
tests/domain/opportunity_flow/test_blif_flow_v2.py
Normal file
244
tests/domain/opportunity_flow/test_blif_flow_v2.py
Normal file
@@ -0,0 +1,244 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from app.domain.opportunity_flow.v2 import (
|
||||
derive_business_facts, derive_effective_operational_action,
|
||||
derive_safe_operational_action, derive_v2_operational_queue,
|
||||
)
|
||||
from scripts.simulate_blif_flow_v2 import _apply_material_identity
|
||||
|
||||
|
||||
NOW = datetime(2026, 8, 15, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("facts", "state", "action", "queue"),
|
||||
[
|
||||
({"customer_request": True}, "INQUIRY", "SEND_INFO", "do_now"),
|
||||
({"customer_request": True, "info_or_offer_sent": True}, "AWAITING_CUSTOMER", None, "waiting"),
|
||||
({"order_intent": True}, "PROFORMA_REQUIRED", "CREATE_PROFORMA", "do_now"),
|
||||
({"order_intent": True, "proforma_exists": True}, "PROFORMA_CREATED", "SEND_PROFORMA", "do_now"),
|
||||
({"order_intent": True, "proforma_exists": True, "proforma_sent": True}, "AWAITING_PAYMENT", None, "waiting"),
|
||||
({"payment_confirmed": True}, "INVOICE_REQUIRED", "CREATE_INVOICE", "do_now"),
|
||||
({"payment_confirmed": True, "invoice_exists": True}, "ODOO_ORDER_REQUIRED", "PREPARE_ORDER", "do_now"),
|
||||
({"payment_confirmed": True, "invoice_exists": True, "odoo_order_exists": True}, "ODOO_ORDER_CREATED", "VALIDATE_ODOO_ORDER", "do_now"),
|
||||
({"payment_confirmed": True, "invoice_exists": True, "odoo_order_exists": True, "odoo_order_validated": True}, "ODOO_ORDER_VALIDATED", "COMPLETE_OPPORTUNITY", "do_now"),
|
||||
],
|
||||
)
|
||||
def test_normal_flow(facts, state, action, queue):
|
||||
decision = derive_v2_operational_queue(derive_business_facts(**facts))
|
||||
assert (decision.business_state, decision.next_action, decision.operational_queue) == (state, action, queue)
|
||||
|
||||
|
||||
def test_inquiry_without_current_request_does_not_create_work():
|
||||
decision = derive_v2_operational_queue(derive_business_facts())
|
||||
assert (decision.business_state, decision.next_action, decision.operational_queue) == (
|
||||
"INQUIRY", None, "not_current"
|
||||
)
|
||||
|
||||
|
||||
def test_order_change_before_payment_requires_new_proforma():
|
||||
decision = derive_v2_operational_queue(derive_business_facts(
|
||||
order_intent=True, proforma_exists=True, proforma_sent=True,
|
||||
proforma_created_at=NOW - timedelta(days=2), material_order_change=True,
|
||||
material_order_change_at=NOW - timedelta(days=1),
|
||||
))
|
||||
assert (decision.business_state, decision.next_action) == ("PROFORMA_REQUIRED", "CREATE_PROFORMA")
|
||||
|
||||
|
||||
def test_order_change_after_payment_requires_review():
|
||||
decision = derive_v2_operational_queue(derive_business_facts(
|
||||
payment_confirmed=True, payment_confirmed_at=NOW - timedelta(days=2),
|
||||
material_order_change=True, material_order_change_at=NOW - timedelta(days=1),
|
||||
))
|
||||
assert (decision.business_state, decision.next_action, decision.operational_queue) == (
|
||||
"REVIEW_REQUIRED", "REVIEW_REQUIRED", "review"
|
||||
)
|
||||
|
||||
|
||||
def test_completed_send_quote_task_does_not_create_proforma():
|
||||
decision = derive_v2_operational_queue(derive_business_facts(
|
||||
order_intent=True, audit_task_codes=["SEND_QUOTE:done"],
|
||||
))
|
||||
assert (decision.business_state, decision.next_action) == ("PROFORMA_REQUIRED", "CREATE_PROFORMA")
|
||||
|
||||
|
||||
def test_stale_pending_task_cannot_override_stronger_fact():
|
||||
decision = derive_v2_operational_queue(derive_business_facts(
|
||||
payment_confirmed=True, invoice_exists=True, audit_task_codes=["SEND_PROFORMA:pending"],
|
||||
))
|
||||
assert (decision.business_state, decision.next_action) == ("ODOO_ORDER_REQUIRED", "PREPARE_ORDER")
|
||||
|
||||
|
||||
def test_invoice_without_confirmed_payment_requires_review_not_prepare_order():
|
||||
decision = derive_v2_operational_queue(derive_business_facts(invoice_exists=True))
|
||||
assert (decision.business_state, decision.next_action, decision.operational_queue) == (
|
||||
"REVIEW_REQUIRED", "REVIEW_REQUIRED", "review"
|
||||
)
|
||||
|
||||
|
||||
def test_later_customer_inbound_satisfies_old_followup_and_needs_response():
|
||||
decision = derive_v2_operational_queue(derive_business_facts(
|
||||
customer_request=True, info_or_offer_sent=True,
|
||||
latest_relevant_outbound_at=NOW - timedelta(days=2),
|
||||
latest_relevant_inbound_at=NOW - timedelta(days=1),
|
||||
later_customer_inbound_satisfies_followup=True,
|
||||
audit_task_codes=["FOLLOW_UP_CUSTOMER_REVIEW:pending"],
|
||||
))
|
||||
assert (decision.business_state, decision.next_action) == ("INQUIRY", "SEND_INFO")
|
||||
|
||||
|
||||
def test_odoo_without_mandatory_financial_evidence_requires_review():
|
||||
decision = derive_v2_operational_queue(derive_business_facts(
|
||||
odoo_order_exists=True, odoo_order_validated=True,
|
||||
))
|
||||
assert (decision.business_state, decision.next_action) == ("REVIEW_REQUIRED", "REVIEW_REQUIRED")
|
||||
|
||||
|
||||
def test_fiscal_prerequisite_changes_effective_not_business_action():
|
||||
business = derive_v2_operational_queue(derive_business_facts(order_intent=True))
|
||||
effective = derive_effective_operational_action(
|
||||
business, fiscal_complete=False, fiscal_required=True,
|
||||
)
|
||||
assert effective.business_next_action == "CREATE_PROFORMA"
|
||||
assert effective.effective_operational_action == "VALIDATE_FISCAL_CUSTOMER"
|
||||
assert effective.precedence == "fiscal_prerequisite"
|
||||
|
||||
|
||||
def test_reconciliation_only_blocks_when_adapter_proves_current_document():
|
||||
business = derive_v2_operational_queue(derive_business_facts(order_intent=True))
|
||||
unblocked = derive_effective_operational_action(business, reconciliation_blocking=False)
|
||||
blocked = derive_effective_operational_action(business, reconciliation_blocking=True)
|
||||
assert unblocked.effective_operational_action == "CREATE_PROFORMA"
|
||||
assert blocked.effective_operational_action == "RECONCILE_DOCUMENTS"
|
||||
|
||||
|
||||
def test_scheduled_call_precedes_business_transition():
|
||||
business = derive_v2_operational_queue(derive_business_facts(order_intent=True))
|
||||
effective = derive_effective_operational_action(business, scheduled_call_current=True)
|
||||
assert (effective.effective_operational_action, effective.precedence) == ("CALL_CUSTOMER", "scheduled_call")
|
||||
|
||||
|
||||
def test_safe_projection_preserves_current_v1_obligation_when_raw_is_uncertain():
|
||||
business = derive_v2_operational_queue(derive_business_facts(customer_request=True))
|
||||
raw = derive_effective_operational_action(business)
|
||||
safe = derive_safe_operational_action(
|
||||
raw, v1_action="CREATE_JASMIN_QUOTE", v1_queue="do_now",
|
||||
strong_current_evidence=False,
|
||||
)
|
||||
assert (safe.effective_operational_action, safe.effective_operational_queue) == (
|
||||
"CREATE_JASMIN_QUOTE", "do_now"
|
||||
)
|
||||
|
||||
|
||||
def test_safe_projection_accepts_strong_raw_transition():
|
||||
business = derive_v2_operational_queue(derive_business_facts(order_intent=True))
|
||||
raw = derive_effective_operational_action(business)
|
||||
safe = derive_safe_operational_action(
|
||||
raw, v1_action="RECONCILE_DOCUMENTS", v1_queue="review",
|
||||
strong_current_evidence=True,
|
||||
)
|
||||
assert safe == raw
|
||||
|
||||
|
||||
def test_safe_projection_keeps_strong_factual_review_over_technical_v1_action():
|
||||
business = derive_v2_operational_queue(derive_business_facts(odoo_order_exists=True))
|
||||
raw = derive_effective_operational_action(business)
|
||||
safe = derive_safe_operational_action(
|
||||
raw, v1_action="CREATE_JASMIN_QUOTE", v1_queue="do_now",
|
||||
strong_current_evidence=True,
|
||||
)
|
||||
assert (safe.effective_operational_action, safe.effective_operational_queue) == (
|
||||
"REVIEW_REQUIRED", "review"
|
||||
)
|
||||
|
||||
|
||||
def test_ambiguity_does_not_promote_review_work():
|
||||
business = derive_v2_operational_queue(derive_business_facts(customer_request=True))
|
||||
raw = derive_effective_operational_action(business, diagnostic_status="ambiguous")
|
||||
safe = derive_safe_operational_action(
|
||||
raw, v1_action=None, v1_queue="not_current", strong_current_evidence=False,
|
||||
)
|
||||
assert (safe.effective_operational_action, safe.effective_operational_queue) == (None, "not_current")
|
||||
assert safe.diagnostic_status == "ambiguous"
|
||||
|
||||
|
||||
def test_historical_incomplete_process_stays_not_current():
|
||||
business = derive_v2_operational_queue(derive_business_facts(odoo_order_exists=True))
|
||||
raw = derive_effective_operational_action(business, diagnostic_status="incomplete_history")
|
||||
safe = derive_safe_operational_action(
|
||||
raw, v1_action=None, v1_queue="not_current", strong_current_evidence=False,
|
||||
)
|
||||
assert (safe.effective_operational_action, safe.effective_operational_queue) == (None, "not_current")
|
||||
|
||||
|
||||
def test_due_customer_followup_becomes_do_now_while_business_waits():
|
||||
business = derive_v2_operational_queue(derive_business_facts(
|
||||
customer_request=True, info_or_offer_sent=True,
|
||||
))
|
||||
effective = derive_effective_operational_action(
|
||||
business, due_followup_action="FOLLOW_UP_CUSTOMER_REVIEW",
|
||||
)
|
||||
assert effective.business_state == "AWAITING_CUSTOMER"
|
||||
assert (effective.effective_operational_action, effective.effective_operational_queue) == (
|
||||
"FOLLOW_UP_CUSTOMER_REVIEW", "do_now"
|
||||
)
|
||||
|
||||
|
||||
def test_future_customer_followup_remains_waiting():
|
||||
business = derive_v2_operational_queue(derive_business_facts(
|
||||
customer_request=True, info_or_offer_sent=True,
|
||||
))
|
||||
effective = derive_effective_operational_action(
|
||||
business, future_followup_action="FOLLOW_UP_CUSTOMER_REVIEW",
|
||||
)
|
||||
assert (effective.effective_operational_action, effective.effective_operational_queue) == (
|
||||
"FOLLOW_UP_CUSTOMER_REVIEW", "waiting"
|
||||
)
|
||||
|
||||
|
||||
def test_backlog_remains_backlog_without_stronger_current_evidence():
|
||||
business = derive_v2_operational_queue(derive_business_facts(customer_request=True))
|
||||
raw = derive_effective_operational_action(business, diagnostic_status="ambiguous")
|
||||
safe = derive_safe_operational_action(
|
||||
raw, v1_action="VALIDATE_FISCAL_CUSTOMER", v1_queue="backlog",
|
||||
strong_current_evidence=False,
|
||||
)
|
||||
assert (safe.effective_operational_action, safe.effective_operational_queue) == (
|
||||
"VALIDATE_FISCAL_CUSTOMER", "backlog"
|
||||
)
|
||||
|
||||
|
||||
def _identity_record(oid, *, odoo_id=None, invoice_number=None, complete=False):
|
||||
business = derive_v2_operational_queue(derive_business_facts(
|
||||
payment_confirmed=complete, invoice_exists=complete,
|
||||
))
|
||||
projection = derive_effective_operational_action(business).to_dict()
|
||||
return {
|
||||
"opportunity_id": oid, "title": oid, "customer": oid,
|
||||
"material_identity_keys": [], "classification": "UNCHANGED",
|
||||
"raw_v2": dict(projection), "safe_v2": dict(projection),
|
||||
"evidence": {
|
||||
"is_reconstructed": not complete, "invoice": ([{"document_number": invoice_number}] if invoice_number else []),
|
||||
"payment": ([{"id": "p"}] if complete else []), "proforma": [],
|
||||
"odoo": ([{"external_type": "sale_order", "external_id": odoo_id, "external_name": f"S{odoo_id}"}] if odoo_id else []),
|
||||
"latest_relevant_inbound": None, "latest_relevant_outbound": None,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_same_odoo_external_id_suppresses_second_current_card():
|
||||
records = [_identity_record("canonical", odoo_id="349", complete=True),
|
||||
_identity_record("reconstructed", odoo_id="349")]
|
||||
groups = _apply_material_identity(records)
|
||||
assert groups[0]["canonical_process_id"] == "canonical"
|
||||
assert records[1]["safe_v2"]["effective_operational_queue"] == "not_current"
|
||||
|
||||
|
||||
def test_same_invoice_identity_suppresses_second_current_card():
|
||||
records = [_identity_record("canonical", invoice_number="FA.186", complete=True),
|
||||
_identity_record("duplicate", invoice_number="FA.186")]
|
||||
groups = _apply_material_identity(records)
|
||||
assert len(groups) == 1
|
||||
assert sum(row["safe_v2"]["effective_operational_queue"] != "not_current" for row in records) == 1
|
||||
Reference in New Issue
Block a user