bugfix reconciliation extractor

This commit is contained in:
2026-09-07 20:17:01 -03:00
parent 76c8cdc738
commit 811d4fd175
194 changed files with 1031 additions and 41 deletions

View File

@@ -0,0 +1,90 @@
from __future__ import annotations
import asyncio
from contas_mcp.servers.contas_mcp_server import main
def _analysis():
return {
"currentInvoice": [
{
"type": "servicos_contratados_de_parceiros",
"items": [
{"desc": "Tamboro Mensal", "value": "14.99", "contestable": True, "classe": "avulso"},
{"desc": "Tamboro Mensal", "value": "19.99", "contestable": True, "classe": "avulso"},
{"desc": "Outro Produto", "value": "19.99", "contestable": True, "classe": "avulso"},
],
}
]
}
def test_subject_catalog_does_not_choose_arbitrary_value_for_repeated_subject():
catalog = main._invoice_subject_catalog({"billing_analysis": _analysis()})
tamboro = next(x for x in catalog if x["name"] == "Tamboro Mensal")
assert sorted(tamboro["values"]) == [14.99, 19.99]
assert tamboro["value"] is None
def test_preflight_does_not_resolve_value_from_subject_when_subject_has_multiple_values(monkeypatch):
class DummyClient:
def billing_analysis(self, _msisdn):
return _analysis()
class DummyService:
client = DummyClient()
monkeypatch.setattr(main, "service", DummyService())
args = {
"msisdn": "11999999999",
"subject": "Tamboro Mensal",
"query": "não reconheço esse Tamboro Mensal",
"billing_analysis": _analysis(),
"resolved_value": "25.5",
}
# The resolver may continue to normal validation, but it must not retain an
# arbitrary/stale resolved_value just because the subject matched.
main._preflight_subject("contestar_cobranca", args)
assert args["subject"] == "Tamboro Mensal"
assert args.get("resolved_value") is None
def test_invalid_amount_is_candidate_not_resolved(monkeypatch):
async def no_enrich(_name, _args):
return None
monkeypatch.setattr(main, "_enrich_invoice_context", no_enrich)
monkeypatch.setattr(main, "_preflight_subject", lambda *_a, **_k: None)
args = {
"msisdn": "11999999999",
"subject": "Tamboro Mensal",
"valor": 25.5,
"billing_analysis": _analysis(),
"target_tool": "contestar_cobranca",
}
result = asyncio.run(main._validate_contestation(args))
assert result["eligible"] is False
assert result["status"] == "NEEDS_PARAMETER"
assert result["parameter"] == "valor"
assert result["candidate_value"] == 25.5
assert result["resolved_value"] is None
def test_valid_amount_is_resolved_only_after_subject_amount_validation(monkeypatch):
async def no_enrich(_name, _args):
return None
monkeypatch.setattr(main, "_enrich_invoice_context", no_enrich)
monkeypatch.setattr(main, "_preflight_subject", lambda *_a, **_k: None)
args = {
"msisdn": "11999999999",
"subject": "Tamboro Mensal",
"valor": 19.99,
"billing_analysis": _analysis(),
"target_tool": "contestar_cobranca",
}
result = asyncio.run(main._validate_contestation(args))
assert result["eligible"] is True
assert result["resolved_value"] == 19.99
resolved = result["transaction_decision"]["resolved_arguments"]
assert resolved["subject"] == "Tamboro Mensal"
assert resolved["resolved_value"] == 19.99

View File

@@ -50,6 +50,8 @@ def test_validator_turns_unresolved_subject_into_recoverable_parameter_request(m
assert result["status"] == "NEEDS_PARAMETER"
assert result["parameter"] == "subject"
assert result["reason"] == "subject_not_resolved"
assert result["resolved_value"] == 10.0
assert result["candidate_value"] == 10.0
assert result["resolved_value"] is None
assert "resolved_value" in result["clear_fields"]
assert result["metadata"]["entity_resolution"] == "invoice_evidence"
assert "error" not in result

View File

@@ -113,7 +113,7 @@ async def test_validar_contestacao_usa_mesma_cval_e_rejeita_valor_acima_da_fatur
@pytest.mark.asyncio
async def test_validar_contestacao_aprova_quando_item_e_valor_sao_comprovados(monkeypatch):
async def test_validar_contestacao_rejeita_valor_que_nao_pertence_ao_item(monkeypatch):
billing = json.loads((ROOT / "app/domain/contas/fixtures/divergencia.json").read_text(encoding="utf-8"))
async def no_enrich(name, args):
@@ -127,8 +127,12 @@ async def test_validar_contestacao_aprova_quando_item_e_valor_sao_comprovados(mo
"motivo": "quero contestar TIM Fashion Mensal",
"billing_analysis": billing,
})
assert out["eligible"] is True
assert out["status"] == "ELIGIBLE"
assert out["eligible"] is False
assert out["status"] == "NEEDS_PARAMETER"
assert out["parameter"] == "valor"
assert out["candidate_value"] == 50
assert out["billed_value"] == "10"
assert out["resolved_value"] is None
def test_termino_desconto_e_valor_divergente_preservam_semantica_do_original():

View File

@@ -274,6 +274,9 @@ async def test_contestation_wrong_amount_is_recoverable_with_invoice_amount(monk
assert result['eligible'] is False
assert result['status'] == 'NEEDS_PARAMETER'
assert result['parameter'] == 'valor'
assert result['resolved_value'] == '14.99'
assert result['candidate_value'] == '29.98'
assert result['billed_value'] == '14.99'
assert result['resolved_value'] is None
assert 'resolved_value' in result['clear_fields']
assert 'R$ 14,99' in result['parameter_message']
assert 'R$ 29,98' in result['parameter_message']