mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 10:08:46 +02:00
Tasa Cero: exclude anchors from budget/analytics aggregates
The anchor stays visible in listings but only its cuotas count toward actuals — future cuotas intentionally show up in future months as committed spend. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ from sqlmodel import Session, func, select
|
||||
from app.auth import get_current_user
|
||||
from app.db import get_session
|
||||
from app.models.models import Category, Transaction, TransactionType
|
||||
from app.services.budget_projection import get_cycle_range
|
||||
from app.services.budget_projection import NOT_INSTALLMENT_ANCHOR, get_cycle_range
|
||||
from app.services.exchange_rate import get_converted_amount_expr
|
||||
|
||||
router = APIRouter(prefix="/analytics", tags=["analytics"])
|
||||
@@ -53,7 +53,10 @@ def spending_by_category(
|
||||
func.sum(amount_crc).label("total"),
|
||||
func.count().label("count"),
|
||||
)
|
||||
.where(Transaction.transaction_type == TransactionType.COMPRA)
|
||||
.where(
|
||||
Transaction.transaction_type == TransactionType.COMPRA,
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
)
|
||||
.group_by(Transaction.category_id)
|
||||
)
|
||||
|
||||
@@ -126,6 +129,7 @@ def monthly_trend(
|
||||
Transaction.transaction_type == TransactionType.COMPRA,
|
||||
Transaction.date >= start,
|
||||
Transaction.date < end,
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
)
|
||||
).first()
|
||||
|
||||
@@ -171,7 +175,10 @@ def daily_spending(
|
||||
func.sum(amount_crc).label("total"),
|
||||
func.count().label("count"),
|
||||
)
|
||||
.where(Transaction.transaction_type == TransactionType.COMPRA)
|
||||
.where(
|
||||
Transaction.transaction_type == TransactionType.COMPRA,
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
)
|
||||
.group_by(func.date(Transaction.date))
|
||||
.order_by(func.date(Transaction.date))
|
||||
)
|
||||
|
||||
@@ -23,6 +23,11 @@ FRESH_START_MONTH = 3
|
||||
# Income-like transaction types that should never be counted as expenses
|
||||
INCOME_TYPES = (TransactionType.DEPOSITO, TransactionType.SALARY)
|
||||
|
||||
# Tasa Cero: the original purchase ("anchor") is excluded from all budget
|
||||
# aggregates — its generated cuota transactions are counted instead.
|
||||
# See app/services/installments.py.
|
||||
NOT_INSTALLMENT_ANCHOR = Transaction.is_installment_anchor == False # noqa: E712
|
||||
|
||||
|
||||
def get_effective_amount(item: RecurringItem, month: int, year: int) -> float | None:
|
||||
"""Return the effective amount for a recurring item in a given month, or None if inactive."""
|
||||
@@ -133,7 +138,7 @@ def compute_actuals_by_source(
|
||||
func.coalesce(func.sum(amount_crc), 0),
|
||||
func.count(),
|
||||
)
|
||||
.where(*where)
|
||||
.where(NOT_INSTALLMENT_ANCHOR, *where)
|
||||
.group_by(Transaction.source, Transaction.transaction_type)
|
||||
).all()
|
||||
return {(s, t): (float(total), cnt) for s, t, total, cnt in rows}
|
||||
@@ -225,6 +230,7 @@ def compute_actuals_by_category(
|
||||
Transaction.source == TransactionSource.CREDIT_CARD,
|
||||
Transaction.category_id.is_not(None), # type: ignore[union-attr]
|
||||
col(Transaction.transaction_type).notin_(INCOME_TYPES),
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
Transaction.deferred_to_next_cycle == False, # noqa: E712
|
||||
)
|
||||
.group_by(Transaction.category_id, Transaction.transaction_type)
|
||||
@@ -245,6 +251,7 @@ def compute_actuals_by_category(
|
||||
Transaction.source == TransactionSource.CREDIT_CARD,
|
||||
Transaction.category_id.is_not(None), # type: ignore[union-attr]
|
||||
col(Transaction.transaction_type).notin_(INCOME_TYPES),
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
Transaction.deferred_to_next_cycle == True, # noqa: E712
|
||||
)
|
||||
.group_by(Transaction.category_id, Transaction.transaction_type)
|
||||
@@ -265,6 +272,7 @@ def compute_actuals_by_category(
|
||||
Transaction.source != TransactionSource.CREDIT_CARD,
|
||||
Transaction.category_id.is_not(None), # type: ignore[union-attr]
|
||||
col(Transaction.transaction_type).notin_(INCOME_TYPES),
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
)
|
||||
.group_by(Transaction.category_id, Transaction.transaction_type)
|
||||
).all()
|
||||
@@ -306,6 +314,7 @@ def compute_cc_by_category(
|
||||
Transaction.date < cc_end,
|
||||
Transaction.source == TransactionSource.CREDIT_CARD,
|
||||
col(Transaction.transaction_type).notin_(INCOME_TYPES),
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
Transaction.deferred_to_next_cycle == False, # noqa: E712
|
||||
)
|
||||
.group_by(Transaction.category_id, Transaction.transaction_type)
|
||||
@@ -324,6 +333,7 @@ def compute_cc_by_category(
|
||||
Transaction.date < prev_end,
|
||||
Transaction.source == TransactionSource.CREDIT_CARD,
|
||||
col(Transaction.transaction_type).notin_(INCOME_TYPES),
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
Transaction.deferred_to_next_cycle == True, # noqa: E712
|
||||
)
|
||||
.group_by(Transaction.category_id, Transaction.transaction_type)
|
||||
@@ -440,6 +450,7 @@ def compute_monthly_projection(
|
||||
Transaction.source == TransactionSource.CREDIT_CARD,
|
||||
Transaction.category_id.is_(None), # type: ignore[union-attr]
|
||||
col(Transaction.transaction_type).notin_(INCOME_TYPES),
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
Transaction.deferred_to_next_cycle == False, # noqa: E712
|
||||
)
|
||||
.group_by(Transaction.transaction_type)
|
||||
@@ -455,6 +466,7 @@ def compute_monthly_projection(
|
||||
Transaction.source == TransactionSource.CREDIT_CARD,
|
||||
Transaction.category_id.is_(None), # type: ignore[union-attr]
|
||||
col(Transaction.transaction_type).notin_(INCOME_TYPES),
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
Transaction.deferred_to_next_cycle == True, # noqa: E712
|
||||
)
|
||||
.group_by(Transaction.transaction_type)
|
||||
@@ -470,6 +482,7 @@ def compute_monthly_projection(
|
||||
Transaction.source != TransactionSource.CREDIT_CARD,
|
||||
Transaction.category_id.is_(None), # type: ignore[union-attr]
|
||||
col(Transaction.transaction_type).notin_(INCOME_TYPES),
|
||||
NOT_INSTALLMENT_ANCHOR,
|
||||
)
|
||||
.group_by(Transaction.transaction_type)
|
||||
).all()
|
||||
|
||||
Reference in New Issue
Block a user