4a4b8f4a05
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m12s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m20s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m27s
AI Code Review / AI Code Review (pull_request) Successful in 7m13s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 11m7s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 14m54s
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
- 编导prompt加粗强调严禁使用'很近',正确用词'最近' - _try_gen增加hard fix字符串替换:口播/分镜对白含'很近'自动替换 - 三级重试合格校验:口播含'很近'视为不合格触发重试 refactor(points): 移除ai_video/数字人/配音/改写/标题/封面/抖音解析扣点,仅保留声音克隆 - 删除5个路由的@points_gate装饰器(generation_preview/generation_cover/generation_tasks/scripts_ai/ai_avatar_render) - 删除tts.py/lipsync.py/narrative_service.py中ai_voice/ai_digital_human手动扣点+refund块 - points_rules.py精简POINTS_SCENES:仅保留voice_clone_train(0积分)和voice_clone_synth(1积分/分钟) - 删除DAILY_FREE_CLIP_LIMIT免费混剪额度逻辑,get_daily_usage返回unlimited占位 - points_gate.py删除ai_video特殊分支,未知/免费场景calculate_points_cost返回0直接放行 - points.py删除UNKNOWN_SCENE 400校验,任意scene可查 - 更新16个测试文件适配新规则:198个扣点相关单测全绿,viral_video 58个测试无回归
100 lines
3.2 KiB
Python
100 lines
3.2 KiB
Python
"""积分消耗规则配置 (#1895)
|
||
|
||
v1.6.1: 按产品决策,智能混剪/AI数字人/AI配音/抖音解析/改写/标题/封面 全部免费,
|
||
仅保留声音克隆合成(voice_clone_synth)的扣点逻辑;声音克隆训练保持免费。
|
||
爆款视频(viral_video)后续走动态定价,暂不加入本文件。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
|
||
# ============ 场景定义 ============
|
||
# 每个场景: base_points(基础积分), unit(计费单位), name(显示名称)
|
||
# 说明:仅保留需要扣点的场景;免费场景不要写入此字典。
|
||
|
||
POINTS_SCENES: dict[str, dict] = {
|
||
"voice_clone_train": {
|
||
"base_points": 0,
|
||
"unit": "次",
|
||
"name": "声音克隆训练",
|
||
"description": "声音克隆训练免费(每用户限 1 个声音)",
|
||
},
|
||
"voice_clone_synth": {
|
||
"base_points": 1,
|
||
"unit": "分钟",
|
||
"name": "声音克隆合成",
|
||
"description": "克隆音色合成每分钟消耗 1 积分",
|
||
},
|
||
}
|
||
|
||
# 免费用户积分消耗上浮系数(仅对 voice_clone_synth 生效)
|
||
FREE_USER_MULTIPLIER = 1.15
|
||
|
||
# ============ 积分包定义 ============
|
||
|
||
POINTS_PACKAGES: dict[str, dict] = {
|
||
"starter_pack": {"name": "体验包", "points": 100, "price_cents": 990},
|
||
"basic_pack": {"name": "基础包", "points": 500, "price_cents": 3900},
|
||
"pro_pack": {"name": "专业包", "points": 2000, "price_cents": 12900},
|
||
}
|
||
|
||
# ============ 会员定价 ============
|
||
|
||
MEMBERSHIP_PRICES: dict[str, dict] = {
|
||
"monthly": {"name": "月卡", "price_cents": 1990, "duration_days": 30},
|
||
"quarterly": {"name": "季卡", "price_cents": 3990, "duration_days": 90},
|
||
"yearly": {"name": "年卡", "price_cents": 15900, "duration_days": 365},
|
||
}
|
||
|
||
# 会员积分折扣(付费会员按此系数打折)
|
||
MEMBER_DISCOUNT: dict[str, float] = {
|
||
"monthly": 0.9,
|
||
"quarterly": 0.87,
|
||
"yearly": 0.8,
|
||
}
|
||
|
||
|
||
def calculate_points_cost(
|
||
scene_key: str,
|
||
is_member: bool,
|
||
quantity: int = 1,
|
||
duration_minutes: float = 0,
|
||
member_type: str | None = None,
|
||
) -> int:
|
||
"""计算指定场景的积分消耗。
|
||
|
||
Args:
|
||
scene_key: 场景标识(当前仅支持 voice_clone_train/voice_clone_synth)
|
||
is_member: 是否付费会员
|
||
quantity: 数量(按次计费场景)
|
||
duration_minutes: 时长分钟数(按时长计费场景)
|
||
member_type: 会员类型 (monthly/quarterly/yearly),用于折扣
|
||
|
||
Returns:
|
||
实际消耗积分(已含免费用户 ×1.15 上浮或会员折扣);免费/已下线场景统一返回 0。
|
||
"""
|
||
scene = POINTS_SCENES.get(scene_key)
|
||
if not scene:
|
||
# 已下线/未注册的场景统一返回 0(免费),保持向后兼容
|
||
return 0
|
||
|
||
base = scene["base_points"]
|
||
if base == 0:
|
||
return 0
|
||
|
||
unit = scene["unit"]
|
||
if unit == "分钟":
|
||
total_base = base * max(1, math.ceil(duration_minutes))
|
||
elif unit in ("次", "张"):
|
||
total_base = base * quantity
|
||
else:
|
||
total_base = base
|
||
|
||
if is_member and member_type and member_type in MEMBER_DISCOUNT:
|
||
total_base = max(1, math.floor(total_base * MEMBER_DISCOUNT[member_type]))
|
||
elif not is_member:
|
||
total_base = math.ceil(total_base * FREE_USER_MULTIPLIER)
|
||
|
||
return total_base
|