Add individual salary deposit agent tool
All checks were successful
Deploy to VPS / test (push) Successful in 1m43s
Deploy to VPS / deploy (push) Successful in 27s

This commit is contained in:
Carlos Escalante
2026-07-14 22:41:53 -06:00
parent ca3115e99c
commit 8ceed0ab2e
3 changed files with 91 additions and 1 deletions

View File

@@ -35,6 +35,9 @@ How to answer:
"ayer", "el martes", a specific date) need get_daily_spending with that
day's bounds — cycle or category summaries have NO per-day data, so never
answer a day question from them.
- For a question about a specific salary deposit or its amount (including
"cuánto recibí hoy"), call get_salary_deposits with the relevant date range;
get_salary_summary is aggregate-only and cannot answer it.
- Answer like an analyst, not a calculator. An aggregate number on its own
is not an answer — show what it is made of, right away, without waiting
to be asked:

View File

@@ -48,6 +48,7 @@ from app.services.exchange_rate import (
)
_session_ctx: contextvars.ContextVar[Session] = contextvars.ContextVar("agent_session")
SALARY_TRANSACTION_TYPES = (TransactionType.SALARY, TransactionType.DEPOSITO)
def set_session(session: Session) -> contextvars.Token:
@@ -339,7 +340,7 @@ def get_salary_summary() -> dict:
func.count(),
func.coalesce(func.sum(amount_crc), 0),
func.max(Transaction.date),
).where(Transaction.transaction_type == TransactionType.SALARY)
).where(col(Transaction.transaction_type).in_(SALARY_TRANSACTION_TYPES))
).first()
count = row[0] if row else 0
total = float(row[1]) if row else 0.0
@@ -347,6 +348,44 @@ def get_salary_summary() -> dict:
return {"count": count, "total_crc": total, "latest_date": latest}
def get_salary_deposits(
limit: Annotated[int, Field(ge=1, le=100, description="How many deposits to return")] = 20,
start_date: Annotated[
Optional[str], Field(description="ISO date lower bound, inclusive")
] = None,
end_date: Annotated[
Optional[str], Field(description="ISO date upper bound, exclusive")
] = None,
) -> list[dict]:
"""Individual salary deposits, newest first. Use this for questions about
a specific salary or a day/range (for example, "cuánto recibí hoy").
Amounts remain in their recorded currency; use the currency field when
presenting them."""
q = select(Transaction).where(
col(Transaction.transaction_type).in_(SALARY_TRANSACTION_TYPES)
)
if start_date:
q = q.where(Transaction.date >= datetime.fromisoformat(start_date))
if end_date:
q = q.where(Transaction.date < datetime.fromisoformat(end_date))
q = q.order_by(col(Transaction.date).desc(), col(Transaction.id).desc()).limit(limit)
return [
{
"id": t.id,
"date": t.date.isoformat(),
"amount": float(t.amount),
"currency": t.currency.value,
"merchant": t.merchant,
"source": t.source.value,
"bank": t.bank.value,
"transaction_type": t.transaction_type.value,
"reference": t.reference,
"notes": t.notes,
}
for t in _s().exec(q).all()
]
def get_municipal_receipts(
limit: Annotated[int, Field(ge=1, le=50)] = 12,
offset: Annotated[int, Field(ge=0, description="Skip the N most recent")] = 0,
@@ -567,6 +606,7 @@ TOOLS = [
list_recurring_items,
get_pension_snapshots,
get_salary_summary,
get_salary_deposits,
get_municipal_receipts,
get_analytics_by_category,
get_monthly_trend,

View File

@@ -17,6 +17,7 @@ from app.models.models import (
MunicipalReceipt,
PensionSnapshot,
Transaction,
TransactionType,
WaterMeterReading,
)
@@ -173,6 +174,52 @@ def test_recent_transactions_exclude_future_cuotas(bound_session):
assert "FUTURE CUOTA:03/06" not in merchants
def test_salary_deposits_returns_individual_filtered_rows(bound_session):
bound_session.add_all(
[
Transaction(
amount=Decimal("1500"),
merchant="SALARY JULY",
date=datetime(2026, 7, 14, 9),
transaction_type=TransactionType.SALARY,
reference="PAY-2026-07",
),
Transaction(
amount=Decimal("1400"),
merchant="SALARY JUNE",
date=datetime(2026, 6, 14, 9),
transaction_type=TransactionType.SALARY,
),
Transaction(
amount=Decimal("999"),
merchant="PURCHASE",
date=datetime(2026, 7, 14, 10),
),
]
)
bound_session.commit()
out = tools.get_salary_deposits(
start_date="2026-07-14", end_date="2026-07-15"
)
json.dumps(out)
assert out == [
{
"id": out[0]["id"],
"date": "2026-07-14T09:00:00",
"amount": 1500.0,
"currency": "CRC",
"merchant": "SALARY JULY",
"source": "CREDIT_CARD",
"bank": "BAC",
"transaction_type": "SALARY",
"reference": "PAY-2026-07",
"notes": None,
}
]
def test_get_current_date_is_costa_rica_and_cycle_consistent():
from app.timeutil import today_cr