Files
WealthySmart/backend/alembic/versions/7f567c8bafdb_money_columns_to_numeric.py
Carlos Escalante 74886fbf98 Migrate all 29 money columns from float to NUMERIC/Decimal (BE-02)
Models use Money = Annotated[Decimal, PlainSerializer(float)] so storage
is exact NUMERIC(15,2) (rates 15,6) while JSON keeps emitting plain
numbers — the frontend contract is unchanged, pinned by
test_models_serialization.py. Service layers coerce to float at their
boundaries (projection math, rate multipliers, agent tool output, the
savings constants become Decimal) so no naive Decimal/float mixing can
raise. Migration 7f567c8bafdb applied to dev: per-row values and the
table sum byte-identical before/after; endpoints verified live on the
new schema. 59/59 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:38:28 -06:00

264 lines
13 KiB
Python

"""money columns to numeric
Revision ID: 7f567c8bafdb
Revises: c3da001a0eb3
Create Date: 2026-06-10 13:37:26.281634
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic.
revision: str = '7f567c8bafdb'
down_revision: Union[str, Sequence[str], None] = 'c3da001a0eb3'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('account', 'balance',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('account', 'next_payment',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=True)
op.alter_column('balanceoverride', 'override_balance',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('exchangerate', 'buy_rate',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=6),
existing_nullable=False)
op.alter_column('exchangerate', 'sell_rate',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=6),
existing_nullable=False)
op.alter_column('municipalreceipt', 'subtotal',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('municipalreceipt', 'interests',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('municipalreceipt', 'iva',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('municipalreceipt', 'total',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'saldo_anterior',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'aportes',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'rendimientos',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'retiros',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'traslados',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'comision',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'correccion',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'bonificacion',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'saldo_final',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('recurringitem', 'amount',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('savingsaccrual', 'memp_amount',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('savingsaccrual', 'mpat_amount',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('transaction', 'amount',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('watermeterreading', 'reading_previous',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('watermeterreading', 'reading_current',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('watermeterreading', 'consumption_m3',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('watermeterreading', 'agua_potable',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('watermeterreading', 'serv_ambientales',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('watermeterreading', 'alcant_sanitario',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
op.alter_column('watermeterreading', 'iva',
existing_type=sa.DOUBLE_PRECISION(precision=53),
type_=sa.Numeric(precision=15, scale=2),
existing_nullable=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('watermeterreading', 'iva',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('watermeterreading', 'alcant_sanitario',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('watermeterreading', 'serv_ambientales',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('watermeterreading', 'agua_potable',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('watermeterreading', 'consumption_m3',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('watermeterreading', 'reading_current',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('watermeterreading', 'reading_previous',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('transaction', 'amount',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('savingsaccrual', 'mpat_amount',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('savingsaccrual', 'memp_amount',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('recurringitem', 'amount',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'saldo_final',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'bonificacion',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'correccion',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'comision',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'traslados',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'retiros',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'rendimientos',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'aportes',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('pensionsnapshot', 'saldo_anterior',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('municipalreceipt', 'total',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('municipalreceipt', 'iva',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('municipalreceipt', 'interests',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('municipalreceipt', 'subtotal',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('exchangerate', 'sell_rate',
existing_type=sa.Numeric(precision=15, scale=6),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('exchangerate', 'buy_rate',
existing_type=sa.Numeric(precision=15, scale=6),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('balanceoverride', 'override_balance',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
op.alter_column('account', 'next_payment',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=True)
op.alter_column('account', 'balance',
existing_type=sa.Numeric(precision=15, scale=2),
type_=sa.DOUBLE_PRECISION(precision=53),
existing_nullable=False)
# ### end Alembic commands ###