from __future__ import annotations from app.domain.contas.service import ContasDomainService from app.domain.contas.workflow_actions import build_contas_workflow_actions class _Client: def __init__(self, *, protocol="INFO-123", fail=False): self.protocol = protocol self.fail = fail self.protocol_calls = [] self.status_calls = [] def abrir_protocolo(self, payload): self.protocol_calls.append(dict(payload)) if self.fail: raise RuntimeError("falha protocolo") return {"interactionProtocol": self.protocol} def status_sr(self, payload): self.status_calls.append(dict(payload)); return {"ok": True} def _action(client): service = ContasDomainService(client=client) reg = build_contas_workflow_actions(service) return reg.get("finalizar_atendimento_action") def _codes(result): return [x["code"] for x in result.get("business_events") or []] def test_invoice_explanation_finalization_orders_events_and_returns_alias(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Atendimento concluído.", "msisdn": "11999999999", "social_sec_no": "123", "message_id": "m1", "business_workflows_executed": ["invoice_explanation"], "invoice_explanation_base": "Explicação dos valores da fatura.", }, {"input": {}}) assert result["finalizacao_protocol"] == "INFO-123" assert _codes(result) == ["CVN.008", "RCT.085", "CVN.010", "MPI.006", "SAD.001"] def test_invoice_explanation_negative_emits_cvn009_mpi005_without_protocol_when_no_notes(): client = _Client() result = _action(client)({ "status": "nao_resolvido", "summary": "Cliente não concordou.", "business_workflows_executed": ["invoice_explanation"], }, {"input": {}}) assert client.protocol_calls == [] assert _codes(result) == ["CVN.009", "MPI.005", "SAD.001"] def test_handoff_suppression_emits_only_sad001(): client = _Client() result = _action(client)({ "status": "nao_resolvido", "summary": "ATH", "business_workflows_executed": ["invoice_explanation"], "invoice_explanation_base": "Explicação.", "suppress_cvn_009_on_finalize": True, "suppress_cvn_lookup_on_finalize": True, "suppress_informational_protocol_on_finalize": True, }, {"input": {}}) assert client.protocol_calls == [] assert _codes(result) == ["SAD.001"] def test_protocol_failure_does_not_fail_finalization_and_emits_negative_events(): client = _Client(fail=True) result = _action(client)({ "status": "resolvido", "summary": "ok", "msisdn": "119", "business_workflows_executed": ["invoice_explanation"], "invoice_explanation_base": "Explicação da fatura.", }, {"input": {}}) assert result["success"] is True assert "finalizacao_protocol" not in result assert _codes(result) == ["CVN.008", "RCT.086", "CVN.011", "MPI.006", "SAD.001"] def test_transactional_workflow_does_not_emit_informational_acceptance(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "concluído", "protocol_closed": True, "protocol_number": "PRT-1", "business_workflows_executed": ["contestacao_tool"], }, {"input": {}}) assert _codes(result) == ["SAD.001"] def test_sad_decision_tree_handoff_second_time(): client = _Client() result = _action(client)({ "status": "nao_resolvido", "summary": "ATH", "sad_exit_decision_enabled": True, "human_handoff": True, "ath_first_time": False, }, {"input": {}}) assert _codes(result) == ["SAD.001", "SAD.003", "SAD.004", "SAD.006"] def test_invoice_explanation_uses_canonical_notes_not_free_text(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "ok", "msisdn": "119", "business_workflows_executed": ["invoice_explanation"], "invoice_explanation_base": "Resumo qualquer vindo do prefetch e do LLM.", }, {"input": {}}) assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação dos valores da fatura" assert result["finalizacao_protocol"] == "INFO-123" def test_strategic_partner_is_classified_without_invoice_detail(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "ok", "msisdn": "119", "informational_service_names": ["Paramount+ Mensal"], }, {"input": {}}) assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação de VAS Estratégico" assert result["finalizacao_protocol"] == "INFO-123" def test_known_avulso_is_classified_without_invoice_detail(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "ok", "msisdn": "119", "informational_service_names": ["TIM Fashion Mensal"], }, {"input": {}}) assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação de VAS Avulso" assert result["finalizacao_protocol"] == "INFO-123" def test_prefetch_invoice_context_guarantees_lookup_and_rt15_without_latched_workflow(): client = _Client() result = _action(client)({ "status": "resolvido_outros_assuntos", "summary": "Encerramento realizado pelo agente.", "msisdn": "11999999999", "invoice_id": "3000131181", "invoice_explanation_base": "Resumo da fatura carregado no prefetch.", }, {"input": {}}) assert result["finalizacao_protocol"] == "INFO-123" assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação dos valores da fatura" assert _codes(result) == ["CVN.002", "CVN.006", "CVN.008", "RCT.085", "CVN.010", "MPI.006", "SAD.001"] def test_unresolved_transition_does_not_reemit_positive_cvn_or_mpi(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Cliente aceitou no fluxo de negócio.", "invoice_id": "3000131182", "conversation_unresolved_transition_emitted": True, "invoice_explanation_base": "Resumo da fatura.", }, {"input": {}}) assert _codes(result) == ["SAD.001"] def test_terminal_veb_resolution_suppresses_cvn_mpi_and_duplicate_rt15(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Cliente aceitou a explicação.", "protocol_number": "VEB-123", "protocol_closed": True, "conversation_explanation_resolved": True, "suppress_cvn_protocol_ic": True, "informational_vas_types": ["bundle"], "informational_service_names": ["Pacote Europa Promocional"], }, {"input": {}}) assert client.protocol_calls == [] assert result["protocol_number"] == "VEB-123" assert _codes(result) == ["SAD.001"] def test_invoice_detail_conflict_overrides_saved_informational_type(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "ok", "msisdn": "119", "informational_service_names": ["TIM Fashion Mensal"], "informational_vas_types": ["bundle"], "invoice_detail": {"119": {"SVA Detalhe Total": [{"desc": "TIM Fashion Mensal", "value": 19.9, "classe": "avulso"}]}}, }, {"input": {}}) assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação de VAS Avulso" assert result["finalizacao_protocol"] == "INFO-123" def test_invoice_detail_without_match_ignores_saved_type_and_does_not_create_protocol(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "ok", "msisdn": "119", "informational_service_names": ["Serviço que não consta"], "informational_vas_types": ["bundle"], "invoice_detail": {"119": {"Serviços Bundle Inclusos": [{"desc": "Outro serviço", "value": 0}]}}, }, {"input": {}}) assert client.protocol_calls == [] assert "finalizacao_protocol" not in result def test_rag_saved_type_is_used_when_invoice_detail_is_absent(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "ok", "msisdn": "119", "informational_service_names": ["Serviço explicado pelo RAG"], "informational_rag_vas_types": ["bundle"], }, {"input": {}}) assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação de VAS Bundle" assert result["finalizacao_protocol"] == "INFO-123" def test_partial_invoice_inference_merges_saved_avulso_type(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Cliente entendeu a explicação e agradeceu.", "msisdn": "119", "informational_service_names": ["Pacote Europa Promocional", "YouTube Premium Mensal", "Aluguel de Filme 3"], "informational_vas_types": ["avulso"], "invoice_detail": {"119": { "Serviços Bundle Inclusos": [{"desc": "Pacote Europa Promocional", "value": 0}], "Serviços Contratados de Terceiros": [{"desc": "YouTube Premium Mensal", "value": 24.9, "classe": "estrategico"}], }}, }, {"input": {}}) assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação de VAS Bundle, VAS Estratégico, VAS Avulso" assert result["finalizacao_protocol"] == "INFO-123" def test_existing_session_protocol_prevents_duplicate_informational_rt15(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Cliente entendeu a explicação.", "customer_protocols": [{"number": "PRT-EXISTENTE"}], "informational_service_names": ["Paramount+ Mensal"], }, {"input": {}}) assert client.protocol_calls == [] assert "finalizacao_protocol" not in result def test_cpf_alias_is_forwarded_to_informational_protocol(): client = _Client() _action(client)({ "status": "resolvido", "summary": "Cliente entendeu a explicação.", "msisdn": "11999999999", "cpf": "12345678901", "informational_service_names": ["Paramount+ Mensal"], }, {"input": {}}) assert client.protocol_calls[0]["socialSecNo"] == "12345678901" def test_three_informational_vas_types_have_canonical_order(): client = _Client() _action(client)({ "status": "resolvido", "summary": "Cliente entendeu.", "msisdn": "119", "informational_vas_types": ["avulso", "bundle", "estrategico"], }, {"input": {}}) assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação de VAS Bundle, VAS Estratégico, VAS Avulso" def test_free_customer_phrase_without_evidence_does_not_create_protocol(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Cliente entendeu a explicação.", "customer_message": "o que é incluído no plano, mas está sendo cobrado?", }, {"input": {}}) assert client.protocol_calls == [] assert "finalizacao_protocol" not in result def test_legacy_final_alias_normalizes_to_resolved_without_spurious_protocol(): client = _Client() result = _action(client)({"status": "final", "summary": ""}, {"input": {}}) assert result["status"] == "resolvido" assert client.protocol_calls == [] def test_transactional_cancel_finalization_does_not_emit_cvn_or_mpi_even_with_summary(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Cancelamento concluído.", "business_workflows_executed": ["cancelamento_vas_avulso"], "protocol_number": "CAN-123", "protocol_closed": True, }, {"input": {}}) assert _codes(result) == ["SAD.001"] def test_strategic_notes_take_priority_over_prior_invoice_explanation_notes(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Cliente entendeu a explicação e agradeceu.", "msisdn": "11999999999", "business_workflows_executed": ["invoice_explanation"], "invoice_explanation_base": "Explicação dos valores da fatura.", "informational_service_names": ["YouTube Premium Mensal"], "informational_vas_types": ["estrategico"], }, {"input": {}}) assert result["finalizacao_protocol"] == "INFO-123" assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação de VAS Estratégico" def test_deferred_bundle_and_strategic_items_create_combined_rt15_without_workflow_latch(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Cliente entendeu a explicação e agradeceu.", "msisdn": "11999999999", "protocol_deferred_to_finalization": True, "informational_service_names": ["Pacote Europa Promocional", "YouTube Premium Mensal"], "informational_vas_types": ["bundle", "estrategico"], "invoice_detail": { "11999999999": { "Serviços Bundle Inclusos": [{"desc": "Pacote Europa Promocional", "value": 0}], "Serviços Contratados de Terceiros": [{"desc": "YouTube Premium Mensal", "value": 24.9, "classe": "estrategico"}], } }, }, {"input": {}}) assert result["finalizacao_protocol"] == "INFO-123" assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação de VAS Bundle, VAS Estratégico" def test_invoice_explanation_only_registers_protocol_at_finalization(): client = _Client() result = _action(client)({ "status": "resolvido", "summary": "Cliente aceitou a explicação.", "msisdn": "11999999999", "business_workflows_executed": ["invoice_explanation"], "invoice_explanation_base": "Explicação dos valores da fatura.", }, {"input": {}}) assert len(client.protocol_calls) == 1 assert result["finalizacao_protocol"] == "INFO-123" assert client.protocol_calls[0]["serviceRequestNotes"] == "Explicação dos valores da fatura"