django admin Groups reorg, Frontend udpates for site settings, #Migration runs
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
"""
|
||||
Credit Service for managing credit transactions and deductions
|
||||
"""
|
||||
import math
|
||||
import logging
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from igny8_core.business.billing.models import CreditTransaction, CreditUsageLog
|
||||
@@ -8,10 +10,124 @@ from igny8_core.business.billing.constants import CREDIT_COSTS
|
||||
from igny8_core.business.billing.exceptions import InsufficientCreditsError, CreditCalculationError
|
||||
from igny8_core.auth.models import Account
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreditService:
|
||||
"""Service for managing credits - Token-based only"""
|
||||
|
||||
@staticmethod
|
||||
def calculate_credits_for_image(model_name: str, num_images: int = 1) -> int:
|
||||
"""
|
||||
Calculate credits for image generation based on AIModelConfig.credits_per_image.
|
||||
|
||||
Args:
|
||||
model_name: The AI model name (e.g., 'dall-e-3', 'flux-1-1-pro')
|
||||
num_images: Number of images to generate
|
||||
|
||||
Returns:
|
||||
int: Credits required
|
||||
|
||||
Raises:
|
||||
CreditCalculationError: If model not found or has no credits_per_image
|
||||
"""
|
||||
from igny8_core.business.billing.models import AIModelConfig
|
||||
|
||||
try:
|
||||
model = AIModelConfig.objects.filter(
|
||||
model_name=model_name,
|
||||
is_active=True
|
||||
).first()
|
||||
|
||||
if not model:
|
||||
raise CreditCalculationError(f"Model {model_name} not found or inactive")
|
||||
|
||||
if model.credits_per_image is None:
|
||||
raise CreditCalculationError(
|
||||
f"Model {model_name} has no credits_per_image configured"
|
||||
)
|
||||
|
||||
credits = model.credits_per_image * num_images
|
||||
|
||||
logger.info(
|
||||
f"Calculated credits for {model_name}: "
|
||||
f"{num_images} images × {model.credits_per_image} = {credits} credits"
|
||||
)
|
||||
|
||||
return credits
|
||||
|
||||
except AIModelConfig.DoesNotExist:
|
||||
raise CreditCalculationError(f"Model {model_name} not found")
|
||||
|
||||
@staticmethod
|
||||
def calculate_credits_from_tokens_by_model(model_name: str, total_tokens: int) -> int:
|
||||
"""
|
||||
Calculate credits from token usage based on AIModelConfig.tokens_per_credit.
|
||||
|
||||
This is the model-specific version that uses the model's configured rate.
|
||||
For operation-based calculation, use calculate_credits_from_tokens().
|
||||
|
||||
Args:
|
||||
model_name: The AI model name (e.g., 'gpt-4o', 'claude-3-5-sonnet')
|
||||
total_tokens: Total tokens used (input + output)
|
||||
|
||||
Returns:
|
||||
int: Credits required (minimum 1)
|
||||
|
||||
Raises:
|
||||
CreditCalculationError: If model not found
|
||||
"""
|
||||
from igny8_core.business.billing.models import AIModelConfig, BillingConfiguration
|
||||
|
||||
try:
|
||||
model = AIModelConfig.objects.filter(
|
||||
model_name=model_name,
|
||||
is_active=True
|
||||
).first()
|
||||
|
||||
if model and model.tokens_per_credit:
|
||||
tokens_per_credit = model.tokens_per_credit
|
||||
else:
|
||||
# Fallback to global default
|
||||
billing_config = BillingConfiguration.get_config()
|
||||
tokens_per_credit = billing_config.default_tokens_per_credit
|
||||
logger.info(
|
||||
f"Model {model_name} has no tokens_per_credit, "
|
||||
f"using default: {tokens_per_credit}"
|
||||
)
|
||||
|
||||
if tokens_per_credit <= 0:
|
||||
raise CreditCalculationError(
|
||||
f"Invalid tokens_per_credit for {model_name}: {tokens_per_credit}"
|
||||
)
|
||||
|
||||
# Get rounding mode
|
||||
billing_config = BillingConfiguration.get_config()
|
||||
rounding_mode = billing_config.credit_rounding_mode
|
||||
|
||||
credits_float = total_tokens / tokens_per_credit
|
||||
|
||||
if rounding_mode == 'up':
|
||||
credits = math.ceil(credits_float)
|
||||
elif rounding_mode == 'down':
|
||||
credits = math.floor(credits_float)
|
||||
else: # nearest
|
||||
credits = round(credits_float)
|
||||
|
||||
# Minimum 1 credit
|
||||
credits = max(credits, 1)
|
||||
|
||||
logger.info(
|
||||
f"Calculated credits for {model_name}: "
|
||||
f"{total_tokens} tokens ÷ {tokens_per_credit} = {credits} credits"
|
||||
)
|
||||
|
||||
return credits
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating credits for {model_name}: {e}")
|
||||
raise CreditCalculationError(f"Error calculating credits: {e}")
|
||||
|
||||
@staticmethod
|
||||
def calculate_credits_from_tokens(operation_type, tokens_input, tokens_output):
|
||||
"""
|
||||
@@ -323,4 +439,56 @@ class CreditService:
|
||||
)
|
||||
|
||||
return account.credits
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def deduct_credits_for_image(
|
||||
account,
|
||||
model_name: str,
|
||||
num_images: int = 1,
|
||||
description: str = None,
|
||||
metadata: dict = None,
|
||||
cost_usd: float = None,
|
||||
related_object_type: str = None,
|
||||
related_object_id: int = None
|
||||
):
|
||||
"""
|
||||
Deduct credits for image generation based on model's credits_per_image.
|
||||
|
||||
Args:
|
||||
account: Account instance
|
||||
model_name: AI model used (e.g., 'dall-e-3', 'flux-1-1-pro')
|
||||
num_images: Number of images generated
|
||||
description: Optional description
|
||||
metadata: Optional metadata dict
|
||||
cost_usd: Optional cost in USD
|
||||
related_object_type: Optional related object type
|
||||
related_object_id: Optional related object ID
|
||||
|
||||
Returns:
|
||||
int: New credit balance
|
||||
"""
|
||||
credits_required = CreditService.calculate_credits_for_image(model_name, num_images)
|
||||
|
||||
if account.credits < credits_required:
|
||||
raise InsufficientCreditsError(
|
||||
f"Insufficient credits. Required: {credits_required}, Available: {account.credits}"
|
||||
)
|
||||
|
||||
if not description:
|
||||
description = f"Image generation: {num_images} images with {model_name} = {credits_required} credits"
|
||||
|
||||
return CreditService.deduct_credits(
|
||||
account=account,
|
||||
amount=credits_required,
|
||||
operation_type='image_generation',
|
||||
description=description,
|
||||
metadata=metadata,
|
||||
cost_usd=cost_usd,
|
||||
model_used=model_name,
|
||||
tokens_input=None,
|
||||
tokens_output=None,
|
||||
related_object_type=related_object_type,
|
||||
related_object_id=related_object_id
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user