mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 09:08:46 +02:00
Prod showed the failure mode after the date fix: get_current_date returned the right day, but the model answered "¿cuánto he gastado hoy?" from cycle-level aggregates (the only spend tools it had) and concluded ₡0. Give it a day-grained tool — per-day CRC totals mirroring /analytics/daily-spending (COMPRA only, no installment anchors, no future cuotas) — and a prompt rule to match tool to time grain. Verified: fresh thread now calls get_current_date → get_daily_spending(2026-07-04, 2026-07-05) and answers correctly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
217 lines
6.0 KiB
Python
217 lines
6.0 KiB
Python
"""Agent tool coverage: session binding, JSON-safe output (no Decimal leaks),
|
|
and the batched queries introduced in Phase 2."""
|
|
|
|
import json
|
|
from datetime import date, datetime
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from app.agent import tools
|
|
from app.agent.tools import reset_session, set_session
|
|
from app.models.models import (
|
|
Account,
|
|
AccountType,
|
|
Bank,
|
|
Currency,
|
|
MunicipalReceipt,
|
|
PensionSnapshot,
|
|
Transaction,
|
|
WaterMeterReading,
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def bound_session(session):
|
|
token = set_session(session)
|
|
yield session
|
|
reset_session(token)
|
|
|
|
|
|
def make_snapshot(fund: Bank, start: date, end: date, saldo: str) -> PensionSnapshot:
|
|
zero = Decimal("0")
|
|
return PensionSnapshot(
|
|
fund=fund,
|
|
contract_number="C-1",
|
|
period_start=start,
|
|
period_end=end,
|
|
saldo_anterior=zero,
|
|
aportes=zero,
|
|
rendimientos=zero,
|
|
retiros=zero,
|
|
traslados=zero,
|
|
comision=zero,
|
|
correccion=zero,
|
|
bonificacion=zero,
|
|
saldo_final=Decimal(saldo),
|
|
source_filename="t.pdf",
|
|
)
|
|
|
|
|
|
def make_receipt(period: str) -> MunicipalReceipt:
|
|
zero = Decimal("0")
|
|
return MunicipalReceipt(
|
|
receipt_date=date(2026, 4, 1),
|
|
due_date=date(2026, 4, 15),
|
|
period=period,
|
|
account="A1",
|
|
finca="F1",
|
|
holder_name="x",
|
|
holder_cedula="x",
|
|
holder_address="x",
|
|
subtotal=Decimal("100"),
|
|
interests=zero,
|
|
iva=Decimal("13"),
|
|
total=Decimal("113"),
|
|
source_filename="r.pdf",
|
|
)
|
|
|
|
|
|
def test_unbound_session_raises(session):
|
|
with pytest.raises(RuntimeError, match="not bound to agent context"):
|
|
tools.get_accounts()
|
|
|
|
|
|
def test_accounts_output_is_json_safe(bound_session):
|
|
bound_session.add(
|
|
Account(
|
|
bank=Bank.BAC,
|
|
currency=Currency.CRC,
|
|
label="Main",
|
|
balance=Decimal("1234.56"),
|
|
account_type=AccountType.BANK,
|
|
)
|
|
)
|
|
bound_session.commit()
|
|
out = tools.get_accounts()
|
|
json.dumps(out) # would raise on Decimal leakage
|
|
assert out[0]["balance"] == 1234.56
|
|
|
|
|
|
def test_pension_latest_only_per_fund(bound_session):
|
|
bound_session.add(
|
|
make_snapshot(Bank.FCL, date(2026, 1, 1), date(2026, 1, 31), "100")
|
|
)
|
|
bound_session.add(
|
|
make_snapshot(Bank.FCL, date(2026, 2, 1), date(2026, 2, 28), "200")
|
|
)
|
|
bound_session.add(
|
|
make_snapshot(Bank.ROP, date(2026, 1, 1), date(2026, 1, 31), "900")
|
|
)
|
|
bound_session.commit()
|
|
|
|
out = tools.get_pension_snapshots(latest_only=True)
|
|
json.dumps(out)
|
|
assert len(out) == 2
|
|
by_fund = {o["fund"]: o for o in out}
|
|
assert by_fund["FCL"]["saldo_final"] == 200.0 # latest period wins
|
|
assert by_fund["ROP"]["saldo_final"] == 900.0
|
|
|
|
full = tools.get_pension_snapshots(latest_only=False)
|
|
assert len(full) == 3
|
|
|
|
|
|
def test_municipal_receipts_batched_consumption(bound_session):
|
|
r1, r2 = make_receipt("2026-03"), make_receipt("2026-04")
|
|
r2.account = "A2"
|
|
bound_session.add(r1)
|
|
bound_session.add(r2)
|
|
bound_session.commit()
|
|
bound_session.refresh(r1)
|
|
bound_session.refresh(r2)
|
|
for rid, m3 in ((r1.id, "10.5"), (r1.id, "4.5"), (r2.id, "7")):
|
|
bound_session.add(
|
|
WaterMeterReading(
|
|
meter_id="7335",
|
|
period=f"p{m3}",
|
|
consumption_m3=Decimal(m3),
|
|
receipt_id=rid,
|
|
)
|
|
)
|
|
bound_session.commit()
|
|
|
|
out = tools.get_municipal_receipts()
|
|
json.dumps(out)
|
|
by_account = {o["account"]: o for o in out}
|
|
assert by_account["A1"]["water_consumption_m3"] == 15.0
|
|
assert by_account["A2"]["water_consumption_m3"] == 7.0
|
|
|
|
|
|
def test_recent_transactions_json_safe(bound_session):
|
|
bound_session.add(
|
|
Transaction(
|
|
amount=Decimal("42.42"),
|
|
merchant="M",
|
|
date=datetime(2026, 4, 1),
|
|
)
|
|
)
|
|
bound_session.commit()
|
|
out = tools.get_recent_transactions()
|
|
json.dumps(out)
|
|
assert out[0]["amount"] == 42.42
|
|
|
|
|
|
def test_recent_transactions_exclude_future_cuotas(bound_session):
|
|
from datetime import timedelta
|
|
|
|
from app.timeutil import today_cr
|
|
|
|
today = datetime.combine(today_cr(), datetime.min.time())
|
|
bound_session.add(
|
|
Transaction(amount=Decimal("10"), merchant="BILLED", date=today)
|
|
)
|
|
bound_session.add(
|
|
Transaction(
|
|
amount=Decimal("20"),
|
|
merchant="FUTURE CUOTA:03/06",
|
|
date=today + timedelta(days=45),
|
|
)
|
|
)
|
|
bound_session.commit()
|
|
merchants = [t["merchant"] for t in tools.get_recent_transactions()]
|
|
assert "BILLED" in merchants
|
|
assert "FUTURE CUOTA:03/06" not in merchants
|
|
|
|
|
|
def test_get_current_date_is_costa_rica_and_cycle_consistent():
|
|
from app.timeutil import today_cr
|
|
|
|
out = tools.get_current_date()
|
|
today = today_cr()
|
|
assert out["date"] == today.isoformat()
|
|
# The active cycle must contain today: [start, end)
|
|
start, end = out["cycle_range"]
|
|
assert start <= out["date"] < end
|
|
assert start.endswith("-18") and end.endswith("-18")
|
|
|
|
|
|
def test_daily_spending_day_scoped(bound_session):
|
|
from datetime import timedelta
|
|
|
|
from app.timeutil import today_cr
|
|
|
|
today = datetime.combine(today_cr(), datetime.min.time())
|
|
bound_session.add(
|
|
Transaction(amount=Decimal("100"), merchant="TODAY-A", date=today.replace(hour=9))
|
|
)
|
|
bound_session.add(
|
|
Transaction(amount=Decimal("50"), merchant="TODAY-B", date=today.replace(hour=15))
|
|
)
|
|
bound_session.add(
|
|
Transaction(
|
|
amount=Decimal("999"),
|
|
merchant="FUTURE CUOTA",
|
|
date=today + timedelta(days=40),
|
|
)
|
|
)
|
|
bound_session.commit()
|
|
|
|
out = tools.get_daily_spending(
|
|
start_date=today.date().isoformat(),
|
|
end_date=(today.date() + timedelta(days=60)).isoformat(),
|
|
)
|
|
assert len(out) == 1
|
|
assert out[0]["date"] == today.date().isoformat()
|
|
assert out[0]["total_crc"] == 150.0
|
|
assert out[0]["count"] == 2
|