# Credits System - Complete Audit and Improvement Plan

**Date:** December 4, 2025  
**Status:** Audit Complete - Awaiting Implementation  
**Priority:** HIGH

---

## 📋 EXECUTIVE SUMMARY

This document provides a comprehensive audit of the IGNY8 credits system, identifies gaps and potential issues, and proposes improvements including backend admin configuration for credit costs per function.

**Current State:** ✅ Working  
**Areas for Improvement:** Configuration Management, Billing Integration, Admin UI, Reporting

---

## 🔍 SYSTEM ARCHITECTURE AUDIT

### Current Credit System Components

```
┌─────────────────────────────────────────────────────────────┐
│                    CREDITS SYSTEM                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  1. Account Model (credits field)                          │
│  2. CreditTransaction Model (history)                      │
│  3. CreditUsageLog Model (detailed usage)                  │
│  4. CreditService (business logic)                         │
│  5. CREDIT_COSTS (hardcoded constants)                     │
│  6. Credit API Endpoints                                    │
│  7. Frontend Dashboard                                      │
│                                                              │
└─────────────────────────────────────────────────────────────┘
```

---

## 🗄️ DATABASE MODELS AUDIT

### 1. Account Model (`auth.Account`)

**Location:** `backend/igny8_core/auth/models.py`

**Credits Field:**
```python
credits = models.IntegerField(default=0, help_text="Current credit balance")
```

**Status:** ✅ Working  
**Findings:**
- Simple integer field for balance
- No soft delete or archive mechanism
- No credit expiration tracking
- No overdraft protection

**Recommendations:**
- ✅ Keep simple design (no changes needed)
- Add `credits_expires_at` field for subscription credits
- Add `bonus_credits` field separate from subscription credits

---

### 2. CreditTransaction Model

**Location:** `backend/igny8_core/business/billing/models.py`

**Current Structure:**
```python
class CreditTransaction(AccountBaseModel):
    TRANSACTION_TYPE_CHOICES = [
        ('purchase', 'Purchase'),
        ('subscription', 'Subscription Renewal'),
        ('refund', 'Refund'),
        ('deduction', 'Usage Deduction'),
        ('adjustment', 'Manual Adjustment'),
    ]
    
    transaction_type = models.CharField(max_length=20, choices=TRANSACTION_TYPE_CHOICES)
    amount = models.IntegerField()  # + for add, - for deduct
    balance_after = models.IntegerField()
    description = models.CharField(max_length=255)
    metadata = models.JSONField(default=dict)
    created_at = models.DateTimeField(auto_now_add=True)
```

**Status:** ✅ Working  
**Findings:**
- Comprehensive transaction types
- Good metadata for context
- Proper indexing on account/type/date
- Missing: invoice_id FK, payment_method

**Recommendations:**
- Add `invoice_id` FK (for billing integration)
- Add `payment_method` field ('stripe', 'manual', 'free')
- Add `status` field ('pending', 'completed', 'failed', 'refunded')
- Add `external_transaction_id` for Stripe payment IDs

---

### 3. CreditUsageLog Model

**Location:** `backend/igny8_core/business/billing/models.py`

**Current Structure:**
```python
class CreditUsageLog(AccountBaseModel):
    OPERATION_TYPE_CHOICES = [
        ('clustering', 'Clustering'),
        ('idea_generation', 'Idea Generation'),
        ('content_generation', 'Content Generation'),
        ('image_prompt_extraction', 'Image Prompt Extraction'),
        ('image_generation', 'Image Generation'),
        ('linking', 'Content Linking'),
        ('optimization', 'Content Optimization'),
        ('site_structure_generation', 'Site Structure Generation'),
        ('site_page_generation', 'Site Page Generation'),
    ]
    
    operation_type = models.CharField(max_length=50, choices=OPERATION_TYPE_CHOICES)
    credits_used = models.IntegerField()
    cost_usd = models.DecimalField(max_digits=10, decimal_places=2, null=True)
    model_used = models.CharField(max_length=100, blank=True)
    tokens_input = models.IntegerField(null=True)
    tokens_output = models.IntegerField(null=True)
    related_object_type = models.CharField(max_length=50, blank=True)
    related_object_id = models.IntegerField(null=True)
    metadata = models.JSONField(default=dict)
    created_at = models.DateTimeField(auto_now_add=True)
```

**Status:** ✅ Working  
**Findings:**
- Excellent detail tracking (model, tokens, cost)
- Good related object tracking
- Proper operation type choices
- Missing: site/sector isolation, duration tracking

**Recommendations:**
- Add `site` FK for multi-tenant filtering
- Add `sector` FK for isolation
- Add `duration_seconds` field (API call time)
- Add `success` boolean field (track failures)
- Add `error_message` field for failed operations

---

## 💳 CREDIT COST CONFIGURATION AUDIT

### Current Implementation: Hardcoded Constants

**Location:** `backend/igny8_core/business/billing/constants.py`

```python
CREDIT_COSTS = {
    '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
    'linking': 8,                        # Per content piece
    'optimization': 1,                   # Per 200 words
    'site_structure_generation': 50,     # Per site blueprint
    'site_page_generation': 20,          # Per page
}
```

**Status:** ⚠️ Working but NOT configurable  
**Problems:**
1. ❌ **Hardcoded values** - Requires code deployment to change
2. ❌ **No admin UI** - Cannot adjust costs without developer
3. ❌ **No versioning** - Cannot track cost changes over time
4. ❌ **No A/B testing** - Cannot test different pricing
5. ❌ **No per-account pricing** - All accounts same cost
6. ❌ **No promotional pricing** - Cannot offer discounts

---

## 💰 BILLING & INVOICING GAPS

### Current State

**✅ Working:**
- Credit deduction on AI operations
- Credit transaction logging
- Credit balance API
- Credit usage API
- Monthly credit replenishment (Celery task)

**❌ Missing (NOT Implemented):**
1. **Invoice Generation** - No Invoice model or PDF generation
2. **Payment Processing** - No Stripe/PayPal integration
3. **Subscription Management** - No recurring billing
4. **Purchase Credits** - No one-time credit purchase flow
5. **Refund Processing** - No refund workflow
6. **Payment History** - No payment records
7. **Tax Calculation** - No tax/VAT handling
8. **Billing Address** - No billing info storage

---

## 🎯 PROPOSED SOLUTION: Backend Admin Configuration

### New Model: CreditCostConfig

**Purpose:** Make credit costs configurable from Django Admin

**Location:** `backend/igny8_core/modules/billing/models.py`

```python
class CreditCostConfig(models.Model):
    """
    Configurable credit costs per AI function
    Admin-editable alternative to hardcoded constants
    """
    # Operation identification
    operation_type = models.CharField(
        max_length=50,
        unique=True,
        choices=CreditUsageLog.OPERATION_TYPE_CHOICES,
        help_text="AI operation type"
    )
    
    # Cost configuration
    credits_cost = models.IntegerField(
        validators=[MinValueValidator(0)],
        help_text="Credits required for this operation"
    )
    
    # Unit of measurement
    unit = models.CharField(
        max_length=50,
        default='per_request',
        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'),
        ],
        help_text="What the cost applies to"
    )
    
    # Metadata
    display_name = models.CharField(max_length=100, help_text="Human-readable name")
    description = models.TextField(blank=True, help_text="What this operation does")
    
    # Status
    is_active = models.BooleanField(default=True, help_text="Enable/disable this operation")
    
    # Audit fields
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    updated_by = models.ForeignKey(
        'auth.User',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        help_text="Admin who last updated"
    )
    
    # Change tracking
    previous_cost = models.IntegerField(
        null=True,
        blank=True,
        help_text="Cost before last update (for audit trail)"
    )
    
    class Meta:
        db_table = 'igny8_credit_cost_config'
        verbose_name = 'Credit Cost Configuration'
        verbose_name_plural = 'Credit Cost Configurations'
        ordering = ['operation_type']
    
    def __str__(self):
        return f"{self.display_name} - {self.credits_cost} credits {self.unit}"
    
    def save(self, *args, **kwargs):
        # Track cost changes
        if self.pk:
            old = CreditCostConfig.objects.get(pk=self.pk)
            if old.credits_cost != self.credits_cost:
                self.previous_cost = old.credits_cost
        super().save(*args, **kwargs)
```

---

### Django Admin Configuration

**Location:** `backend/igny8_core/modules/billing/admin.py`

```python
from django.contrib import admin
from django.utils.html import format_html
from .models import CreditCostConfig

@admin.register(CreditCostConfig)
class CreditCostConfigAdmin(admin.ModelAdmin):
    list_display = [
        'operation_type',
        'display_name',
        'credits_cost_display',
        'unit',
        'is_active',
        'cost_change_indicator',
        'updated_at',
        'updated_by'
    ]
    
    list_filter = ['is_active', 'unit', 'updated_at']
    search_fields = ['operation_type', 'display_name', 'description']
    
    fieldsets = (
        ('Operation', {
            'fields': ('operation_type', 'display_name', 'description')
        }),
        ('Cost Configuration', {
            'fields': ('credits_cost', 'unit', 'is_active')
        }),
        ('Audit Trail', {
            'fields': ('previous_cost', 'updated_by', 'created_at', 'updated_at'),
            'classes': ('collapse',)
        }),
    )
    
    readonly_fields = ['created_at', 'updated_at', 'previous_cost']
    
    def credits_cost_display(self, obj):
        """Show cost with color coding"""
        if obj.credits_cost >= 20:
            color = 'red'
        elif obj.credits_cost >= 10:
            color = 'orange'
        else:
            color = 'green'
        return format_html(
            '<span style="color: {}; font-weight: bold;">{} credits</span>',
            color,
            obj.credits_cost
        )
    credits_cost_display.short_description = 'Cost'
    
    def cost_change_indicator(self, obj):
        """Show if cost changed recently"""
        if obj.previous_cost is not None:
            if obj.credits_cost > obj.previous_cost:
                icon = '📈'  # Increased
                color = 'red'
            elif obj.credits_cost < obj.previous_cost:
                icon = '📉'  # Decreased
                color = 'green'
            else:
                icon = '➡️'  # Same
                color = 'gray'
            
            return format_html(
                '{} <span style="color: {};">({} → {})</span>',
                icon,
                color,
                obj.previous_cost,
                obj.credits_cost
            )
        return '—'
    cost_change_indicator.short_description = 'Recent Change'
    
    def save_model(self, request, obj, form, change):
        """Track who made the change"""
        obj.updated_by = request.user
        super().save_model(request, obj, form, change)
```

---

### Updated CreditService to Use Database

**Location:** `backend/igny8_core/business/billing/services/credit_service.py`

```python
class CreditService:
    """Service for managing credits"""
    
    @staticmethod
    def get_credit_cost(operation_type, amount=None):
        """
        Get credit cost for an operation.
        Now checks database config first, falls back to constants.
        
        Args:
            operation_type: Type of operation
            amount: Optional amount (word count, image count, etc.)
        
        Returns:
            int: Credit cost
        """
        # Try to get from database config first
        try:
            from igny8_core.modules.billing.models import CreditCostConfig
            
            config = CreditCostConfig.objects.filter(
                operation_type=operation_type,
                is_active=True
            ).first()
            
            if config:
                base_cost = config.credits_cost
                
                # Apply unit-based calculation
                if config.unit == 'per_100_words' and amount:
                    return max(1, (amount // 100)) * base_cost
                elif config.unit == 'per_200_words' and amount:
                    return max(1, (amount // 200)) * base_cost
                elif config.unit in ['per_item', 'per_image'] and amount:
                    return amount * base_cost
                else:
                    return base_cost
        
        except Exception as e:
            logger.warning(f"Failed to get cost from database, using constants: {e}")
        
        # Fallback to hardcoded constants
        from igny8_core.business.billing.constants import CREDIT_COSTS
        
        base_cost = CREDIT_COSTS.get(operation_type, 1)
        
        # Apply multipliers for word-based operations
        if operation_type == 'content_generation' and amount:
            return max(1, (amount // 100))  # 1 credit per 100 words
        elif operation_type == 'optimization' and amount:
            return max(1, (amount // 200))  # 1 credit per 200 words
        elif operation_type in ['image_generation'] and amount:
            return amount * base_cost
        else:
            return base_cost
```

---

## 📊 ADMIN UI - CREDIT COST CONFIGURATION

### Management Command: Initialize Credit Costs

**Location:** `backend/igny8_core/modules/billing/management/commands/init_credit_costs.py`

```python
from django.core.management.base import BaseCommand
from igny8_core.modules.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'
            },
        }
        
        created_count = 0
        updated_count = 0
        
        for operation_type, cost in CREDIT_COSTS.items():
            # Skip legacy aliases
            if operation_type in ['ideas', 'content', 'images', 'reparse']:
                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')
        )
```

**Run command:**
```bash
python manage.py init_credit_costs
```

---

## 🔍 POTENTIAL ISSUES & FIXES

### Issue 1: Race Conditions in Credit Deduction

**Problem:**
```python
# Current code (simplified)
if account.credits < required:
    raise InsufficientCreditsError()
account.credits -= required  # Race condition here!
account.save()
```

**Risk:** Two requests could both check balance and deduct simultaneously

**Fix:** Use database-level atomic update
```python
from django.db.models import F

@transaction.atomic
def deduct_credits(account, amount):
    # Atomic update with check
    updated = Account.objects.filter(
        id=account.id,
        credits__gte=amount  # Check in database
    ).update(
        credits=F('credits') - amount
    )
    
    if updated == 0:
        raise InsufficientCreditsError()
    
    account.refresh_from_db()
    return account.credits
```

---

### Issue 2: Negative Credit Balance

**Problem:** No hard constraint preventing negative credits

**Fix 1:** Database constraint
```python
# Migration
operations = [
    migrations.AddConstraint(
        model_name='account',
        constraint=models.CheckConstraint(
            check=models.Q(credits__gte=0),
            name='credits_non_negative'
        ),
    ),
]
```

**Fix 2:** Service layer validation (current approach - OK)

---

### Issue 3: Missing Credit Expiration

**Problem:** Subscription credits never expire

**Fix:** Add expiration tracking
```python
# Account model
credits_expires_at = models.DateTimeField(null=True, blank=True)

# Celery task (daily)
@shared_task
def expire_credits():
    """Expire old credits"""
    expired = Account.objects.filter(
        credits_expires_at__lt=timezone.now(),
        credits__gt=0
    )
    
    for account in expired:
        # Transfer to expired_credits field or log
        account.credits = 0
        account.save()
```

---

### Issue 4: No Usage Analytics

**Problem:** Hard to analyze which functions cost most credits

**Fix:** Add aggregation views
```python
# Backend
@action(detail=False, methods=['get'])
def cost_breakdown(self, request):
    """Get credit cost breakdown by operation"""
    from django.db.models import Sum
    
    breakdown = CreditUsageLog.objects.filter(
        account=request.account
    ).values('operation_type').annotate(
        total_credits=Sum('credits_used'),
        count=Count('id')
    ).order_by('-total_credits')
    
    return Response(breakdown)
```

---

### Issue 5: No Budget Alerts

**Problem:** Users can run out of credits unexpectedly

**Fix:** Add threshold alerts
```python
# After each deduction
def check_low_balance(account):
    if account.credits < 100:  # Configurable threshold
        send_low_balance_email(account)
        
    if account.credits < 50:
        send_critical_balance_email(account)
```

---

## 📈 FUTURE ENHANCEMENTS

### Phase 1: Billing Integration (Priority: HIGH)

**Models to Add:**
1. `Invoice` - Store invoice records
2. `Payment` - Track payments
3. `Subscription` - Recurring billing
4. `CreditPackage` - One-time credit purchases

**Integrations:**
- Stripe for payments
- PDF generation for invoices
- Email notifications

---

### Phase 2: Advanced Pricing (Priority: MEDIUM)

**Features:**
1. **Volume Discounts**
   - 1000+ credits/month: 10% discount
   - 5000+ credits/month: 20% discount

2. **Per-Account Pricing**
   - Enterprise accounts: Custom pricing
   - Trial accounts: Limited operations

3. **Promotional Codes**
   - Discount codes
   - Free credit grants

4. **Credit Bundles**
   - Starter: 500 credits
   - Pro: 2000 credits
   - Enterprise: 10000 credits

---

### Phase 3: Usage Analytics Dashboard (Priority: MEDIUM)

**Features:**
1. **Cost Breakdown Charts**
   - By operation type
   - By time period
   - By site/sector

2. **Trend Analysis**
   - Daily/weekly/monthly usage
   - Forecasting
   - Budget alerts

3. **Comparison Reports**
   - Compare accounts
   - Compare time periods
   - Benchmark against averages

---

## 🧪 TESTING CHECKLIST

### Unit Tests Required

- [ ] Test CreditCostConfig model creation
- [ ] Test CreditService with database config
- [ ] Test fallback to constants
- [ ] Test atomic credit deduction
- [ ] Test negative balance prevention
- [ ] Test cost calculation with units

### Integration Tests Required

- [ ] Test full credit deduction flow
- [ ] Test monthly replenishment
- [ ] Test admin UI operations
- [ ] Test concurrent deductions (race conditions)
- [ ] Test cost changes propagate correctly

### Manual Testing Required

- [ ] Create credit config in Django Admin
- [ ] Update cost and verify in logs
- [ ] Deactivate operation and verify rejection
- [ ] Test with different units (per 100 words, per image)
- [ ] Verify audit trail (previous_cost, updated_by)

---

## 📋 IMPLEMENTATION ROADMAP

### Week 1: Database Configuration
- [ ] Create `CreditCostConfig` model
- [ ] Create migration
- [ ] Create Django Admin
- [ ] Create init_credit_costs command
- [ ] Update CreditService to use database

### Week 2: Testing & Refinement
- [ ] Write unit tests
- [ ] Write integration tests
- [ ] Manual QA testing
- [ ] Fix race conditions
- [ ] Add constraints

### Week 3: Documentation & Deployment
- [ ] Update API documentation
- [ ] Create admin user guide
- [ ] Deploy to staging
- [ ] User acceptance testing
- [ ] Deploy to production

### Week 4: Monitoring & Optimization
- [ ] Monitor cost changes
- [ ] Analyze usage patterns
- [ ] Optimize slow queries
- [ ] Plan Phase 2 features

---

## 🎯 SUCCESS CRITERIA

✅ **Backend Admin:** Credits configurable via Django Admin  
✅ **No Code Deploys:** Cost changes don't require deployment  
✅ **Audit Trail:** Track who changed costs and when  
✅ **Backward Compatible:** Existing code continues to work  
✅ **Performance:** No regression in credit deduction speed  
✅ **Data Integrity:** No race conditions or negative balances  
✅ **Testing:** 100% test coverage for critical paths

---

## 📊 CREDITS SYSTEM FLOWCHART

```mermaid
graph TD
    A[AI Operation Request] --> B{Check CreditCostConfig}
    B -->|Found| C[Get Cost from Database]
    B -->|Not Found| D[Get Cost from Constants]
    C --> E[Calculate Total Cost]
    D --> E
    E --> F{Sufficient Credits?}
    F -->|Yes| G[Atomic Deduct Credits]
    F -->|No| H[Raise InsufficientCreditsError]
    G --> I[Create CreditTransaction]
    I --> J[Create CreditUsageLog]
    J --> K[Return Success]
    H --> L[Return Error]
```

---

## 🔐 SECURITY CONSIDERATIONS

### Credit Manipulation Prevention

1. **No Client-Side Credit Calculation**
   - All calculations server-side
   - Credits never exposed in API responses

2. **Atomic Transactions**
   - Use database transactions
   - Prevent race conditions

3. **Audit Logging**
   - Log all credit changes
   - Track who/when/why

4. **Rate Limiting**
   - Prevent credit abuse
   - Throttle expensive operations

5. **Admin Permissions**
   - Only superusers can modify costs
   - Track all admin changes

---

## END OF AUDIT

This comprehensive audit identifies all aspects of the credits system, proposes a database-driven configuration approach, and provides a clear roadmap for implementation. The system is currently working well but lacks flexibility for cost adjustments without code deployments.

**Recommendation:** Implement the CreditCostConfig model in Phase 1 to enable admin-configurable costs.
