from decimal import Decimal from rest_framework import serializers from apps.accounts.models import Team, TeamMember, User from apps.ai.models import AITask, QualityWord from apps.assets.models import Asset # 成本异常阈值:实际成本 > 预估 × 此倍数(且预估 > 0)即标异常 COST_ANOMALY_RATIO = Decimal("1.5") def is_cost_anomaly(estimated, actual) -> bool: est = estimated or Decimal("0") act = actual or Decimal("0") return bool(est > 0 and act > est * COST_ANOMALY_RATIO) class QualityWordSerializer(serializers.ModelSerializer): class Meta: model = QualityWord fields = ["id", "stage", "slot", "text", "sort", "enabled", "created_at"] read_only_fields = ["id", "created_at"] class AdminReviewAssetSerializer(serializers.ModelSerializer): """跨团队人像审核队列行:复用 AssetFileSerializer 的首图 preview_url。""" team_name = serializers.CharField(source="team.name", read_only=True, default=None) preview_url = serializers.SerializerMethodField() class Meta: model = Asset fields = ["id", "name", "category", "review_status", "review_error", "team", "team_name", "preview_url", "created_at"] read_only_fields = fields def get_preview_url(self, obj): from apps.assets.serializers import AssetFileSerializer # 用 prefetch 缓存(list(...))而非 .first():后者会另发查询,列表页 N+1 拖慢到数秒 files = list(obj.files.all()) return AssetFileSerializer(files[0]).data.get("preview_url", "") if files else "" class AdminTeamSerializer(serializers.ModelSerializer): owner_username = serializers.CharField(source="owner.username", read_only=True, default=None) member_count = serializers.SerializerMethodField() balance = serializers.SerializerMethodField() class Meta: model = Team fields = ["id", "name", "status", "owner", "owner_username", "member_count", "balance", "created_at"] read_only_fields = fields def get_member_count(self, obj): anno = getattr(obj, "member_count_anno", None) return anno if anno is not None else obj.members.count() def get_balance(self, obj): acct = getattr(obj, "credit_account", None) return str(acct.balance) if acct is not None else "0" class AdminTeamMemberSerializer(serializers.ModelSerializer): username = serializers.CharField(source="user.username", read_only=True) user_status = serializers.CharField(source="user.status", read_only=True) class Meta: model = TeamMember fields = ["id", "username", "role", "status", "user_status", "monthly_credit_limit"] read_only_fields = fields class AdminUserSerializer(serializers.ModelSerializer): teams = serializers.SerializerMethodField() class Meta: model = User fields = ["id", "username", "status", "is_platform_admin", "date_joined", "teams"] read_only_fields = fields def get_teams(self, obj): return [ {"team_id": str(m.team_id), "team_name": m.team.name, "role": m.role} for m in obj.team_memberships.select_related("team").all() ] class AdminTaskSerializer(serializers.ModelSerializer): team_name = serializers.CharField(source="team.name", read_only=True, default=None) model_name = serializers.CharField(source="model_config.name", read_only=True, default=None) cost_anomaly = serializers.SerializerMethodField() class Meta: model = AITask fields = [ "id", "task_type", "status", "team", "team_name", "model_name", "estimated_cost", "actual_cost", "cost_anomaly", "error_code", "created_at", ] read_only_fields = fields def get_cost_anomaly(self, obj) -> bool: return is_cost_anomaly(obj.estimated_cost, obj.actual_cost) class AdminTaskDetailSerializer(AdminTaskSerializer): class Meta(AdminTaskSerializer.Meta): fields = AdminTaskSerializer.Meta.fields + [ "project", "idempotency_key", "request_payload", "response_payload", "error_message", "submitted_at", "completed_at", ] read_only_fields = fields