Files
yingqing/core/qa/audit_billing.py
T
zycandClaude Fable 5 216a711291 后端生成闸+多项修复;前端全站更新;QA 审计与报告
后端:
- 新增 celery_health 生成前置闸——无 worker 在线时图片/视频生成入口
  一律 503,防"提交到 ARK 后无人轮询、结果悬空+额度冻结"的数据丢失
- 拼接导出:帧率跟随源众数、字幕逐句重映射、原声/BGM 混音修复
- 资金核算审计脚本 + genesis 账目回填 + 滞留预留清理命令
- 接入 yunqi provider 与豆包 TTS 模型(catalog/migrations/bootstrap 命令)

前端:全站页面更新(pipeline/library/products/projects/team/account 等),
新增共享 pager 分页组件

QA:刷新 function-audit 全量输出,新增 full-qa 报告
文档:BP 产品介绍资料、design/CLAUDE.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 10:06:15 +08:00

160 lines
8.1 KiB
Python

"""资金核算审计:针对真实 DB,逐条验证账目不变量,任何不平即报。
跑法:cd backend && DJANGO_SETTINGS_MODULE=airshelf.settings.development .venv/bin/python ../qa/audit_billing.py
不变量:
I1 余额自洽 balance == Σ(recharge+refund) - Σ(charge) + Σ(adjustment 带符号)
I2 冻结自洽 reserved_balance == Σ(amount of ACTIVE reservations)
I3 非负 & 可用 balance>=0, reserved>=0, balance>=reserved(可用余额>=0)
I4 预留闭环 每个 reservation 状态与其 ledger 一致;CHARGED 恰好 1 条 charge 且 amount<=预留
I5 失败不扣费 失败 task 的 reservation 不得为 CHARGED
I6 无重复扣费 同一 task 不得有 >1 条 charge ledger
I7 流水连续 按时间重放,recharge/charge 的 balance_after 与重放值一致
"""
import os, sys
from decimal import Decimal
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "airshelf.settings.development")
import django # noqa: E402
django.setup()
from django.db.models import Sum # noqa: E402
from apps.billing.models import CreditAccount, CreditLedger, CreditReservation # noqa: E402
from apps.ai.models import AITask # noqa: E402
Z = Decimal("0")
problems = []
def flag(account, code, msg):
problems.append((str(account.team), code, msg))
print(f" ✗ [{code}] {msg}")
def audit_account(acct):
team = acct.team
led = CreditLedger.objects.filter(team=team)
print(f"\n== team={team} | balance={acct.balance} reserved={acct.reserved_balance} ==")
def s(t):
return led.filter(ledger_type=t).aggregate(x=Sum("amount"))["x"] or Z
recharge, charge, refund, adjustment = s("recharge"), s("charge"), s("refund"), s("adjustment")
# I1 余额=流水终点:account.balance 应等于最后一条动-balance 流水的 balance_after(不依赖开户 genesis)
last_bal_led = led.filter(ledger_type__in=["recharge", "refund", "charge", "adjustment"]).order_by("created_at", "id").last()
if last_bal_led is not None:
if acct.balance != last_bal_led.balance_after:
flag(acct, "I1", f"账户余额与流水终点脱节:account.balance={acct.balance} 但末条动账流水 balance_after={last_bal_led.balance_after}")
else:
print(f" ✓ I1 余额=流水终点 {acct.balance}")
else:
print(f" · I1 无动账流水(纯开户额度 {acct.balance})")
# I2 冻结自洽
active_sum = CreditReservation.objects.filter(team=team, status="active").aggregate(x=Sum("amount"))["x"] or Z
if acct.reserved_balance != active_sum:
flag(acct, "I2", f"冻结不平:account.reserved_balance={acct.reserved_balance} 但 ACTIVE 预留之和={active_sum}(差 {acct.reserved_balance - active_sum})")
else:
print(f" ✓ I2 冻结自洽 ACTIVE 预留之和={active_sum}")
# I3 非负 & 可用
if acct.balance < Z:
flag(acct, "I3", f"余额为负 {acct.balance}")
if acct.reserved_balance < Z:
flag(acct, "I3", f"冻结为负 {acct.reserved_balance}")
if acct.balance < acct.reserved_balance:
flag(acct, "I3", f"可用余额为负:balance {acct.balance} < reserved {acct.reserved_balance}")
if acct.balance >= Z and acct.reserved_balance >= Z and acct.balance >= acct.reserved_balance:
print(f" ✓ I3 非负&可用余额>=0(可用 {acct.balance - acct.reserved_balance})")
# I4/I5/I6 预留闭环 + 失败不扣 + 无重复扣
i4 = i5 = i6 = True
for r in CreditReservation.objects.filter(team=team).select_related("task"):
charges = led.filter(task=r.task, ledger_type="charge")
charge_sum = charges.aggregate(x=Sum("amount"))["x"] or Z
adj = led.filter(task=r.task, ledger_type="adjustment").aggregate(x=Sum("amount"))["x"] or Z
net_charge = charge_sum - adj # 扣费扣除回冲后的净额
dup_reconciled = charges.count() > 1 and net_charge == r.amount
if charges.count() > 1:
if dup_reconciled:
print(f" · I6 task={str(r.task_id)[:8]} 历史双扣 {charges.count()} 笔,已被 adjustment 回冲(净扣 {net_charge} = 预留 {r.amount},账平)")
else:
i6 = False
flag(acct, "I6", f"task={str(r.task_id)[:8]} {charges.count()} 条扣费,净扣 {net_charge} != 预留 {r.amount}(未完全回冲,真多扣)")
if r.status == "charged":
if charges.count() != 1 and not dup_reconciled:
i4 = False
flag(acct, "I4", f"reservation(task={str(r.task_id)[:8]}) 状态 CHARGED 却有 {charges.count()} 条扣费")
elif net_charge > r.amount:
i4 = False
flag(acct, "I4", f"净扣费 {net_charge} > 预留 {r.amount}(task={str(r.task_id)[:8]})")
elif r.status == "active":
if charges.exists():
i4 = False
flag(acct, "I4", f"reservation ACTIVE 却已有扣费(task={str(r.task_id)[:8]})")
# I5 失败不扣费
task = r.task
if task and task.status in ("failed", "cancelled") and r.status == "charged":
i5 = False
flag(acct, "I5", f"失败/取消 task={str(r.task_id)[:8]}(status={task.status})却被扣费")
if i4:
print(" ✓ I4 预留状态与扣费/释放流水一致")
if i5:
print(" ✓ I5 失败/取消任务均未扣费")
if i6:
print(" ✓ I6 无重复扣费")
# I7 逐笔差分自洽(不依赖开户起点):每条动账流水的 balance_after 必须 = 前一条动账流水的 balance_after ± 本笔金额。
# reserve/release 不动 balance,其 balance_after 应 = 当时余额快照(上一条动账流水的 balance_after)。
i7 = True
prev_bal = None # 上一条动账后的余额
for L in led.order_by("created_at", "id"):
if L.ledger_type in ("recharge", "refund", "adjustment"):
expected = (prev_bal if prev_bal is not None else Z) + L.amount
if prev_bal is not None and L.balance_after != expected:
i7 = False
flag(acct, "I7", f"差分不符 {L.ledger_type}@{str(L.created_at)[:19]} amount={L.amount} 记录={L.balance_after} 应为前值{prev_bal}+{L.amount}={expected}")
prev_bal = L.balance_after
elif L.ledger_type == "charge":
if prev_bal is not None:
expected = prev_bal - L.amount
if L.balance_after != expected:
i7 = False
flag(acct, "I7", f"差分不符 charge@{str(L.created_at)[:19]} amount={L.amount} 记录={L.balance_after} 应为前值{prev_bal}-{L.amount}={expected}")
prev_bal = L.balance_after
else: # reserve / release:不动 balance,快照应等于上一条动账后的余额
if prev_bal is not None and L.balance_after != prev_bal:
i7 = False
flag(acct, "I7", f"reserve/release 余额快照漂移@{str(L.created_at)[:19]} 记录={L.balance_after} 应={prev_bal}")
if i7:
print(" ✓ I7 逐笔差分自洽(每条 balance_after = 前一条 ± 本笔金额)")
# I8 流水完整性(可审计):首条流水之前的隐含余额应为 0(开户额度也应有 genesis 流水)
first = led.order_by("created_at", "id").first()
if first is not None:
delta = first.amount if first.ledger_type in ("recharge", "refund", "adjustment") else (-first.amount if first.ledger_type == "charge" else Z)
opening = first.balance_after - delta
if opening != Z:
flag(acct, "I8", f"开户额度无流水凭证:首条流水前隐含余额={opening}(应为 0,缺 genesis 赠送流水)")
else:
print(" ✓ I8 流水完整(开户额度有凭证)")
def main():
accts = CreditAccount.objects.select_related("team").all()
print(f"审计 {accts.count()} 个团队账户…")
for a in accts:
audit_account(a)
print("\n" + "=" * 60)
if problems:
print(f"发现 {len(problems)} 处账目问题:")
for team, code, msg in problems:
print(f" [{code}] {team}: {msg}")
sys.exit(1)
print("✓ 全部账户账目自洽,无金额核算错误")
if __name__ == "__main__":
main()