Files
igny8/backend/igny8_core/modules/billing/serializers.py
IGNY8 VPS (Salman) 02d4f1fa46 AI MODELS & final updates - feat: Implement AI Model Configuration with dynamic pricing and REST API
- Added AIModelConfig model to manage AI model configurations in the database.
- Created serializers and views for AI model configurations, enabling read-only access via REST API.
- Implemented filtering capabilities for model type, provider, and default status in the API.
- Seeded initial data for text and image models, including pricing and capabilities.
- Updated Django Admin interface for managing AI models with enhanced features and bulk actions.
- Added validation methods for model and image size checks.
- Comprehensive migration created to establish the AIModelConfig model and seed initial data.
- Documented implementation and validation results in summary and report files.
2025-12-24 13:37:36 +00:00

201 lines
7.6 KiB
Python

"""
Serializers for Billing Models
"""
from typing import Any, Dict, Optional
from decimal import Decimal
from rest_framework import serializers
from .models import CreditTransaction, CreditUsageLog
from igny8_core.auth.models import Account
from igny8_core.business.billing.models import PaymentMethodConfig, Payment
class CreditTransactionSerializer(serializers.ModelSerializer):
"""Serializer for credit transactions"""
transaction_type_display: serializers.CharField = serializers.CharField(
source='get_transaction_type_display',
read_only=True
)
class Meta:
model = CreditTransaction
fields = [
'id', 'transaction_type', 'transaction_type_display', 'amount',
'balance_after', 'description', 'metadata', 'created_at'
]
read_only_fields = ['created_at', 'account']
class CreditUsageLogSerializer(serializers.ModelSerializer):
"""Serializer for credit usage logs"""
operation_type_display: serializers.CharField = serializers.CharField(
source='get_operation_type_display',
read_only=True
)
class Meta:
model = CreditUsageLog
fields = [
'id', 'operation_type', 'operation_type_display', 'credits_used',
'cost_usd', 'model_used', 'tokens_input', 'tokens_output',
'related_object_type', 'related_object_id', 'metadata', 'created_at'
]
read_only_fields = ['created_at', 'account']
class CreditBalanceSerializer(serializers.Serializer):
"""Serializer for credit balance response"""
credits: serializers.IntegerField = serializers.IntegerField()
plan_credits_per_month: serializers.IntegerField = serializers.IntegerField()
credits_used_this_month: serializers.IntegerField = serializers.IntegerField()
credits_remaining: serializers.IntegerField = serializers.IntegerField()
class UsageSummarySerializer(serializers.Serializer):
"""Serializer for usage summary response"""
period: serializers.DictField = serializers.DictField()
total_credits_used: serializers.IntegerField = serializers.IntegerField()
total_cost_usd: serializers.DecimalField = serializers.DecimalField(max_digits=10, decimal_places=2)
by_operation: serializers.DictField = serializers.DictField()
by_model: serializers.DictField = serializers.DictField()
class PaymentMethodConfigSerializer(serializers.ModelSerializer):
"""Serializer for payment method configuration"""
payment_method_display: serializers.CharField = serializers.CharField(
source='get_payment_method_display',
read_only=True
)
class Meta:
model = PaymentMethodConfig
fields = [
'id', 'country_code', 'payment_method', 'payment_method_display',
'is_enabled', 'display_name', 'instructions',
'bank_name', 'account_number', 'swift_code',
'wallet_type', 'wallet_id', 'sort_order'
]
read_only_fields = ['id']
class PaymentConfirmationSerializer(serializers.Serializer):
"""Serializer for manual payment confirmation"""
invoice_id: serializers.IntegerField = serializers.IntegerField(required=True)
payment_method: serializers.ChoiceField = serializers.ChoiceField(
choices=['bank_transfer', 'local_wallet'],
required=True
)
manual_reference: serializers.CharField = serializers.CharField(
required=True,
max_length=255,
help_text="Transaction reference number"
)
manual_notes: serializers.CharField = serializers.CharField(
required=False,
allow_blank=True,
help_text="Additional notes about the payment"
)
amount: serializers.DecimalField = serializers.DecimalField(
max_digits=10,
decimal_places=2,
required=True
)
proof_url: serializers.URLField = serializers.URLField(
required=False,
allow_blank=True,
help_text="URL to receipt/proof of payment"
)
def validate_proof_url(self, value: Optional[str]) -> Optional[str]:
"""Validate proof_url is a valid URL format"""
if value and not value.strip():
raise serializers.ValidationError("Proof URL cannot be empty if provided")
if value:
# Additional validation: must be http or https
if not value.startswith(('http://', 'https://')):
raise serializers.ValidationError("Proof URL must start with http:// or https://")
return value
def validate_amount(self, value: Optional[Decimal]) -> Decimal:
"""Validate amount has max 2 decimal places"""
if value is None:
raise serializers.ValidationError("Amount is required")
if value <= 0:
raise serializers.ValidationError("Amount must be greater than 0")
# Check decimal precision (max 2 decimal places)
if value.as_tuple().exponent < -2:
raise serializers.ValidationError("Amount can have maximum 2 decimal places")
return value
class LimitCardSerializer(serializers.Serializer):
"""Serializer for individual limit card"""
title: serializers.CharField = serializers.CharField()
limit: serializers.IntegerField = serializers.IntegerField()
used: serializers.IntegerField = serializers.IntegerField()
available: serializers.IntegerField = serializers.IntegerField()
unit: serializers.CharField = serializers.CharField()
category: serializers.CharField = serializers.CharField()
percentage: serializers.FloatField = serializers.FloatField()
class UsageLimitsSerializer(serializers.Serializer):
"""Serializer for usage limits response"""
limits: LimitCardSerializer = LimitCardSerializer(many=True)
class AIModelConfigSerializer(serializers.Serializer):
"""
Serializer for AI Model Configuration (Read-Only API)
Provides model information for frontend dropdowns and displays
"""
model_name = serializers.CharField(read_only=True)
display_name = serializers.CharField(read_only=True)
model_type = serializers.CharField(read_only=True)
provider = serializers.CharField(read_only=True)
# Text model fields
input_cost_per_1m = serializers.DecimalField(
max_digits=10,
decimal_places=4,
read_only=True,
allow_null=True
)
output_cost_per_1m = serializers.DecimalField(
max_digits=10,
decimal_places=4,
read_only=True,
allow_null=True
)
context_window = serializers.IntegerField(read_only=True, allow_null=True)
max_output_tokens = serializers.IntegerField(read_only=True, allow_null=True)
# Image model fields
cost_per_image = serializers.DecimalField(
max_digits=10,
decimal_places=4,
read_only=True,
allow_null=True
)
valid_sizes = serializers.ListField(read_only=True, allow_null=True)
# Capabilities
supports_json_mode = serializers.BooleanField(read_only=True)
supports_vision = serializers.BooleanField(read_only=True)
supports_function_calling = serializers.BooleanField(read_only=True)
# Status
is_default = serializers.BooleanField(read_only=True)
sort_order = serializers.IntegerField(read_only=True)
# Computed field
pricing_display = serializers.SerializerMethodField()
def get_pricing_display(self, obj):
"""Generate pricing display string based on model type"""
if obj.model_type == 'text':
return f"${obj.input_cost_per_1m}/{obj.output_cost_per_1m} per 1M"
elif obj.model_type == 'image':
return f"${obj.cost_per_image} per image"
return ""