chore(bug/qa): 归档测试清单、bug 报告与 QA 诊断脚本

- core/bug: 各轮电商项目测试清单 xlsx + 验证报告/套图提示词梳理 md
- core/qa: consumption_summary / diag_login / diag_sessions / probe_usage 诊断脚本

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-29 19:26:36 +08:00
co-authored by Claude Opus 4.8
parent 0465838f84
commit 1d5c2c0910
15 changed files with 1428 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
"""消费汇总:把 DB 里所有扣费/充值/退费流水按团队、按任务类型、按模型聚合。
跑法:cd core/backend && DJANGO_SETTINGS_MODULE=airshelf.settings.development .venv/bin/python ../qa/consumption_summary.py
"""
import os, sys
from collections import defaultdict
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, Count # noqa: E402
from apps.billing.models import CreditLedger # noqa: E402
from apps.ai.models import AITask # noqa: E402
Z = Decimal("0")
print("=" * 60)
print("一、全平台流水汇总(内部额度,单位 CNY)")
print("=" * 60)
for row in (
CreditLedger.objects.values("ledger_type")
.annotate(total=Sum("amount"), n=Count("id"))
.order_by("ledger_type")
):
print(f" {row['ledger_type']:12} 笔数={row['n']:6} 合计={row['total'] or Z}")
charge_total = CreditLedger.objects.filter(ledger_type="charge").aggregate(s=Sum("amount"))["s"] or Z
recharge_total = CreditLedger.objects.filter(ledger_type="recharge").aggregate(s=Sum("amount"))["s"] or Z
refund_total = CreditLedger.objects.filter(ledger_type="refund").aggregate(s=Sum("amount"))["s"] or Z
print(f"\n >>> 实际消费(charge)总额 = {charge_total}")
print(f" >>> 充值(recharge)总额 = {recharge_total}")
print(f" >>> 退费(refund)总额 = {refund_total}")
print("\n" + "=" * 60)
print("二、按团队拆分消费(charge)")
print("=" * 60)
for row in (
CreditLedger.objects.filter(ledger_type="charge")
.values("team__name", "team_id")
.annotate(total=Sum("amount"), n=Count("id"))
.order_by("-total")
):
print(f" team={row['team__name']!r:24} 任务数={row['n']:5} 消费={row['total']}")
print("\n" + "=" * 60)
print("三、按任务类型拆分消费(charge 关联的 AITask.task_type)")
print("=" * 60)
agg = defaultdict(lambda: [0, Z]) # task_type -> [count, sum]
qs = (
CreditLedger.objects.filter(ledger_type="charge", task__isnull=False)
.select_related("task")
.values_list("task__task_type", "amount")
)
for tt, amt in qs:
agg[tt][0] += 1
agg[tt][1] += amt or Z
for tt, (n, s) in sorted(agg.items(), key=lambda kv: -kv[1][1]):
print(f" {tt:16} 次数={n:6} 消费={s}")
print("\n" + "=" * 60)
print("四、单价表(ModelConfig.unit_price = 平台每次固定收费,非三方实际成本)")
print("=" * 60)
from apps.ai.models import ModelConfig # noqa: E402
for mc in ModelConfig.objects.all().order_by("capability", "name"):
print(f" {mc.capability:8} {mc.provider.name}:{mc.name:30} 单价={mc.unit_price} status={mc.status}")
print("\n" + "=" * 60)
print("五、AITask 成本字段抽样(estimated_cost vs actual_cost)")
print("=" * 60)
print(f" task 总数={AITask.objects.count()} 成功={AITask.objects.filter(status='succeeded').count()} 失败={AITask.objects.filter(status='failed').count()}")
print(f" actual_cost 总和={AITask.objects.aggregate(s=Sum('actual_cost'))['s'] or Z}")
# 检查 response_payload 里有没有三方返回的 usage/token/费用
sample = AITask.objects.filter(status="succeeded").exclude(response_payload={}).first()
if sample:
keys = list(sample.response_payload.keys()) if isinstance(sample.response_payload, dict) else type(sample.response_payload)
print(f" response_payload 样本 keys = {keys}")
+47
View File
@@ -0,0 +1,47 @@
import os, sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "airshelf.settings.development")
import django; django.setup()
from django.contrib.auth import get_user_model
from apps.accounts.models import Team, TeamMember
U = get_user_model()
print("=" * 60)
print("一、用户整体状态分布")
print("=" * 60)
total = U.objects.count()
print(f" 用户总数 = {total}")
print(f" is_active=True = {U.objects.filter(is_active=True).count()}")
print(f" is_active=False = {U.objects.filter(is_active=False).count()} <-- Django 登录直接拒")
print(f" status=active = {U.objects.filter(status='active').count()}")
print(f" status=disabled = {U.objects.filter(status='disabled').count()} <-- is_disabled 拒登")
# 密码可用性
unusable = sum(1 for u in U.objects.all() if not u.has_usable_password())
print(f" 无可用密码(密码被清/损坏) = {unusable}")
print("\n" + "=" * 60)
print("二、团队状态")
print("=" * 60)
print(f" 团队总数 = {Team.objects.count()} active={Team.objects.filter(status='active').count()} disabled={Team.objects.filter(status='disabled').count()}")
for t in Team.objects.filter(status='disabled'):
print(f" 停用团队: {t.name} (id={t.id})")
print("\n" + "=" * 60)
print("三、ZWQ 相关账号详情")
print("=" * 60)
qs = U.objects.filter(username__icontains="zwq") | U.objects.filter(username__icontains="ZWQ")
qs = qs.distinct()
if not qs:
print(" 没找到 username 含 zwq 的用户,列出全部用户名供你认:")
for u in U.objects.all().order_by("username")[:60]:
print(f" {u.username}")
for u in qs:
print(f"\n username={u.username!r}")
print(f" is_active={u.is_active} status={u.status} has_usable_password={u.has_usable_password()}")
print(f" is_platform_admin={u.is_platform_admin} is_superuser={u.is_superuser}")
print(f" last_login={u.last_login} date_joined={u.date_joined}")
print(f" password_hash_prefix={u.password[:25]!r}")
mems = TeamMember.objects.filter(user=u).select_related("team")
for m in mems:
print(f" 团队成员: team={m.team.name!r} team_status={m.team.status} member_status={m.status} role={m.role}")
+18
View File
@@ -0,0 +1,18 @@
import os, sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "airshelf.settings.development")
import django; django.setup()
from apps.accounts.models import LoginSession
from rest_framework.authtoken.models import Token
print("=== 最近 30 条成功登录(LoginSession) ===")
for s in LoginSession.objects.select_related("user").order_by("-created_at")[:30]:
fields = {f.name: getattr(s, f.name) for f in s._meta.fields}
extra = {k: v for k, v in fields.items() if k not in ("id", "user", "created_at", "updated_at")}
print(f" {s.created_at:%Y-%m-%d %H:%M:%S} user={s.user.username:18} {extra}")
print(f"\n LoginSession 总数 = {LoginSession.objects.count()}")
print(f" 现存 Token(已登录会话) 数 = {Token.objects.count()}")
print("\n=== 现存 Token 的用户 + 签发时间 ===")
for t in Token.objects.select_related("user").order_by("-created")[:30]:
print(f" {t.created:%Y-%m-%d %H:%M:%S} {t.user.username}")
+16
View File
@@ -0,0 +1,16 @@
import os, sys, json
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "airshelf.settings.development")
import django; django.setup()
from apps.ai.models import AITask
for tt in ["product_image", "script_generation", "video_segment", "storyboard", "voiceover"]:
t = AITask.objects.filter(task_type=tt, status="succeeded").exclude(response_payload={}).first()
print("=" * 50, tt)
if not t:
print(" (无样本)"); continue
rp = t.response_payload if isinstance(t.response_payload, dict) else {}
usage = rp.get("usage")
print(" usage =", json.dumps(usage, ensure_ascii=False))
# 顶层除大字段外的 keys
print(" payload keys =", [k for k in rp.keys()])