feat: add shadow BLIF Flow v2 projection

This commit is contained in:
plx
2026-08-15 23:03:55 +00:00
parent 47f21888f9
commit 20bc91dec5
3 changed files with 1359 additions and 0 deletions

View 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