# Free Account Options - Architecture Analysis

**Date:** January 14, 2026  
**Status:** Planning Phase  
**Purpose:** Compare two approaches for free user onboarding with limited AI operations

---

## Current System Architecture

### 1. **Account & Plan System**
```python
Account Model:
- plan (FK to Plan)
- credits (Integer, current balance)
- status (trial, active, suspended, pending_payment, cancelled)
- payment_method
- usage_ahrefs_queries (monthly counter)
- usage_period_start/end

Plan Model:
- name, slug, price, billing_cycle
- is_internal (hide from public listings)
- max_sites, max_keywords, max_users, max_author_profiles
- included_credits (monthly allocation)
- extra_credit_price
- allow_credit_topup
- max_ahrefs_queries (monthly limit)
```

### 2. **AI Configuration System**
```python
AIModelConfig (Global - Single Source of Truth):
- model_name (e.g., 'gpt-4o-mini', 'hidream-full')
- model_type (text/image)
- provider (openai, runware, etc.)
- is_default (one default per type)
- is_active
- cost_per_1k_input/output (text models)
- credits_per_image (image models)
- tokens_per_credit (text models)

AISettings (Per-Account Overrides):
- account (FK)
- integration_type (openai, runware)
- config (API keys, settings)
- model_preferences (per operation type)
- cost_limits (budgets)
```

### 3. **Credit Tracking System**
```python
CreditTransaction:
- transaction_type (purchase, subscription, deduction, adjustment)
- amount (positive/negative)
- balance_after
- description, metadata

CreditUsageLog (Per AI Operation):
- operation_type (clustering, idea_generation, content_generation, image_generation)
- credits_used
- cost_usd
- model_used
- tokens_input/output
- site (FK for filtering)
- related_object_type/id
```

### 4. **Current Registration Flow**
1. User registers → `RegisterSerializer.create()`
2. If `plan_slug` not provided or = 'free':
   - Assigns Plan.slug='free' (must exist)
   - Account.status = 'trial'
   - Account.credits = plan.included_credits
   - Creates CreditTransaction (initial allocation)
3. User can perform AI operations until credits exhausted

---

## 📊 Option 1: Individual Free Accounts (Isolated)

### **Concept**
Each user gets their own free account with:
- Fixed cheaper AI models (GPT-4o mini, Hidream-full)
- Low credit allocation (50-100 operations)
- Own isolated data/workspace
- Ability to upgrade to paid plan

### **Implementation Plan**

#### **Step 1: Create Free Plan**
```sql
-- Admin action via Django Admin
INSERT INTO igny8_plans (
    name, slug, price, billing_cycle, 
    is_featured, is_internal, is_active,
    max_sites, max_users, max_keywords,
    included_credits, allow_credit_topup,
    max_ahrefs_queries
) VALUES (
    'Free Starter', 'free', 0.00, 'monthly',
    false, true, true,
    1,  -- max_sites: 1 site only
    1,  -- max_users: owner only
    100,  -- max_keywords: 100
    100,  -- included_credits: 100 credits (~50 operations)
    false,  -- No credit topup for free
    0  -- No Ahrefs access
);
```

#### **Step 2: Create AI Model Configs (If Not Exist)**
```sql
-- GPT-4o Mini (cheaper text model)
INSERT INTO igny8_ai_model_config (
    model_name, model_type, provider, display_name,
    is_default, is_active,
    cost_per_1k_input, cost_per_1k_output,
    tokens_per_credit, max_tokens, context_window
) VALUES (
    'gpt-4o-mini', 'text', 'openai', 'GPT-4o Mini (Fast & Efficient)',
    false, true,
    0.00015, 0.0006,  -- Cheaper than GPT-4
    1000, 16384, 128000
);

-- Hidream Full (cheaper image model)
INSERT INTO igny8_ai_model_config (
    model_name, model_type, provider, display_name,
    is_default, is_active,
    credits_per_image, quality_tier,
    square_size, landscape_size
) VALUES (
    'hidream-full', 'image', 'runware', 'Hidream Full (Standard Quality)',
    false, true,
    1,  -- 1 credit per image (cheapest)
    'basic',
    '1024x1024', '1280x768'
);
```

#### **Step 3: Update Registration Logic**
```python
# In auth/serializers.py RegisterSerializer.create()

# No changes needed! Current logic already handles this:
if not plan_slug or plan_slug == 'free':
    plan = Plan.objects.get(slug='free', is_active=True)
    account_status = 'trial'
    initial_credits = plan.included_credits  # 100 credits
```

#### **Step 4: Force Free Plan AI Models**
**Option A: Global Default (Simplest)**
```sql
-- Set GPT-4o Mini and Hidream as defaults
UPDATE igny8_ai_model_config 
SET is_default = false 
WHERE model_type = 'text';

UPDATE igny8_ai_model_config 
SET is_default = true 
WHERE model_name = 'gpt-4o-mini';

UPDATE igny8_ai_model_config 
SET is_default = false 
WHERE model_type = 'image';

UPDATE igny8_ai_model_config 
SET is_default = true 
WHERE model_name = 'hidream-full';
```
**Pros:** Zero code changes, all free accounts inherit defaults  
**Cons:** Affects ALL accounts (paid users too)

**Option B: Per-Account AI Settings (Recommended)**
```python
# In auth/serializers.py RegisterSerializer.create()
# After account creation:

if account_status == 'trial':  # Free accounts only
    from igny8_core.modules.system.settings_models import AISettings
    
    # Create AI settings for OpenAI (text)
    AISettings.objects.create(
        account=account,
        integration_type='openai',
        model_preferences={
            'clustering': 'gpt-4o-mini',
            'idea_generation': 'gpt-4o-mini',
            'content_generation': 'gpt-4o-mini',
            'optimization': 'gpt-4o-mini',
        },
        is_active=True
    )
    
    # Create AI settings for Runware (images)
    AISettings.objects.create(
        account=account,
        integration_type='runware',
        model_preferences={
            'image_generation': 'hidream-full',
        },
        is_active=True
    )
```
**Pros:** Free accounts locked to cheap models, paid accounts unaffected  
**Cons:** Requires code change in registration flow

**Option C: Plan-Level AI Model Configuration**
```python
# Add new field to Plan model (migration required)
class Plan(models.Model):
    # ... existing fields ...
    allowed_text_models = models.JSONField(
        default=list,
        help_text="Allowed text AI models (empty = all)"
    )
    allowed_image_models = models.JSONField(
        default=list,
        help_text="Allowed image AI models (empty = all)"
    )
    force_default_models = models.BooleanField(
        default=False,
        help_text="Force plan defaults, ignore user overrides"
    )

# Update Free plan:
plan = Plan.objects.get(slug='free')
plan.allowed_text_models = ['gpt-4o-mini']
plan.allowed_image_models = ['hidream-full']
plan.force_default_models = True
plan.save()

# In AI operation logic (ai/services.py or similar):
def get_ai_model_for_account(account, operation_type):
    plan = account.plan
    if plan.force_default_models:
        if operation_type in ['clustering', 'idea_generation', 'content_generation']:
            return 'gpt-4o-mini'
        elif operation_type == 'image_generation':
            return 'hidream-full'
    # ... existing logic for paid accounts
```
**Pros:** Centralized plan-based control, scalable  
**Cons:** Requires migration + AI operation logic changes

#### **Step 5: Frontend Restrictions**
```typescript
// In frontend, check plan limits
if (user.account.plan.slug === 'free') {
  // Hide model selector (force defaults)
  // Show "Upgrade for more models" message
  // Disable credit topup
  // Disable Ahrefs research
}
```

### **✅ Pros: Individual Free Accounts**
1. **Full User Experience** - Users get their own workspace, test all features
2. **Data Isolation** - Private data, no cross-contamination
3. **Smooth Upgrade Path** - Existing account → upgrade plan → keep data
4. **Proper Multi-Tenancy** - Each account is isolated, secure
5. **Credit Tracking** - Accurate per-user usage analytics
6. **Marketing Value** - "100 Free Credits" sounds generous

### **❌ Cons: Individual Free Accounts**
1. **Database Growth** - Each user = new Account + User + potential Sites/Keywords
2. **Abuse Potential** - Users can create multiple emails for free credits
3. **Complex Enforcement** - Need to enforce model restrictions per account
4. **Storage Costs** - Each account stores independent data
5. **Migration Complexity** - If user upgrades, need to handle plan transition

### **Effort Estimate: Individual Free Accounts**
- **Minimal Approach** (Option A): **1 hour**
  - Create free plan via admin
  - Set default models globally
  - Update frontend to hide topup for free users
  
- **Recommended Approach** (Option B): **4-6 hours**
  - Create free plan via admin
  - Update registration to create AISettings per free account
  - Update AI operation logic to read account-specific models
  - Frontend: Hide model selector for free users
  - Testing across all AI operations

- **Enterprise Approach** (Option C): **1-2 days**
  - Migration: Add allowed_models fields to Plan
  - Update registration flow
  - Refactor AI operation logic (all modules)
  - Admin UI for plan model management
  - Comprehensive testing

---

## 🎭 Option 2: Shared Demo Account (Multi-User)

### **Concept**
One demo account shared by multiple users:
- Users provide email → get "demo access" token
- Limited operations pool (50-100 per user, tracked separately)
- Shared data (users see what others created)
- Pre-configured cheaper AI models
- No upgrade path (must create new account)

### **Implementation Plan**

#### **Step 1: Create Demo Account**
```sql
-- Create demo plan (internal)
INSERT INTO igny8_plans (
    name, slug, price, billing_cycle,
    is_internal, is_active,
    max_sites, max_users, max_keywords,
    included_credits, allow_credit_topup
) VALUES (
    'Demo Access', 'demo', 0.00, 'monthly',
    true, true,
    1,  -- 1 demo site
    999,  -- Unlimited demo users
    50,  -- Limited keywords
    10000,  -- Large shared pool
    false
);

-- Create demo account
INSERT INTO igny8_tenants (
    name, slug, owner_id, plan_id, 
    credits, status
) VALUES (
    'IGNY8 Demo Workspace', 'igny8-demo', 1,  -- owner = admin
    (SELECT id FROM igny8_plans WHERE slug='demo'),
    10000, 'active'
);

-- Create demo site
INSERT INTO igny8_sites (
    name, url, account_id, is_active
) VALUES (
    'Demo Content Site', 'https://demo.example.com',
    (SELECT id FROM igny8_tenants WHERE slug='igny8-demo'),
    true
);
```

#### **Step 2: Create DemoUserAccess Model**
```python
# In auth/models.py
class DemoUserAccess(models.Model):
    """Track individual demo user access and limits"""
    email = models.EmailField(unique=True, db_index=True)
    demo_account = models.ForeignKey(
        'Account',
        on_delete=models.CASCADE,
        related_name='demo_users'
    )
    access_token = models.CharField(max_length=255, unique=True)
    operations_used = models.IntegerField(default=0)
    operations_limit = models.IntegerField(default=50)
    created_at = models.DateTimeField(auto_now_add=True)
    last_accessed = models.DateTimeField(auto_now=True)
    is_active = models.BooleanField(default=True)
    
    class Meta:
        db_table = 'igny8_demo_user_access'
        indexes = [
            models.Index(fields=['email', 'is_active']),
            models.Index(fields=['access_token']),
        ]
    
    def __str__(self):
        return f"Demo: {self.email} ({self.operations_used}/{self.operations_limit})"
    
    def has_operations_remaining(self):
        return self.operations_used < self.operations_limit
```

#### **Step 3: Migration**
```python
# migrations/0014_demo_user_access.py
from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = [
        ('igny8_core_auth', '0013_add_plan_is_internal'),
    ]
    
    operations = [
        migrations.CreateModel(
            name='DemoUserAccess',
            fields=[
                ('id', models.AutoField(primary_key=True)),
                ('email', models.EmailField(unique=True, db_index=True)),
                ('demo_account', models.ForeignKey(
                    on_delete=models.CASCADE,
                    to='igny8_core_auth.Account',
                    related_name='demo_users'
                )),
                ('access_token', models.CharField(max_length=255, unique=True)),
                ('operations_used', models.IntegerField(default=0)),
                ('operations_limit', models.IntegerField(default=50)),
                ('created_at', models.DateTimeField(auto_now_add=True)),
                ('last_accessed', models.DateTimeField(auto_now=True)),
                ('is_active', models.BooleanField(default=True)),
            ],
            options={'db_table': 'igny8_demo_user_access'},
        ),
    ]
```

#### **Step 4: Demo Access Endpoint**
```python
# In auth/views.py AuthViewSet

@action(detail=False, methods=['post'], permission_classes=[])
def request_demo_access(self, request):
    """Request demo account access with email only"""
    email = request.data.get('email')
    if not email:
        return error_response(
            error='Email is required',
            status_code=status.HTTP_400_BAD_REQUEST,
            request=request
        )
    
    # Validate email format
    from django.core.validators import validate_email
    from django.core.exceptions import ValidationError
    try:
        validate_email(email)
    except ValidationError:
        return error_response(
            error='Invalid email format',
            status_code=status.HTTP_400_BAD_REQUEST,
            request=request
        )
    
    # Get demo account
    try:
        demo_account = Account.objects.get(slug='igny8-demo', status='active')
    except Account.DoesNotExist:
        return error_response(
            error='Demo account not configured',
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            request=request
        )
    
    # Get or create demo user access
    from .models import DemoUserAccess
    import secrets
    
    demo_user, created = DemoUserAccess.objects.get_or_create(
        email=email,
        demo_account=demo_account,
        defaults={
            'access_token': secrets.token_urlsafe(32),
            'operations_limit': 50,
        }
    )
    
    if not demo_user.is_active:
        return error_response(
            error='Demo access suspended. Please contact support.',
            status_code=status.HTTP_403_FORBIDDEN,
            request=request
        )
    
    if not demo_user.has_operations_remaining():
        return error_response(
            error='Demo operation limit reached. Please sign up for a full account.',
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            request=request
        )
    
    # Generate temporary JWT for demo account
    # (Custom token that includes demo_user_id)
    access_token = generate_demo_access_token(demo_account, demo_user)
    
    return success_response(
        data={
            'access_token': access_token,
            'demo_user': {
                'email': demo_user.email,
                'operations_remaining': demo_user.operations_limit - demo_user.operations_used,
                'operations_limit': demo_user.operations_limit,
            },
            'account': {
                'id': demo_account.id,
                'name': demo_account.name,
                'is_demo': True,
            }
        },
        message='Demo access granted' if created else 'Welcome back to demo',
        request=request
    )
```

#### **Step 5: Custom JWT with Demo Context**
```python
# In auth/utils.py

def generate_demo_access_token(account, demo_user):
    """Generate JWT for demo access with demo_user context"""
    import jwt
    from datetime import datetime, timedelta
    from django.conf import settings
    
    expiry = datetime.utcnow() + timedelta(hours=24)  # 24-hour demo session
    
    payload = {
        'account_id': account.id,
        'account_slug': account.slug,
        'demo_user_id': demo_user.id,
        'demo_user_email': demo_user.email,
        'is_demo': True,
        'exp': expiry,
        'iat': datetime.utcnow(),
    }
    
    return jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256')
```

#### **Step 6: Demo Operation Tracking Middleware**
```python
# In middleware/demo_tracking.py

class DemoOperationTrackingMiddleware:
    """Track demo user operations and enforce limits"""
    
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        # Check if demo user
        if hasattr(request, 'demo_user_id'):
            from igny8_core.auth.models import DemoUserAccess
            
            demo_user = DemoUserAccess.objects.select_for_update().get(
                id=request.demo_user_id
            )
            
            # Check limit before processing
            if not demo_user.has_operations_remaining():
                return JsonResponse({
                    'success': False,
                    'error': 'Demo operation limit reached. Please sign up for a full account.',
                    'upgrade_url': '/pricing'
                }, status=429)
            
            # Store demo_user in request for operation tracking
            request.demo_user = demo_user
        
        response = self.get_response(request)
        return response

# Add to settings.py MIDDLEWARE
```

#### **Step 7: Update AI Operation Logic**
```python
# In ai/services.py or wherever AI operations are tracked

def log_ai_operation(account, operation_type, credits_used, **kwargs):
    """Log AI operation and increment demo counter if demo user"""
    from igny8_core.business.billing.models import CreditUsageLog
    from django.db import transaction
    
    with transaction.atomic():
        # Create credit usage log
        CreditUsageLog.objects.create(
            account=account,
            operation_type=operation_type,
            credits_used=credits_used,
            **kwargs
        )
        
        # Deduct credits from account
        account.credits -= credits_used
        account.save()
        
        # If demo user, increment their personal counter
        from threading import local
        _request = getattr(local(), 'request', None)
        if _request and hasattr(_request, 'demo_user'):
            demo_user = _request.demo_user
            demo_user.operations_used += 1
            demo_user.save()
```

#### **Step 8: Frontend Demo Flow**
```typescript
// New demo signup flow
async function requestDemoAccess(email: string) {
  const response = await api.post('/v1/auth/request-demo-access/', { email });
  
  if (response.success) {
    // Store demo token
    localStorage.setItem('demo_token', response.data.access_token);
    localStorage.setItem('is_demo', 'true');
    
    // Show demo banner
    showDemoBanner({
      operationsRemaining: response.data.demo_user.operations_remaining,
      operationsLimit: response.data.demo_user.operations_limit,
    });
    
    // Redirect to demo workspace
    router.push('/dashboard');
  }
}

// Demo banner component
<DemoBanner>
  <p>🎭 You're in Demo Mode - {operationsRemaining} operations remaining</p>
  <Button onClick={() => router.push('/pricing')}>
    Upgrade for Full Access
  </Button>
</DemoBanner>

// Disable certain features in demo mode
if (isDemo) {
  disableFeatures(['integrations', 'automation', 'wordpress_sync']);
  showSharedDataWarning();
}
```

### **✅ Pros: Shared Demo Account**
1. **Zero Database Growth** - One account, minimal new records
2. **Instant Access** - No account creation, just email → token
3. **Showcase Content** - Users see real AI-generated examples from others
4. **Anti-Abuse** - Email-based tracking, hard limits per email
5. **Conversion Pressure** - "See others creating, sign up for your own workspace"
6. **Cost Efficient** - Shared credit pool, bulk tracking

### **❌ Cons: Shared Demo Account**
1. **No Data Privacy** - All users see shared workspace (could be feature or bug)
2. **Complex Access Control** - Need custom JWT + middleware + tracking
3. **No Upgrade Path** - Demo token ≠ real account, must register separately
4. **Shared Credit Pool** - If pool exhausted, demo is down for everyone
5. **Feature Limitations** - Can't show integrations, automation, publishing
6. **User Confusion** - "Why do I see others' content?" + "Lost my demo data!"
7. **Backend Complexity** - New model, middleware, JWT type, operation tracking

### **Effort Estimate: Shared Demo Account**
**Full Implementation**: **2-3 days**
- Create demo plan + account + site (1 hour)
- Create DemoUserAccess model + migration (2 hours)
- Build request_demo_access endpoint (2 hours)
- Custom JWT generation with demo context (2 hours)
- Middleware for demo tracking + limits (3 hours)
- Update AI operation logging (2 hours)
- Frontend: Demo flow + banner + restrictions (4 hours)
- Admin: Dashboard to manage demo users (2 hours)
- Testing: Edge cases, limits, shared data (4 hours)

---

## 🎯 Recommendation

### **🏆 Winner: Option 1 - Individual Free Accounts (Option B)**

**Rationale:**
1. **Simpler Architecture** - Leverages existing multi-tenancy, no custom JWT/middleware
2. **Better UX** - Private workspace, smooth upgrade path, feels like real product
3. **Faster Implementation** - 4-6 hours vs 2-3 days
4. **Lower Risk** - No shared data confusion, no new access control layer
5. **Marketing Win** - "100 Free Credits" > "Demo Access with Shared Data"
6. **Scalable** - If abuse becomes issue, add email verification or captcha

**Implementation Checklist:**
```markdown
- [ ] Create 'free' plan via Django Admin
  - [ ] Set: included_credits=100, max_sites=1, max_keywords=100
  - [ ] Set: is_internal=true, allow_credit_topup=false
  
- [ ] Verify AI Model Configs exist
  - [ ] GPT-4o Mini (text, cheap)
  - [ ] Hidream Full (image, cheap)
  
- [ ] Update RegisterSerializer (auth/serializers.py)
  - [ ] After account creation for trial status:
  - [ ] Create AISettings for openai (text) → gpt-4o-mini
  - [ ] Create AISettings for runware (images) → hidream-full
  
- [ ] Update Frontend
  - [ ] Hide model selector for free plan
  - [ ] Disable credit topup for free plan
  - [ ] Show "Upgrade for more models" CTA
  
- [ ] Testing
  - [ ] Register new free account
  - [ ] Run text AI operation → verify gpt-4o-mini used
  - [ ] Run image AI operation → verify hidream-full used
  - [ ] Verify 100 credits allocated
  - [ ] Verify upgrade flow works
```

---

## 🔮 Future Enhancements (Optional)

### For Option 1 (Individual Free Accounts):
1. **Email Verification** - Require verified email to prevent abuse
2. **Captcha** - Add reCAPTCHA on free signups
3. **Usage Analytics** - Track free-to-paid conversion rates
4. **Referral Credits** - Give 50 bonus credits for referrals
5. **Time-Limited Trial** - 30-day access instead of credit-limited

### For Option 2 (Shared Demo - If Pursued):
1. **Demo Content Curation** - Pre-seed with high-quality examples
2. **Demo Reset** - Daily reset to clean state
3. **Anonymous Mode** - Show "User A, User B" instead of emails
4. **Live Activity Feed** - "User just generated an article about X"
5. **Demo Leaderboard** - Gamify the experience

---

## 📚 Reference Files

**Models:**
- `/backend/igny8_core/auth/models.py` - Account, Plan, User
- `/backend/igny8_core/business/billing/models.py` - AIModelConfig, CreditTransaction, CreditUsageLog
- `/backend/igny8_core/modules/system/settings_models.py` - AISettings

**Registration:**
- `/backend/igny8_core/auth/serializers.py` - RegisterSerializer
- `/backend/igny8_core/auth/views.py` - AuthViewSet.register()

**AI Operations:**
- Check modules: clustering, ideas, content, images for credit deduction logic

---

## ✅ Decision

**Recommended:** Proceed with **Option 1 - Individual Free Accounts (Option B)**  
**Estimated Time:** 4-6 hours  
**Risk Level:** Low  
**User Experience:** Excellent

Consider **Option 2** only if:
- Need to showcase "collaborative" aspect
- Want zero database growth (high traffic expected)
- Marketing wants "see what others create" feature
