Files
igny8/docs/plans/CREDITS-LIMITS-IMPLEMENTATION-PLAN.md
2026-01-06 21:28:13 +00:00

80 KiB
Raw Blame History

Credits & Limits System - Implementation Plan

Date: January 5, 2026
Status: 🚧 IMPLEMENTATION PLAN - Pending Execution
Prepared For: IGNY8 Platform
Purpose: Comprehensive plan to simplify, optimize, and properly enforce credits and limits system


Executive Summary

This implementation plan addresses the IGNY8 platform's credits and limits system based on complete codebase analysis.

Core Philosophy: SIMPLICITY

Keep ONLY 4 Hard Limits:

  1. Sites - How many sites per account
  2. Team Users - How many team members
  3. Keywords - Total keywords in workspace
  4. Ahrefs Queries - Monthly research queries (NEW)

Everything Else = Credits - Let users consume credits however they want (content, images, ideas, etc.)

What We're Doing

REMOVE all unnecessary monthly limits (content_ideas, content_words, images_basic, images_premium, image_prompts)
SIMPLIFY to credit-based system only
ENFORCE the 4 hard limits properly
ELIMINATE data duplication across pages
REDESIGN Usage page with multi-dimensional insights
IMPLEMENT Ahrefs keyword research structure
VALIDATE all credit balance checks
USE user-friendly terminology (no "API", "operations", etc.)


1. FINAL SIMPLIFIED MODEL

1.1 The Only Limits That Matter

Limit Type Description Enforcement
Sites Hard Max sites per account Keep & Enforce
Team Users Hard Max team members Keep & Enforce
Keywords Hard Total keywords in workspace Keep & Enforce
Ahrefs Queries Monthly Research queries per month 🆕 Implement

1.2 What Gets REMOVED

REMOVE These Limits (Use Credits Instead):

  • max_content_ideas - Let credits control this
  • max_content_words - Let credits control this
  • max_images_basic - Let credits control this
  • max_images_premium - Let credits control this
  • max_image_prompts - Let credits control this
  • max_clusters - Let credits control this (or combine with keywords)
  • usage_content_ideas - Not needed
  • usage_content_words - Not needed
  • usage_images_basic - Not needed
  • usage_images_premium - Not needed
  • usage_image_prompts - Not needed

Why Remove?

  • Confusing for users ("I have credits but can't generate content?")
  • Double limiting (credits + monthly limits)
  • Maintenance overhead
  • Credit system already provides control

1.3 Keyword Research Structure (NEW)

Two Ways to Add Keywords:

┌─────────────────────────────────────────────────────────────────┐
│                   KEYWORD RESEARCH OPTIONS                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Option 1: IGNY8 Pre-Researched Keywords (FREE)                │
│  ────────────────────────────────────────────                  │
│  • Access global keyword database                              │
│  • Filter by industry, sector, country                         │
│  • Pre-analyzed for search volume, difficulty                  │
│  • Free to browse and add to workspace                         │
│  • Limited by: max_keywords (total workspace limit)            │
│                                                                  │
│  Option 2: Ahrefs Live Research (LIMITED)                      │
│  ────────────────────────────────────────────                  │
│  • Query Ahrefs API directly                                   │
│  • Get fresh, custom keyword data                              │
│  • Monthly query limit (e.g., 50-500 depending on plan)        │
│  • Limited by: max_ahrefs_queries (monthly limit)              │
│  • Results can be added to workspace (counts toward max_keywords)│
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Implementation:

  • Rename current "Add Keywords" to "Browse Pre-Researched Keywords"
  • Add new tab/section "Research with Ahrefs" (with monthly limit indicator)
  • Show remaining queries: "You have 42/50 Ahrefs queries remaining this month"

2. Current System Analysis (What We Found)

1.1 Frontend Pages Breakdown

Plans & Billing Dropdown (/account/plans)

Page Route Primary Purpose Current Data Displayed
Current Plan /account/plans View active subscription Plan name, price, renewal date, included credits
Upgrade Plan /account/plans/upgrade Compare and purchase plans Pricing table with all plans, features, limits
History /account/plans/history Invoices and payments Invoice list, payment methods, transaction history

File: frontend/src/pages/account/PlansAndBillingPage.tsx (881 lines)

Tab Structure:

type TabType = 'plan' | 'upgrade' | 'invoices';

Key Data Shown:

  • Current plan details (name, price, billing cycle)
  • Upgrade options (pricing table with all plans)
  • Credit balance (via getCreditBalance())
  • Billing history (invoices, payments)
  • Payment methods management

Usage Dropdown (/account/usage)

Page Route Primary Purpose Current Data Displayed
Limits & Usage /account/usage Track plan limits Hard limits + monthly limits with progress bars
Credit History /account/usage/credits View credit transactions Transaction log (purchases, deductions, adjustments)
Activity /account/usage/activity Monitor API operations API call statistics by operation type

File: frontend/src/pages/account/UsageAnalyticsPage.tsx (266 lines)

Tab Structure:

type TabType = 'limits' | 'activity' | 'api';

Key Data Shown:

  • Usage summary (hard limits: sites, users, keywords, clusters)
  • Monthly limits (content ideas, words, images)
  • Credit balance and monthly usage
  • Transaction history
  • API activity by operation type

1.2 Backend Models

Account Model (backend/igny8_core/auth/models.py)

Credits Field:

credits = models.IntegerField(default=0, validators=[MinValueValidator(0)])

Monthly Usage Tracking Fields:

usage_content_ideas = models.IntegerField(default=0)        # Monthly limit tracking
usage_content_words = models.IntegerField(default=0)        # Monthly limit tracking
usage_images_basic = models.IntegerField(default=0)         # Monthly limit tracking
usage_images_premium = models.IntegerField(default=0)       # Monthly limit tracking
usage_image_prompts = models.IntegerField(default=0)        # Monthly limit tracking
usage_period_start = models.DateTimeField(null=True)        # Billing period tracking
usage_period_end = models.DateTimeField(null=True)          # Billing period tracking

Plan Model

Hard Limits (Never Reset):

max_sites = models.IntegerField(default=1)           # Sites allowed per account
max_users = models.IntegerField(default=1)           # Team members allowed
max_keywords = models.IntegerField(default=1000)     # Total keywords allowed
max_clusters = models.IntegerField(default=100)      # Total clusters allowed

Monthly Limits (Reset on Billing Cycle):

max_content_ideas = models.IntegerField(default=300)      # Ideas per month
max_content_words = models.IntegerField(default=100000)   # Words per month
max_images_basic = models.IntegerField(default=300)       # Basic images per month
max_images_premium = models.IntegerField(default=60)      # Premium images per month
max_image_prompts = models.IntegerField(default=300)      # Image prompts per month

Credits:

included_credits = models.IntegerField(default=0)        # Monthly credit allocation
extra_credit_price = models.DecimalField(default=0.01)   # Price per additional credit

2. Data Flow Analysis

2.1 Credit Deduction Flow

┌─────────────────────────────────────────────────────────────────┐
│                   CREDIT DEDUCTION WORKFLOW                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. User triggers AI operation (e.g., generate content)         │
│                        ↓                                         │
│  2. Backend service calls CreditService                          │
│                        ↓                                         │
│  3. Check balance: CreditService.check_credits(account, credits)│
│                        ↓                                         │
│  4. Execute AI operation (OpenAI/Runware/etc.)                  │
│                        ↓                                         │
│  5. AI returns tokens used (input + output)                     │
│                        ↓                                         │
│  6. Calculate credits: CreditService.calculate_credits_from_tokens()│
│     • Lookup AIModelConfig.tokens_per_credit for model          │
│     • credits = ceil(total_tokens / tokens_per_credit)          │
│                        ↓                                         │
│  7. Deduct credits:                                             │
│     • Create CreditTransaction (amount=-credits)                │
│     • Create CreditUsageLog (operation details)                 │
│     • Update Account.credits                                    │
│                        ↓                                         │
│  8. Return result to user                                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Key Services:

  • CreditService.check_credits() - Pre-flight balance check
  • CreditService.calculate_credits_from_tokens() - Token-based calculation
  • CreditService.deduct_credits_for_operation() - Deduct and log

2.2 Limit Enforcement Flow

┌─────────────────────────────────────────────────────────────────┐
│                   LIMIT ENFORCEMENT WORKFLOW                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  HARD LIMITS (Sites, Users, Keywords, Clusters)                 │
│  ───────────────────────────────────────────────                │
│  1. User attempts to create new resource                        │
│                        ↓                                         │
│  2. LimitService.check_hard_limit(account, limit_type, count)   │
│     • Query database for current count                          │
│     • Compare: current_count + new_count <= plan.max_XXX        │
│                        ↓                                         │
│  3. If exceeded: Raise HardLimitExceededError                   │
│     If OK: Allow creation                                       │
│                                                                  │
│  MONTHLY LIMITS (Ideas, Words, Images, Prompts)                 │
│  ─────────────────────────────────────────────                  │
│  1. User attempts AI operation                                  │
│                        ↓                                         │
│  2. LimitService.check_monthly_limit(account, limit_type, amount)│
│     • Read from Account.usage_XXX field                         │
│     • Compare: current_usage + amount <= plan.max_XXX           │
│                        ↓                                         │
│  3. If exceeded: Raise MonthlyLimitExceededError                │
│     If OK: Proceed with operation                               │
│                        ↓                                         │
│  4. After operation: LimitService.increment_usage()             │
│     • Update Account.usage_XXX field                            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Key Services:

  • LimitService.check_hard_limit() - Check persistent limits
  • LimitService.check_monthly_limit() - Check monthly allowances
  • LimitService.increment_usage() - Update monthly usage counters
  • LimitService.get_usage_summary() - Get all limits and usage

3. Current Limits Configuration

3.1 Items That ARE Limit-Based (User-Requested)

Item Type Plan Field Enforcement Location Status
Sites Hard Limit max_sites Account creation, Site model Enforced
Team Users Hard Limit max_users User invite, SiteUserAccess model Enforced
Keywords Hard Limit max_keywords Keyword import/creation ⚠️ PARTIAL (see below)

3.2 Items That Should Have Limits (Future)

Item Type Plan Field Status Notes
Ahrefs Queries Monthly Limit max_ahrefs_queries (new) Not Implemented Future feature for keyword research

3.3 Items NOT Limit-Based (Credit-Based)

Item Enforcement Notes
AI Content Generation Credits Token-based calculation via AIModelConfig
AI Image Generation Credits Fixed credits per image (1, 5, or 15)
AI Clustering Credits Token-based calculation
AI Idea Generation Credits Token-based calculation
Internal Linking Credits 8 credits per content piece
SEO Optimization Credits 1 credit per 200 words

Important: Monthly limits exist for these (max_content_ideas, max_content_words, max_images_basic, etc.) but they serve as soft limits for cost control, not hard enforcement. Users are primarily constrained by credits.


4. Data Duplication Issues

4.1 Problem: Overlapping Data Across Pages

Current Situation:

Data Type Plans & Billing Usage Analytics Duplication Level
Credit Balance Shown in Current Plan tab Shown in all tabs (top cards) 🔴 HIGH
Monthly Usage Credits used this month Credits used this month 🔴 HIGH
Plan Limits Shown in plan details Full limits panel with progress bars 🟡 MEDIUM
Credit Transactions Billing History tab Credit History tab 🔴 HIGH

Example of Duplication:

Plans & Billing - Current Plan Tab:

<Card>
  <h3>Current Plan: {planName}</h3>
  <div>Credits: {creditBalance.credits}</div>
  <div>Used This Month: {creditBalance.credits_used_this_month}</div>
  <div>Monthly Allowance: {creditBalance.plan_credits_per_month}</div>
</Card>

Usage Analytics - Limits Tab:

<Card>
  <div>Credits Left: {creditBalance.credits}</div>
  <div>Credits Used This Month: {creditBalance.credits_used_this_month}</div>
  <div>Your Monthly Limit: {creditBalance.plan_credits_per_month}</div>
</Card>

4.2 Problem: Confusing User Journey

Current User Experience Issues:

  1. Credit Balance appears in BOTH dropdowns

    • Plans & Billing → Current Plan → Shows credit balance
    • Usage → All tabs → Shows credit balance in top cards
  2. Transaction History appears in BOTH dropdowns

    • Plans & Billing → History → Shows invoices and payments
    • Usage → Credit History → Shows credit transactions
  3. Plan Limits appears in BOTH dropdowns

    • Plans & Billing → Current Plan → Shows plan features and limits
    • Usage → Limits & Usage → Shows detailed progress bars for all limits

User Confusion:

  • "Where do I check my credits?" → Two places
  • "Where do I see my usage?" → Two places
  • "Where do I see my transaction history?" → Two places

5. Enforcement Analysis

5.1 Well-Enforced Limits

Sites (max_sites)

Enforcement Location: backend/igny8_core/auth/models.py (Site model)

# When creating a new site
LimitService.check_hard_limit(account, 'sites', 1)

Status: FULLY ENFORCED
Works: Users cannot create more sites than their plan allows

Team Users (max_users)

Enforcement Location: User invite flow

# When inviting a new user
LimitService.check_hard_limit(account, 'users', 1)

Status: FULLY ENFORCED
Works: Users cannot invite more team members than their plan allows


5.2 Partially Enforced Limits ⚠️

Keywords (max_keywords)

Current State:

  • Plan field exists: Plan.max_keywords
  • LimitService has mapping:
    'keywords': {
        'model': 'planner.Keywords',
        'plan_field': 'max_keywords',
        'display_name': 'Keywords',
        'filter_field': 'account',
    }
    
  • ⚠️ ENFORCEMENT INCONSISTENT

Evidence from Code Search:

Found in backend/igny8_core/business/planning/models.py:

from igny8_core.business.billing.services.limit_service import LimitService

# Only enforced in SOME locations:
LimitService.increment_usage(
    account=self.account,
    limit_type='keywords',
    amount=1
)

Problem:

  • Keyword import from SeedKeywords may not check limit
  • Keyword creation via API may not check limit consistently
  • Manual keyword creation may bypass checks

Impact:

  • Users may exceed keyword limits without errors
  • Limits shown in UI but not enforced at all entry points

Recommended Fix:

  1. Add pre-create check to all keyword creation flows:
    # Before creating keyword
    LimitService.check_hard_limit(account, 'keywords', 1)
    
  2. Locations to add enforcement:
    • addSeedKeywordsToWorkflow API endpoint
    • Keyword bulk import
    • Manual keyword creation form
    • CSV import

5.3 Missing Future Limits

Ahrefs Keyword Queries (Planned)

User Requirement:

"in future soon we will have option for user to query and research keywords from ahrefs, which will also have monthly limit"

Current State:

  • No plan field defined
  • No LimitService mapping
  • No enforcement

Current Keywords Situation:

  • Existing: Pre-searched keywords from IGNY8's own database (SeedKeywords)
  • Limited by: max_keywords (how many they can add to their workspace)
  • Future: Ahrefs API queries (need new monthly limit)

Recommended Implementation:

  1. Add Plan Field:

    # backend/igny8_core/auth/models.py - Plan model
    max_ahrefs_queries = models.IntegerField(
        default=50,
        validators=[MinValueValidator(0)],
        help_text="Maximum Ahrefs keyword queries per month"
    )
    
  2. Add Account Usage Field:

    # backend/igny8_core/auth/models.py - Account model
    usage_ahrefs_queries = models.IntegerField(
        default=0,
        validators=[MinValueValidator(0)],
        help_text="Ahrefs queries used this month"
    )
    
  3. Add LimitService Mapping:

    # backend/igny8_core/business/billing/services/limit_service.py
    MONTHLY_LIMIT_MAPPINGS = {
        # ... existing mappings ...
        'ahrefs_queries': {
            'plan_field': 'max_ahrefs_queries',
            'usage_field': 'usage_ahrefs_queries',
            'display_name': 'Ahrefs Keyword Queries',
        },
    }
    
  4. Enforce Before Query:

    # In Ahrefs query service
    def query_ahrefs_keywords(account, query):
        # Check limit
        LimitService.check_monthly_limit(account, 'ahrefs_queries', 1)
    
        # Execute query
        results = ahrefs_api.search_keywords(query)
    
        # Increment usage
        LimitService.increment_usage(account, 'ahrefs_queries', 1)
    
        return results
    

6. IMPLEMENTATION PLAN

6.1 Phase 1: Backend Cleanup (Week 1)

1.1 Remove Unused Monthly Limits from Database

File: backend/igny8_core/auth/models.py

Remove from Plan Model:

# REMOVE THESE FIELDS:
max_content_ideas = models.IntegerField(...)      # ❌ DELETE
max_content_words = models.IntegerField(...)      # ❌ DELETE
max_images_basic = models.IntegerField(...)       # ❌ DELETE
max_images_premium = models.IntegerField(...)     # ❌ DELETE
max_image_prompts = models.IntegerField(...)      # ❌ DELETE
max_clusters = models.IntegerField(...)           # ❌ DELETE (or merge with keywords)

Remove from Account Model:

# REMOVE THESE FIELDS:
usage_content_ideas = models.IntegerField(...)    # ❌ DELETE
usage_content_words = models.IntegerField(...)    # ❌ DELETE
usage_images_basic = models.IntegerField(...)     # ❌ DELETE
usage_images_premium = models.IntegerField(...)   # ❌ DELETE
usage_image_prompts = models.IntegerField(...)    # ❌ DELETE

KEEP ONLY:

# Plan Model - KEEP THESE:
max_sites = models.IntegerField(default=1)        # ✅ KEEP
max_users = models.IntegerField(default=1)        # ✅ KEEP
max_keywords = models.IntegerField(default=1000)  # ✅ KEEP
max_ahrefs_queries = models.IntegerField(default=50)  # 🆕 ADD

# Account Model - KEEP THESE:
credits = models.IntegerField(default=0)          # ✅ KEEP
usage_ahrefs_queries = models.IntegerField(default=0)  # 🆕 ADD
usage_period_start = models.DateTimeField(...)    # ✅ KEEP
usage_period_end = models.DateTimeField(...)      # ✅ KEEP

Migration Script:

# Create migration: 0XXX_remove_unused_limits.py
operations = [
    # Remove from Plan
    migrations.RemoveField(model_name='plan', name='max_content_ideas'),
    migrations.RemoveField(model_name='plan', name='max_content_words'),
    migrations.RemoveField(model_name='plan', name='max_images_basic'),
    migrations.RemoveField(model_name='plan', name='max_images_premium'),
    migrations.RemoveField(model_name='plan', name='max_image_prompts'),
    migrations.RemoveField(model_name='plan', name='max_clusters'),
    
    # Remove from Account
    migrations.RemoveField(model_name='account', name='usage_content_ideas'),
    migrations.RemoveField(model_name='account', name='usage_content_words'),
    migrations.RemoveField(model_name='account', name='usage_images_basic'),
    migrations.RemoveField(model_name='account', name='usage_images_premium'),
    migrations.RemoveField(model_name='account', name='usage_image_prompts'),
    
    # Add Ahrefs fields
    migrations.AddField(
        model_name='plan',
        name='max_ahrefs_queries',
        field=models.IntegerField(default=50, validators=[MinValueValidator(0)])
    ),
    migrations.AddField(
        model_name='account',
        name='usage_ahrefs_queries',
        field=models.IntegerField(default=0, validators=[MinValueValidator(0)])
    ),
]

1.2 Update LimitService

File: backend/igny8_core/business/billing/services/limit_service.py

Remove from MONTHLY_LIMIT_MAPPINGS:

# DELETE THESE:
'content_ideas': {...},       # ❌ DELETE
'content_words': {...},       # ❌ DELETE
'images_basic': {...},        # ❌ DELETE
'images_premium': {...},      # ❌ DELETE
'image_prompts': {...},       # ❌ DELETE

Keep/Add:

MONTHLY_LIMIT_MAPPINGS = {
    # ONLY THIS ONE:
    'ahrefs_queries': {
        'plan_field': 'max_ahrefs_queries',
        'usage_field': 'usage_ahrefs_queries',
        'display_name': 'Ahrefs Keyword Research',
    },
}

# Remove from HARD_LIMIT_MAPPINGS:
'clusters': {...},  # ❌ DELETE (or keep if needed)

1.3 Update Serializers

File: backend/igny8_core/auth/serializers.py

class PlanSerializer(serializers.ModelSerializer):
    class Meta:
        fields = [
            'id', 'name', 'slug', 'price', 'billing_cycle',
            # KEEP ONLY THESE LIMITS:
            'max_sites',
            'max_users', 
            'max_keywords',
            'max_ahrefs_queries',  # NEW
            # CREDITS:
            'included_credits',
            'extra_credit_price',
            # REMOVE ALL OTHER max_* fields
        ]

1.4 Remove Limit Checks from AI Operations

Search and Remove:

# Find all places checking monthly limits
grep -r "check_monthly_limit.*content_ideas" backend/
grep -r "check_monthly_limit.*content_words" backend/
grep -r "check_monthly_limit.*images" backend/
grep -r "increment_usage.*content_ideas" backend/

Replace with credit checks only:

# OLD (REMOVE):
LimitService.check_monthly_limit(account, 'content_ideas', 1)
LimitService.increment_usage(account, 'content_ideas', 1)

# NEW (KEEP):
CreditService.check_credits(account, required_credits)
# Then after operation:
CreditService.deduct_credits_for_operation(...)

1.5 Add Credit Balance Check to Automation

File: backend/igny8_core/business/automation/services.py (or similar)

from igny8_core.business.billing.services.credit_service import CreditService
from igny8_core.business.billing.exceptions import InsufficientCreditsError

def run_automation(automation_config, account):
    """Run automation with credit pre-check"""
    
    # Estimate credits needed
    estimated_credits = estimate_automation_cost(automation_config)
    
    # Check balance BEFORE starting
    try:
        CreditService.check_credits(account, estimated_credits)
    except InsufficientCreditsError:
        raise AutomationError(
            f"Insufficient credits. Need {estimated_credits}, have {account.credits}. "
            f"Please add credits to continue."
        )
    
    # Run automation stages
    for stage in automation_config.stages:
        # Each stage checks/deducts its own credits
        run_stage(stage, account)

1.6 Enforce Keywords Limit Properly

Files to Update:

  • backend/igny8_core/business/planning/views.py (keyword creation)
  • backend/igny8_core/api/endpoints/seed_keywords.py (SeedKeyword import)
from igny8_core.business.billing.services.limit_service import LimitService, HardLimitExceededError

# Before creating keywords:
try:
    LimitService.check_hard_limit(account, 'keywords', num_keywords_to_add)
except HardLimitExceededError as e:
    return Response({
        'error': 'keyword_limit_exceeded',
        'message': f'You have reached your keyword limit ({plan.max_keywords}). Upgrade your plan to add more.',
        'current_count': current_count,
        'limit': plan.max_keywords,
        'upgrade_url': '/account/plans/upgrade'
    }, status=402)

# If check passes, create keywords
keywords = Keywords.objects.bulk_create([...])

6.2 Phase 2: Frontend Cleanup (Week 2)

2.1 Remove Duplicate Data from Plans & Billing Page

File: frontend/src/pages/account/PlansAndBillingPage.tsx

Current Plan Tab - Remove:

// ❌ DELETE: Credit balance display (move to Usage only)
// ❌ DELETE: Usage breakdown charts
// ❌ DELETE: Limit progress bars
// ❌ DELETE: "Credits used this month" details

Current Plan Tab - Keep:

// ✅ KEEP: Plan name, price, billing cycle
// ✅ KEEP: Renewal date
// ✅ KEEP: Brief summary text: "50 Pages/Articles • 2 Sites • 2 Users"
// ✅ KEEP: Upgrade CTA button

2.2 Redesign Usage Page with Multi-Dimensional Insights

File: frontend/src/pages/account/UsageAnalyticsPage.tsx

New Structure:

/**
 * Usage & Insights Page - Complete Redesign
 * 
 * Tab 1: Overview (NEW)
 *   - Credit balance cards
 *   - Quick stats (sites, users, keywords count)
 *   - Period selector (7, 30, 90 days)
 *   - Top metrics
 * 
 * Tab 2: Your Limits
 *   - Sites, Users, Keywords progress bars
 *   - Ahrefs queries remaining
 * 
 * Tab 3: Credit Insights (NEW)
 *   - Credits by Site
 *   - Credits by Content Type (articles, images, etc.)
 *   - Credits by Image Quality (basic, quality, premium)
 *   - Credits by Automation
 *   - Timeline chart (7/30/90 days)
 * 
 * Tab 4: Activity Log
 *   - Detailed transaction history
 */

type TabType = 'overview' | 'limits' | 'insights' | 'activity';

Tab 1: Overview (NEW)

<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
  {/* Credit Balance Card */}
  <Card>
    <ZapIcon />
    <div>Credits Available</div>
    <div className="text-3xl">{credits.toLocaleString()}</div>
  </Card>

  {/* Sites Used Card */}
  <Card>
    <GlobeIcon />
    <div>Sites</div>
    <div className="text-3xl">{sitesCount} / {maxSites}</div>
  </Card>

  {/* Team Members Card */}
  <Card>
    <UsersIcon />
    <div>Team Members</div>
    <div className="text-3xl">{usersCount} / {maxUsers}</div>
  </Card>

  {/* Keywords Card */}
  <Card>
    <TagIcon />
    <div>Keywords</div>
    <div className="text-3xl">{keywordsCount.toLocaleString()} / {maxKeywords.toLocaleString()}</div>
  </Card>
</div>

{/* Period Selector */}
<div className="flex gap-2">
  <Button onClick={() => setPeriod(7)}>Last 7 Days</Button>
  <Button onClick={() => setPeriod(30)}>Last 30 Days</Button>
  <Button onClick={() => setPeriod(90)}>Last 90 Days</Button>
</div>

Tab 3: Credit Insights (NEW)

{/* Credits by Site */}
<Card>
  <h3>Credits by Site</h3>
  {sites.map(site => (
    <div key={site.id}>
      <div>{site.name}</div>
      <ProgressBar value={site.credits_used} max={totalCredits} />
      <div>{site.credits_used.toLocaleString()} credits</div>
    </div>
  ))}
</Card>

{/* Credits by Content Type */}
<Card>
  <h3>Credits by Content Type</h3>
  <DonutChart data={[
    { label: 'Articles', value: contentCredits },
    { label: 'Images', value: imageCredits },
    { label: 'Ideas & Planning', value: ideaCredits },
    { label: 'Other', value: otherCredits },
  ]} />
</Card>

{/* Credits by Image Quality */}
<Card>
  <h3>Image Generation by Quality</h3>
  {imageQualityBreakdown.map(tier => (
    <div key={tier.quality}>
      <div>{tier.quality} Quality</div>
      <div>{tier.count} images  {tier.credits_used} credits</div>
      <div className="text-xs">({tier.credits_per_image} credits each)</div>
    </div>
  ))}
</Card>

{/* Credits by Automation */}
<Card>
  <h3>Automation Runs</h3>
  {automations.map(auto => (
    <div key={auto.id}>
      <div>{auto.name}</div>
      <div>{auto.runs_count} runs  {auto.credits_used} credits</div>
    </div>
  ))}
</Card>

{/* Timeline Chart */}
<Card>
  <h3>Credit Usage Over Time</h3>
  <LineChart 
    data={usageTimeline}
    xAxis="date"
    yAxis="credits_used"
  />
</Card>

2.3 Update API Interfaces

File: frontend/src/services/billing.api.ts

// REMOVE from Plan interface:
export interface Plan {
  id: number;
  name: string;
  price: number;
  
  // KEEP ONLY THESE LIMITS:
  max_sites?: number;
  max_users?: number;
  max_keywords?: number;
  max_ahrefs_queries?: number;  // NEW
  
  included_credits?: number;
  
  // REMOVE THESE:
  // max_content_ideas?: number;     // ❌ DELETE
  // max_content_words?: number;     // ❌ DELETE
  // max_images_basic?: number;      // ❌ DELETE
  // max_images_premium?: number;    // ❌ DELETE
  // max_image_prompts?: number;     // ❌ DELETE
  // max_clusters?: number;          // ❌ DELETE
}

// UPDATE UsageSummary interface:
export interface UsageSummary {
  account_id: number;
  plan_name: string;
  period_start: string;
  period_end: string;
  
  hard_limits: {
    sites?: LimitUsage;
    users?: LimitUsage;
    keywords?: LimitUsage;
    // clusters?: LimitUsage;  // ❌ DELETE
  };
  
  monthly_limits: {
    ahrefs_queries?: LimitUsage;  // ONLY THIS ONE
    // content_ideas?: LimitUsage;     // ❌ DELETE
    // content_words?: LimitUsage;     // ❌ DELETE
    // images_basic?: LimitUsage;      // ❌ DELETE
    // images_premium?: LimitUsage;    // ❌ DELETE
    // image_prompts?: LimitUsage;     // ❌ DELETE
  };
}

// NEW: Multi-dimensional insights
export interface CreditInsights {
  period_days: number;
  total_credits_used: number;
  
  by_site: Array<{
    site_id: number;
    site_name: string;
    credits_used: number;
    percentage: number;
  }>;
  
  by_operation: Array<{
    operation_type: string;
    display_name: string;
    credits_used: number;
    count: number;
    percentage: number;
  }>;
  
  by_image_quality: Array<{
    quality_tier: 'basic' | 'quality' | 'premium';
    model_name: string;
    credits_per_image: number;
    images_generated: number;
    total_credits: number;
  }>;
  
  by_automation: Array<{
    automation_id: number;
    automation_name: string;
    runs_count: number;
    credits_used: number;
  }>;
  
  timeline: Array<{
    date: string;
    credits_used: number;
  }>;
}

// NEW API function
export async function getCreditInsights(days: number = 30): Promise<CreditInsights> {
  return fetchAPI(`/v1/billing/credits/insights/?days=${days}`);
}

2.4 Update UsageLimitsPanel Component

File: frontend/src/components/billing/UsageLimitsPanel.tsx

// REMOVE monthly limit configs for deleted fields
const monthlyLimitConfig = {
  // ONLY THIS ONE:
  ahrefs_queries: { 
    icon: <SearchIcon className="w-5 h-5" />, 
    color: 'purple' as const,
    description: 'Keyword research queries per month'
  },
  
  // DELETE THESE:
  // content_ideas: {...},    // ❌ DELETE
  // content_words: {...},    // ❌ DELETE
  // images_basic: {...},     // ❌ DELETE
  // images_premium: {...},   // ❌ DELETE
  // image_prompts: {...},    // ❌ DELETE
};

// KEEP hard limits
const hardLimitConfig = {
  sites: { icon: <GlobeIcon />, color: 'success' },
  users: { icon: <UsersIcon />, color: 'info' },
  keywords: { icon: <TagIcon />, color: 'purple' },
  // clusters: {...},  // ❌ DELETE if not needed
};

2.5 Use User-Friendly Terminology

Global Search & Replace:

Technical Term User-Friendly Term
"API Activity" "Activity Log" or "Recent Actions"
"API Operations" "Actions" or "Activities"
"operation_type" "Action Type"
"Monthly Limits" "Monthly Allowances"
"Hard Limits" "Plan Limits"
"Credits Used" "Credits Spent"
"Balance" "Credits Available"

6.3 Phase 3: Keyword Research Implementation (Week 3)

3.1 Backend: Ahrefs Service

Create: backend/igny8_core/business/keywords/ahrefs_service.py

from igny8_core.business.billing.services.limit_service import LimitService, MonthlyLimitExceededError
import requests

class AhrefsService:
    """Service for Ahrefs keyword research with monthly limit enforcement"""
    
    @staticmethod
    def query_keywords(account, query_params):
        """
        Query Ahrefs for keywords with limit enforcement.
        
        Args:
            account: Account instance
            query_params: dict with search parameters
            
        Returns:
            dict: Ahrefs API response with keyword data
            
        Raises:
            MonthlyLimitExceededError: If monthly Ahrefs query limit exceeded
        """
        # Check monthly limit BEFORE querying
        try:
            LimitService.check_monthly_limit(account, 'ahrefs_queries', 1)
        except MonthlyLimitExceededError as e:
            raise AhrefsQueryLimitExceeded(
                f"You've used all your Ahrefs queries this month. "
                f"Limit: {account.plan.max_ahrefs_queries}. "
                f"Resets on {account.usage_period_end.strftime('%B %d, %Y')}. "
                f"Upgrade your plan for more queries."
            )
        
        # Make Ahrefs API call
        try:
            response = requests.post(
                'https://api.ahrefs.com/v3/site-explorer/keywords',
                headers={'Authorization': f'Bearer {settings.AHREFS_API_KEY}'},
                json=query_params
            )
            response.raise_for_status()
            results = response.json()
            
            # Increment usage counter
            LimitService.increment_usage(account, 'ahrefs_queries', 1)
            
            return results
            
        except requests.RequestException as e:
            logger.error(f"Ahrefs API error: {e}")
            raise AhrefsAPIError("Failed to fetch keyword data from Ahrefs")

3.2 Frontend: Keyword Research Page

Create: frontend/src/pages/Planner/KeywordResearchPage.tsx

/**
 * Keyword Research Page - Two Options
 * 1. Browse IGNY8 pre-researched keywords (free)
 * 2. Research with Ahrefs (monthly limit)
 */

type ResearchTab = 'browse' | 'ahrefs';

export default function KeywordResearchPage() {
  const [activeTab, setActiveTab] = useState<ResearchTab>('browse');
  const [ahrefsLimit, setAhrefsLimit] = useState({ used: 0, limit: 50 });

  return (
    <>
      <PageHeader 
        title="Keyword Research" 
        description="Find high-opportunity keywords for your content strategy"
      />

      {/* Tab Selector */}
      <div className="flex gap-4 mb-6">
        <Button
          variant={activeTab === 'browse' ? 'primary' : 'outline'}
          onClick={() => setActiveTab('browse')}
        >
          <DatabaseIcon className="w-4 h-4" />
          Browse Pre-Researched Keywords
        </Button>
        
        <Button
          variant={activeTab === 'ahrefs' ? 'primary' : 'outline'}
          onClick={() => setActiveTab('ahrefs')}
        >
          <SearchIcon className="w-4 h-4" />
          Research with Ahrefs
          <Badge variant="soft" tone="purple">
            {ahrefsLimit.limit - ahrefsLimit.used} remaining
          </Badge>
        </Button>
      </div>

      {/* Tab Content */}
      {activeTab === 'browse' && (
        <BrowseKeywordsPanel />
      )}

      {activeTab === 'ahrefs' && (
        <AhrefsResearchPanel 
          limit={ahrefsLimit}
          onQuerySuccess={() => {
            setAhrefsLimit(prev => ({ 
              ...prev, 
              used: prev.used + 1 
            }));
          }}
        />
      )}
    </>
  );
}

Browse Keywords Panel (Existing SeedKeywords):

function BrowseKeywordsPanel() {
  return (
    <Card>
      <h3>Pre-Researched High-Opportunity Keywords</h3>
      <p>Browse thousands of analyzed keywords, ready to use.</p>
      
      {/* Filters */}
      <div className="flex gap-4">
        <Select label="Industry" options={industries} />
        <Select label="Sector" options={sectors} />
        <Select label="Country" options={countries} />
        <Input placeholder="Search keywords..." />
      </div>

      {/* Results Table */}
      <Table>
        <thead>
          <tr>
            <th>Keyword</th>
            <th>Search Volume</th>
            <th>Difficulty</th>
            <th>Opportunity Score</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          {keywords.map(kw => (
            <tr key={kw.id}>
              <td>{kw.keyword}</td>
              <td>{kw.search_volume}</td>
              <td><Badge>{kw.difficulty}</Badge></td>
              <td>{kw.opportunity_score}/100</td>
              <td>
                <Button size="sm" onClick={() => addToWorkspace(kw)}>
                  Add to Workspace
                </Button>
              </td>
            </tr>
          ))}
        </tbody>
      </Table>
    </Card>
  );
}

Ahrefs Research Panel (NEW):

function AhrefsResearchPanel({ limit, onQuerySuccess }) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);

  const remaining = limit.limit - limit.used;
  const canQuery = remaining > 0;

  const handleSearch = async () => {
    if (!canQuery) {
      toast.error('You have used all your Ahrefs queries this month.');
      return;
    }

    try {
      setLoading(true);
      const data = await fetchAPI('/v1/keywords/ahrefs/search/', {
        method: 'POST',
        body: JSON.stringify({ query })
      });
      setResults(data.keywords);
      onQuerySuccess();
      toast.success('Keywords fetched from Ahrefs!');
    } catch (error) {
      toast.error(error.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Card>
      <div className="flex items-center justify-between mb-4">
        <h3>Live Keyword Research with Ahrefs</h3>
        <Badge variant="soft" tone={remaining > 10 ? 'success' : 'warning'}>
          {remaining} / {limit.limit} queries remaining
        </Badge>
      </div>

      {!canQuery && (
        <Alert tone="warning">
          <AlertCircleIcon />
          <div>
            <div>No queries remaining</div>
            <div>Your limit resets on {resetDate}. Upgrade for more queries.</div>
            <Button size="sm" onClick={() => navigate('/account/plans/upgrade')}>
              Upgrade Plan
            </Button>
          </div>
        </Alert>
      )}

      {canQuery && (
        <>
          <div className="flex gap-4 mb-4">
            <Input
              placeholder="Enter seed keyword (e.g., 'digital marketing')"
              value={query}
              onChange={e => setQuery(e.target.value)}
              onKeyPress={e => e.key === 'Enter' && handleSearch()}
            />
            <Button onClick={handleSearch} loading={loading}>
              <SearchIcon /> Research Keywords
            </Button>
          </div>

          {/* Results */}
          {results.length > 0 && (
            <Table>
              <thead>
                <tr>
                  <th>Keyword</th>
                  <th>Volume</th>
                  <th>Difficulty</th>
                  <th>CPC</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {results.map((kw, idx) => (
                  <tr key={idx}>
                    <td>{kw.keyword}</td>
                    <td>{kw.volume.toLocaleString()}</td>
                    <td><Badge>{kw.difficulty}</Badge></td>
                    <td>${kw.cpc}</td>
                    <td>
                      <Button size="sm" onClick={() => addToWorkspace(kw)}>
                        Add to Workspace
                      </Button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </Table>
          )}
        </>
      )}
    </Card>
  );
}

6.4 Phase 4: Validation & Enforcement (Week 4)

4.1 Credit Balance Validation Checklist

File: Search all AI operation services

# REQUIRED PATTERN for all AI operations:

# 1. BEFORE operation - estimate and check
estimated_credits = estimate_credits_needed(operation_params)
CreditService.check_credits(account, estimated_credits)

# 2. EXECUTE operation
result = ai_service.execute(operation_params)

# 3. AFTER operation - calculate actual and deduct
actual_tokens = result.usage.total_tokens
actual_credits = CreditService.calculate_credits_from_tokens(
    operation_type='content_generation',
    tokens_input=result.usage.prompt_tokens,
    tokens_output=result.usage.completion_tokens
)
CreditService.deduct_credits_for_operation(
    account=account,
    operation_type='content_generation',
    amount=actual_credits,
    model=result.model,
    tokens_in=result.usage.prompt_tokens,
    tokens_out=result.usage.completion_tokens,
    metadata={'content_id': content.id}
)

Locations to Verify:

  • Content generation service
  • Image generation service
  • Idea generation service
  • Clustering service
  • Internal linking service
  • Optimization service
  • Automation runner (PRE-CHECK before starting!)

4.2 Automation Credit Pre-Check

File: backend/igny8_core/business/automation/services.py

def estimate_automation_cost(automation_config):
    """Estimate credits needed for full automation run"""
    estimated = 0
    
    for stage in automation_config.stages:
        if stage.type == 'clustering':
            estimated += 10  # Base estimate
        elif stage.type == 'idea_generation':
            estimated += stage.num_ideas * 2
        elif stage.type == 'content_generation':
            estimated += stage.num_articles * 50  # Rough estimate
        elif stage.type == 'image_generation':
            estimated += stage.num_images * stage.credits_per_image
        elif stage.type == 'linking':
            estimated += stage.num_articles * 8
    
    # Add 20% buffer
    return int(estimated * 1.2)

def run_automation(automation_config, account, trigger='manual'):
    """
    Run automation with upfront credit validation.
    
    Raises:
        InsufficientCreditsError: If not enough credits available
    """
    # Estimate total cost
    estimated_credits = estimate_automation_cost(automation_config)
    
    # CHECK CREDITS BEFORE STARTING
    try:
        CreditService.check_credits(account, estimated_credits)
    except InsufficientCreditsError:
        # Log failed attempt
        AutomationRun.objects.create(
            automation=automation_config,
            account=account,
            status='failed',
            error_message=f'Insufficient credits. Need {estimated_credits}, have {account.credits}.',
            trigger=trigger
        )
        raise
    
    # Create run record
    run = AutomationRun.objects.create(
        automation=automation_config,
        account=account,
        status='running',
        estimated_credits=estimated_credits,
        trigger=trigger
    )
    
    try:
        # Execute stages (each deducts credits)
        for stage in automation_config.stages:
            execute_stage(stage, account, run)
        
        # Mark complete
        run.status = 'completed'
        run.actual_credits = run.credits_used
        run.save()
        
    except Exception as e:
        run.status = 'failed'
        run.error_message = str(e)
        run.save()
        raise

4.3 Frontend Credit Check Before Actions

Component: Add to all credit-consuming actions

// Hook for credit validation
function useCreditCheck() {
  const { user } = useAuthStore();
  
  const checkCredits = useCallback(async (estimatedCredits: number) => {
    if (user.account.credits < estimatedCredits) {
      const confirmed = await showConfirmDialog({
        title: 'Insufficient Credits',
        message: `This action requires approximately ${estimatedCredits} credits, but you only have ${user.account.credits} available.`,
        confirmText: 'Add Credits',
        cancelText: 'Cancel',
      });
      
      if (confirmed) {
        navigate('/account/plans');
      }
      
      return false;
    }
    
    return true;
  }, [user]);
  
  return { checkCredits };
}

// Usage example:
function GenerateContentButton({ taskId }) {
  const { checkCredits } = useCreditCheck();
  
  const handleGenerate = async () => {
    // Estimate credits (rough estimate: 50 for article)
    const canProceed = await checkCredits(50);
    if (!canProceed) return;
    
    // Proceed with generation
    await generateContent(taskId);
  };
  
  return <Button onClick={handleGenerate}>Generate Content</Button>;
}

6.5 Phase 5: Testing & Validation (Week 5)

Test Cases

Limit Enforcement:

  • Try to create site when at max_sites → Should fail with upgrade prompt
  • Try to invite user when at max_users → Should fail with upgrade prompt
  • Try to add 100 keywords when 50 slots remain and limit is 1000 → Should succeed
  • Try to add 100 keywords when 30 slots remain → Should fail
  • Try to query Ahrefs when at monthly limit → Should fail with reset date
  • Query Ahrefs successfully → Counter increments
  • Monthly reset → Ahrefs counter resets to 0

Credit Validation:

  • Try to generate content with 0 credits → Should fail immediately
  • Try to run automation with insufficient credits → Should fail before starting
  • Generate content → Credits deducted correctly based on tokens
  • Generate image (basic) → 1 credit deducted
  • Generate image (quality) → 5 credits deducted
  • Generate image (premium) → 15 credits deducted
  • Run automation → All stages check/deduct credits properly

Page Reorganization:

  • Plans & Billing → Current Plan: NO credit usage details
  • Plans & Billing → Current Plan: Brief summary text only
  • Usage → Overview: Shows credit balance, sites/users/keywords count
  • Usage → Limits: Shows only 4 limits (sites, users, keywords, ahrefs)
  • Usage → Insights: Shows multi-dimensional breakdowns
  • No duplicate data between Plans & Usage

Terminology:

  • No "API" references in user-facing text
  • "Operations" changed to "Actions" or "Activities"
  • User-friendly language throughout

7. Updated Page Reorganization

7.1 Page Reorganization (High Priority) 🔥

Problem: Data duplication creates confusion and maintenance overhead.

Proposed Solution: Clear separation of concerns between Plans & Billing vs Usage.

Plans & Billing → Focus on FINANCIAL aspects

Tab 1: Current Plan

  • Show: Plan name, price, billing cycle, renewal date
  • Show: Brief limits summary (e.g., "50 Pages/Articles, 2 Sites, 2 Users")
  • Remove: Detailed limit progress bars (move to Usage)
  • Remove: Credits used this month breakdown (move to Usage)
  • Action: "Upgrade Plan" button

Tab 2: Upgrade Plan

  • Show: Pricing table with plan comparison
  • Show: Plan features and limits (static, for comparison)
  • Action: Purchase/upgrade plan

Tab 3: Billing History

  • Show: Invoices (with download PDF)
  • Show: Payment methods management
  • Show: Credit package purchases (financial transactions only)

Usage → Focus on CONSUMPTION tracking

Tab 1: Limits & Usage (Keep as is - this is perfect)

  • Show: All hard limits with progress bars (sites, users, keywords, clusters)
  • Show: All monthly limits with progress bars (ideas, words, images)
  • Show: Days until reset for monthly limits
  • Show: Credit balance and monthly usage
  • Action: "Upgrade for more" CTA when approaching limits

Tab 2: Credit History (Keep as is)

  • Show: Credit transaction log (purchases, deductions, adjustments)
  • Show: Operation details (what consumed credits)
  • Filter: By operation type, date range

Tab 3: Activity Log (Keep as is)

  • Show: API operations by type
  • Show: Total operations count
  • Show: Breakdown by operation type

Summary of Changes

Page Current State Proposed Change
Plans & Billing → Current Plan Shows credits, usage, limits Remove detailed usage, keep financial summary only
Plans & Billing → History Invoices and payments Keep as is (financial focus)
Usage → Limits & Usage Detailed limits panel Keep as is (consumption focus)
Usage → Credit History Transaction log Keep as is (consumption focus)

Rationale:

  • Plans & Billing = "What am I paying for?" (financial/subscription management)
  • Usage = "What am I using?" (consumption monitoring)

7.1 Plans & Billing Page (Simplified)

Purpose: Financial management and subscription control

Tab 1: Current Plan

<Card>
  <h2>{plan.name} Plan</h2>
  <div className="text-3xl">${plan.price}/month</div>
  <div>Renews on {renewalDate}</div>
  
  {/* Brief Summary - NO detailed limits */}
  <div className="text-sm text-gray-600">
    Your plan includes: {plan.included_credits} credits per month  
    {plan.max_sites} sites  {plan.max_users} team members
  </div>
  
  {/* Upgrade CTA */}
  <Button onClick={() => navigate('/account/plans/upgrade')}>
    Upgrade Plan
  </Button>
</Card>

{/* ❌ REMOVE: Credit usage charts */}
{/* ❌ REMOVE: Limit progress bars */}
{/* ❌ REMOVE: "Credits used this month" */}

Tab 2: Upgrade Plan (No changes needed)

Tab 3: Billing History (No changes needed)


7.2 Usage Page (Multi-Dimensional Insights)

Purpose: Monitor consumption and optimize usage

Tab 1: Overview (NEW)

{/* Quick Stats Cards */}
<div className="grid grid-cols-4 gap-4">
  <StatCard 
    icon={<ZapIcon />}
    label="Credits Available"
    value={credits.toLocaleString()}
    color="brand"
  />
  <StatCard 
    icon={<GlobeIcon />}
    label="Sites"
    value={`${sitesCount} / ${maxSites}`}
    color="success"
  />
  <StatCard 
    icon={<UsersIcon />}
    label="Team Members"
    value={`${usersCount} / ${maxUsers}`}
    color="info"
  />
  <StatCard 
    icon={<TagIcon />}
    label="Keywords"
    value={`${keywordsCount} / ${maxKeywords}`}
    color="purple"
  />
</div>

{/* Period Selector */}
<ButtonGroup>
  <Button onClick={() => setPeriod(7)}>Last 7 Days</Button>
  <Button onClick={() => setPeriod(30)}>Last 30 Days</Button>
  <Button onClick={() => setPeriod(90)}>Last 90 Days</Button>
</ButtonGroup>

{/* Top Metrics for Selected Period */}
<div className="grid grid-cols-3 gap-4">
  <Card>
    <div>Credits Spent</div>
    <div className="text-2xl">{periodCredits.toLocaleString()}</div>
  </Card>
  <Card>
    <div>Articles Created</div>
    <div className="text-2xl">{periodArticles}</div>
  </Card>
  <Card>
    <div>Images Generated</div>
    <div className="text-2xl">{periodImages}</div>
  </Card>
</div>

Tab 2: Your Limits

{/* Only 4 limits total */}
<LimitCard
  title="Sites"
  icon={<GlobeIcon />}
  current={sitesCount}
  limit={maxSites}
  type="permanent"
/>

<LimitCard
  title="Team Members"
  icon={<UsersIcon />}
  current={usersCount}
  limit={maxUsers}
  type="permanent"
/>

<LimitCard
  title="Keywords in Workspace"
  icon={<TagIcon />}
  current={keywordsCount}
  limit={maxKeywords}
  type="permanent"
/>

<LimitCard
  title="Ahrefs Research Queries"
  icon={<SearchIcon />}
  current={ahrefsUsed}
  limit={ahrefsLimit}
  type="monthly"
  daysUntilReset={daysUntilReset}
/>

Tab 3: Credit Insights (NEW)

{/* Multi-dimensional breakdowns */}

{/* By Site */}
<Card>
  <h3>Credits by Site</h3>
  <div>See which sites consume the most credits</div>
  {insights.by_site.map(site => (
    <div key={site.id}>
      <div className="flex justify-between">
        <span>{site.name}</span>
        <span>{site.credits_used.toLocaleString()} credits ({site.percentage}%)</span>
      </div>
      <ProgressBar value={site.percentage} />
    </div>
  ))}
</Card>

{/* By Action Type */}
<Card>
  <h3>Credits by Action Type</h3>
  <DonutChart data={insights.by_operation} />
  <div>
    {insights.by_operation.map(op => (
      <div key={op.type}>
        <span>{op.display_name}</span>
        <span>{op.credits_used} credits ({op.count} times)</span>
      </div>
    ))}
  </div>
</Card>

{/* By Image Quality */}
<Card>
  <h3>Image Generation Breakdown</h3>
  <div>Credits vary by quality tier</div>
  {insights.by_image_quality.map(tier => (
    <div key={tier.quality_tier}>
      <div>{tier.quality_tier} Quality</div>
      <div>{tier.images_generated} images × {tier.credits_per_image} credits = {tier.total_credits} credits</div>
      <Badge>{tier.model_name}</Badge>
    </div>
  ))}
</Card>

{/* By Automation */}
<Card>
  <h3>Automation Runs</h3>
  {insights.by_automation.map(auto => (
    <div key={auto.id}>
      <div>{auto.name}</div>
      <div>{auto.runs_count} runs  {auto.credits_used} credits total</div>
    </div>
  ))}
</Card>

{/* Timeline Chart */}
<Card>
  <h3>Credit Usage Over Time</h3>
  <LineChart 
    data={insights.timeline}
    xAxis="date"
    yAxis="credits_used"
  />
</Card>

Tab 4: Activity Log (Keep existing, but rename from "API Activity")


8. Backend Changes Summary

8.1 Files to Modify

File Action Description
backend/igny8_core/auth/models.py Edit Remove unused limit fields from Plan & Account
backend/igny8_core/auth/migrations/0XXX_*.py Create Migration to remove fields
backend/igny8_core/business/billing/services/limit_service.py Edit Remove unused limit mappings
backend/igny8_core/auth/serializers.py Edit Remove fields from PlanSerializer
backend/igny8_core/business/keywords/ahrefs_service.py Create New service for Ahrefs integration
backend/igny8_core/business/automation/services.py Edit Add credit pre-check
backend/igny8_core/business/planning/views.py Edit Add keyword limit enforcement
backend/igny8_core/modules/billing/views.py Edit Add credit insights endpoint

8.2 New API Endpoints

# Credit Insights (NEW)
GET /api/v1/billing/credits/insights/?days=30
Response: CreditInsights object with multi-dimensional breakdowns

# Ahrefs Search (NEW)
POST /api/v1/keywords/ahrefs/search/
Body: { "query": "digital marketing", "country": "us" }
Response: { "keywords": [...], "queries_remaining": 42 }

9. Frontend Changes Summary

9.1 Files to Modify

File Action Description
frontend/src/pages/account/PlansAndBillingPage.tsx Edit Remove credit usage details from Current Plan tab
frontend/src/pages/account/UsageAnalyticsPage.tsx Rewrite Add Overview & Credit Insights tabs
frontend/src/components/billing/UsageLimitsPanel.tsx Edit Remove unused limits, add Ahrefs
frontend/src/services/billing.api.ts Edit Remove unused fields, add new interfaces
frontend/src/pages/Planner/KeywordResearchPage.tsx Create New page for keyword research
frontend/src/components/keywords/AhrefsResearchPanel.tsx Create Ahrefs search component

9.2 New Components

// Credit Insights Components
CreditInsightsDashboard.tsx
CreditsBySiteWidget.tsx
CreditsByOperationWidget.tsx
CreditsByImageQualityWidget.tsx
CreditsByAutomationWidget.tsx
CreditTimelineChart.tsx

// Ahrefs Research
KeywordResearchPage.tsx
AhrefsResearchPanel.tsx
BrowseKeywordsPanel.tsx

10. Testing Checklist

Problem: Keywords have max_keywords limit defined but enforcement is inconsistent.

Required Changes:

  1. Add Pre-Create Checks:

    Location: backend/igny8_core/business/planning/views.py (or wherever keywords are created)

    # Before creating keywords
    from igny8_core.business.billing.services.limit_service import LimitService
    
    def create_keywords(account, keyword_data):
        # Check if adding keywords would exceed limit
        num_new_keywords = len(keyword_data)
        LimitService.check_hard_limit(account, 'keywords', num_new_keywords)
    
        # If check passes, create keywords
        keywords = Keywords.objects.bulk_create([...])
    
        return keywords
    
  2. Add Check to SeedKeyword Import:

    Location: backend/igny8_core/api/endpoints/seed_keywords.py (or similar)

    # In addSeedKeywordsToWorkflow endpoint
    def add_seed_keywords_to_workflow(seed_keyword_ids, site_id, sector_id):
        account = Site.objects.get(id=site_id).account
    
        # Check limit BEFORE importing
        LimitService.check_hard_limit(account, 'keywords', len(seed_keyword_ids))
    
        # Import keywords
        for seed_kw in SeedKeyword.objects.filter(id__in=seed_keyword_ids):
            Keywords.objects.create(...)
    
  3. Add Check to Bulk Import:

    Ensure CSV/Excel keyword imports also check limits before processing.

  4. User-Facing Error Messages:

    try:
        LimitService.check_hard_limit(account, 'keywords', 50)
    except HardLimitExceededError as e:
        return Response({
            'error': 'keyword_limit_exceeded',
            'message': 'You have reached your keyword limit. Upgrade your plan to add more keywords.',
            'current': 950,
            'limit': 1000,
            'upgrade_url': '/account/plans/upgrade'
        }, status=402)
    

Testing:

  • Try to import keywords beyond limit → Should fail with clear error
  • Try to create single keyword at limit → Should fail
  • Try to bulk import → Should fail if total exceeds
  • Error message should show current count, limit, and upgrade CTA

6.3 Implement Ahrefs Query Limit (Medium Priority)

Problem: Future feature needs limit definition and enforcement.

Required Changes:

  1. Database Migration:

    # Create migration: 0XXX_add_ahrefs_query_limits.py
    
    operations = [
        migrations.AddField(
            model_name='plan',
            name='max_ahrefs_queries',
            field=models.IntegerField(default=50, validators=[MinValueValidator(0)]),
        ),
        migrations.AddField(
            model_name='account',
            name='usage_ahrefs_queries',
            field=models.IntegerField(default=0, validators=[MinValueValidator(0)]),
        ),
    ]
    
  2. Update Plan Admin:

    # backend/igny8_core/auth/admin.py - PlanAdmin
    fieldsets = (
        # ... existing fieldsets ...
        ('Monthly Limits (Reset on Billing Cycle)', {
            'fields': (
                'max_content_ideas', 'max_content_words',
                'max_images_basic', 'max_images_premium', 'max_image_prompts',
                'max_ahrefs_queries',  # ADD THIS
            ),
        }),
    )
    
  3. Update LimitService:

    # backend/igny8_core/business/billing/services/limit_service.py
    MONTHLY_LIMIT_MAPPINGS = {
        # ... existing ...
        'ahrefs_queries': {
            'plan_field': 'max_ahrefs_queries',
            'usage_field': 'usage_ahrefs_queries',
            'display_name': 'Ahrefs Keyword Research Queries',
        },
    }
    
  4. Add to Plan Serializer:

    # backend/igny8_core/auth/serializers.py - PlanSerializer
    class PlanSerializer(serializers.ModelSerializer):
        class Meta:
            fields = [
                # ... existing fields ...
                'max_ahrefs_queries',  # ADD THIS
            ]
    
  5. Enforce in Ahrefs Service:

    # Create new service: backend/igny8_core/business/keywords/ahrefs_service.py
    from igny8_core.business.billing.services.limit_service import LimitService
    
    class AhrefsService:
        @staticmethod
        def query_keywords(account, query_params):
            # Check monthly limit
            LimitService.check_monthly_limit(account, 'ahrefs_queries', 1)
    
            # Execute Ahrefs API call
            results = ahrefs_api.search_keywords(**query_params)
    
            # Increment usage counter
            LimitService.increment_usage(account, 'ahrefs_queries', 1)
    
            return results
    
  6. Update Frontend:

    // frontend/src/components/billing/UsageLimitsPanel.tsx
    const monthlyLimitConfig = {
        // ... existing ...
        ahrefs_queries: { 
            icon: <SearchIcon className="w-5 h-5" />, 
            color: 'purple' as const 
        },
    };
    

Plan Values (Suggested):

Plan max_ahrefs_queries/month
Free 0 (no access)
Starter 50 queries
Growth 200 queries
Scale 500 queries

6.4 Add Cluster Limit Enforcement (Low Priority)

Current State: Clusters have max_clusters limit but may not be consistently enforced.

Recommendation: Apply same enforcement pattern as keywords:

  1. Check limit before creating clusters
  2. Add to all cluster creation flows (auto-clustering, manual clustering)
  3. User-facing error messages

6.5 Monthly Limits Reset Automation (Medium Priority)

Current State: Monthly limits should reset at billing cycle, but automation may not be in place.

Check Required:

  • Is there a scheduled task that calls LimitService.reset_monthly_limits(account)?
  • When do subscriptions renew?
  • How are usage fields reset?

Recommended:

# backend/igny8_core/business/billing/tasks.py (Celery)
from celery import shared_task
from igny8_core.auth.models import Account
from igny8_core.business.billing.services.limit_service import LimitService

@shared_task
def reset_monthly_limits_for_accounts():
    """Reset monthly limits for accounts whose billing period has ended"""
    from django.utils import timezone
    
    now = timezone.now()
    accounts = Account.objects.filter(
        usage_period_end__lte=now,
        status='active'
    )
    
    for account in accounts:
        LimitService.reset_monthly_limits(account)
        logger.info(f"Reset monthly limits for account {account.id}")

7. Credit System (Working Well)

No changes needed - the credit system is well-designed:

  1. Token-Based Calculation: Uses AIModelConfig.tokens_per_credit for accurate pricing
  2. Image Fixed Pricing: Uses AIModelConfig.credits_per_image (1, 5, or 15)
  3. Proper Logging: CreditUsageLog tracks every operation with metadata
  4. Transaction Ledger: CreditTransaction maintains audit trail
  5. Balance Tracking: Account.credits is source of truth

Already Enforced Properly:

  • Pre-flight balance checks before operations
  • Token-based credit calculation after API calls
  • Proper transaction logging
  • Clear error messages (402 Payment Required)

8. Summary of Required Backend Changes

8.1 Database Schema Changes

Add to Plan Model:

max_ahrefs_queries = models.IntegerField(default=50, validators=[MinValueValidator(0)])

Add to Account Model:

usage_ahrefs_queries = models.IntegerField(default=0, validators=[MinValueValidator(0)])

8.2 Enforcement Additions

Locations Needing Limit Checks:

Location Limit Type Method to Add
Keyword creation Hard LimitService.check_hard_limit(account, 'keywords', count)
SeedKeyword import Hard LimitService.check_hard_limit(account, 'keywords', count)
Bulk keyword import Hard LimitService.check_hard_limit(account, 'keywords', count)
Ahrefs query (future) Monthly LimitService.check_monthly_limit(account, 'ahrefs_queries', 1)
Cluster creation Hard LimitService.check_hard_limit(account, 'clusters', count)

8.3 Service Updates

LimitService Mappings:

# Add to MONTHLY_LIMIT_MAPPINGS
'ahrefs_queries': {
    'plan_field': 'max_ahrefs_queries',
    'usage_field': 'usage_ahrefs_queries',
    'display_name': 'Ahrefs Keyword Research Queries',
}

9. Summary of Required Frontend Changes

9.1 Page Content Adjustments

Plans & Billing Page:

// Remove from Current Plan tab:
-  Detailed credit usage breakdown (move to Usage)
-  Limit progress bars (move to Usage)

// Keep in Current Plan tab:
-  Plan name, price, renewal date
-  Brief limits summary (text only)
-  Upgrade CTA

// Keep History tab as is

Usage Analytics Page:

// Keep all tabs as is - no changes needed
// This page is perfectly organized

9.2 Component Updates

UsageLimitsPanel.tsx:

// Add Ahrefs queries to monthly limits config
const monthlyLimitConfig = {
    // ... existing ...
    ahrefs_queries: { 
        icon: <SearchIcon className="w-5 h-5" />, 
        color: 'purple' as const 
    },
};

billing.api.ts:

// Add to Plan interface
export interface Plan {
    // ... existing fields ...
    max_ahrefs_queries?: number;  // ADD THIS
}

// Add to UsageSummary interface
export interface UsageSummary {
    // ... existing ...
    monthly_limits: {
        // ... existing ...
        ahrefs_queries?: LimitUsage;  // ADD THIS
    };
}

10. Implementation Priority

Phase 1: Critical Fixes (Do First) 🔥

  1. Enforce Keywords Limit (Backend)

    • Add checks to keyword creation flows
    • Estimated effort: 4 hours
    • Impact: Prevents users from exceeding limits
  2. Page Reorganization (Frontend)

    • Remove duplicate data from Plans & Billing → Current Plan
    • Estimated effort: 2 hours
    • Impact: Reduces user confusion

Phase 2: Future Features (Do When Implementing Ahrefs)

  1. Implement Ahrefs Query Limit (Backend + Frontend)
    • Database migration
    • LimitService mapping
    • Enforcement in Ahrefs service
    • Frontend display
    • Estimated effort: 6 hours
    • Impact: Ready for Ahrefs integration

Phase 3: Nice-to-Have Improvements

  1. Enforce Cluster Limit (Backend)

    • Similar to keywords enforcement
    • Estimated effort: 2 hours
  2. Monthly Limits Reset Automation (Backend)

    • Celery task for auto-reset
    • Estimated effort: 3 hours


11. Migration Strategy

Week-by-Week Rollout

Week 1: Backend Foundation

  • Create database migration to remove unused fields
  • Update LimitService mappings
  • Update serializers
  • Add Ahrefs service skeleton
  • Deploy to staging

Week 2: Enforcement

  • Add keyword limit checks to all entry points
  • Add automation credit pre-checks
  • Test all validation flows
  • Deploy to staging

Week 3: Frontend Cleanup

  • Remove duplicate data from Plans & Billing
  • Update UsageLimitsPanel
  • Update terminology (remove "API", "operations")
  • Deploy to staging

Week 4: New Features

  • Build Credit Insights tab
  • Build Keyword Research page
  • Integrate Ahrefs (when ready)
  • Add multi-dimensional widgets
  • Deploy to staging

Week 5: Testing & Production

  • Full regression testing
  • User acceptance testing
  • Deploy to production
  • Monitor for issues

12. Final Limits Configuration

12.1 Database Schema (After Cleanup)

Plan Model - FINAL:

class Plan(models.Model):
    # Basic Info
    name = models.CharField(max_length=255)
    slug = models.SlugField(unique=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    
    # ONLY 4 LIMITS:
    max_sites = models.IntegerField(default=1)
    max_users = models.IntegerField(default=1)
    max_keywords = models.IntegerField(default=1000)
    max_ahrefs_queries = models.IntegerField(default=50)
    
    # Credits
    included_credits = models.IntegerField(default=0)
    extra_credit_price = models.DecimalField(max_digits=10, decimal_places=2)

Account Model - FINAL:

class Account(models.Model):
    # Credits
    credits = models.IntegerField(default=0)
    
    # ONLY 1 Usage Tracker:
    usage_ahrefs_queries = models.IntegerField(default=0)
    
    # Billing Period
    usage_period_start = models.DateTimeField(null=True)
    usage_period_end = models.DateTimeField(null=True)

12.2 Suggested Plan Values

Plan Price Included Credits Sites Users Keywords Ahrefs Queries/mo
Free $0 2,000 1 1 100 0
Starter $49 10,000 2 2 1,000 50
Growth $149 40,000 5 3 5,000 200
Scale $399 120,000 Unlimited 5 20,000 500

13. Success Criteria

13.1 Technical Success

  • All unused limit fields removed from database
  • Migration runs successfully without data loss
  • All 4 limits properly enforced
  • Credit balance checked before ALL operations
  • Automation pre-checks credit balance
  • Ahrefs queries counted and limited
  • No duplicate data across pages
  • User-friendly terminology throughout

13.2 User Experience Success

  • Users understand the simple 4-limit model
  • Clear separation: Plans & Billing = financial, Usage = consumption
  • Multi-dimensional insights provide actionable data
  • Keyword research flow is intuitive
  • Credit exhaustion messages are clear and actionable
  • Upgrade prompts appear at right moments

13.3 Business Success

  • Reduced support questions about limits
  • Clearer upgrade paths
  • Better credit consumption visibility drives upgrades
  • Ahrefs integration ready for launch
  • System scales without complexity

14. Risks & Mitigation

Risk 1: Data Loss During Migration

Mitigation:

  • Backup database before migration
  • Test migration on staging with production data clone
  • Keep removed fields as comments in code for 1 month

Risk 2: Users Confused by Changes

Mitigation:

  • In-app changelog notification
  • Update help documentation
  • Add tooltips to new UI elements
  • Gradual rollout (staging → 10% → 50% → 100%)

Risk 3: Breaking Changes

Mitigation:

  • Maintain backward compatibility in API for 2 weeks
  • Version API endpoints if needed
  • Monitor error logs closely after deployment

15. Post-Launch Monitoring

Metrics to Track

Technical:

  • API error rates (especially 402 Insufficient Credits)
  • Failed automation runs due to credits
  • Keyword limit violations
  • Ahrefs query usage patterns

Business:

  • Upgrade conversion rate
  • Support tickets about limits/credits
  • Credit package purchase rate
  • User engagement with new Usage insights

User Behavior:

  • Time spent on Usage page
  • Click-through on upgrade prompts
  • Ahrefs query usage distribution
  • Most-used insights widgets

16. Documentation Updates Required

  • Update docs/10-MODULES/BILLING.md
  • Update docs/40-WORKFLOWS/CREDIT-SYSTEM.md
  • Create docs/10-MODULES/KEYWORD-RESEARCH.md
  • Update API documentation
  • Update user help docs
  • Update admin guides

17. Summary

Before (Complex)

Keywords Limit Testing

  • Try to create single keyword when at limit → Should fail
  • Try to import 50 SeedKeywords when 30 slots remain → Should fail
  • Try to bulk import CSV with 1000 keywords when at limit → Should fail
  • Error message shows current count, limit, and upgrade link
  • Upgrade plan → New limit applies immediately
  • Delete keywords → Can add more up to new total

Ahrefs Limit Testing (Future)

  • Query Ahrefs when at monthly limit → Should fail
  • Error message shows queries used, limit, and reset date
  • Monthly reset correctly resets counter
  • Upgrade plan → New monthly allowance applies

Page Reorganization Testing

  • Plans & Billing → Current Plan shows only plan info and brief summary
  • Plans & Billing → Current Plan does NOT show detailed usage breakdown
  • Usage → Limits & Usage shows all limits with progress bars
  • Usage → Credit History shows transaction log
  • No duplicate data between the two sections

Before (Complex & Confusing)

  • 10+ limit fields (max_content_ideas, max_content_words, max_images_basic, max_images_premium, etc.)
  • Duplicate data across Plans & Billing and Usage pages
  • Inconsistent enforcement
  • Technical terminology ("API Activity")
  • Double limiting (credits + monthly limits)
  • User confusion: "I have credits but can't generate content?"

After (Simple & Clear)

  • 4 limits only: Sites, Users, Keywords, Ahrefs Queries
  • Everything else = Credits: Let users consume how they want
  • Clear page separation: Plans & Billing (financial) vs Usage (consumption)
  • Multi-dimensional insights for optimization
  • User-friendly language throughout
  • Proper enforcement everywhere
  • Simple upgrade paths

What Changes

Area Before After
Limits 10+ different limits 4 limits only
Plan Model 12 limit fields 4 limit fields
Account Model 5 usage tracking fields 1 usage tracking field
Pages Duplicate data everywhere Clean separation
Terminology "API", "operations" "Actions", "activities"
Enforcement Inconsistent Consistent everywhere
Insights Basic usage log Multi-dimensional analysis
Keyword Research One way (SeedKeywords) Two ways (Browse + Ahrefs)

18. Next Steps for Development Team

Backend Developer Tasks

  1. Week 1:

    • Review this implementation plan
    • Create database migration script
    • Test migration on local/staging with production data clone
    • Update models (Plan, Account)
    • Update LimitService
    • Update serializers
    • Verify all tests pass
  2. Week 2:

    • Add keyword limit enforcement to all entry points
    • Update automation service with credit pre-check
    • Search codebase for monthly limit checks and remove
    • Add Ahrefs service skeleton
    • Create credit insights API endpoint
    • Write tests for new validations
  3. Week 3:

    • Code review
    • Deploy to staging
    • Integration testing
    • Performance testing

Frontend Developer Tasks

  1. Week 1:

    • Review this implementation plan
    • Update Plans & Billing page (remove duplicates)
    • Update Usage page structure
    • Remove unused limit displays
    • Update terminology throughout
  2. Week 2:

    • Build Credit Insights tab with widgets
    • Build Keyword Research page
    • Add Ahrefs research panel
    • Update TypeScript interfaces
    • Add credit pre-check hooks
  3. Week 3:

    • Testing & bug fixes
    • Deploy to staging
    • User acceptance testing

QA Tasks

  • Execute full testing checklist (see Section 10)
  • Regression testing on existing features
  • Performance testing (page load times, API response times)
  • Edge case testing (limits, credits, errors)
  • Cross-browser testing
  • Mobile responsiveness testing

DevOps Tasks

  • Database backup strategy
  • Migration rollback plan
  • Staging deployment
  • Production deployment plan
  • Monitoring setup
  • Alerting configuration

19. Key Implementation Principles

Principle 1: Simplicity First

Remove complexity wherever possible. If a limit isn't absolutely necessary, remove it.

Principle 2: Credit-Based Philosophy

Credits are the primary control mechanism. Hard limits only for true boundaries (sites, users, keywords, Ahrefs).

Principle 3: Clear Separation

Plans & Billing = "What am I paying for?" Usage = "What am I using?"

Principle 4: User-Friendly Language

No technical jargon. Write for business users, not developers.

Principle 5: Proper Enforcement

Every limit must be checked at every entry point. No exceptions.

Principle 6: Actionable Insights

Don't just show numbers. Show breakdowns that help users optimize their usage.

Principle 7: Clear Upgrade Paths

When users hit limits, show clear value proposition for upgrading.


20. Conclusion

This implementation plan transforms the IGNY8 credits and limits system from complex and confusing to simple and powerful.

Core Changes:

  • 4 limits only (sites, users, keywords, Ahrefs queries)
  • Remove 10+ unused limit fields from database
  • Eliminate data duplication across pages
  • Add multi-dimensional insights for better optimization
  • Implement Ahrefs research with monthly query limits
  • Enforce all limits properly at every entry point
  • Validate credit balance before all operations
  • Use user-friendly terminology throughout

Expected Outcomes:

  • Reduced user confusion
  • Fewer support tickets
  • Higher upgrade conversion
  • Better credit management
  • Clearer value proposition
  • Scalable system architecture

Timeline: 5 weeks from start to production deployment

Status: READY FOR IMPLEMENTATION


Document prepared by: GitHub Copilot (Claude Sonnet 4.5)
Date: January 5, 2026
Next Review: After Week 1 completion
Approval Required: Backend Lead, Frontend Lead, Product Manager


End of Implementation Plan

For questions or clarification during implementation:

  • See docs/10-MODULES/BILLING.md for current system
  • See docs/40-WORKFLOWS/CREDIT-SYSTEM.md for credit workflows
  • Review backend/igny8_core/business/billing/services/ for service architecture
  • Check this document's commit history for rationale behind decisions