fix(billing): make quota seed and column names match the product SSOT
The product plan is the only source for quota numbers, but the 0001 seed used a second set (agent_chat 20/50, tryon hard 10) and omitted hairstyle, hd_export and report regen. Migration 0003 replaces the seed and renames points_reserved → quota_reserved and bonus_value → bonus_granted so the docs and the schema stop contradicting each other. Effective limit now clips at hard_cap. CI runs the integration slice against the Postgres/Redis services it already starts.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""Align quota seed and column names with the product SSOT.
|
||||
|
||||
三件事必须一起做,否则文档声称与实现继续各说各话:
|
||||
|
||||
1. ``tk_task.points_reserved`` → ``quota_reserved``
|
||||
架构 v1.2 已改名,0001 没跟上。V1 扣的是配额不是点数。
|
||||
2. ``bl_quota.bonus_value`` → ``bonus_granted``
|
||||
与架构 §7.4 判定公式用词对齐;``limit_value`` 保留(本周期 base_limit 快照)。
|
||||
3. ``bl_quota_rule`` 种子改按产品方案 §3.2,并补 ``hairstyle`` / ``hd_export`` /
|
||||
``report_regen``。原种子 ``agent_chat`` 20/50、``tryon`` hard 10 是第二套口径,
|
||||
趁表里没有生产数据一次改掉。
|
||||
|
||||
游客额度依赖 ``id_guest``,本迁移不写 ``guest_*`` key。
|
||||
记忆条数 200 是存储上限,不是消耗型配额,不进本表。
|
||||
|
||||
Reversible: 是。降级把列名改回,规则恢复为 0001 的三行。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "0003"
|
||||
down_revision: str | None = "0002"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
_RULES = sa.table(
|
||||
"bl_quota_rule",
|
||||
sa.column("quota_key", sa.String),
|
||||
sa.column("period_type", sa.String),
|
||||
sa.column("base_limit", sa.Integer),
|
||||
sa.column("task_bonus_max", sa.Integer),
|
||||
sa.column("hard_cap", sa.Integer),
|
||||
sa.column("description", sa.String),
|
||||
)
|
||||
|
||||
# 产品方案 §3.2。改数字只改那里,再回来改这一份。
|
||||
_SSOT = [
|
||||
{
|
||||
"quota_key": "agent_chat",
|
||||
"period_type": "day",
|
||||
"base_limit": 10,
|
||||
"task_bonus_max": 10,
|
||||
"hard_cap": 40,
|
||||
"description": "AI 对话(产品方案 §3.2)",
|
||||
},
|
||||
{
|
||||
"quota_key": "tryon",
|
||||
"period_type": "day",
|
||||
"base_limit": 3,
|
||||
"task_bonus_max": 3,
|
||||
"hard_cap": 8,
|
||||
"description": "换装试穿(产品方案 §3.2)",
|
||||
},
|
||||
{
|
||||
"quota_key": "hairstyle",
|
||||
"period_type": "day",
|
||||
"base_limit": 3,
|
||||
"task_bonus_max": 2,
|
||||
"hard_cap": 6,
|
||||
"description": "发型 / 发色试戴(产品方案 §3.2)",
|
||||
},
|
||||
{
|
||||
"quota_key": "hd_export",
|
||||
"period_type": "day",
|
||||
"base_limit": 1,
|
||||
"task_bonus_max": 0,
|
||||
"hard_cap": 3,
|
||||
"description": "高清导出(产品方案 §3.2)",
|
||||
},
|
||||
{
|
||||
"quota_key": "report_basic",
|
||||
"period_type": "lifetime",
|
||||
"base_limit": 1,
|
||||
"task_bonus_max": 0,
|
||||
"hard_cap": 1,
|
||||
"description": "初级美学报告首次(终身,产品方案 §3.2)",
|
||||
},
|
||||
{
|
||||
"quota_key": "report_regen",
|
||||
"period_type": "month",
|
||||
"base_limit": 1,
|
||||
"task_bonus_max": 1,
|
||||
"hard_cap": 2,
|
||||
"description": "报告重新生成(每 30 天;月硬上限 2 即「每日 ≤ 2」)",
|
||||
},
|
||||
]
|
||||
|
||||
_LEGACY = [
|
||||
{
|
||||
"quota_key": "agent_chat",
|
||||
"period_type": "day",
|
||||
"base_limit": 20,
|
||||
"task_bonus_max": 10,
|
||||
"hard_cap": 50,
|
||||
"description": "穿搭 Agent 每日对话轮次",
|
||||
},
|
||||
{
|
||||
"quota_key": "report_basic",
|
||||
"period_type": "month",
|
||||
"base_limit": 1,
|
||||
"task_bonus_max": 1,
|
||||
"hard_cap": 3,
|
||||
"description": "初级美学报告生成次数",
|
||||
},
|
||||
{
|
||||
"quota_key": "tryon",
|
||||
"period_type": "day",
|
||||
"base_limit": 3,
|
||||
"task_bonus_max": 3,
|
||||
"hard_cap": 10,
|
||||
"description": "智能试穿出图次数",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.alter_column("tk_task", "points_reserved", new_column_name="quota_reserved")
|
||||
op.alter_column("bl_quota", "bonus_value", new_column_name="bonus_granted")
|
||||
op.execute(sa.text("DELETE FROM bl_quota_rule"))
|
||||
op.bulk_insert(_RULES, _SSOT)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(sa.text("DELETE FROM bl_quota_rule"))
|
||||
op.bulk_insert(_RULES, _LEGACY)
|
||||
op.alter_column("bl_quota", "bonus_granted", new_column_name="bonus_value")
|
||||
op.alter_column("tk_task", "quota_reserved", new_column_name="points_reserved")
|
||||
Reference in New Issue
Block a user