fina autoamtiona adn billing and credits
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Initialize Credit Cost Configurations
|
||||
Migrates hardcoded CREDIT_COSTS constants to database
|
||||
"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from igny8_core.business.billing.models import CreditCostConfig
|
||||
from igny8_core.business.billing.constants import CREDIT_COSTS
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Initialize credit cost configurations from constants'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Migrate hardcoded costs to database"""
|
||||
|
||||
operation_metadata = {
|
||||
'clustering': {
|
||||
'display_name': 'Auto Clustering',
|
||||
'description': 'Group keywords into semantic clusters using AI',
|
||||
'unit': 'per_request'
|
||||
},
|
||||
'idea_generation': {
|
||||
'display_name': 'Idea Generation',
|
||||
'description': 'Generate content ideas from keyword clusters',
|
||||
'unit': 'per_request'
|
||||
},
|
||||
'content_generation': {
|
||||
'display_name': 'Content Generation',
|
||||
'description': 'Generate article content using AI',
|
||||
'unit': 'per_100_words'
|
||||
},
|
||||
'image_prompt_extraction': {
|
||||
'display_name': 'Image Prompt Extraction',
|
||||
'description': 'Extract image prompts from content',
|
||||
'unit': 'per_request'
|
||||
},
|
||||
'image_generation': {
|
||||
'display_name': 'Image Generation',
|
||||
'description': 'Generate images using AI (DALL-E, Runware)',
|
||||
'unit': 'per_image'
|
||||
},
|
||||
'linking': {
|
||||
'display_name': 'Content Linking',
|
||||
'description': 'Generate internal links between content',
|
||||
'unit': 'per_request'
|
||||
},
|
||||
'optimization': {
|
||||
'display_name': 'Content Optimization',
|
||||
'description': 'Optimize content for SEO',
|
||||
'unit': 'per_200_words'
|
||||
},
|
||||
'site_structure_generation': {
|
||||
'display_name': 'Site Structure Generation',
|
||||
'description': 'Generate complete site blueprint',
|
||||
'unit': 'per_request'
|
||||
},
|
||||
'site_page_generation': {
|
||||
'display_name': 'Site Page Generation',
|
||||
'description': 'Generate site pages from blueprint',
|
||||
'unit': 'per_item'
|
||||
},
|
||||
'reparse': {
|
||||
'display_name': 'Content Reparse',
|
||||
'description': 'Reparse and update existing content',
|
||||
'unit': 'per_request'
|
||||
},
|
||||
}
|
||||
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
|
||||
for operation_type, cost in CREDIT_COSTS.items():
|
||||
# Skip legacy aliases
|
||||
if operation_type in ['ideas', 'content', 'images']:
|
||||
continue
|
||||
|
||||
metadata = operation_metadata.get(operation_type, {})
|
||||
|
||||
config, created = CreditCostConfig.objects.get_or_create(
|
||||
operation_type=operation_type,
|
||||
defaults={
|
||||
'credits_cost': cost,
|
||||
'display_name': metadata.get('display_name', operation_type.replace('_', ' ').title()),
|
||||
'description': metadata.get('description', ''),
|
||||
'unit': metadata.get('unit', 'per_request'),
|
||||
'is_active': True
|
||||
}
|
||||
)
|
||||
|
||||
if created:
|
||||
created_count += 1
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'✅ Created: {config.display_name} - {cost} credits')
|
||||
)
|
||||
else:
|
||||
updated_count += 1
|
||||
self.stdout.write(
|
||||
self.style.WARNING(f'⚠️ Already exists: {config.display_name}')
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'\n✅ Complete: {created_count} created, {updated_count} already existed')
|
||||
)
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.2.8 on 2025-12-04 14:38
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('billing', '0002_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CreditCostConfig',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('operation_type', models.CharField(choices=[('clustering', 'Keyword Clustering'), ('idea_generation', 'Content Ideas Generation'), ('content_generation', 'Content Generation'), ('image_generation', 'Image Generation'), ('reparse', 'Content Reparse'), ('ideas', 'Content Ideas Generation'), ('content', 'Content Generation'), ('images', 'Image Generation')], help_text='AI operation type', max_length=50, unique=True)),
|
||||
('credits_cost', models.IntegerField(help_text='Credits required for this operation', validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('unit', models.CharField(choices=[('per_request', 'Per Request'), ('per_100_words', 'Per 100 Words'), ('per_200_words', 'Per 200 Words'), ('per_item', 'Per Item'), ('per_image', 'Per Image')], default='per_request', help_text='What the cost applies to', max_length=50)),
|
||||
('display_name', models.CharField(help_text='Human-readable name', max_length=100)),
|
||||
('description', models.TextField(blank=True, help_text='What this operation does')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Enable/disable this operation')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('previous_cost', models.IntegerField(blank=True, help_text='Cost before last update (for audit trail)', null=True)),
|
||||
('updated_by', models.ForeignKey(blank=True, help_text='Admin who last updated', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='credit_cost_updates', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Credit Cost Configuration',
|
||||
'verbose_name_plural': 'Credit Cost Configurations',
|
||||
'db_table': 'igny8_credit_cost_config',
|
||||
'ordering': ['operation_type'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
# Backward compatibility aliases - models moved to business/billing/
|
||||
from igny8_core.business.billing.models import CreditTransaction, CreditUsageLog
|
||||
from igny8_core.business.billing.models import CreditTransaction, CreditUsageLog, CreditCostConfig
|
||||
|
||||
__all__ = ['CreditTransaction', 'CreditUsageLog']
|
||||
__all__ = ['CreditTransaction', 'CreditUsageLog', 'CreditCostConfig']
|
||||
|
||||
@@ -595,6 +595,7 @@ class KeywordViewSet(SiteSectorModelViewSet):
|
||||
def auto_cluster(self, request):
|
||||
"""Auto-cluster keywords using ClusteringService"""
|
||||
import logging
|
||||
from igny8_core.ai.validators.cluster_validators import validate_minimum_keywords
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -611,6 +612,32 @@ class KeywordViewSet(SiteSectorModelViewSet):
|
||||
request=request
|
||||
)
|
||||
|
||||
# NEW: Validate minimum keywords BEFORE queuing task
|
||||
if not keyword_ids:
|
||||
return error_response(
|
||||
error='No keyword IDs provided',
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
request=request
|
||||
)
|
||||
|
||||
validation = validate_minimum_keywords(
|
||||
keyword_ids=keyword_ids,
|
||||
account=account,
|
||||
min_required=5
|
||||
)
|
||||
|
||||
if not validation['valid']:
|
||||
return error_response(
|
||||
error=validation['error'],
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
request=request,
|
||||
extra_data={
|
||||
'count': validation.get('count'),
|
||||
'required': validation.get('required')
|
||||
}
|
||||
)
|
||||
|
||||
# Validation passed - proceed with clustering
|
||||
# Use service to cluster keywords
|
||||
service = ClusteringService()
|
||||
try:
|
||||
@@ -621,7 +648,7 @@ class KeywordViewSet(SiteSectorModelViewSet):
|
||||
# Async task queued
|
||||
return success_response(
|
||||
data={'task_id': result['task_id']},
|
||||
message=result.get('message', 'Clustering started'),
|
||||
message=f'Clustering started with {validation["count"]} keywords',
|
||||
request=request
|
||||
)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user