Files
agent_contas/tests/migration/original_test_invoice_to_text.py

222 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from app.domain.contas.parsers.invoice_to_text import get_vas_product_names, to_text
def _build_lines(text: str) -> list[str]:
return text.splitlines()
def test_sva_renderiza_acao_por_item() -> None:
"""SVA: header "Itens Eventuais | cobrados a parte" (a seção "SVA Detalhe Total"
é impressa sob esse rótulo); estrategico -> 'falar sobre'; avulso -> 'cancelar'."""
data = {
"11999990000": {
"SVA Detalhe Total": [
{"desc": "Aluguel de Filme 1", "value": 19.90, "period": "02/05/26"},
{
"desc": "YouTube Premium Mensal",
"value": 24.90,
"period": "02/05/26",
"estrategico": True,
},
],
}
}
lines = _build_lines(to_text(data))
# header "cobrados a parte", sem "| ação=... | avulso"
assert "Itens Eventuais | cobrados a parte" in lines
assert "SVA Detalhe Total" not in to_text(data)
avulso = next(l for l in lines if "Aluguel de Filme 1" in l)
estrategico = next(l for l in lines if "YouTube Premium Mensal" in l)
assert avulso.endswith("| ação=cancelar")
# Item estratégico no SVA: ação "falar sobre" + marcador "type=estrategico".
assert "| ação=falar sobre" in estrategico
assert estrategico.endswith("| type=estrategico")
def test_itens_eventuais_cobrados_a_parte_acao_por_item() -> None:
"""Itens Eventuais: header "cobrados a parte" + ação por item (avulso ->
'cancelar'; estratégico -> 'falar sobre' + 'type=estrategico')."""
data = {
"11999990000": {
"Itens Eventuais": [
{"desc": "Serviço Avulso X", "value": 9.90, "period": "02/05/26"},
{
"desc": "Max Mensal",
"value": 34.90,
"period": "02/05/26",
"estrategico": True,
},
],
}
}
lines = _build_lines(to_text(data))
assert "Itens Eventuais | cobrados a parte" in lines
assert "Itens Eventuais | ação=cancelar | avulso" not in lines
avulso = next(l for l in lines if "Serviço Avulso X" in l)
estrategico = next(l for l in lines if "Max Mensal" in l)
assert avulso.endswith("| ação=cancelar")
assert "| ação=falar sobre" in estrategico
assert estrategico.endswith("| type=estrategico")
def test_sva_e_itens_eventuais_mesclam_num_unico_cabecalho() -> None:
"""Linha com as DUAS seções ("SVA Detalhe Total" e "Itens Eventuais"): um único
cabeçalho "Itens Eventuais", na posição da primeira delas, com os itens das duas
embaixo. A ação por item continua vindo da seção de ORIGEM."""
data = {
"11999990000": {
"SVA Detalhe Total": [
{"desc": "Tamboro Mensal", "value": 14.99, "period": "01/11/25"},
],
"Mensalidades Adicionais": [{"desc": "Plugin 5G Plus", "value": 5.00}],
"Itens Eventuais": [
{"desc": "Serviço Avulso X", "value": 9.90, "period": "02/05/26"},
{"desc": "CRÉDITO: PAGAMENTO", "value": -20.00},
],
}
}
full = to_text(data)
lines = _build_lines(full)
assert lines.count("Itens Eventuais | cobrados a parte") == 1
assert "SVA Detalhe Total" not in full
# Posição: o bloco mesclado fica onde a PRIMEIRA das duas seções aparecia,
# antes de "Mensalidades Adicionais".
assert [l for l in lines if not l[:1].isspace() and l] == [
"LINHA 11999990000",
"Itens Eventuais | cobrados a parte",
"Mensalidades Adicionais",
"multiplas_linhas = false",
]
assert " Tamboro Mensal | valor=14.99 | data=01/11/25 | ação=cancelar" in lines
assert " Serviço Avulso X | valor=9.90 | data=02/05/26 | ação=cancelar" in lines
assert " CRÉDITO: PAGAMENTO | valor=-20.00 | ação=cancelar" in lines
# No modo enxuto o cabeçalho mesclado é só o rótulo (sem "| cobrados a parte").
lean_lines = _build_lines(to_text(data, lean=True))
assert lean_lines.count("Itens Eventuais") == 1
assert " Tamboro Mensal | valor=14.99 | data=01/11/25" in lean_lines
assert " Serviço Avulso X | valor=9.90 | data=02/05/26" in lean_lines
def test_lean_enxuga_para_o_intent() -> None:
"""``lean=True`` (formato do intent classifier): mantém LINHA/seção, o NOME dos
itens e ``valor``/``data`` por item (para desambiguar cobranças duplicadas);
descarta Fatura Resumo, Planos, Outros Valores e os demais campos por-item
(ação/type/franquia/consumo). Item ``Incluído`` e período em faixa não geram
valor/data."""
data = {
"Fatura Resumo": [{"desc": "total geral", "value": 207.43}],
"1199999999": {
"Planos": {"TIM Black A 8.0": {"valor_final": 104.99}},
"Outros Valores": [{"desc": "juros: (VENC 10/11)", "value": 0.26}],
"SVA Detalhe Total": [
{"desc": "Tamboro Mensal", "value": 14.99, "period": "01/11/25"},
{"desc": "Youtube Premium", "value": 10.0, "estrategico": True},
],
"Serviços Bundle Inclusos": [
{"desc": "Minutos Locais e DDD com 41", "value": "Incluído",
"period": "19/05 a 18/06", "franchise": "Ilimitado",
"consumption": "43m30s"},
],
},
}
lean = to_text(data, lean=True)
lines = _build_lines(lean)
# Estrutura + nome + valor/data por item (data só p/ período de data única).
assert "LINHA 1199999999" in lines
assert " Tamboro Mensal | valor=14.99 | data=01/11/25" in lines
# Estratégico sem período: valor sim, data não.
assert " Youtube Premium | valor=10.00" in lines
# Bundle "Incluído" com período em faixa: só o nome (sem valor/data).
assert " Minutos Locais e DDD com 41" in lines
assert "multiplas_linhas = false" in lines
# Cabeçalho de seção é só o nome (sem "| ação=… | classe") no modo enxuto.
assert "Benefícios do Plano" in lines
assert "ação=" not in lean
assert "| bundle" not in lean
# valor/data por item agora PRESENTES (distinguem cobranças duplicadas).
assert "valor=14.99" in lean
assert "data=01/11/25" in lean
# "Incluído" nunca vira valor=; período em faixa nunca vira data=.
assert "valor=Incluído" not in lean
assert "data=19/05" not in lean
# Descartado: resumo, planos, outros valores e os demais campos por-item.
assert "Fatura Resumo" not in lean
assert "Planos" not in lean
assert "Outros Valores" not in lean
assert "TIM Black A 8.0" not in lean
assert "type=estrategico" not in lean
assert "franquia=" not in lean
assert "consumo=" not in lean
# O formato completo (default) SEGUE com tudo — orquestrador inalterado.
full = to_text(data)
assert "Fatura Resumo" in full
assert "Planos" in full
assert "valor=14.99" in full
def test_get_vas_product_names_ordem_e_filtra_nao_vas() -> None:
"""Só VAS, na ordem avulso -> estrategico -> bundle; plano e não-VAS ficam de
fora. `classe` é a fonte; item com só `estrategico=True` (seção sem classe) cai
no fallback estratégico."""
data = {
"Fatura Resumo": [{"desc": "total geral", "value": 100.0}],
"vocalized_msisdn": {"11999990000": "um um ..."},
"11999990000": {
"Planos": {"TIM Black A 8.0": {"valor_final": 100.0}},
"SVA Detalhe Total": [
{"desc": "Focus Mensal", "value": 14.99, "classe": "avulso"},
{"desc": "Netflix", "value": 39.9, "classe": "estrategico",
"estrategico": True},
],
"Itens Eventuais": [
{"desc": "Tamboro Mensal", "value": 9.9, "classe": "avulso"},
],
"Serviços Bundle Inclusos": [
{"desc": "HBO Bundle", "classe": "bundle"},
],
# seção sem `classe`, só a flag -> fallback estratégico.
"Cobranças de Terceiros": [
{"desc": "Globoplay", "value": 19.9, "estrategico": True},
],
"Descontos": [{"desc": "Desconto Fidelidade", "value": -10.0}],
},
}
result = get_vas_product_names(data)
assert result == "Focus Mensal, Tamboro Mensal, Netflix, Globoplay, HBO Bundle"
# plano e seção não-VAS não entram.
assert "TIM Black" not in result
assert "Desconto" not in result
def test_get_vas_product_names_deduplica_entre_linhas() -> None:
"""Mesmo produto em duas linhas (MSISDN) aparece 1×, na primeira ocorrência."""
data = {
"11999990000": {"SVA Detalhe Total": [{"desc": "Focus Mensal", "classe": "avulso"}]},
"11888880000": {"SVA Detalhe Total": [{"desc": "Focus Mensal", "classe": "avulso"}]},
}
assert get_vas_product_names(data) == "Focus Mensal"
def test_get_vas_product_names_sem_vas_retorna_vazio() -> None:
assert get_vas_product_names({}) == ""
assert get_vas_product_names(
{"11999990000": {"Descontos": [{"desc": "X", "value": -1.0}]}}
) == ""