Files
docqube_backend/alembic/versions/d2_0_credit_limit_precision.py
T
2026-09-08 11:00:05 +05:30

62 lines
2.2 KiB
Python

"""D2 — a credit limit stops being rounded to a whole number
`chat_credit_quota.daily_credit_limit` is `bigint` in the database and
`DECIMAL(12, 2)` on the model. PostgreSQL resolves that disagreement by
rounding, silently, on write:
INSERT ... VALUES (0.60::numeric(12,2)) -> stored: 1
INSERT ... VALUES (10.75::numeric(12,2)) -> stored: 11
INSERT ... VALUES (10.25::numeric(12,2)) -> stored: 10
So a tenant admin setting a limit of 0.60 credits gets 1 — a 67% overshoot — and
nothing anywhere reports it. Whether that has bitten depends on whether anyone
has entered a fraction, which is not a property worth relying on.
Widening, so no value is at risk: every bigint currently stored is representable
in `numeric(12, 2)`. `credits_used_today` and `total_credits_ever` are left as
`bigint` deliberately — they count tokens, which are whole.
The reverse casts back to `bigint` and therefore *does* round. That is the
honest reverse of a widening: going back to a narrower type cannot preserve what
the wider one held. It is written that way rather than left unimplemented so the
migration can actually be reversed, but rolling this back after fractional
limits have been set will round them.
Found by `alembic revision --autogenerate`, which had never been run against
this schema, and which after the model-registry fix reports this as the single
remaining difference between the models and the database.
Revision ID: d2_0_credit_limit_precision
Revises: d1_0_plans_and_entitlements
"""
import sqlalchemy as sa
from alembic import op
revision = "d2_0_credit_limit_precision"
down_revision = "d1_0_plans_and_entitlements"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.alter_column(
"chat_credit_quota",
"daily_credit_limit",
existing_type=sa.BigInteger(),
type_=sa.DECIMAL(precision=12, scale=2),
existing_nullable=False,
postgresql_using="daily_credit_limit::numeric(12,2)",
)
def downgrade() -> None:
op.alter_column(
"chat_credit_quota",
"daily_credit_limit",
existing_type=sa.DECIMAL(precision=12, scale=2),
type_=sa.BigInteger(),
existing_nullable=False,
postgresql_using="round(daily_credit_limit)::bigint",
)