Projeto do Agent Contas ORACLE

This commit is contained in:
2026-08-19 09:35:50 -03:00
commit 950a2bcd33
1366 changed files with 177217 additions and 0 deletions

View File

@@ -0,0 +1,352 @@
"""Paridade do compositor determinístico com o prompt
``fluxo_vas_cancelamento_resposta_cliente`` (regras 1.1, 28).
Os goldens de sucesso/erro/SMS vêm dos próprios exemplos do prompt em
``builtin/prompts/vas_cancellation_response.yaml``.
"""
from __future__ import annotations
from app.domain.contas.vas_cancellation_message import (
compose_vas_cancellation_message,
)
PRT = "PRT475D8F1C1F"
def test_regra_3_sucesso_com_sms_singular() -> None:
# Exemplo do prompt: "Exemplo (sucesso com SMS)".
msg = compose_vas_cancellation_message(
{
"contested_items": [{"itemName": "Tamboro Mensal"}],
"contested_invoice_amount_open": "22,00",
"sms_sent": True,
"sms_not_send_error": False,
"msisdn": "11999999999",
"contestacao_protocol": PRT,
}
)
assert msg == (
"O cancelamento foi concluído com sucesso. O valor contestado foi retirado "
"da sua fatura. Enviamos um SMS com o novo código de barras e o valor "
"atualizado. O prazo para pagamento é de 4 dias. "
f"Seu número de protocolo é {PRT}."
)
def test_regra_3_e_6_2_falha_no_sms_plural() -> None:
# Exemplo do prompt: "Exemplo (sucesso com falha no SMS)".
msg = compose_vas_cancellation_message(
{
"contested_items": [
{"itemName": "Tamboro Mensal"},
{"itemName": "TIM Fashion"},
],
"contested_invoice_amount_open": "22,00",
"sms_sent": True,
"sms_not_send_error": True,
"msisdn": "11999999999",
"contestacao_protocol": PRT,
}
)
assert msg == (
"O cancelamento dos itens foi concluído com sucesso. O valor total de "
"R$ 22,00 foi retirado da sua fatura. Identificamos uma instabilidade no "
"envio do SMS com o novo código para pagamento. Você pode consultar o "
"código atualizado e o prazo de pagamento diretamente no app Meu TIM. "
f"Seu número de protocolo é {PRT}."
)
def test_regra_2_credito_plural_com_regra_5() -> None:
# Baseado no "Exemplo (crédito em fatura)" do prompt; o conector "No entanto,"
# era floreio do LLM — a forma determinística segue o template literal da regra 5.
msg = compose_vas_cancellation_message(
{
"contested_items": [
{"itemName": "Aluguel de Série 1"},
{"itemName": "Aluguel de Série 2"},
],
"not_contested_items": [{"itemName": "Tamboro Mensal"}],
"contested_invoice_amount_open": "22,00",
"sms_sent": False,
"contestacao_protocol": PRT,
}
)
assert msg == (
"O cancelamento dos itens foi feito com sucesso. O crédito no valor total "
"de R$ 22,00 ficou registrado como crédito para sua próxima fatura. "
"Não foi possível contestar o item Tamboro Mensal. "
f"Seu número de protocolo é {PRT}."
)
def test_regra_2_credito_singular() -> None:
msg = compose_vas_cancellation_message(
{
"contested_items": [{"itemName": "Tamboro Mensal"}],
"contested_invoice_amount_open": "22,00",
"sms_sent": False,
"contestacao_protocol": PRT,
}
)
assert msg == (
"O cancelamento do item Tamboro Mensal no valor de R$ 22,00 foi concluído "
"com sucesso. O valor contestado ficou registrado como crédito para sua "
f"próxima fatura. Seu número de protocolo é {PRT}."
)
def test_regra_8_erro_terminal_copia_literal_e_protocolo() -> None:
# Exemplo do prompt: "Exemplo (falha, usando um dos textos de ...)".
erro = (
"Verifiquei aqui que este item já foi contestado anteriormente, por isso "
"não consigo seguir com uma nova contestação. Peço que aguarde um instante."
)
msg = compose_vas_cancellation_message(
{
"contestation_error_description": erro,
"contestacao_protocol": PRT,
}
)
assert msg == f"{erro} Seu número de protocolo é {PRT}."
def test_regra_8_adiciona_ponto_quando_texto_nao_termina_em_pontuacao() -> None:
msg = compose_vas_cancellation_message(
{
"contestation_error_description": "Não foi possível processar",
"contestacao_protocol": PRT,
}
)
assert msg == f"Não foi possível processar. Seu número de protocolo é {PRT}."
def test_regra_8_item_ja_contestado_informa_cancelamentos_realizados() -> None:
msg = compose_vas_cancellation_message(
{
"cancelados": [{"servico": "A"}, {"servico": "B"}],
"contestation_error_description": (
"Verifiquei aqui que estes itens já foram contestados anteriormente, "
"por isso não consigo seguir com uma nova contestação."
),
"contestacao_protocol": PRT,
}
)
assert msg == (
"O cancelamento dos itens A e B foi concluído com sucesso. "
"Verifiquei aqui que estes itens já foram contestados anteriormente, "
"por isso não consigo seguir com uma nova contestação. "
f"Seu número de protocolo é {PRT}."
)
def test_regra_1_1_misto_credito() -> None:
msg = compose_vas_cancellation_message(
{
"itens_ja_contestados": ["Tamboro Mensal"],
"contested_items": [{"itemName": "TIM Fashion"}],
"contested_invoice_amount_open": "10,00",
"sms_sent": False,
"contestacao_protocol": PRT,
}
)
assert msg == (
"Identifiquei que o item Tamboro Mensal já havia sido contestado "
"anteriormente e, por esse motivo, não é possível registrar uma nova "
"contestação para ele. Já a contestação do item TIM Fashion no valor de "
"R$ 10,00 foi concluída com sucesso. O valor contestado ficou registrado "
f"como crédito para sua próxima fatura. Seu número de protocolo é {PRT}."
)
def test_regra_1_1_misto_com_sms_aplica_regra_6() -> None:
msg = compose_vas_cancellation_message(
{
"itens_ja_contestados": ["A", "B"],
"contested_items": [{"itemName": "C"}, {"itemName": "D"}],
"contested_invoice_amount_open": "30,00",
"sms_sent": True,
"sms_not_send_error": False,
"msisdn": "11988887777",
"contestacao_protocol": PRT,
}
)
assert msg == (
"O cancelamento foi concluído com sucesso. O valor contestado foi retirado "
"da sua fatura. Enviamos um SMS com o novo código de barras e o valor "
"atualizado. O prazo para pagamento é de 4 dias. "
f"Seu número de protocolo é {PRT}."
)
def test_sms_enviado_prevalece_quando_contested_items_esta_vazio() -> None:
# Regressão do trace 6ae8bd24: SMS efetivamente enviado nunca vira crédito.
msg = compose_vas_cancellation_message(
{
"itens_ja_contestados": ["Tamboro Mensal"],
"contested_items": [],
"contested_invoice_amount_open": "22,00",
"sms_sent": True,
"contestacao_protocol": PRT,
}
)
assert msg == (
"O cancelamento foi concluído com sucesso. O valor contestado foi retirado "
"da sua fatura. Enviamos um SMS com o novo código de barras e o valor "
"atualizado. O prazo para pagamento é de 4 dias. "
f"Seu número de protocolo é {PRT}."
)
assert "próxima fatura" not in msg
def test_regra_7_protocolos_multiplos() -> None:
msg = compose_vas_cancellation_message(
{
"contested_items": [{"itemName": "Tamboro Mensal"}],
"contested_invoice_amount_open": "22,00",
"sms_sent": False,
"contestacao_protocols": ["PRTAAA", "PRTBBB"],
}
)
assert msg.endswith("Seus números de protocolo são PRTAAA e PRTBBB.")
def test_no_match_credito_todos_nao_encontrados_singular() -> None:
# Cenário 2 (crédito): serviço não localizado na plataforma; valor contestado.
msg = compose_vas_cancellation_message(
{
"success": True,
"contested_items": [{"itemName": "Tamboro Mensal"}],
"nao_encontrados": [{"servico": "Tamboro Mensal"}],
"cancelados": [],
"contested_invoice_amount_open": "22,00",
"sms_sent": False,
"contestacao_protocol": PRT,
}
)
assert msg == (
"O valor total de R$ 22,00 ficou registrado como crédito para sua próxima "
"fatura. Sobre o cancelamento do serviço Tamboro Mensal, não consegui "
"realizar por aqui. Você pode cancelar pelo App Meu TIM, na opção "
f"Gerenciar Benefícios. Seu número de protocolo é {PRT}."
)
def test_no_match_sms_todos_nao_encontrados() -> None:
# Cenário 2 (SMS/boleto): valor retirado + SMS + no-match.
msg = compose_vas_cancellation_message(
{
"success": True,
"contested_items": [{"itemName": "Tamboro Mensal"}],
"nao_encontrados": [{"servico": "Tamboro Mensal"}],
"cancelados": [],
"contested_invoice_amount_open": "22,00",
"sms_sent": True,
"sms_not_send_error": False,
"msisdn": "11999999999",
"contestacao_protocol": PRT,
}
)
assert msg == (
"O valor total de R$ 22,00 foi retirado da sua fatura. Enviamos um SMS com "
"o novo código de barras e o valor atualizado. O prazo para pagamento é de "
"4 dias. Sobre o cancelamento do serviço Tamboro "
"Mensal, não consegui realizar por aqui. Você pode cancelar pelo App Meu "
f"TIM, na opção Gerenciar Benefícios. Seu número de protocolo é {PRT}."
)
def test_no_match_misto_credito() -> None:
# Cenário 3 (crédito): A,B,C cancelados; D,E,F no-match.
msg = compose_vas_cancellation_message(
{
"success": True,
"contested_items": [{"itemName": x} for x in ("A", "B", "C", "D", "E", "F")],
"cancelados": [{"servico": x} for x in ("A", "B", "C")],
"nao_encontrados": [{"servico": x} for x in ("D", "E", "F")],
"contested_invoice_amount_open": "60,00",
"sms_sent": False,
"contestacao_protocol": PRT,
}
)
assert msg == (
"O valor total de R$ 60,00 ficou registrado como crédito para sua próxima "
"fatura. O cancelamento dos serviços A, B e C foram realizados com sucesso. "
"Não consegui realizar por aqui o cancelamento dos serviços D, E e F. Você "
"pode cancelá-los pelo App Meu TIM, na opção Gerenciar Benefícios. "
f"Seu número de protocolo é {PRT}."
)
def test_no_match_misto_sms() -> None:
# Cenário 3 (SMS/boleto).
msg = compose_vas_cancellation_message(
{
"success": True,
"contested_items": [{"itemName": x} for x in ("A", "B", "C", "D", "E", "F")],
"cancelados": [{"servico": x} for x in ("A", "B", "C")],
"nao_encontrados": [{"servico": x} for x in ("D", "E", "F")],
"contested_invoice_amount_open": "60,00",
"sms_sent": True,
"sms_not_send_error": False,
"msisdn": "11988887777",
"contestacao_protocol": PRT,
}
)
assert msg == (
"O valor total de R$ 60,00 foi retirado da sua fatura. Enviamos um SMS com "
"o novo código de barras e o valor atualizado. O prazo para pagamento é de "
"4 dias. O cancelamento dos serviços A, B e C foram "
"realizados com sucesso. Não consegui realizar por aqui o cancelamento dos "
"serviços D, E e F. Você pode cancelá-los pelo App Meu TIM, na opção "
f"Gerenciar Benefícios. Seu número de protocolo é {PRT}."
)
def test_no_match_misto_singular_cada_lista() -> None:
# Concordância: 1 cancelado + 1 no-match.
msg = compose_vas_cancellation_message(
{
"success": True,
"contested_items": [{"itemName": "A"}, {"itemName": "B"}],
"cancelados": [{"servico": "A"}],
"nao_encontrados": [{"servico": "B"}],
"contested_invoice_amount_open": "20,00",
"sms_sent": False,
"contestacao_protocol": PRT,
}
)
assert msg == (
"O valor total de R$ 20,00 ficou registrado como crédito para sua próxima "
"fatura. O cancelamento do serviço A foi realizado com sucesso. Não "
"consegui realizar por aqui o cancelamento do serviço B. Você pode "
f"cancelá-lo pelo App Meu TIM, na opção Gerenciar Benefícios. Seu número "
f"de protocolo é {PRT}."
)
def test_sucesso_sem_itens_detalhados_gera_confirmacao_generica() -> None:
# Sem itens contestados/não contestados/já contestados, mas cancelamento OK:
# confirmação neutra + protocolo (regra 7).
msg = compose_vas_cancellation_message(
{
"success": True,
"contested_items": [],
"not_contested_items": [],
"contestacao_protocol": PRT,
}
)
assert msg == f"O cancelamento foi concluído com sucesso. Seu número de protocolo é {PRT}."
def test_sucesso_sem_itens_e_sem_protocolo_so_confirmacao() -> None:
# Protocolo de cancelamento (não de contestação) é anexado pelo backend, não aqui.
msg = compose_vas_cancellation_message({"success": True, "contested_items": []})
assert msg == "O cancelamento foi concluído com sucesso."
def test_payload_nao_classificavel_sem_sucesso_retorna_vazio() -> None:
assert compose_vas_cancellation_message({}) == ""
assert compose_vas_cancellation_message({"success": False}) == ""
assert compose_vas_cancellation_message("nao-mapping") == "" # type: ignore[arg-type]