mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 08:08:48 +02:00
Add individual salary deposit agent tool
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user