
## Complete End-to-End Analysis & Restructuring Plan

### Current State Summary (from actual database queries)

**AIModelConfig (6 records - all needed):**
| model_name | type | provider | active | default | cost | tokens_per_credit |
|------------|------|----------|--------|---------|------|-------------------|
| gpt-4o-mini | text | openai | ✅ | ❌ | $0.15/$0.60 per 1M | 10,000 |
| gpt-4o | text | openai | ❌ | ❌ | $2.50/$10.00 per 1M | 1,000 |
| gpt-5.1 | text | openai | ✅ | ✅ | $1.25/$10.00 per 1M | 1,000 |
| runware:97@1 | image | runware | ✅ | ❌ | $0.012/image | 1 credit/image |
| dall-e-3 | image | openai | ✅ | ✅ | $0.04/image | 5 credits/image |
| google:4@2 | image | runware | ✅ | ❌ | $0.14/image | 15 credits/image |

---

### Current End-to-End Flow (Traced from Code)

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        CURRENT ARCHITECTURE                              │
└─────────────────────────────────────────────────────────────────────────┘

1. CONFIGURATION LAYER
   ┌────────────────────────────────────────┐
   │  GlobalIntegrationSettings (singleton) │  ← API keys stored here
   │  - openai_api_key                      │
   │  - runware_api_key                     │
   │  - anthropic_api_key (unused)          │
   │  - bria_api_key (unused)               │
   │  - openai_model: gpt-4o-mini ❌        │  ← Should be gpt-5.1
   │  - runware_model: bria:10@1 ❌         │  ← Model doesn't exist!
   │  - HARDCODED CHOICES duplicating DB    │
   └────────────────────────────────────────┘
                     │
                     ▼
   ┌────────────────────────────────────────┐
   │  AIModelConfig (database)              │  ← Source of truth for models
   │  - model_name, provider, costs         │
   │  - is_active, is_default               │
   └────────────────────────────────────────┘
                     │
                     ▼
   ┌────────────────────────────────────────┐
   │  constants.py                          │  ← DUPLICATE/LEGACY
   │  - MODEL_RATES (hardcoded)             │
   │  - IMAGE_MODEL_RATES (hardcoded)       │
   └────────────────────────────────────────┘

2. SETTINGS RESOLUTION LAYER
   ┌────────────────────────────────────────┐
   │  settings.py                        │
   │  get_model_config(function, account)   │
   │  - Gets model from GlobalIntegration   │
   │  - Gets max_tokens from AIModelConfig  │
   │  - Allows IntegrationSettings override │
   └────────────────────────────────────────┘
                     │
                     ▼
   ┌────────────────────────────────────────┐
   │  model_registry.py                  │
   │  ModelRegistry.get_model(model_id)     │
   │  - Try DB (AIModelConfig) first        │
   │  - Fallback to constants.py ❌         │  ← Should only use DB
   └────────────────────────────────────────┘

3. AI EXECUTION LAYER
   ┌────────────────────────────────────────┐
   │  engine.py (AIEngine)               │
   │  - Orchestrates all AI functions       │
   │  - Progress tracking, cost tracking    │
   └────────────────────────────────────────┘
                     │
                     ▼
   ┌────────────────────────────────────────┐
   │  ai_core.py (AICore)                │
   │  - _load_account_settings()            │  ← Gets API keys from Global
   │  - run_ai_request() for text           │
   │  - generate_image() for images         │
   │  - Uses IMAGE_MODEL_RATES fallback ❌  │
   └────────────────────────────────────────┘
                     │
                     ▼
   ┌────────────────────────────────────────┐
   │  ai/functions/                         │
   │  - generate_images.py                  │
   │  - generate_content.py                 │
   │  - auto_cluster.py                     │
   │  - Each function uses AICore           │
   └────────────────────────────────────────┘

4. CREDIT CALCULATION LAYER
   ┌────────────────────────────────────────┐
   │  CreditCostConfig (database)           │
   │  - operation_type                      │
   │  - tokens_per_credit                   │
   │  - min_credits                         │
   │  - price_per_credit_usd                │
   │  - For images: 50 tokens/credit, min 5 │
   └────────────────────────────────────────┘
                     │
                     ▼
   ┌────────────────────────────────────────┐
   │  CreditService                         │
   │  - calculate_credits_from_tokens()     │
   │  - deduct_credits_for_operation()      │
   │  - Text: tokens → credits AFTER call   │
   │  - Images: ??? (not token-based)       │
   └────────────────────────────────────────┘

5. FRONTEND (Sites/Settings.tsx)
   ┌────────────────────────────────────────┐
   │  HARDCODED model choices ❌            │
   │  - QUALITY_TO_CONFIG                   │
   │  - RUNWARE_MODEL_CHOICES               │
   │  - DALLE_MODEL_CHOICES                 │
   │  - MODEL_LANDSCAPE_SIZES               │
   └────────────────────────────────────────┘
```

---

### Problems Identified

| # | Problem | Location | Impact |
|---|---------|----------|--------|
| 1 | GlobalIntegrationSettings has hardcoded model choices | global_settings_models.py | Duplicates AIModelConfig |
| 2 | runware_model = "bria:10@1" but model doesn't exist | GlobalIntegrationSettings | Broken fallback |
| 3 | API keys mixed with model config | GlobalIntegrationSettings | No separation of concerns |
| 4 | IMAGE_MODEL_RATES still used as fallback | ai_core.py, model_registry.py | Inconsistent pricing |
| 5 | Frontend hardcodes model choices | Settings.tsx | Not dynamic |
| 6 | Image credit calculation unclear | CreditService | Not based on cost_per_image |
| 7 | constants.py duplicates DB data | constants.py | Maintenance burden |

---

### Target Architecture

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        TARGET ARCHITECTURE                               │
└─────────────────────────────────────────────────────────────────────────┘

1. NEW: IntegrationProvider Model (stores ALL 3rd party API keys)
   ┌────────────────────────────────────────┐
   │  IntegrationProvider                   │  ← Future-proof: ALL integrations
   │  - provider_id: str (primary key)      │
   │    Examples: openai, runware, google,  │
   │    resend, stripe, etc.                │
   │  - display_name: str                   │
   │  - provider_type: ai | email | payment │
   │  - api_key: encrypted str              │
   │  - api_endpoint: URL (optional)        │
   │  - is_active: bool                     │
   │  - config: JSON (rate limits, etc.)    │
   └────────────────────────────────────────┘
                     │
                     ▼
2. CLEANED: AIModelConfig (references IntegrationProvider)
   ┌────────────────────────────────────────┐
   │  AIModelConfig                         │
   │  - model_name: str                     │
   │  - display_name: str                   │
   │  - model_type: text | image            │
   │  - provider: str → IntegrationProvider │
   │  - cost fields (unchanged)             │
   │  + credits_per_image: int (NEW)        │  ← For image models
   │  + tokens_per_credit: int (NEW)        │  ← For text models
   │  + quality_tier: basic|quality|premium │  ← For UI display
   │  - is_default: bool                    │  ← Loads automatically
   └────────────────────────────────────────┘
                     │
                     ▼
3. SIMPLIFIED: GlobalIntegrationSettings
   ┌────────────────────────────────────────┐
   │  GlobalIntegrationSettings             │
   │  NO hardcoded model names              │
   │  NO API keys (in IntegrationProvider)  │
   │  Loads defaults from AIModelConfig     │
   │    where is_default=True               │
   │  - image_style: str                    │
   │  - max_in_article_images: int          │
   │  - image_quality: str                  │
   └────────────────────────────────────────┘
                     │
                     ▼
4. UNIFIED: Model Resolution
   ┌────────────────────────────────────────┐
   │  ModelRegistry                         │
   │  - get_default_model(type) → from DB   │
   │  - get_model(model_id) → AIModelConfig │
   │  - get_provider(id) → IntegrationProv  │
   │  - get_api_key(provider) → key         │
   │  - NO fallback to constants            │
   │  - NO hardcoded defaults               │
   └────────────────────────────────────────┘
                     │
                     ▼
5. DYNAMIC: Frontend API
   ┌────────────────────────────────────────┐
   │  GET /api/v1/system/ai-models/         │
   │  Returns models from DB with defaults  │
   │  marked, no hardcoding needed          │
   └────────────────────────────────────────┘
```

---

### Implementation Plan (Complete)

#### Phase 1: Database Schema Changes

**1.1 Create IntegrationProvider Model (Future-proof for ALL integrations)**

File: models.py

```python
class IntegrationProvider(models.Model):
    """
    Centralized 3rd party integration provider configuration.
    Single location for ALL external service API keys and configs.
    """
    PROVIDER_TYPE_CHOICES = [
        ('ai', 'AI Provider'),
        ('email', 'Email Service'),
        ('payment', 'Payment Gateway'),
        ('storage', 'Storage Service'),
        ('other', 'Other'),
    ]
    
    provider_id = models.CharField(max_length=50, unique=True, primary_key=True)
    # Examples: openai, runware, google, resend, stripe, aws_s3, etc.
    
    display_name = models.CharField(max_length=100)
    provider_type = models.CharField(max_length=20, choices=PROVIDER_TYPE_CHOICES, default='ai')
    api_key = models.CharField(max_length=500, blank=True)  # Should be encrypted
    api_secret = models.CharField(max_length=500, blank=True)  # For services needing secret
    api_endpoint = models.URLField(blank=True)  # Custom endpoint if needed
    webhook_secret = models.CharField(max_length=500, blank=True)  # For Stripe etc.
    is_active = models.BooleanField(default=True)
    config = models.JSONField(default=dict, blank=True)  # Rate limits, regions, etc.
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        db_table = 'igny8_integration_providers'
        verbose_name = 'Integration Provider'
        verbose_name_plural = 'Integration Providers'
```

**1.2 Add fields to AIModelConfig**

```python
# Add to AIModelConfig for IMAGE models
credits_per_image = models.IntegerField(
    null=True, blank=True,
    help_text="Fixed credits per image generated. For image models only."
)

# Add to AIModelConfig for TEXT models  
tokens_per_credit = models.IntegerField(
    null=True, blank=True,
    help_text="Number of tokens that equal 1 credit. For text models only."
)

# Add quality tier for UI display (image models)
quality_tier = models.CharField(
    max_length=20,
    choices=[('basic', 'Basic'), ('quality', 'Quality'), ('premium', 'Premium')],
    null=True, blank=True,
    help_text="Quality tier for frontend UI display"
)
```

**1.3 Migration to populate data**

Create IntegrationProvider records:
```
| provider_id | display_name      | provider_type | Notes                    |
|-------------|-------------------|---------------|--------------------------|
| openai      | OpenAI            | ai            | GPT models, DALL-E       |
| runware     | Runware           | ai            | Image generation         |
| google      | Google Cloud      | ai            | Future: Gemini, etc.     |
| resend      | Resend            | email         | Transactional email      |
| stripe      | Stripe            | payment       | Payment processing       |
```

Update AIModelConfig with credit/token values:
```
| model_name    | type  | tokens_per_credit | credits_per_image | quality_tier |
|---------------|-------|-------------------|-------------------|--------------|
| gpt-4o-mini   | text  | 10000             | -                 | -            |
| gpt-4o        | text  | 1000              | -                 | -            |
| gpt-5.1       | text  | 1000              | -                 | -            |
| runware:97@1  | image | -                 | 1                 | basic        |
| dall-e-3      | image | -                 | 5                 | quality      |
| google:4@2    | image | -                 | 15                | premium      |
```

#### Phase 2: Backend Code Changes

**2.1 Remove hardcoded constants**

File: constants.py
- Remove: MODEL_RATES, IMAGE_MODEL_RATES
- Keep: JSON_MODE_MODELS (or move to AIModelConfig.supports_json_mode check)

**2.2 Update ModelRegistry**

File: model_registry.py
- Remove fallback to constants.py
- Add: `get_default_model(model_type) → AIModelConfig where is_default=True`
- Add: `get_provider(provider_id) → IntegrationProvider`
- Add: `get_api_key(provider_id) → str`
- **NO hardcoded model names** - always query DB

**2.3 Update AICore**

File: ai_core.py
- Change `_load_account_settings()` to use IntegrationProvider for API keys
- Remove IMAGE_MODEL_RATES import and usage
- Use `ModelRegistry.get_default_model('text')` instead of hardcoded model
- Use `ModelRegistry.calculate_cost()` exclusively

**2.4 Update CreditService**

File: credit_service.py

For IMAGE models:
```python
def calculate_credits_for_image(model_name: str, num_images: int) -> int:
    """Calculate credits for image generation from AIModelConfig"""
    model = AIModelConfig.objects.get(model_name=model_name, is_active=True)
    return model.credits_per_image * num_images
```

For TEXT models:
```python
def calculate_credits_from_tokens(model_name: str, total_tokens: int) -> int:
    """Calculate credits from token usage based on model's tokens_per_credit"""
    model = AIModelConfig.objects.get(model_name=model_name, is_active=True)
    tokens_per_credit = model.tokens_per_credit or 1000  # fallback
    return math.ceil(total_tokens / tokens_per_credit)
```

**2.5 Simplify GlobalIntegrationSettings**

File: global_settings_models.py
- Remove: All API key fields (moved to IntegrationProvider)
- Remove: All hardcoded CHOICES
- Remove: Model name fields (defaults loaded from AIModelConfig.is_default)
- Keep: image_style, max_in_article_images, image_quality
- Add: Helper methods to get defaults from AIModelConfig

#### Phase 3: API Endpoints

**3.1 New endpoint: GET /api/v1/system/ai-models/**

Returns all active models from database with defaults marked (no hardcoding):
```json
{
  "text_models": [
    {
      "model_name": "gpt-5.1",
      "display_name": "GPT-5.1 Premium",
      "is_default": true,
      "tokens_per_credit": 1000,
      "max_output_tokens": 8192
    },
    {
      "model_name": "gpt-4o-mini",
      "display_name": "GPT-4o Mini",
      "is_default": false,
      "tokens_per_credit": 10000,
      "max_output_tokens": 16000
    }
  ],
  "image_models": [
    {
      "model_name": "runware:97@1",
      "display_name": "Basic",
      "quality_tier": "basic",
      "is_default": false,
      "credits_per_image": 1,
      "valid_sizes": ["1024x1024", "1280x768"]
    },
    {
      "model_name": "dall-e-3",
      "display_name": "Quality",
      "quality_tier": "quality",
      "is_default": true,
      "credits_per_image": 5,
      "valid_sizes": ["1024x1024", "1792x1024"]
    },
    {
      "model_name": "google:4@2",
      "display_name": "Premium",
      "quality_tier": "premium",
      "is_default": false,
      "credits_per_image": 15,
      "valid_sizes": ["1024x1024", "1376x768"]
    }
  ],
  "image_settings": {
    "style": "photorealistic",
    "max_in_article_images": 4,
    "quality": "hd"
  }
}
```

#### Phase 4: Frontend Changes

**4.1 Settings.tsx**

- Remove: QUALITY_TO_CONFIG, RUNWARE_MODEL_CHOICES, DALLE_MODEL_CHOICES hardcodes
- Remove: MODEL_LANDSCAPE_SIZES hardcodes
- Add: Fetch models from `/api/v1/system/ai-models/`
- Load valid_sizes from API response per model
- Display to user (no provider/model names visible):
  - **"Basic (1 credit/image)"**
  - **"Quality (5 credits/image)"**  
  - **"Premium (15 credits/image)"**
- Default selection: model where `is_default=true` from API

#### Phase 5: Cleanup

**5.1 Files to clean/remove**
- Remove unused fields from GlobalIntegrationSettings: anthropic_*, bria_*, all API key fields, hardcoded model fields
- Remove deprecated methods from AICore
- Update all imports removing constants.py usage
- Remove CreditCostConfig dependency for image operations (use AIModelConfig.credits_per_image directly)

---

### Credit Calculation Summary

**Text Models (token-based):**
| Model | tokens_per_credit | Example: 5000 tokens |
|-------|-------------------|----------------------|
| gpt-5.1 | 1,000 | 5 credits |
| gpt-4o | 1,000 | 5 credits |
| gpt-4o-mini | 10,000 | 1 credit |

**Image Models (per-image):**
| Model | credits_per_image | quality_tier | Display |
|-------|-------------------|--------------|---------|
| runware:97@1 | 1 | basic | "Basic (1 credit/image)" |
| dall-e-3 | 5 | quality | "Quality (5 credits/image)" |
| google:4@2 | 15 | premium | "Premium (15 credits/image)" |

---

### Migration Order

1. Create IntegrationProvider model + migration
2. Add credits_per_image, tokens_per_credit, quality_tier to AIModelConfig + migration
3. Data migration: populate IntegrationProvider, update AIModelConfig with credit values
4. Update ModelRegistry (remove constants fallback, add get_default_model)
5. Update AICore (use IntegrationProvider for keys)
6. Update CreditService (model-based credit calculation)
7. Create API endpoint /api/v1/system/ai-models/
8. Update frontend (load from API, no hardcodes)
9. Cleanup GlobalIntegrationSettings (remove API keys, hardcoded choices)
10. Remove constants.py hardcoded rates

---

### Files Changed Summary

| File | Action |
|------|--------|
| models.py | Add IntegrationProvider, update AIModelConfig with credit fields |
| model_registry.py | Remove constants fallback, add get_default_model(), get_provider() |
| ai_core.py | Use IntegrationProvider for keys, ModelRegistry for defaults |
| constants.py | Remove MODEL_RATES, IMAGE_MODEL_RATES |
| credit_service.py | Model-based credit calculation for both text and images |
| global_settings_models.py | Remove API keys, hardcoded choices, model fields |
| backend/igny8_core/api/views/system.py | Add ai-models endpoint |
| Settings.tsx | Load models from API, remove all hardcodes |

---

### Key Principles

1. **No hardcoded model names** - GlobalIntegrationSettings loads defaults from AIModelConfig where is_default=True
2. **Single source of truth** - AIModelConfig is THE source for all model info including credit costs
3. **Future-proof** - IntegrationProvider handles ALL 3rd party integrations (AI, email, payment, etc.)
4. **Dynamic frontend** - All model choices loaded from API, not hardcoded
5. **Configurable credits** - Change credits_per_image or tokens_per_credit in admin, no code changes needed