# Usage & Content System

**Last Verified:** December 25, 2025

---

## Overview

IGNY8 uses a content-based allowance system. Users see "Content Pieces" while the backend tracks detailed credit consumption for internal cost monitoring.

**User View:** `47/50 Content Pieces Remaining`  
**Backend Tracks:** Idea credits, content credits, image credits (for cost analysis)

---

## How It Works

### User-Facing (Simple)

| What Users See | Description |
|----------------|-------------|
| **Content Pieces** | Monthly allowance of pages/articles |
| **X/Y Remaining** | Used vs total for the month |
| **Upgrade Plan** | Get more content pieces |

### Backend (Detailed - Internal Only)

| Credit Type | Used For | Tracked For |
|-------------|----------|-------------|
| Idea Credits | Clustering, idea generation | Cost analysis |
| Content Credits | Article generation | Usage limits |
| Image Credits | Image generation | Cost analysis |
| Optimization Credits | SEO optimization (future) | Cost analysis |

---

## Plan Allowances

| Plan | Content Pieces/Month | Sites | Users |
|------|---------------------|-------|-------|
| Starter | 50 | 2 | 2 |
| Growth | 200 | 5 | 3 |
| Scale | 500 | Unlimited | 5 |

**Included with every content piece:**
- AI keyword clustering
- AI idea generation  
- AI content writing (1000-2000 words)
- 3 images (1 featured + 2 in-article)
- Internal linking
- SEO optimization
- WordPress publishing

---

## Backend Soft Limits (Hidden from Users)

To prevent abuse, the backend enforces hidden limits:

| Limit | Starter | Growth | Scale |
|-------|---------|--------|-------|
| Keyword imports/mo | 500 | 2,000 | 5,000 |
| Clustering operations | 100 | 400 | 1,000 |
| Idea generations | 150 | 600 | 1,500 |
| Images generated | 200 | 800 | 2,000 |

If users hit these limits, they see: "You've reached your preparation limit for this month."

---

## Content Deduction Flow

```
┌─────────────────────────────────────────────────────────────────┐
│                   CONTENT CREATION FLOW                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  User clicks                Check                 Generate       │
│  "Generate"   ──────►      Allowance  ──────►    Content        │
│                               │                      │          │
│                               │ Limit                │          │
│                               │ Reached              ▼          │
│                               ▼                  Deduct 1       │
│                          Show Upgrade            Content        │
│                          Modal                   Piece          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

---

## Operations & Credit Costs

### Planner Operations

| Operation | Credits | Type |
|-----------|---------|------|
| Add keyword | 0 | Free |
| Auto-cluster keywords | 1 | Idea |
| Generate content ideas | 1 per idea | Idea |

### Writer Operations

| Operation | Credits | Type |
|-----------|---------|------|
| Create task | 0 | Free |
| Generate content | 1 | Content |
| Regenerate content | 1 | Content |
| Generate images | 1 per image | Image |
| Regenerate image | 1 | Image |
| Edit content | 0 | Free |

### Automation Operations

| Operation | Credits | Type |
|-----------|---------|------|
| Run automation | Sum of operations | Mixed |
| Pause/resume | 0 | Free |

### Publisher Operations

| Operation | Credits | Type |
|-----------|---------|------|
| Publish to WordPress | 0 | Free |
| Sync from WordPress | 0 | Free |

### Optimizer Operations (Future)

| Operation | Credits | Type |
|-----------|---------|------|
| Optimize content | 1 | Optimization |
| Batch optimize | 1 per item | Optimization |

---

## Database Models

### CreditBalance

```python
class CreditBalance(models.Model):
    account = models.ForeignKey(Account)
    site = models.ForeignKey(Site, null=True)
    
    idea_credits = models.IntegerField(default=0)
    content_credits = models.IntegerField(default=0)
    image_credits = models.IntegerField(default=0)
    optimization_credits = models.IntegerField(default=0)
    
    period_start = models.DateField()
    period_end = models.DateField()
```

### CreditUsage

```python
class CreditUsage(models.Model):
    account = models.ForeignKey(Account)
    site = models.ForeignKey(Site, null=True)
    user = models.ForeignKey(User)
    
    credit_type = models.CharField()  # idea/content/image/optimization
    amount = models.IntegerField()
    operation = models.CharField()    # generate_content, etc.
    
    created_at = models.DateTimeField(auto_now_add=True)
```

---

## Business Logic

### CreditService

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

**Key Methods:**

```python
class CreditService:
    def check_balance(account, site, credit_type, amount) -> bool:
        """Check if sufficient credits available"""
        
    def deduct_credits(account, site, user, credit_type, amount, operation) -> bool:
        """Deduct credits and log usage"""
        
    def get_balance(account, site) -> CreditBalance:
        """Get current balance"""
        
    def reset_monthly_credits(account) -> None:
        """Reset credits at period start"""
        
    def add_credits(account, credit_type, amount, reason) -> None:
        """Add credits (admin/purchase)"""
```

### Usage in AI Operations

```python
# In content generation service
def generate_content(task, user):
    # 1. Check balance
    if not credit_service.check_balance(
        account=task.site.account,
        site=task.site,
        credit_type='content',
        amount=1
    ):
        raise InsufficientCreditsError()
    
    # 2. Execute AI function
    content = ai_engine.generate_content(task)
    
    # 3. Deduct credits
    credit_service.deduct_credits(
        account=task.site.account,
        site=task.site,
        user=user,
        credit_type='content',
        amount=1,
        operation='generate_content'
    )
    
    return content
```

---

## API Responses

### Successful Deduction

```json
{
  "success": true,
  "data": { ... },
  "credits_used": {
    "type": "content",
    "amount": 1
  },
  "balance": {
    "content_credits": 49
  }
}
```

### Insufficient Credits

```json
HTTP 402 Payment Required

{
  "success": false,
  "error": "Insufficient content credits",
  "code": "INSUFFICIENT_CREDITS",
  "required": 1,
  "available": 0
}
```

---

## Frontend Handling

### Balance Display

- Header shows credit balances
- Updates after each operation
- Warning at low balance (< 10%)

### Error Handling

```typescript
// In writer store
async generateContent(taskId: string) {
  try {
    const response = await api.generateContent(taskId);
    // Update billing store
    billingStore.fetchBalance();
    return response;
  } catch (error) {
    if (error.code === 'INSUFFICIENT_CREDITS') {
      // Show upgrade modal
      uiStore.showUpgradeModal();
    }
    throw error;
  }
}
```

---

## Usage Tracking

### Usage Summary Endpoint

```
GET /api/v1/billing/usage/summary/?period=month
```

Response:
```json
{
  "period": "2025-01",
  "usage": {
    "idea_credits": 45,
    "content_credits": 23,
    "image_credits": 67,
    "optimization_credits": 0
  },
  "by_operation": {
    "auto_cluster": 12,
    "generate_ideas": 33,
    "generate_content": 23,
    "generate_images": 67
  }
}
```

---

## Automation Credit Estimation

Before running automation:

```
GET /api/v1/automation/estimate/?site_id=...
```

Response:
```json
{
  "estimated_credits": {
    "idea_credits": 25,
    "content_credits": 10,
    "image_credits": 30
  },
  "stages": {
    "clustering": 5,
    "ideas": 20,
    "content": 10,
    "images": 30
  },
  "has_sufficient_credits": true
}
```

---

## Credit Reset

Credits reset monthly based on billing cycle:

1. **Monthly Reset Job** runs at period end
2. **Unused credits** do not roll over
3. **Purchased credits** may have different expiry

### Celery Task

```python
@celery.task
def reset_monthly_credits():
    """
    Run daily, resets credits for accounts
    whose period_end is today
    """
    today = date.today()
    balances = CreditBalance.objects.filter(period_end=today)
    
    for balance in balances:
        credit_service.reset_monthly_credits(balance.account)
```

---

## Admin Operations

### Manual Credit Adjustment

Via Django Admin or API:

```python
# Add credits
credit_service.add_credits(
    account=account,
    credit_type='content',
    amount=100,
    reason='Customer support adjustment'
)
```

### Usage Audit

All credit changes logged in `CreditUsage` with:
- Timestamp
- User who triggered
- Operation type
- Amount deducted
- Related object ID
