Phase 0: Foundation & Credit System - Initial implementation

- Updated CREDIT_COSTS constants to Phase 0 format with new operations
- Enhanced CreditService with get_credit_cost() method and operation_type support
- Created AccountModuleSettings model for module enable/disable functionality
- Added AccountModuleSettingsSerializer and ViewSet
- Registered module settings API endpoint: /api/v1/system/settings/account-modules/
- Maintained backward compatibility with existing credit system
This commit is contained in:
Desktop
2025-11-16 23:24:44 +05:00
parent f84be4194f
commit 72a31b2edb
7 changed files with 220 additions and 45 deletions

View File

@@ -1,22 +1,25 @@
"""
Credit Cost Constants
Credit Cost Constants - Phase 0: Credit-Only System
All features are unlimited. Only credits restrict usage.
"""
CREDIT_COSTS = {
'clustering': {
'base': 1, # 1 credit per 30 keywords
'per_keyword': 1 / 30,
},
'ideas': {
'base': 1, # 1 credit per idea
},
'content': {
'base': 3, # 3 credits per full blog post
},
'images': {
'base': 1, # 1 credit per image
},
'reparse': {
'base': 1, # 1 credit per reparse
},
# Existing operations
'clustering': 10, # Per clustering request
'idea_generation': 15, # Per cluster → ideas request
'content_generation': 1, # Per 100 words
'image_prompt_extraction': 2, # Per content piece
'image_generation': 5, # Per image
# Legacy operation names (for backward compatibility)
'ideas': 15, # Alias for idea_generation
'content': 1, # Alias for content_generation (per 100 words)
'images': 5, # Alias for image_generation
'reparse': 2, # Alias for image_prompt_extraction
# NEW: Phase 2+ operations
'linking': 8, # Per content piece (NEW)
'optimization': 1, # Per 200 words (NEW)
'site_structure_generation': 50, # Per site blueprint (NEW)
'site_page_generation': 20, # Per page (NEW)
}

View File

@@ -13,17 +13,49 @@ class CreditService:
"""Service for managing credits"""
@staticmethod
def check_credits(account, required_credits):
def get_credit_cost(operation_type, amount=None):
"""
Get credit cost for operation.
Args:
operation_type: Type of operation (from CREDIT_COSTS)
amount: Optional amount (word count, etc.) for variable costs
Returns:
int: Number of credits required
"""
base_cost = CREDIT_COSTS.get(operation_type, 0)
# Variable costs based on amount
if operation_type == 'content_generation' and amount:
# Per 100 words
return max(1, int(base_cost * (amount / 100)))
elif operation_type == 'optimization' and amount:
# Per 200 words
return max(1, int(base_cost * (amount / 200)))
return base_cost
@staticmethod
def check_credits(account, required_credits=None, operation_type=None, amount=None):
"""
Check if account has enough credits.
Args:
account: Account instance
required_credits: Number of credits required
required_credits: Number of credits required (legacy parameter)
operation_type: Type of operation (new parameter)
amount: Optional amount for variable costs (new parameter)
Raises:
InsufficientCreditsError: If account doesn't have enough credits
"""
# Support both old and new API
if operation_type:
required_credits = CreditService.get_credit_cost(operation_type, amount)
elif required_credits is None:
raise ValueError("Either required_credits or operation_type must be provided")
if account.credits < required_credits:
raise InsufficientCreditsError(
f"Insufficient credits. Required: {required_credits}, Available: {account.credits}"
@@ -121,6 +153,9 @@ class CreditService:
"""
Calculate credits needed for an operation.
DEPRECATED: Use get_credit_cost() instead.
Kept for backward compatibility.
Args:
operation_type: Type of operation
**kwargs: Operation-specific parameters
@@ -131,31 +166,31 @@ class CreditService:
Raises:
CreditCalculationError: If calculation fails
"""
if operation_type not in CREDIT_COSTS:
raise CreditCalculationError(f"Unknown operation type: {operation_type}")
# Map old operation types to new ones
operation_mapping = {
'ideas': 'idea_generation',
'content': 'content_generation',
'images': 'image_generation',
'reparse': 'image_prompt_extraction',
}
cost_config = CREDIT_COSTS[operation_type]
mapped_type = operation_mapping.get(operation_type, operation_type)
if operation_type == 'clustering':
# 1 credit per 30 keywords
# Handle variable costs
if mapped_type == 'content_generation':
word_count = kwargs.get('word_count') or kwargs.get('content_count', 1000) * 100
return CreditService.get_credit_cost(mapped_type, word_count)
elif mapped_type == 'clustering':
keyword_count = kwargs.get('keyword_count', 0)
credits = max(1, int(keyword_count * cost_config['per_keyword']))
return credits
elif operation_type == 'ideas':
# 1 credit per idea
# Clustering is fixed cost per request
return CreditService.get_credit_cost(mapped_type)
elif mapped_type == 'idea_generation':
idea_count = kwargs.get('idea_count', 1)
return cost_config['base'] * idea_count
elif operation_type == 'content':
# 3 credits per content piece
content_count = kwargs.get('content_count', 1)
return cost_config['base'] * content_count
elif operation_type == 'images':
# 1 credit per image
# Fixed cost per request
return CreditService.get_credit_cost(mapped_type)
elif mapped_type == 'image_generation':
image_count = kwargs.get('image_count', 1)
return cost_config['base'] * image_count
elif operation_type == 'reparse':
# 1 credit per reparse
return cost_config['base']
return CreditService.get_credit_cost(mapped_type) * image_count
return cost_config['base']
return CreditService.get_credit_cost(mapped_type)