temproary docs uplaoded

This commit is contained in:
IGNY8 VPS (Salman)
2026-03-23 09:02:49 +00:00
parent cb6eca4483
commit 128b186865
113 changed files with 68897 additions and 0 deletions

View File

@@ -0,0 +1,452 @@
# Automation Module
**Last Verified:** January 18, 2026
**Version:** 1.8.1
**Status:** ✅ Active
**Backend Path:** `backend/igny8_core/business/automation/`
**Frontend Path:** `frontend/src/pages/Automation/`
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Models | `business/automation/models.py` | `AutomationConfig`, `AutomationRun`, `DefaultAutomationConfig` |
| Service | `business/automation/services/automation_service.py` | `AutomationService` |
| Logger | `business/automation/services/automation_logger.py` | `AutomationLogger` |
| Celery Tasks | `business/automation/tasks.py` | `run_automation_task`, `check_scheduled_automations`, `check_test_triggers` |
| Publishing Tasks | `igny8_core/tasks/publishing_scheduler.py` | Scheduled publishing |
| **Unified Settings** | `api/unified_settings.py` | **v1.8.0** Consolidated settings API |
| **Default Settings API** | `api/unified_settings.py` | **v1.8.1** `DefaultSettingsAPIView` for reset |
| Frontend Overview | `pages/Automation/AutomationOverview.tsx` | **v1.8.0** Run history dashboard |
| Frontend Run Detail | `pages/Automation/AutomationRunDetail.tsx` | **v1.8.0** Detailed run view |
| Frontend Manual Run | `pages/Automation/AutomationPage.tsx` | Manual run UI |
| **Site Settings** | `pages/Sites/AIAutomationSettings.tsx` | **v1.8.0** Unified settings UI |
| Progress Bar | `components/Automation/GlobalProgressBar.tsx` | Full pipeline progress |
| Processing Card | `components/Automation/CurrentProcessingCard.tsx` | Real-time progress |
| **Scheduling Doc** | `docs/40-WORKFLOWS/AUTOMATION-AND-PUBLISHING-SCHEDULING.md` | **v1.8.1** Complete scheduling guide |
---
## Purpose
The Automation module runs the complete 7-stage content pipeline automatically:
```
Keywords → Clusters → Ideas → Tasks → Content → Image Prompts → Images → Published
```
**Settings Location (v1.8.0+):** Site Settings → Automation tab
---
## Scheduling (v1.8.1)
| Task | Schedule | Purpose |
|------|----------|---------|
| `check_scheduled_automations` | Every hour at `:05` | Check if automations should run |
| `check_test_triggers` | Every minute | Check for admin test triggers |
**How it works:**
- Users select hour (12-hour AM/PM format), stored as `HH:00` (24-hour)
- Celery compares `scheduled_hour == current_hour`
- 23-hour block prevents re-runs within same day
**Test Mode (Admin):**
- `test_mode_enabled` + `test_trigger_at` fields on `AutomationConfig`
- Allows immediate triggering without waiting for schedule
- Bypasses 23-hour blocking
---
## 7-Stage Pipeline
| Stage | Name | AI Function | Credit Cost | Can Skip |
|-------|------|-------------|-------------|----------|
| 1 | Keywords → Clusters | `AutoClusterFunction` | Per batch | ✅ |
| 2 | Clusters → Ideas | `GenerateIdeasFunction` | Per idea | ✅ |
| 3 | Ideas → Tasks | None (local) | None | ✅ |
| 4 | Tasks → Content | `GenerateContentFunction` | Per 100 words | ✅ |
| 5 | Content → Image Prompts | `GenerateImagePromptsFunction` | Per prompt | ✅ |
| 6 | Image Prompts → Images | `process_image_generation_queue` | Per image | ✅ |
| 7 | Review → Published | Publishing Scheduler | None | ✅ |
**Note:** Stage 7 uses the Publishing Scheduler with `PublishingSettings` for auto-approval and scheduling.
---
## Data Models
### AutomationConfig
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Target site |
| enabled | Boolean | Enable/disable automation |
| frequency | CharField | hourly/daily/weekly |
| scheduled_time | TimeField | Time to run |
| stage_1_batch_size | Integer | Keywords per batch |
| stage_2_batch_size | Integer | Clusters per batch |
| stage_3_batch_size | Integer | Ideas per batch |
| stage_4_batch_size | Integer | Tasks per batch |
| stage_5_batch_size | Integer | Content per batch |
| stage_6_batch_size | Integer | Images per batch |
| **stage_1_enabled** | Boolean | **v1.8.0** Enable stage 1 |
| **stage_2_enabled** | Boolean | **v1.8.0** Enable stage 2 |
| **stage_3_enabled** | Boolean | **v1.8.0** Enable stage 3 |
| **stage_4_enabled** | Boolean | **v1.8.0** Enable stage 4 |
| **stage_5_enabled** | Boolean | **v1.8.0** Enable stage 5 |
| **stage_6_enabled** | Boolean | **v1.8.0** Enable stage 6 |
| **stage_7_enabled** | Boolean | **v1.8.0** Enable stage 7 |
| **stage_X_use_testing** | Boolean | **v1.8.1** Use testing model (AI stages 1,2,4,5,6) |
| **stage_X_budget_pct** | Integer | **v1.8.1** Credit budget % (AI stages) |
| **max_keywords_per_run** | Integer | **v1.8.0** Per-run limit stage 1 |
| **max_clusters_per_run** | Integer | **v1.8.0** Per-run limit stage 2 |
| **max_ideas_per_run** | Integer | **v1.8.0** Per-run limit stage 3 |
| **max_tasks_per_run** | Integer | **v1.8.0** Per-run limit stage 4 |
| **max_content_per_run** | Integer | **v1.8.0** Per-run limit stage 5 |
| **max_images_per_run** | Integer | **v1.8.0** Per-run limit stage 6 |
| within_stage_delay | Integer | Seconds between batches |
| between_stage_delay | Integer | Seconds between stages |
| last_run_at | DateTime | Last execution |
| next_run_at | DateTime | Next scheduled run |
| **test_mode_enabled** | Boolean | **v1.8.1** Enable test mode for admin |
| **test_trigger_at** | DateTime | **v1.8.1** When to trigger test run |
### DefaultAutomationConfig (v1.8.1)
Singleton model for centralized default settings. See [AUTOMATION-AND-PUBLISHING-SCHEDULING.md](../40-WORKFLOWS/AUTOMATION-AND-PUBLISHING-SCHEDULING.md) for full schema.
| Field | Type | Purpose |
|-------|------|---------|
| is_enabled | Boolean | Default: Enable scheduled automation |
| frequency | CharField | Default frequency (daily/weekly/monthly) |
| next_scheduled_hour | Integer | Next hour to assign (auto-increments) |
| stage_X_enabled | Boolean | Default: Enable each stage |
| stage_X_batch_size | Integer | Default: Batch size per stage |
| stage_X_use_testing | Boolean | Default: Use testing model |
| stage_X_budget_pct | Integer | Default: Credit budget % |
| max_X_per_run | Integer | Default: Per-run limits |
| auto_approval_enabled | Boolean | Default: Auto-approve content |
| auto_publish_enabled | Boolean | Default: Auto-publish content |
| publish_days | JSONField | Default: Days to publish |
| publish_time_slots | JSONField | Default: Time slots to publish |
### AutomationRun
| Field | Type | Purpose |
|-------|------|---------|
| config | FK | Parent config |
| trigger_type | CharField | manual/scheduled |
| status | CharField | running/paused/cancelled/completed/failed |
| current_stage | Integer | Current stage (1-7) |
| started_at | DateTime | Start time |
| paused_at | DateTime | Pause time (nullable) |
| resumed_at | DateTime | Resume time (nullable) |
| cancelled_at | DateTime | Cancel time (nullable) |
| completed_at | DateTime | Completion time (nullable) |
| total_credits_used | Decimal | Total credits consumed |
| **initial_snapshot** | JSON | **v1.3.0** Queue sizes at run start |
| stage_1_result | JSON | Stage 1 results |
| stage_2_result | JSON | Stage 2 results |
| stage_3_result | JSON | Stage 3 results |
| stage_4_result | JSON | Stage 4 results |
| stage_5_result | JSON | Stage 5 results |
| stage_6_result | JSON | Stage 6 results |
| stage_7_result | JSON | Stage 7 results |
| error_message | TextField | Error details (nullable) |
---
## API Endpoints
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/automation/config/` | Get/create config | Get automation config |
| PUT | `/api/v1/automation/update_config/` | Update config | Update settings |
| POST | `/api/v1/automation/run_now/` | Start manual run | Start automation |
| GET | `/api/v1/automation/current_run/` | Get current run | Run status/progress |
| GET | `/api/v1/automation/pipeline_overview/` | Get pipeline | Stage status counts |
| GET | `/api/v1/automation/current_processing/` | Get processing | Live processing status |
| **GET** | `/api/v1/automation/run_progress/` | **v1.3.0** | Unified progress data |
| POST | `/api/v1/automation/pause/` | Pause run | Pause after current item |
| POST | `/api/v1/automation/resume/` | Resume run | Resume from saved stage |
| POST | `/api/v1/automation/cancel/` | Cancel run | Cancel after current item |
| GET | `/api/v1/automation/history/` | Get history | Last 20 runs |
| GET | `/api/v1/automation/logs/` | Get logs | Activity log for run |
| GET | `/api/v1/automation/estimate/` | Get estimate | Credit estimate |
| **GET** | `/api/v1/integration/settings/defaults/` | **v1.8.1** | Get default settings for reset |
**Query Parameters:** All require `?site_id=`, run-specific require `?run_id=`
### Default Settings Endpoint (v1.8.1)
Returns centralized defaults from `DefaultAutomationConfig`:
```json
{
"automation": { "enabled": false, "frequency": "daily", "time": "02:00" },
"stages": [
{ "number": 1, "enabled": true, "batch_size": 50, "per_run_limit": 0, "use_testing": false, "budget_pct": 15 },
...
],
"delays": { "within_stage": 3, "between_stage": 5 },
"publishing": {
"auto_approval_enabled": false,
"auto_publish_enabled": false,
"daily_publish_limit": 3,
"publish_days": ["mon", "tue", "wed", "thu", "fri"],
"time_slots": ["09:00", "14:00", "18:00"]
}
}
```
### run_progress Endpoint (v1.3.0)
Returns unified progress data for frontend:
```json
{
"run": { "run_id": "...", "status": "running", "current_stage": 3 },
"global_progress": { "total_items": 100, "completed_items": 45, "percentage": 45 },
"stages": [
{ "number": 1, "status": "completed", "input_count": 50, "processed_count": 50 },
...
],
"metrics": { "credits_used": 120, "duration_seconds": 3600 },
"initial_snapshot": { "stage_1_initial": 50, ... }
}
```
---
## Execution Flow
### Manual Run
1. User clicks "Run Now" on frontend
2. Frontend calls `POST /automation/run_now/?site_id=X`
3. Backend acquires cache lock `automation_lock_{site_id}`
4. **v1.3.0:** Captures initial snapshot with `_capture_initial_snapshot()`
5. Estimates credits required (1.2x buffer)
6. Validates balance >= estimate
7. Creates `AutomationRun` record
8. Enqueues `run_automation_task` Celery task
8. Returns run ID immediately
### Stage Execution
For each stage (1-7):
1. Check `_check_should_stop()` (paused/cancelled?)
2. Load items for processing
3. Process in batches (respecting batch_size)
4. For AI stages: Call AIEngine function
5. Wait `within_stage_delay` between batches
6. Save stage result JSON
7. Wait `between_stage_delay` before next stage
### Stage Result Fields
**Stage 1 (Clustering):**
```json
{
"keywords_processed": 150,
"clusters_created": 12,
"batches_run": 3,
"credits_used": 45,
"time_elapsed": 120,
"skipped": false,
"partial": false
}
```
**Stage 2 (Ideas):**
```json
{
"clusters_processed": 12,
"ideas_created": 36,
"batches_run": 2,
"credits_used": 72
}
```
**Stage 3 (Tasks):**
```json
{
"ideas_processed": 36,
"tasks_created": 36,
"batches_run": 4
}
```
**Stage 4 (Content):**
```json
{
"tasks_processed": 36,
"content_created": 36,
"total_words": 54000,
"batches_run": 6,
"credits_used": 540
}
```
**Stage 5 (Image Prompts):**
```json
{
"content_processed": 36,
"prompts_created": 180,
"batches_run": 4,
"credits_used": 36
}
```
**Stage 6 (Images):**
```json
{
"images_processed": 180,
"images_generated": 180,
"batches_run": 18
}
```
**Stage 7 (Review):**
```json
{
"ready_for_review": 36
}
```
---
## Settings Configuration (v1.8.0)
**Location:** Site Settings → Automation tab
**API:** `GET/PATCH /api/v1/integration/sites/{site_id}/unified-settings/`
> ⚠️ **v1.8.0 Change:** Settings are now consolidated in Site Settings. The previous standalone `/automation/settings` page has been removed.
### Settings UI Sections
1. **Schedule & Frequency Card**
- Enable/disable toggle
- Frequency (hourly/daily/weekly)
- Days of week selection
- Time slot selection
- Next run display
2. **Capacity Card**
- Total items per run (calculated)
- Stage-by-stage breakdown
3. **AI Configuration Card**
- Testing model selection (is_testing=true)
- Live model selection (is_testing=false)
- Image model selection
4. **Stage Configuration (Matrix)**
- Per-stage Enable/Disable toggle
- Per-stage batch size
- Per-stage max items per run (**new in v1.8.0**)
5. **Help Cards**
- Pipeline flow visualization
- Stage descriptions
---
## Scheduling
**Celery Beat Task:** `check_scheduled_automations`
**Frequency:** Hourly
**Logic:**
1. Find configs where `enabled=True`
2. Check if `next_run_at <= now`
3. Check if no active run exists
4. Start `run_automation_task` for eligible configs
5. Update `next_run_at` based on frequency
---
## Lock Mechanism
**Purpose:** Prevent concurrent runs for same site
**Key:** `automation_lock_{site_id}`
**Storage:** Redis cache
**Acquired:** On run start
**Released:** On completion/failure/cancel
---
## Credit Validation
Before starting:
1. Calculate estimated credits for all stages
2. Apply 1.2x safety buffer
3. Compare with account balance
4. Reject if balance < estimate
During execution:
- Each AI stage checks credits before processing
- Deductions happen after successful AI calls
- `total_credits_used` accumulates across stages
---
## Frontend Integration
### AutomationOverview (v1.8.0)
**Path:** `/automation/overview`
**Purpose:** Run history dashboard with:
- All automation runs with status
- Filter by status, date range
- Click to view detailed run information
### AutomationRunDetail (v1.8.0)
**Path:** `/automation/runs/:runId`
**Purpose:** Detailed view of individual run with:
- Stage-by-stage progress
- Items processed per stage
- Errors and logs
### AutomationPage
**Path:** `/automation`
**Purpose:** Manual run control with:
- **Pipeline Cards:** Stage-by-stage status with pending counts
- **Processing Card:** Live processing status during run
- **Control Buttons:** Run Now, Pause, Resume, Cancel
- **Activity Log:** Real-time log streaming
### Polling
- Every ~5s while run is running/paused
- Fetches: `current_run`, `pipeline_overview`, `current_processing`
- Lighter polling when idle
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| "Already running" error | Lock exists from previous run | Wait or check if stuck |
| Insufficient credits | Balance < 1.2x estimate | Add credits |
| Stage skipped | No items to process | Check previous stages |
| Run stuck | Worker crashed | Clear lock, restart |
| Images not generating | Stage 5 didn't create prompts | Check stage 5 result |
---
## Changelog
| Version | Changes |
|---------|---------|
| v1.8.0 | Unified settings in Site Settings, per-run limits, skip stage functionality |
| v1.7.0 | AutomationOverview dashboard, AutomationRunDetail page |
| v1.3.2 | Stage 7 publishing scheduler integration |
| v1.3.0 | Progress tracking with initial snapshots |

View File

@@ -0,0 +1,756 @@
# IGNY8 Billing & Payments - Complete Reference
> **Last Updated:** January 20, 2026
> **Version:** 2.0 (Two-Pool Credit System)
---
## Table of Contents
1. [System Overview](#1-system-overview)
2. [Credit System](#2-credit-system)
3. [Payment Methods](#3-payment-methods)
4. [Subscription Lifecycle](#4-subscription-lifecycle)
5. [Credit Packages](#5-credit-packages)
6. [Invoice System](#6-invoice-system)
7. [Renewal Workflow](#7-renewal-workflow)
8. [Admin Operations](#8-admin-operations)
9. [API Reference](#9-api-reference)
10. [Database Schema](#10-database-schema)
---
## 1. System Overview
### Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ IGNY8 BILLING SYSTEM │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ STRIPE │ │ PAYPAL │ │ BANK TRANSFER│ │
│ │ (Card/Intl) │ │ (Intl) │ │ (PK Only) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PAYMENT GATEWAY LAYER │ │
│ │ • Webhook Processing • Payment Verification • Logging │ │
│ └─────────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ INVOICE SERVICE │ │
│ │ • Create Invoice • Update Status • PDF Generation │ │
│ └─────────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CREDIT SERVICE │ │
│ │ • Plan Credits • Bonus Credits • Deduction • Reset │ │
│ └─────────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ACCOUNT │ │
│ │ credits (plan) │ bonus_credits (purchased) │ status │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### Key Files
| Component | File Path |
|-----------|-----------|
| Credit Service | `business/billing/services/credit_service.py` |
| Invoice Service | `business/billing/services/invoice_service.py` |
| Payment Service | `business/billing/services/payment_service.py` |
| Email Service | `business/billing/services/email_service.py` |
| Stripe Webhooks | `business/billing/views/stripe_views.py` |
| PayPal Webhooks | `business/billing/views/paypal_views.py` |
| Subscription Renewal | `business/billing/tasks/subscription_renewal.py` |
| Invoice Lifecycle | `business/billing/tasks/invoice_lifecycle.py` |
| Billing Admin | `modules/billing/admin.py` |
| Billing Models | `business/billing/models.py` |
---
## 2. Credit System
### Two-Pool Credit Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ ACCOUNT CREDITS │
├────────────────────────────┬────────────────────────────────┤
│ PLAN CREDITS │ BONUS CREDITS │
│ (account.credits) │ (account.bonus_credits) │
├────────────────────────────┼────────────────────────────────┤
│ • From subscription plan │ • From credit packages │
│ • Resets on renewal │ • NEVER expire │
│ • Used FIRST │ • Used SECOND (after plan=0) │
│ • Max = plan.included_ │ • No maximum limit │
│ credits │ │
└────────────────────────────┴────────────────────────────────┘
```
### Credit Deduction Flow
```
┌──────────────────┐
│ CREDIT REQUEST │
│ (e.g., 50) │
└────────┬─────────┘
┌──────────────────┐
│ Check Total │
│ credits + bonus │
│ >= requested? │
└────────┬─────────┘
┌──────────────┴──────────────┐
│ NO │ YES
▼ ▼
┌────────────────┐ ┌────────────────────┐
│ INSUFFICIENT │ │ Plan Credits >= 50?│
│ Return False │ └─────────┬──────────┘
└────────────────┘ │
┌──────────┴──────────┐
│ YES │ NO
▼ ▼
┌───────────────┐ ┌────────────────────┐
│ Deduct from │ │ Deduct plan credits│
│ plan credits │ │ to 0, remainder │
│ only │ │ from bonus_credits │
└───────────────┘ └────────────────────┘
```
> **Note:** The two-pool logic is internal to `CreditService.deduct_credits()`. All AI functions and other credit consumers are unaffected - they call the same credit check/deduct methods as before.
### Credit Operations
| Operation | Method | Affects | Description |
|-----------|--------|---------|-------------|
| Add Plan Credits | `add_credits()` | `credits` | Initial subscription, manual adjustment |
| Add Bonus Credits | `add_bonus_credits()` | `bonus_credits` | Credit package purchases |
| Deduct Credits | `deduct_credits()` | Both pools | AI operations, uses plan first |
| Reset on Renewal | `reset_credits_for_renewal()` | `credits` only | Sets plan credits to new amount |
| Check Balance | `check_credits()` | Read-only | Returns total available |
### Credit Transaction Types
| Type | Description |
|------|-------------|
| `subscription` | Plan credits from subscription |
| `purchase` | Bonus credits from credit package |
| `usage` | Credit consumption for AI operations |
| `refund` | Credits returned due to failed operation |
| `manual` | Admin adjustment |
| `renewal` | Reset during subscription renewal |
| `bonus` | Promotional bonus credits |
---
## 3. Payment Methods
### Payment Method by Country
```
┌─────────────────────────────────────────────────────────────┐
│ PAYMENT METHOD MATRIX │
├─────────────────┬───────────────────────────────────────────┤
│ COUNTRY │ AVAILABLE METHODS │
├─────────────────┼───────────────────────────────────────────┤
│ Pakistan (PK) │ ✅ Bank Transfer ✅ Stripe (Card) │
│ │ ❌ PayPal (not available) │
├─────────────────┼───────────────────────────────────────────┤
│ Other (Intl) │ ✅ Stripe (Card) ✅ PayPal │
│ │ ❌ Bank Transfer (not available) │
└─────────────────┴───────────────────────────────────────────┘
```
### Payment Flow by Method
#### Stripe (Card) Flow
```
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
│ User │────▶│ Frontend │────▶│ Create │────▶│ Stripe │
│ Clicks │ │ Checkout │ │ Checkout │ │ Hosted │
│ Pay │ │ │ │ Session │ │ Page │
└─────────┘ └──────────┘ └─────────────┘ └────┬─────┘
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
│ Account │◀────│ Credit │◀────│ Invoice │◀────│ Webhook │
│ Active │ │ Added │ │ Paid │ │ Received │
└─────────┘ └──────────┘ └─────────────┘ └──────────┘
```
#### PayPal Flow
```
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
│ User │────▶│ Frontend │────▶│ Create │────▶│ PayPal │
│ Clicks │ │ Checkout │ │ Order/Sub │ │ Hosted │
│ Pay │ │ │ │ │ │ Page │
└─────────┘ └──────────┘ └─────────────┘ └────┬─────┘
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
│ Account │◀────│ Credit │◀────│ Invoice │◀────│ Webhook │
│ Active │ │ Added │ │ Paid │ │ CAPTURE │
└─────────┘ └──────────┘ └─────────────┘ └──────────┘
```
#### Bank Transfer Flow (PK Only)
```
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
│ User │────▶│ View │────▶│ Manual │────▶│ Upload │
│ Selects │ │ Bank │ │ Transfer │ │ Proof │
│ Bank │ │ Details │ │ to Bank │ │ │
└─────────┘ └──────────┘ └─────────────┘ └────┬─────┘
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
│ Account │◀────│ Credit │◀────│ Admin │◀────│ Payment │
│ Active │ │ Added │ │ Approves │ │ Created │
└─────────┘ └──────────┘ └─────────────┘ └──────────┘
```
---
## 4. Subscription Lifecycle
### Subscription States
```
┌─────────────────────────────────────────────────────────────────────────┐
│ SUBSCRIPTION STATE MACHINE │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────┐
│ NEW │
│ SIGNUP │
└────┬────┘
│ Create subscription + invoice
┌─────────────┐ Payment Failed ┌─────────────┐
│ PENDING │─────────────────────────▶│ FAILED │
│ (awaiting │ │ │
│ payment) │ └─────────────┘
└──────┬──────┘
│ Payment Success
┌─────────────┐
│ ACTIVE │◀─────────────────────────────────────┐
│ │ Renewal Payment Success │
└──────┬──────┘ │
│ Renewal Date │
▼ │
┌─────────────┐ Payment Within ┌───────────┴─┐
│ PENDING │ Grace Period │ │
│ RENEWAL │────────────────────────▶│ ACTIVE │
│ │ │ (renewed) │
└──────┬──────┘ └─────────────┘
│ Grace Period Expired (7 days)
┌─────────────┐
│ EXPIRED │ Manual Reactivation
│ │────────────────────────▶ Back to PENDING
└─────────────┘
┌─────────────┐
│ CANCELLED │ User-initiated cancellation
│ │ (end of current period)
└─────────────┘
```
### Subscription Status Reference
| Status | Credits Access | Can Use Features | Next Action |
|--------|----------------|------------------|-------------|
| `pending` | ❌ No | ❌ No | Complete payment |
| `active` | ✅ Yes | ✅ Yes | None (auto-renews) |
| `pending_renewal` | ✅ Yes (24h) | ✅ Yes | Pay invoice |
| `expired` | ❌ No | ❌ Limited | Resubscribe |
| `cancelled` | ✅ Until end | ✅ Until end | None |
| `failed` | ❌ No | ❌ No | Retry payment |
---
## 5. Credit Packages
### Available Packages
| Package | Credits | USD Price | PKR Price | Per Credit |
|---------|---------|-----------|-----------|------------|
| Starter | 500 | $50.00 | ≈ PKR 14,000 | $0.10 |
| Growth | 2,000 | $200.00 | ≈ PKR 56,000 | $0.10 |
| Scale | 5,000 | $300.00 | ≈ PKR 83,000 | $0.06 |
| Enterprise | 20,000 | $1,200.00 | ≈ PKR 334,000 | $0.06 |
### Credit Package Purchase Flow
```
┌───────────────────────────────────────────────────────────────────────────┐
│ CREDIT PACKAGE PURCHASE FLOW │
└───────────────────────────────────────────────────────────────────────────┘
User selects package
┌───────────────────┐
│ Create Invoice │
│ type='credit_ │
│ package' │
└─────────┬─────────┘
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ Stripe/PayPal? │────▶│ Auto-process via │────▶│ Webhook confirms │
│ │ │ payment gateway │ │ payment success │
└─────────┬─────────┘ └───────────────────┘ └─────────┬─────────┘
│ │
│ Bank Transfer? │
▼ │
┌───────────────────┐ ┌───────────────────┐ │
│ Payment created │────▶│ Admin reviews & │ │
│ status='pending_ │ │ approves payment │ │
│ approval' │ └─────────┬─────────┘ │
└───────────────────┘ │ │
▼ ▼
┌───────────────────────────────────────┐
│ PAYMENT APPROVED │
└─────────────────┬─────────────────────┘
┌───────────────────────────────────────┐
│ CreditService.add_bonus_credits() │
│ • Adds to account.bonus_credits │
│ • Creates CreditTransaction │
│ • NEVER expires │
└───────────────────────────────────────┘
```
### Credit Package Invoice Lifecycle
```
Invoice Created ──▶ 48 hours ──▶ Reminder Sent ──▶ 48 hours ──▶ Invoice Voided
│ │ │
│ │ │
▼ ▼ ▼
status='pending' status='pending' status='void'
(reminder sent) (auto-cancelled)
```
---
## 6. Invoice System
### Invoice Types
| Type | Description | Auto-Pay | Manual Pay |
|------|-------------|----------|------------|
| `subscription` | Monthly plan payment | Stripe/PayPal | Bank Transfer |
| `credit_package` | One-time credit purchase | Stripe/PayPal | Bank Transfer |
| `addon` | Additional features | Stripe/PayPal | Bank Transfer |
| `custom` | Manual/admin-created | ❌ | ✅ |
### Invoice Status Flow
```
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ DRAFT │────▶│ SENT │────▶│ PENDING │────▶│ PAID │
└─────────┘ └─────────┘ └────┬────┘ └─────────┘
┌────────┴────────┐
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ OVERDUE │ │ FAILED │
└────┬─────┘ └──────────┘
┌──────────┐ ┌──────────┐
│ VOID │ │CANCELLED │
└──────────┘ └──────────┘
```
### Invoice Status Reference
| Status | Description | User Action |
|--------|-------------|-------------|
| `draft` | Being created | - |
| `sent` | Delivered to user | Pay Now |
| `pending` | Awaiting payment | Pay Now |
| `overdue` | Past due date | Pay Now (urgent) |
| `paid` | Payment received | Download PDF |
| `failed` | Payment failed | Retry/Pay Now |
| `void` | Cancelled by system | - |
| `cancelled` | Cancelled by admin | - |
---
## 7. Renewal Workflow
### Renewal Timeline by Payment Method
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ RENEWAL TIMELINE - STRIPE/PAYPAL (AUTO-PAY) │
│ Industry Standard: No Advance Notice │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Day 0 (Renewal) Day +1 Day +7 │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌────────────┐ ┌──────────┐ │
│ │Auto-Pay │ │ If Failed: │ │ Expired │ │
│ │ Attempt │ │ Retry + │ │ (after │ │
│ └────┬─────┘ │ Email Sent │ │ retries) │ │
│ │ └────────────┘ └──────────┘ │
│ ┌────┴────┐ │
│ │ SUCCESS │──▶ Receipt email + Credits reset to plan amount │
│ └────┬────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ FAILURE │──▶ Payment failed email, Stripe retries 4x over 7 days │
│ └─────────┘ │
│ │
│ EMAIL SCHEDULE (Industry Standard - Netflix, Spotify, Adobe): │
│ • Payment Success: Receipt immediately │
│ • Payment Failed: Notification after each retry attempt │
│ • Final Warning: 1 day before account suspension │
│ • Account Suspended: When grace period ends │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ RENEWAL TIMELINE - BANK TRANSFER │
│ Simplified 3-Email Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Day -3 Day 0 Day +1 Day +7 │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Invoice │ │Reminder │ │ Urgent │ │ Expired │ │
│ │Created │ │ Email │ │Reminder │ │ │ │
│ │+Email │ │(if not │ │+Credits │ │ │ │
│ │ │ │ paid) │ │ Reset │ │ │ │
│ └────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Timeline: │
│ • Day -3: Invoice created + Email sent with payment instructions │
│ • Day 0: Reminder email (renewal day, if still unpaid) │
│ • Day +1: Urgent reminder + credits reset to 0 (if unpaid) │
│ • Day +7: Subscription expired │
│ │
│ EMAILS: │
│ 1. Invoice Email (Day -3): Invoice attached, bank details, Pay Now link │
│ 2. Renewal Reminder (Day 0): "Your subscription renews today" │
│ 3. Urgent Reminder (Day +1): "Payment overdue - action required" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### Renewal Credit Behavior
```
┌───────────────────────────────────────────────────────────────────┐
│ CREDIT RESET ON RENEWAL │
├───────────────────────────────────────────────────────────────────┤
│ │
│ SCENARIO 1: Payment Before Renewal │
│ ───────────────────────────────────── │
│ • Credits NOT reset early │
│ • On payment confirmation: │
│ - plan credits → reset to plan.included_credits │
│ - bonus_credits → UNCHANGED (never affected) │
│ │
│ SCENARIO 2: Payment After Renewal (within 24h) │
│ ────────────────────────────────────────────── │
│ • Credits stay unchanged for 24 hours │
│ • On payment confirmation: │
│ - plan credits → reset to plan.included_credits │
│ - bonus_credits → UNCHANGED │
│ │
│ SCENARIO 3: No Payment After 24 Hours │
│ ───────────────────────────────────── │
│ • plan credits → reset to 0 (task: send_day_after_reminders) │
│ • bonus_credits → UNCHANGED (user can still use these) │
│ • Warning email sent │
│ │
│ SCENARIO 4: Payment After Credit Reset │
│ ────────────────────────────────────── │
│ • On payment confirmation: │
│ - plan credits → reset to plan.included_credits (restored) │
│ - bonus_credits → UNCHANGED │
│ │
└───────────────────────────────────────────────────────────────────┘
```
### Celery Tasks Schedule
| Task | Schedule | Purpose |
|------|----------|---------|
| `create_bank_transfer_invoices` | Daily 09:00 | Create invoices 3 days before renewal (bank transfer only) |
| `process_subscription_renewals` | Daily 00:05 | Process auto-pay renewals (Stripe/PayPal) |
| `send_renewal_day_reminders` | Daily 10:00 | Send Day 0 reminder for bank transfer (if unpaid) |
| `send_day_after_reminders` | Daily 09:15 | Send Day +1 urgent reminders + reset credits |
| `check_expired_renewals` | Daily 00:15 | Mark subscriptions expired after 7-day grace period |
| `send_credit_invoice_expiry_reminders` | Daily 09:30 | Remind about expiring credit package invoices |
| `void_expired_credit_invoices` | Daily 00:45 | Auto-void credit invoices after 48h |
### Email Schedule Summary
**Stripe/PayPal (Auto-Pay) - Industry Standard:**
| Event | Email |
|-------|-------|
| Payment Success | ✅ Receipt/Confirmation |
| Payment Failed | ⚠️ Retry notification (per attempt) |
| Final Warning | 🚨 1 day before suspension |
| Account Suspended | ❌ Subscription expired |
**Bank Transfer (Manual Pay):**
| Day | Email |
|-----|-------|
| Day -3 | 📧 Invoice created + payment instructions |
| Day 0 | ⏰ Renewal day reminder (if unpaid) |
| Day +1 | 🚨 Urgent reminder + credits reset warning |
---
## 8. Admin Operations
### Webhook Events (Payment Logs)
**Location:** Admin → Billing → Webhook Events
| Column | Description |
|--------|-------------|
| Event ID | Unique ID from Stripe/PayPal |
| Provider | STRIPE or PAYPAL badge |
| Event Type | e.g., `checkout.session.completed`, `PAYMENT.CAPTURE.COMPLETED` |
| Status | Processed ✓, Failed ✗, Pending ⏳ |
| Process Time | Actual processing duration |
| Created | When webhook received |
**Actions:**
- Mark as processed
- Retry processing
- View full payload (JSON)
### Payment Approval (Bank Transfer)
**Location:** Admin → Billing → Payments
**Approval Flow:**
1. Filter by `status = pending_approval`
2. Review `manual_reference` and `manual_notes`
3. Check proof of payment upload
4. Change status to `succeeded`
5. System automatically:
- Updates invoice to `paid`
- Activates account (if subscription)
- Adds credits (plan or bonus based on invoice type)
### Manual Credit Adjustment
**Location:** Admin → Billing → Credit Transactions
**To add credits manually:**
1. Go to Account admin
2. Edit the account
3. Modify `credits` (plan) or `bonus_credits` (purchased)
4. Save with note in admin_notes
**OR use shell:**
```python
from igny8_core.business.billing.services.credit_service import CreditService
# Add plan credits
CreditService.add_credits(
account=account,
amount=500,
transaction_type='manual',
description='Manual adjustment - support ticket #123'
)
# Add bonus credits
CreditService.add_bonus_credits(
account=account,
amount=500,
description='Promotional bonus - January 2026'
)
```
---
## 9. API Reference
### Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/billing/credits/` | GET | Get credit balance |
| `/api/v1/billing/credits/usage/` | GET | Get usage statistics |
| `/api/v1/billing/invoices/` | GET | List invoices |
| `/api/v1/billing/invoices/{id}/` | GET | Invoice detail |
| `/api/v1/billing/invoices/{id}/pdf/` | GET | Download invoice PDF |
| `/api/v1/billing/payments/` | GET | List payments |
| `/api/v1/billing/plans/` | GET | List available plans |
| `/api/v1/billing/subscriptions/` | GET | List subscriptions |
| `/api/v1/billing/credit-packages/` | GET | List credit packages |
| `/api/v1/billing/purchase/credits/` | POST | Purchase credit package |
| `/api/v1/billing/subscribe/` | POST | Subscribe to plan |
| `/api/v1/webhooks/stripe/` | POST | Stripe webhook endpoint |
| `/api/v1/webhooks/paypal/` | POST | PayPal webhook endpoint |
### Credit Balance Response
```json
{
"credits": 3500,
"bonus_credits": 2000,
"total_credits": 5500,
"credits_used_this_month": 1500,
"plan_credits_per_month": 5000,
"subscription_plan": "Scale",
"period_end": "2026-02-12T00:00:00Z"
}
```
---
## 10. Database Schema
### Core Tables
```
┌─────────────────────────────────────────────────────────────────────────┐
│ accounts │
├─────────────────────────────────────────────────────────────────────────┤
│ id │ PK │
│ name │ Account name │
│ status │ pending, active, expired, etc. │
│ credits │ Plan credits (resets on renewal) │
│ bonus_credits │ Purchased credits (never expire) │
│ plan_id │ FK → plans │
│ billing_email │ Email for invoices │
│ billing_country │ Country code (PK, US, etc.) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ subscriptions │
├─────────────────────────────────────────────────────────────────────────┤
│ id │ PK │
│ account_id │ FK → accounts │
│ plan_id │ FK → plans │
│ status │ pending, active, pending_renewal, expired, etc. │
│ current_period_start │ Start of current billing period │
│ current_period_end │ End of current billing period (renewal date) │
│ metadata │ JSON (stripe_subscription_id, paypal_sub_id) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ invoices │
├─────────────────────────────────────────────────────────────────────────┤
│ id │ PK │
│ invoice_number │ Unique invoice number (INV-2026-00001) │
│ account_id │ FK → accounts │
│ subscription_id │ FK → subscriptions (nullable) │
│ invoice_type │ subscription, credit_package, addon, custom │
│ status │ draft, sent, pending, paid, overdue, void, etc. │
│ total_amount │ Total amount │
│ currency │ USD, PKR │
│ due_date │ Payment due date │
│ paid_at │ When payment received │
│ metadata │ JSON (credit_package_id, etc.) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ payments │
├─────────────────────────────────────────────────────────────────────────┤
│ id │ PK │
│ invoice_id │ FK → invoices │
│ account_id │ FK → accounts │
│ amount │ Payment amount │
│ currency │ USD, PKR │
│ payment_method │ stripe, paypal, bank_transfer │
│ status │ pending_approval, processing, succeeded, failed│
│ stripe_payment_intent_id│ Stripe reference │
│ paypal_order_id │ PayPal reference │
│ manual_reference │ Bank transfer reference │
│ approved_by_id │ FK → users (admin who approved) │
│ approved_at │ When approved │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ credit_transactions │
├─────────────────────────────────────────────────────────────────────────┤
│ id │ PK │
│ account_id │ FK → accounts │
│ transaction_type │ subscription, purchase, usage, refund, manual, etc. │
│ amount │ Credits added (+) or deducted (-) │
│ balance_after │ Balance after this transaction │
│ description │ Human-readable description │
│ metadata │ JSON (invoice_id, payment_id, etc.) │
│ created_at │ Timestamp │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ webhook_events │
├─────────────────────────────────────────────────────────────────────────┤
│ id │ PK │
│ event_id │ Unique event ID from provider │
│ provider │ stripe, paypal │
│ event_type │ checkout.session.completed, PAYMENT.CAPTURE.COMPLETED │
│ payload │ JSON - full webhook payload │
│ processed │ Boolean - successfully processed? │
│ processed_at │ When processed │
│ error_message│ Error if processing failed │
│ retry_count │ Number of retry attempts │
│ created_at │ When received │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## Quick Reference Card
### Credit Consumption Priority
1. **Plan credits** used first
2. **Bonus credits** used only when plan credits = 0
### What Happens on Renewal
| Event | Plan Credits | Bonus Credits |
|-------|--------------|---------------|
| Payment success | Reset to plan amount | No change |
| No payment (24h) | Reset to 0 | No change |
| Late payment | Reset to plan amount | No change |
### Payment Method Availability
| Country | Stripe | PayPal | Bank Transfer |
|---------|--------|--------|---------------|
| Pakistan (PK) | ✅ | ❌ | ✅ |
| Others | ✅ | ✅ | ❌ |
### Invoice Expiry
| Invoice Type | Expiry |
|--------------|--------|
| Subscription | 7 days (grace period) |
| Credit Package | 48 hours |
---
*Document generated from current codebase implementation as of January 20, 2026*

View File

@@ -0,0 +1,384 @@
# Billing Module
**Last Verified:** January 20, 2026
**Status:** ✅ Active (Two-Pool Credit System v2.0 January 2026)
**Backend Path:** `backend/igny8_core/modules/billing/` + `backend/igny8_core/business/billing/`
**Frontend Path:** `frontend/src/pages/Billing/` + `frontend/src/pages/Account/`
> **Complete Billing Reference:** For comprehensive billing & payment documentation, see [BILLING-PAYMENTS-COMPLETE.md](BILLING-PAYMENTS-COMPLETE.md)
> **Payment System Reference:** For payment gateway documentation (Stripe, PayPal, Bank Transfer), see [PAYMENT-SYSTEM.md](../90-REFERENCE/PAYMENT-SYSTEM.md)
---
## Two-Pool Credit System (v2.0)
| Pool | Field | Source | Behavior |
|------|-------|--------|----------|
| **Plan Credits** | `account.credits` | Subscription plan | Reset on renewal, reset to 0 if unpaid after 24h |
| **Bonus Credits** | `account.bonus_credits` | Credit packages | **NEVER expire**, **NEVER reset** |
### Credit Usage Priority
1. **Plan credits** used FIRST
2. **Bonus credits** only used when plan credits = 0
### What Happens on Renewal
| Event | Plan Credits | Bonus Credits |
|-------|--------------|---------------|
| Payment success | Reset to plan amount | No change |
| No payment (24h) | Reset to 0 | No change |
| Late payment | Reset to plan amount | No change |
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Models | `business/billing/models.py` | `CreditTransaction`, `CreditUsageLog`, `CreditCostConfig`, `AIModelConfig` |
| Service | `business/billing/services/credit_service.py` | `CreditService` |
| Limit Service | `business/billing/services/limit_service.py` | `LimitService` (4 limits only) |
| Views | `modules/billing/views.py` | `CreditBalanceViewSet`, `CreditUsageViewSet` |
| Frontend | `pages/Account/PlansAndBillingPage.tsx` | Plans, credits, billing history |
| Store | `store/billingStore.ts` | `useBillingStore` |
---
## Purpose
The Billing module manages:
- Credit balance and transactions
- AI model pricing and credit configuration (v1.4.0)
- Usage tracking with 4 simplified limits (v1.5.0)
- Plan enforcement
- Payment processing
---
## Data Models
### AIModelConfig (NEW v1.4.0)
Single Source of Truth for all AI models with pricing.
| Field | Type | Purpose |
|-------|------|---------|
| model_name | CharField(100) | Model identifier (gpt-4o-mini, dall-e-3) |
| model_type | CharField(20) | text / image |
| provider | CharField(50) | openai, anthropic, runware |
| display_name | CharField(200) | Human-readable name |
| is_default | BooleanField | One default per type |
| is_active | BooleanField | Enable/disable |
| cost_per_1k_input | DecimalField | USD cost per 1K input tokens (text) |
| cost_per_1k_output | DecimalField | USD cost per 1K output tokens (text) |
| tokens_per_credit | IntegerField | Text: tokens per 1 credit (e.g., 1000) |
| credits_per_image | IntegerField | Image: credits per image (1, 5, 15) |
| quality_tier | CharField(20) | basic / quality / premium |
| max_tokens | IntegerField | Model token limit |
| context_window | IntegerField | Model context size |
| capabilities | JSONField | vision, function_calling, etc. |
**Credit Examples:**
| Model | tokens_per_credit | credits_per_image | quality_tier |
|-------|-------------------|-------------------|--------------|
| gpt-4o | 1000 | - | - |
| gpt-4o-mini | 10000 | - | - |
| runware:97@1 | - | 1 | basic |
| dall-e-3 | - | 5 | quality |
| google:4@2 | - | 15 | premium |
### CreditTransaction (Ledger)
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| transaction_type | CharField | purchase/subscription/refund/deduction/adjustment |
| amount | Decimal | Positive (add) or negative (deduct) |
| balance_after | Decimal | Balance after transaction |
| description | CharField | Transaction description |
| metadata | JSON | Additional data |
| payment | FK(Payment) | Payment that triggered this (v1.4.0) |
| reference_id | CharField | DEPRECATED: Use payment FK |
| created_at | DateTime | Transaction time |
### CreditUsageLog (Analytics)
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| operation_type | CharField | clustering/idea_generation/content_generation/image_generation |
| credits_used | Decimal | Credits consumed |
| cost_usd | Decimal | USD cost |
| model_used | CharField | AI model used |
| tokens_in | Integer | Input tokens |
| tokens_out | Integer | Output tokens |
| content_type | FK | Related content type |
| object_id | Integer | Related object ID |
| metadata | JSON | Additional data |
| created_at | DateTime | Usage time |
### CreditCostConfig (Updated v1.4.0)
| Field | Type | Purpose |
|-------|------|---------|
| operation_type | CharField(50) PK | Unique operation ID |
| display_name | CharField(100) | Human-readable name |
| base_credits | IntegerField | Fixed credits per operation |
| is_active | BooleanField | Enable/disable |
| description | TextField | Admin notes |
**Note:** `tokens_per_credit` moved to AIModelConfig in v1.4.0.
---
## Credit Calculation Methods
### Method 1: Token-Based (Text Models) - Updated v1.4.0
Used for: Clustering, Ideas, Content Generation, Optimization
**Flow:**
1. AI call completes
2. OpenAI returns actual token usage
3. Calculate: `total_tokens = input_tokens + output_tokens`
4. Look up `AIModelConfig.tokens_per_credit` for the model used
5. Calculate: `credits = ceil(total_tokens / tokens_per_credit)`
6. Deduct from balance
**Example:**
- Model: `gpt-4o-mini` (tokens_per_credit = 10000)
- Tokens used: 15000
- Credits: `ceil(15000 / 10000)` = 2 credits
### Method 2: Fixed Cost (Image Models) - Updated v1.4.0
Used for: Image Generation
**Flow:**
1. User selects quality tier (basic/quality/premium)
2. Look up `AIModelConfig.credits_per_image` for selected tier
3. Check balance sufficient: `balance >= num_images * credits_per_image`
4. Deduct credits
5. Make AI call
**Example:**
- Quality Tier: "quality" (dall-e-3, credits_per_image = 5)
- Images: 3
- Credits: 3 × 5 = 15 credits
2. Calculate: `credits = num_images * cost_per_image`
3. Check balance sufficient
4. Deduct credits
5. Then make AI call
---
## API Endpoints
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/billing/balance/` | `CreditBalanceViewSet.list` | Current balance + monthly usage |
| GET | `/api/v1/billing/usage/` | `CreditUsageViewSet.list` | Usage log (paginated) |
| GET | `/api/v1/billing/usage/summary/` | `CreditUsageViewSet.summary` | Aggregated by operation |
| GET | `/api/v1/billing/usage/limits/` | `CreditUsageViewSet.limits` | Plan limits + current usage |
| GET | `/api/v1/billing/transactions/` | `TransactionViewSet.list` | Transaction history |
---
## Credit Service API
### Check Credits
```python
CreditService.check_credits(account, required_credits)
# Raises InsufficientCreditsError if balance < required
```
### Deduct Credits
```python
CreditService.deduct_credits_for_operation(
account=account,
operation_type='content_generation',
amount=word_count, # For per_word operations
model='gpt-4o-mini',
tokens_in=2500,
tokens_out=1500,
metadata={'content_id': 123}
)
```
### Add Credits
```python
CreditService.add_credits(
account=account,
amount=1000,
transaction_type='purchase',
description='Credit package purchase'
)
```
### Calculate Credits for Images (NEW v1.4.0)
```python
# Calculate credits needed for image generation based on model
credits = CreditService.calculate_credits_for_image(
model_name='dall-e-3', # Model with credits_per_image = 5
num_images=3
)
# Returns: 15 (3 images × 5 credits)
```
### Check Credits for Image Generation (NEW v1.7.1)
```python
# Pre-check credits before image generation starts
# Raises InsufficientCreditsError if not enough credits
required = CreditService.check_credits_for_image(
account=account,
model_name='dall-e-3', # Model with credits_per_image = 5
num_images=3
)
# Returns: 15 (required credits) if sufficient, raises exception if not
```
### Deduct Credits for Image Generation (v1.7.1 Verified)
```python
# Called after each successful image generation
credits_deducted = CreditService.deduct_credits_for_image(
account=account,
model_name='dall-e-3',
num_images=1,
description='Image generation: Article Title',
metadata={'image_id': 123, 'content_id': 456},
cost_usd=0.04,
related_object_type='image',
related_object_id=123
)
# Logs to CreditTransaction and CreditUsageLog
```
### Calculate Credits from Tokens by Model (NEW v1.4.0)
```python
# Calculate credits from token usage based on model's tokens_per_credit
credits = CreditService.calculate_credits_from_tokens_by_model(
model_name='gpt-4o-mini', # Model with tokens_per_credit = 10000
total_tokens=15000
)
# Returns: 2 (ceil(15000 / 10000))
```
---
## Plan Limits
### Hard Limits (Never Reset)
| Limit | Field | Description |
|-------|-------|-------------|
| Sites | `max_sites` | Maximum sites per account |
| Users | `max_users` | Maximum team members |
| Keywords | `max_keywords` | Total keywords allowed |
### Monthly Limits (Reset on Billing Cycle)
| Limit | Field | Description |
|-------|-------|-------------|
| Ahrefs Queries | `max_ahrefs_queries` | Live Ahrefs API queries per month |
**Note:** As of January 2026, the limit system was simplified from 10+ limits to just 4. Credits handle all AI operation costs (content generation, image generation, clustering, etc.) instead of separate per-operation limits.
---
## Usage Limits Panel
**Component:** `UsageLimitsPanel.tsx`
Displays:
- Progress bars for 4 limits only (Sites, Users, Keywords, Ahrefs Queries)
- Color coding: blue (safe), yellow (warning), red (critical)
- Days until reset for monthly limits (Ahrefs Queries)
- Upgrade CTA when approaching limits
---
## Credit Costs Reference (Updated v1.4.0)
**Text Model Credits** (from `AIModelConfig.tokens_per_credit`):
| Model | tokens_per_credit | Cost/Credit | Notes |
|-------|-------------------|-------------|-------|
| gpt-4o | 1000 | ~$0.015 | High quality, lower throughput |
| gpt-4o-mini | 10000 | ~$0.001 | Fast, cost-effective |
| gpt-4.5-preview | 500 | ~$0.05 | Highest quality |
**Image Model Credits** (from `AIModelConfig.credits_per_image`):
| Quality Tier | credits_per_image | Model Example | Notes |
|--------------|-------------------|---------------|-------|
| Basic | 1 | runware:97@1 | Fast generation |
| Quality | 5 | dall-e-3 | Balanced |
| Premium | 15 | google:4@2 | Best quality |
**Operation Base Costs** (from `CreditCostConfig.base_credits`):
| Operation | Base Credits | Notes |
|-----------|--------------|-------|
| Clustering | 10 | Per clustering request |
| Idea Generation | 2 | Per idea generated |
| Content Optimization | 5 | Per optimization run |
---
## Frontend Pages
### Plans & Billing (`/account/plans`)
**Tabs:**
1. **Current Plan** - Active plan details, renewal date, "View Usage" link
2. **Upgrade Plan** - Pricing table with plan comparison
3. **Billing History** - Invoices and payment history
### Usage Analytics (`/account/usage`)
**Tabs:**
1. **Limits & Usage** - Plan limits with progress bars (4 limits only)
2. **Credit History** - Credit transaction history
3. **Credit Insights** - Charts: credits by type, daily timeline, operations breakdown
4. **Activity Log** - API call statistics and operation details
---
## Integration Points
| From | To | Trigger |
|------|----|---------|
| AIEngine | CreditService | Pre-check and post-deduct |
| Automation | CreditService | Per-stage credit tracking |
| Subscription | Account | Credit allocation |
| Admin | CreditCostConfig | Price adjustments |
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| 402 error | Insufficient credits | Add credits or reduce operation |
| Usage not showing | Log not created | Check CreditService called |
| Limits wrong | Cache stale | Clear cache, reload |
| Monthly usage high | Automation running | Pause automation |
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| ~~AI Model Config database~~ | ✅ v1.4.0 | Model pricing moved to AIModelConfig |
| ~~Image model quality tiers~~ | ✅ v1.4.0 | credits_per_image per quality tier |
| ~~Credit service enhancements~~ | ✅ v1.7.1 | Model-specific calculation methods |
| ~~Image generation credit check~~ | ✅ v1.7.1 | Pre-generation credit verification |
| ~~Image generation logging~~ | ✅ v1.7.1 | AITaskLog + notifications for images |

View File

@@ -0,0 +1,368 @@
# Integrations Module
**Last Verified:** January 20, 2026
**Version:** 1.8.4
**Status:** ✅ Active
**Backend Path:** `backend/igny8_core/modules/integration/` + `backend/igny8_core/business/integration/`
**Frontend Path:** `frontend/src/pages/Settings/IntegrationSettings.tsx`
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Models | `business/integration/models.py` | `SiteIntegration`, `SyncEvent` |
| Views | `modules/integration/views.py` | `SiteIntegrationViewSet` |
| Webhooks | `modules/integration/webhooks.py` | `wordpress_webhook` |
| Services | `business/integration/services/*.py` | Sync services |
| AI Core | `ai/ai_core.py` | OpenAI, Anthropic, Runware, Bria clients |
| Model Registry | `ai/model_registry.py` | Centralized model configs |
| Frontend | `pages/Settings/IntegrationSettings.tsx` | Integration UI |
---
## AI Provider Integrations (v1.3.0)
### Supported Providers
| Provider | Type | Models | API Key Field |
|----------|------|--------|---------------|
| OpenAI | Text/Image | gpt-4o, gpt-4o-mini, dall-e-3 | `openai_api_key` |
| Anthropic | Text | claude-3-5-sonnet, claude-3-opus, claude-3-haiku | `anthropic_api_key` |
| Runware | Image | runware-v2 | `runware_api_key` |
| Bria AI | Image | bria-2.3, bria-2.3-fast, bria-2.2 | `bria_api_key` |
### GlobalIntegrationSettings
Located in `modules/system/global_settings_models.py`:
| Field | Type | Purpose |
|-------|------|---------|
| openai_api_key | CharField | OpenAI API key |
| anthropic_api_key | CharField | Anthropic API key (v1.3.0) |
| runware_api_key | CharField | Runware API key |
| bria_api_key | CharField | Bria AI API key (v1.3.0) |
| default_text_model | CharField | Default text model |
| default_image_model | CharField | Default image model |
| default_image_provider | CharField | openai/runware/bria |
### Model Registry
Centralized model configuration with caching:
```python
from igny8_core.ai.model_registry import ModelRegistry
# Get model config
model = ModelRegistry.get_model('gpt-4o-mini')
# Calculate cost
cost = ModelRegistry.calculate_cost('gpt-4o-mini', input_tokens=1000, output_tokens=500)
# List all models
models = ModelRegistry.list_models(model_type='text', provider='openai')
```
### Migrations
- `0012_add_bria_integration.py` - Adds Bria AI integration settings
- `0013_add_anthropic_integration.py` - Adds Anthropic integration settings
- `0009_seed_ai_model_configs.py` - Seeds model configurations
---
## Purpose
The Integrations module manages:
- AI provider connections (OpenAI, Anthropic, Runware, Bria)
- WordPress site connections
- Two-way content synchronization
- Webhook handling
- External platform integrations
---
## Data Models
### Authentication Note
**⚠️ Important:** For WordPress integrations, `Site.wp_api_key` is the **SINGLE source of truth** for API authentication, NOT SiteIntegration fields. The SiteIntegration model is used for sync tracking and multi-platform support (future: Shopify).
### SiteIntegration
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | IGNY8 site |
| platform | CharField | wordpress/shopify |
| external_site_url | URLField | External site URL |
| is_active | Boolean | Enable/disable |
| sync_enabled | Boolean | Enable auto-sync |
| last_sync_at | DateTime | Last sync time |
| sync_status | CharField | pending/syncing/completed/error |
| sync_error | TextField | Sync error message |
| connection_status | CharField | connected/error |
| config_json | JSON | Platform-specific configuration |
| credentials_json | JSON | (Reserved for future platforms, NOT used for WordPress) |
| created_at | DateTime | Creation date |
### SyncEvent
| Field | Type | Purpose |
|-------|------|---------|
| integration | FK | Parent integration |
| event_type | CharField | content_push/content_pull/webhook |
| direction | CharField | igny8_to_wp/wp_to_igny8 |
| content_type | CharField | post/page/product |
| content_id | Integer | IGNY8 content ID |
| external_id | Integer | WordPress post ID |
| status | CharField | pending/success/failed |
| error_message | TextField | Error details |
| metadata | JSON | Additional data |
| created_at | DateTime | Event time |
### PublishingSettings (v1.3.2)
Site-level publishing configuration. Used by the publishing scheduler.
| Field | Type | Purpose |
|-------|------|---------|
| site | OneToOneField | Parent site (unique per site) |
| auto_approval_enabled | BooleanField | Auto-approve content (default: False) |
| auto_publish_enabled | BooleanField | Auto-publish approved content (default: False) |
| daily_publish_limit | IntegerField | Max publications per day (default: 5) |
| weekly_publish_limit | IntegerField | Max per week (default: 20) |
| monthly_publish_limit | IntegerField | Max per month (default: 60) |
| publish_days | JSONField | Days for publishing ["mon","wed","fri"] |
| publish_time_slots | JSONField | Time slots [{"start":"09:00","end":"17:00"}] |
**Helper Method:**
```python
# Get or create settings for a site
settings, created = PublishingSettings.get_or_create_for_site(site)
```
**Related:** See [PUBLISHER.md](PUBLISHER.md) for publishing scheduler details.
---
## API Endpoints
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/integration/` | `SiteIntegrationViewSet.list` | List integrations |
| POST | `/api/v1/integration/` | `SiteIntegrationViewSet.create` | Create integration |
| PUT | `/api/v1/integration/{id}/` | `SiteIntegrationViewSet.update` | Update integration |
| DELETE | `/api/v1/integration/{id}/` | `SiteIntegrationViewSet.destroy` | Remove integration |
| POST | `/api/v1/integration/{id}/test_connection/` | Test connection | Verify credentials |
| POST | `/api/v1/integration/{id}/test_collection_connection/` | Test per-collection | Test specific content type |
| POST | `/api/v1/integration/{id}/sync/` | Trigger sync | Start synchronization |
| GET | `/api/v1/integration/{id}/sync_status/` | Get sync status | Current sync progress |
| POST | `/api/v1/integration/{id}/update_structure/` | Update structure | Refresh site structure |
| GET | `/api/v1/integration/{id}/content_types/` | Get content types | Available types + counts |
| GET | `/api/v1/integration/{id}/sync_health/` | Get sync health | Sync statistics |
| POST | `/api/v1/integration/site_sync/` | Site-level sync | Sync by site ID |
### Webhooks
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| POST | `/api/v1/integration/webhook/wordpress/` | `wordpress_webhook` | Receive WP updates |
---
## WordPress Integration
### Setup Flow
1. User enters WordPress site URL
2. User creates Application Password in WordPress
3. User enters credentials in IGNY8
4. IGNY8 tests connection via WordPress REST API
5. If successful, saves `SiteIntegration` record
### Required WordPress Setup
- WordPress 5.6+ (REST API enabled)
- Application Passwords enabled
- Pretty permalinks enabled
- REST API accessible (no blocking plugins)
### Test Connection
**Endpoint:** `POST /integration/{id}/test_connection/`
**Flow:**
1. Call WordPress `/wp-json/wp/v2/users/me`
2. Verify authentication works
3. Return user info and site capabilities
---
## Content Synchronization
### IGNY8 → WordPress (Push)
**Trigger:** User clicks "Publish to WordPress"
**Flow:**
1. Content is in `approved` status
2. Get `SiteIntegration` for content's site
3. Build WordPress post payload:
- title, content, excerpt, slug
- status: `publish` or `draft`
- categories, tags (mapped)
- featured media (if image exists)
4. Call WordPress REST API:
- `POST /wp-json/wp/v2/posts` (new)
- `PUT /wp-json/wp/v2/posts/{id}` (update)
5. Store `wordpress_id` on Content
6. Create `SyncEvent` record
7. Update Content status to `published`
### WordPress → IGNY8 (Pull)
**Trigger:** Manual sync or webhook
**Flow:**
1. Call WordPress `/wp-json/wp/v2/posts`
2. For each post:
- Check if exists in IGNY8 by `wordpress_id`
- If exists: Update if modified
- If new: Create Content record
3. Sync taxonomies (categories, tags)
4. Create `SyncEvent` records
### Webhook Handling
**WordPress Plugin** sends webhooks to IGNY8 when:
- Post created/updated/deleted
- Post status changed (draft → published)
**Webhook Payload:**
```json
{
"action": "post_updated",
"post_id": 123,
"post_type": "post",
"site_url": "https://example.com"
}
```
**IGNY8 Processing:**
1. Validate webhook signature (optional)
2. Find matching `SiteIntegration`
3. Fetch full post from WordPress
4. Update/create Content in IGNY8
5. Create `SyncEvent` record
---
## Image Sync
### Push (IGNY8 → WordPress)
1. Image record has `image_url`
2. Download image from URL
3. Upload to WordPress Media Library
4. Get WordPress attachment ID
5. Set as featured image on post
### Pull (WordPress → IGNY8)
1. Get featured media ID from post
2. Fetch media URL from WordPress
3. Store URL in IGNY8 Images record
---
## Taxonomy Sync
### Categories
1. Get WordPress categories via `/wp-json/wp/v2/categories`
2. Create `ContentTaxonomy` records (type=category)
3. Store `wordpress_id` for mapping
### Tags
1. Get WordPress tags via `/wp-json/wp/v2/tags`
2. Create `ContentTaxonomy` records (type=tag)
3. Store `wordpress_id` for mapping
---
## Sync Status Tracking
| Status | Description |
|--------|-------------|
| idle | No sync in progress |
| syncing | Sync currently running |
| error | Last sync failed |
**Sync Health Metrics:**
- Total synced posts
- Last successful sync
- Failed sync count
- Pending items
---
## Frontend Pages
### Integration Settings (`/settings/integration`)
- Add new integration button
- List of configured integrations
- Status indicators (connected/error)
- Test connection button
- Sync now button
- Sync status and history
### WordPress Sync Dashboard (`/sites/{id}/sync`)
- Detailed sync status
- Content sync queue
- Error log
- Manual sync triggers
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Connection failed | Invalid credentials | Verify Application Password |
| 401 Unauthorized | Password expired | Regenerate Application Password |
| 403 Forbidden | REST API blocked | Check security plugins |
| Images not syncing | CORS/URL issues | Verify image URLs accessible |
| Categories missing | Not synced | Run sync_structure first |
---
## Security
### Credential Storage
- API keys encrypted at rest
- Never exposed in API responses
- Application Passwords rotatable
### Webhook Security
- Optional signature verification
- Site URL validation
- Rate limiting on webhook endpoint
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| Shopify integration | 🔜 Planned | E-commerce platform support |
| Ghost integration | 🔜 Planned | Ghost CMS support |
| Webflow integration | 🔜 Planned | Webflow CMS support |
| Scheduled sync | 🔜 Planned | Automatic periodic sync |
| Conflict resolution | 🔜 Planned | Handle edit conflicts |

View File

@@ -0,0 +1,184 @@
# Linker Module
**Last Verified:** January 20, 2026
**Version:** 1.8.4
**Status:** ⏸️ Inactive (Disabled by Default)
**Backend Path:** `backend/igny8_core/modules/linker/`
**Frontend Path:** `frontend/src/pages/Linker/`
---
## Module Status
| Aspect | Current State | Notes |
|--------|---------------|-------|
| Backend API | ✅ Implemented | Endpoints functional |
| Frontend Pages | ✅ Implemented | UI exists |
| Sidebar Nav | ⚠️ Conditional | Hidden when disabled |
| Route Protection | ❌ Not Protected | Direct URL access still works |
| Dashboard References | ❌ Not Hidden | May show in dashboard |
| Default State | Disabled | `linker_enabled = True` in model, but typically disabled |
**⚠️ Pending Implementation:** Extend module disable to route-level protection and all page references.
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Views | `modules/linker/views.py` | `LinkerViewSet` |
| API | `modules/linker/urls.py` | Linker endpoints |
| Frontend Pages | `pages/Linker/index.tsx` | Linker overview |
| Frontend Pages | `pages/Linker/LinkerContent.tsx` | Content for linking |
| API Client | `api/linker.api.ts` | `linkerApi` |
| Module Control | `modules/system/settings_models.py` | `ModuleEnableSettings.linker_enabled` |
---
## Purpose
The Linker module provides internal linking automation:
- Identify link opportunities in content
- Suggest relevant internal links
- Auto-inject links into content body
---
## API Endpoints
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| POST | `/api/v1/linker/process/` | `LinkerViewSet.process` | Process single content for linking |
| POST | `/api/v1/linker/batch_process/` | `LinkerViewSet.batch_process` | Process multiple content items |
### Process Request
```json
{
"content_id": 123,
"max_links": 5,
"anchor_strategy": "exact_match"
}
```
### Process Response
```json
{
"content_id": 123,
"links_added": 3,
"link_opportunities": [
{
"anchor_text": "SEO optimization",
"target_url": "/blog/seo-guide/",
"target_content_id": 456,
"position": 234,
"confidence": 0.85
}
]
}
```
---
## Business Logic
### Link Candidate Discovery
**Trigger:** User initiates linking process
**Flow:**
1. Load source content body
2. Extract potential anchor phrases
3. Find matching content by:
- Keyword overlap
- Title similarity
- Same sector/industry
4. Score candidates by relevance
5. Filter to avoid over-linking
### Link Injection
**Trigger:** User approves link suggestions
**Flow:**
1. Locate anchor text in content
2. Wrap in `<a>` tag with target URL
3. Ensure no nested links
4. Save updated content body
---
## Module Enable Control
### Backend Model
```python
# modules/system/settings_models.py
class ModuleEnableSettings(AccountBaseModel):
linker_enabled = models.BooleanField(default=True)
```
### Frontend Check
```typescript
// layout/AppSidebar.tsx
if (isModuleEnabled('linker')) {
workflowItems.push({
name: "Linker",
path: "/linker/content",
});
}
```
### Current Limitation
Direct URL access to `/linker/*` routes still works even when module is disabled.
---
## Frontend Pages
### Linker Overview (`/linker`)
- Module overview
- Quick stats
- Start linking action
### Content for Linking (`/linker/content`)
- List of content available for linking
- Link status indicators
- Process button per content
- Batch processing action
---
## Integration Points
| From | To | Trigger |
|------|----|---------|
| Content | Linker | Manual process |
| Linker | Content | Link injection |
| Automation | Linker | Automated linking (future) |
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Module visible when disabled | Only sidebar hidden | Pending: route protection |
| No link candidates | No matching content | Create more related content |
| Links broken | Target content deleted | Validate target exists |
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| Route-level protection | 🔜 Pending | Block access when module disabled |
| Dashboard card hiding | 🔜 Pending | Hide from dashboard when disabled |
| Automation integration | 🔜 Planned | Add to automation pipeline |
| Link density control | 🔜 Planned | Prevent over-linking |
| External link support | 🔜 Planned | Add outbound links |

View File

@@ -0,0 +1,594 @@
# Notifications Module
**Last Verified:** January 20, 2026
**Version:** 1.8.4
**Status:** ✅ Active
**Backend Path:** `backend/igny8_core/business/notifications/`
**Frontend Path:** `frontend/src/store/notificationStore.ts`, `frontend/src/components/header/NotificationDropdown.tsx`, `frontend/src/pages/account/NotificationsPage.tsx`
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Models | `business/notifications/models.py` | `Notification`, `NotificationPreference` |
| Views | `business/notifications/views.py` | `NotificationViewSet` |
| Serializers | `business/notifications/serializers.py` | `NotificationSerializer` |
| Services | `business/notifications/services.py` | `NotificationService` |
| Frontend Store | `store/notificationStore.ts` | Zustand notification store |
| Frontend Dropdown | `components/header/NotificationDropdown.tsx` | Notification UI (header bell) |
| Frontend Page | `pages/account/NotificationsPage.tsx` | Full notifications history page |
| API Client | `services/notifications.api.ts` | API methods |
---
## Purpose
The Notifications module provides real-time user notifications for AI operations, system events, and workflow milestones. Notifications appear in the header dropdown and persist in the database.
**Key Features:**
- Real-time notifications for AI task completion/failure
- Account-wide or user-specific notifications
- Read/unread state tracking
- Action buttons with navigation
- Auto-fetch and auto-sync with API
---
## Data Models
### Notification
| Field | Type | Currently Used? | Purpose |
|-------|------|----------------|---------|
| account | FK | ✅ **Yes** | Owner account (required for multi-tenancy) |
| user | FK | ❌ **No** | User-specific notifications (nullable - if null, visible to all account users). **Currently unused** - all notifications are account-wide |
| notification_type | CharField | ✅ **Yes** | Type from NotificationType choices (for filtering, icons) |
| title | CharField(200) | ✅ **Yes** | Notification title shown in dropdown |
| message | TextField | ✅ **Yes** | Notification body text shown in dropdown |
| severity | CharField | ✅ **Yes** | info/success/warning/error (affects icon color) |
| site | FK | ✅ **Partial** | Related site (nullable). Created but **not displayed** in current dropdown |
| content_type | FK | ❌ **No** | Generic relation to any object. **Future:** Link notification to specific Content/Task/etc. for direct access |
| object_id | PositiveInteger | ❌ **No** | Generic relation ID. **Future:** Used with content_type for object linking |
| action_url | CharField(500) | ✅ **Yes** | Frontend route for action button (e.g., `/planner/clusters`) |
| action_label | CharField(50) | ✅ **Yes** | Action button text (e.g., "View Clusters") |
| is_read | Boolean | ✅ **Yes** | Read status (default: False). Used for unread badge count |
| read_at | DateTime | ✅ **Partial** | When marked read (nullable). Created but **not displayed** in dropdown |
| metadata | JSON | ✅ **Partial** | Additional data (counts, details). Stored but **not displayed** in dropdown |
| created_at | DateTime | ✅ **Yes** | Creation timestamp. Shown as relative time ("2 minutes ago") |
| updated_at | DateTime | ❌ **No** | Last update timestamp. **Currently unused** |
**Indexes:**
- `(account, -created_at)` - List notifications by account ✅ **Used**
- `(account, is_read, -created_at)` - Filter unread ✅ **Used**
- `(user, -created_at)` - User-specific notifications ❌ **Unused** (all notifications are account-wide)
---
### Why So Many Unused Fields?
The model was designed with **future expansion** in mind, but the current "dropdown-only" implementation uses less than half of the fields. Here's why they exist:
**Over-Engineered for Current Use:**
- `content_type` + `object_id` → For direct object linking (planned: click notification, go to that exact content item)
- `user` → For user-specific notifications (planned: "@John, your content is ready")
- `metadata` → For rich data (planned: show counts, progress, details in dedicated page)
- `read_at` → For analytics (planned: "You read this 2 days ago")
- `updated_at` → For editing notifications (planned: update notification instead of creating duplicate)
**Currently Essential:**
- `account`, `title`, `message`, `severity`, `notification_type` → Core notification data
- `action_url`, `action_label` → Navigation buttons
- `is_read`, `created_at` → Dropdown functionality
**The Design Mismatch:**
You're right - this is a **database schema designed for a full-featured notification system** (with dedicated page, filtering, search, history) but the frontend only implements a **simple dropdown**. The backend is over-built for the current use case.
---
## Notification Types
### AI Operations
| Type | Severity | Trigger | Message Format |
|------|----------|---------|----------------|
| `ai_cluster_complete` | success | Clustering finishes | "Created X clusters from Y keywords" |
| `ai_cluster_failed` | error | Clustering fails | "Failed to cluster keywords: [error]" |
| `ai_ideas_complete` | success | Idea generation finishes | "Generated X content ideas from Y clusters" |
| `ai_ideas_failed` | error | Idea generation fails | "Failed to generate ideas: [error]" |
| `ai_content_complete` | success | Content generation finishes | "Generated X articles (Y words)" |
| `ai_content_failed` | error | Content generation fails | "Failed to generate content: [error]" |
| `ai_images_complete` | success | Image generation finishes | "Generated X images" |
| `ai_images_failed` | error | Image generation fails | "Failed to generate X images: [error]" |
| `ai_prompts_complete` | success | Image prompts created | "X image prompts ready (1 featured + Y in-article)" |
| `ai_prompts_failed` | error | Image prompt creation fails | "Failed to create image prompts: [error]" |
### Workflow Events
| Type | Severity | Trigger | Message Format |
|------|----------|---------|----------------|
| `content_ready_review` | info | Content moved to review | "[Title] ready for review" |
| `content_published` | success | Content published | '"[Title]" published to [site]' |
| `content_publish_failed` | error | Publishing fails | 'Failed to publish "[Title]": [error]' |
### WordPress Sync
| Type | Severity | Trigger | Message Format |
|------|----------|---------|----------------|
| `wordpress_sync_success` | success | Sync completes | "Synced X items with [site]" |
| `wordpress_sync_failed` | error | Sync fails | "WordPress sync failed for [site]: [error]" |
### Credits & Billing
| Type | Severity | Trigger | Message Format |
|------|----------|---------|----------------|
| `credits_low` | warning | Credits < 20% | "You've used X% of your credits. Y remaining." |
| `credits_depleted` | error | Credits exhausted | "Your credits are exhausted. Upgrade to continue." |
### Setup & System
| Type | Severity | Trigger | Message Format |
|------|----------|---------|----------------|
| `site_setup_complete` | success | All setup steps done | "[Site] is fully configured and ready!" |
| `keywords_imported` | info | Keywords added | "Added X keywords to [site]" |
| `system_info` | info | System messages | Custom message |
---
## Notification Triggers
### AI Task Completion (AIEngine)
**Location:** `backend/igny8_core/ai/engine.py`
**Methods:** `_create_success_notification()`, `_create_failure_notification()`
**Flow:**
1. AIEngine executes AI function (`auto_cluster`, `generate_ideas`, etc.)
2. On **success** (after DONE phase):
- Calls `_create_success_notification(function_name, save_result, payload)`
- Maps function to NotificationService method
- Creates notification with counts/metrics
3. On **failure** (in `_handle_error()`):
- Calls `_create_failure_notification(function_name, error)`
- Creates error notification with error message
**Mapping:**
```python
auto_cluster NotificationService.notify_clustering_complete/failed
generate_ideas NotificationService.notify_ideas_complete/failed
generate_content NotificationService.notify_content_complete/failed
generate_image_prompts NotificationService.notify_prompts_complete/failed
generate_images NotificationService.notify_images_complete/failed
```
### WordPress Publishing
**Location:** `backend/igny8_core/tasks/wordpress_publishing.py`
**Trigger:** After publishing content to WordPress
```python
NotificationService.notify_content_published(
account=account,
site=site,
title=content.title,
content_object=content
)
```
### WordPress Sync
**Location:** `backend/igny8_core/tasks/wordpress_publishing.py`
**Trigger:** After syncing posts from WordPress
```python
NotificationService.notify_wordpress_sync_success(
account=account,
site=site,
count=synced_count
)
```
### Credit Alerts
**Location:** `backend/igny8_core/business/billing/services/credit_service.py`
**Trigger:** After deducting credits
```python
# When credits drop below threshold
NotificationService.notify_credits_low(
account=account,
percentage_used=80,
credits_remaining=remaining
)
# When credits exhausted
NotificationService.notify_credits_depleted(account=account)
```
### Manual Triggers (Optional)
Notifications can also be created manually from anywhere:
```python
from igny8_core.business.notifications.services import NotificationService
NotificationService.notify_keywords_imported(
account=account,
site=site,
count=keyword_count
)
```
**Note:** As of v1.2.1, the following actions **DO create notifications**:
- ✅ AI task completion/failure (clustering, ideas, content, images, prompts)
- ✅ Keyword import via "Add to Workflow" - **Fixed in v1.2.1**
**Actions that DON'T yet create notifications** (planned):
- ❌ WordPress publishing (needs integration in wordpress_publishing.py)
- ❌ WordPress sync (needs integration in wordpress_publishing.py)
- ❌ Credit alerts (needs integration in credit_service.py)
- ❌ Automation completion (planned for future)
---
## User Interface
### Notification Dropdown (Header)
**Location:** Header bell icon (top right)
**File:** `frontend/src/components/header/NotificationDropdown.tsx`
**Features:**
- Shows last 50 notifications
- Animated badge with unread count
- Click notification to mark read + navigate
- "Mark all read" button
- Auto-refreshes every 30 seconds
- Refreshes when opened (if stale > 1 minute)
- "View All Notifications" link to full page
### Notifications Page (Full History)
**Location:** `/account/notifications`
**File:** `frontend/src/pages/account/NotificationsPage.tsx`
**Access:** Sidebar → ACCOUNT → Notifications OR NotificationDropdown → "View All Notifications"
**Features:**
- **Filters:**
- Severity (info/success/warning/error)
- Notification type (AI operations, WordPress sync, credits, etc.)
- Read status (all/unread/read)
- Site (filter by specific site)
- Date range (from/to dates)
- **Actions:**
- Mark individual notifications as read
- Mark all as read (bulk action)
- Delete individual notifications
- Click notification to navigate to action URL
- **Display:**
- Full notification history with pagination
- Severity icons with color coding
- Relative timestamps ("2 hours ago")
- Site badge when applicable
- Action buttons for related pages
- Unread badge in sidebar menu
**v1.2.2 Implementation:**
- ✅ Full-page notifications view created
- ✅ Advanced filtering by severity, type, read status, site, date range
- ✅ Bulk actions (mark all read)
- ✅ Individual actions (mark read, delete)
- ✅ Added to sidebar under ACCOUNT section
- ✅ Unread count badge in sidebar
- ✅ Fixed broken link in NotificationDropdown (was `/notifications`, now `/account/notifications`)
---
## API Endpoints
### List Notifications
```http
GET /api/v1/notifications/
```
**Query Parameters:**
- `page` - Page number (default: 1)
- `page_size` - Results per page (default: 20)
- `is_read` - Filter by read status (`true`/`false`)
- `notification_type` - Filter by type (e.g., `ai_cluster_complete`)
- `severity` - Filter by severity (`info`/`success`/`warning`/`error`)
**Response:**
```json
{
"count": 42,
"next": "/api/v1/notifications/?page=2",
"previous": null,
"results": [
{
"id": 123,
"notification_type": "ai_cluster_complete",
"severity": "success",
"title": "Clustering Complete",
"message": "Created 5 clusters from 50 keywords",
"is_read": false,
"created_at": "2025-12-28T10:30:00Z",
"read_at": null,
"action_label": "View Clusters",
"action_url": "/planner/clusters",
"site": {"id": 1, "name": "My Blog"},
"metadata": {
"cluster_count": 5,
"keyword_count": 50
}
}
]
}
```
### Get Unread Count
```http
GET /api/v1/notifications/unread-count/
```
**Response:**
```json
{
"unread_count": 7
}
```
### Mark as Read
```http
POST /api/v1/notifications/{id}/read/
```
**Response:**
```json
{
"id": 123,
"is_read": true,
"read_at": "2025-12-28T10:35:00Z"
}
```
### Mark All as Read
```http
POST /api/v1/notifications/read-all/
```
**Response:**
```json
{
"updated_count": 7,
"message": "Marked 7 notifications as read"
}
```
### Delete Notification
```http
DELETE /api/v1/notifications/{id}/
```
**Response:** `204 No Content`
---
## Frontend Implementation
### Notification Store
**File:** `frontend/src/store/notificationStore.ts`
**Features:**
- Zustand store for state management
- In-memory queue for optimistic UI updates
- API sync for persistent notifications
- Auto-fetch on mount and periodic sync (every 30 seconds)
- Auto-refresh when dropdown opens (if stale > 1 minute)
**Key Methods:**
```typescript
// Add local notification (optimistic)
addNotification(notification)
// Mark as read (optimistic + API sync)
markAsRead(id)
// Mark all as read
markAllAsRead()
// Fetch from API
fetchNotifications()
// Sync unread count
syncUnreadCount()
```
### Notification Dropdown
**File:** `frontend/src/components/header/NotificationDropdown.tsx`
**Features:**
- Badge with unread count (animated ping when > 0)
- Dropdown with notification list
- Click notification to mark read and navigate
- "Mark all read" action
- Empty state when no notifications
- Auto-fetch on open if stale
**Icon Mapping:**
```typescript
auto_cluster GroupIcon
generate_ideas BoltIcon
generate_content FileTextIcon
generate_images FileIcon
system AlertIcon
success CheckCircleIcon
```
---
## Business Logic
### Visibility Rules
1. **Account-wide notifications** (`user=NULL`):
- Visible to ALL users in the account
- Example: "Automation completed 10 tasks"
2. **User-specific notifications** (`user=User`):
- Only visible to that specific user
- Example: "Your content is ready for review"
3. **Site-filtered** (frontend):
- Frontend can filter by site in UI
- Backend always returns all account notifications
### Read Status
- Notifications start as `is_read=False`
- Clicking notification marks it read (API call + optimistic update)
- "Mark all read" bulk updates all unread notifications
- Read notifications stay in dropdown (can be filtered out in future)
### Retention Policy
- Notifications never auto-delete (future: add retention policy)
- Users can manually delete notifications
- Admin can clean up old notifications via management command (future)
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Notifications not appearing | AIEngine not calling NotificationService | Fixed in v1.2.1 |
| "Add to workflow" no notification | KeywordViewSet not calling NotificationService | Fixed in v1.2.1 |
| Can't see notification history | No dedicated notifications page | Fixed in v1.2.2 - Page created at /account/notifications |
| "View All" button → 404 | Link to `/notifications` but page doesn't exist | Fixed in v1.2.2 - Link updated to /account/notifications |
| Duplicate notifications | Multiple AI task retries | Check task retry logic |
| Missing notifications | Celery worker crashed | Check Celery logs |
| Unread count wrong | Race condition in state | Refresh page or wait for sync |
| Action URL not working | Incorrect route in action_url | Check NotificationService methods |
---
## Integration Points
| From | To | Trigger |
|------|----|---------|
| AIEngine | NotificationService | AI task success/failure |
| WordPress Publisher | NotificationService | Publishing/sync events |
| Credit Service | NotificationService | Low credits/depleted |
| Automation | NotificationService | Automation milestones (future) |
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| Notification preferences | 🔜 Planned | User can toggle notification types |
| Email notifications | 🔜 Planned | Send email for critical notifications |
| Push notifications | 🔜 Planned | Browser push for real-time alerts |
| Notification retention | 🔜 Planned | Auto-delete after 30/60 days |
| Notification categories | 🔜 Planned | Group by module (Planner, Writer, etc.) |
| Notification sounds | 🔜 Planned | Audio alerts for important events |
| Webhook notifications | 🔜 Planned | POST to external webhook URLs |
---
## Version History
| Version | Date | Changes |
|---------|------|---------|
| v1.2.2 | Dec 28, 2025 | **NEW:** Full notifications page at /account/notifications with filtering, bulk actions, and sidebar integration |
| v1.2.1 | Dec 28, 2025 | **Fixed:** Notifications now created on AI task completion + keyword import |
| v1.2.0 | Dec 27, 2025 | Initial notifications system implementation |
---
## Testing
### Test Notification Creation
```python
# In Django shell: docker exec -it igny8_backend python manage.py shell
from igny8_core.business.notifications.services import NotificationService
from igny8_core.auth.models import Account
account = Account.objects.first()
# Test clustering notification
NotificationService.notify_clustering_complete(
account=account,
cluster_count=5,
keyword_count=50
)
# Test error notification
NotificationService.notify_clustering_failed(
account=account,
error="Insufficient credits"
)
```
### Test Frontend
1. Run AI operation (clustering, idea generation, etc.)
2. Check notification dropdown (bell icon in header)
3. Verify unread badge appears
4. Click notification to mark read and navigate
5. Click "Mark all read" to clear badge
---
## Debug Commands
```bash
# Check Celery logs for notification creation
docker logs igny8_celery_worker -f | grep -i "notification"
# Check notifications in database
docker exec -it igny8_backend python manage.py shell
>>> from igny8_core.business.notifications.models import Notification
>>> Notification.objects.count()
>>> Notification.objects.filter(is_read=False).count()
>>> Notification.objects.last().title
# Test notification API
curl -H "Authorization: Bearer <token>" \
http://localhost:8011/api/v1/notifications/
# Check notification creation code
grep -r "notify_clustering_complete" backend/
```
---
## Architecture Notes
**Design Pattern:** Service Layer + Repository Pattern
- `NotificationService` provides static methods for creating notifications
- Each notification type has dedicated method with validation
- `Notification.create_notification()` is class method for low-level creation
- Views use `AccountModelViewSet` for automatic account filtering
**Why Not Signals?**
- Explicit is better than implicit
- Clear call sites for debugging
- Easier to test and mock
- No hidden side effects
- Can pass context-specific data
**Lazy Imports:**
- NotificationService uses lazy imports in AIEngine to avoid Django app loading issues
- Import inside method, not at module level

View File

@@ -0,0 +1,263 @@
# Optimizer Module
**Last Verified:** January 20, 2026
**Version:** 1.8.4
**Status:** ⏸️ Inactive (Disabled by Default)
**Backend Path:** `backend/igny8_core/modules/optimizer/` + `backend/igny8_core/business/optimization/`
**Frontend Path:** `frontend/src/pages/Optimizer/`
---
## Module Status
| Aspect | Current State | Notes |
|--------|---------------|-------|
| Backend API | ✅ Implemented | Endpoints functional |
| Business Logic | ✅ Implemented | Optimization service exists |
| Frontend Pages | ✅ Implemented | UI exists |
| Sidebar Nav | ⚠️ Conditional | Hidden when disabled |
| Route Protection | ❌ Not Protected | Direct URL access still works |
| Dashboard References | ❌ Not Hidden | May show in dashboard |
| Default State | Disabled | `optimizer_enabled = True` in model, but typically disabled |
**⚠️ Pending Implementation:** Extend module disable to route-level protection and all page references.
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Views | `modules/optimizer/views.py` | `OptimizerViewSet` |
| Models | `business/optimization/models.py` | `OptimizationTask` |
| Services | `business/optimization/services/*.py` | Optimization logic |
| AI Function | `ai/functions/optimize.py` | `OptimizeContentFunction` |
| Frontend Pages | `pages/Optimizer/index.tsx` | Optimizer overview |
| Frontend Pages | `pages/Optimizer/OptimizerContent.tsx` | Select content |
| Frontend Pages | `pages/Optimizer/OptimizerPreview.tsx` | Analysis preview |
| API Client | `api/optimizer.api.ts` | `optimizerApi` |
| Module Control | `modules/system/settings_models.py` | `ModuleEnableSettings.optimizer_enabled` |
---
## Purpose
The Optimizer module provides AI-powered content optimization:
- Analyze existing content for SEO improvements
- Suggest and apply optimizations
- Track before/after scores
---
## Data Models
### OptimizationTask
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| sector | FK | Parent sector |
| content | FK | Target content |
| entry_point | CharField | auto/igny8/wordpress/external/manual |
| status | CharField | pending/analyzing/optimizing/completed/failed |
| original_score | Integer | Score before optimization |
| optimized_score | Integer | Score after optimization |
| original_content | TextField | Content before changes |
| optimized_content | TextField | Content after changes |
| suggestions | JSON | Optimization suggestions |
| applied_changes | JSON | Changes that were applied |
| credits_used | Decimal | Credits consumed |
| created_at | DateTime | Creation date |
| completed_at | DateTime | Completion date |
---
## API Endpoints
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| POST | `/api/v1/optimizer/optimize/` | `OptimizerViewSet.optimize` | Optimize single content |
| POST | `/api/v1/optimizer/batch_optimize/` | `OptimizerViewSet.batch_optimize` | Optimize multiple items |
| POST | `/api/v1/optimizer/analyze/` | `OptimizerViewSet.analyze` | Analyze without applying |
### Entry Points
| Entry Point | Description |
|-------------|-------------|
| `auto` | Auto-detect based on content source |
| `igny8` | IGNY8-generated content |
| `wordpress` | WordPress-synced content |
| `external` | External platform content |
| `manual` | Manual optimization trigger |
### Optimize Request
```json
{
"content_id": 123,
"entry_point": "igny8",
"optimization_goals": ["seo", "readability", "keyword_density"]
}
```
### Optimize Response
```json
{
"task_id": 456,
"content_id": 123,
"original_score": 65,
"optimized_score": 85,
"suggestions": [
{
"type": "keyword_density",
"description": "Increase keyword 'SEO' usage",
"applied": true
},
{
"type": "meta_description",
"description": "Meta description too short",
"applied": true
}
],
"credits_used": 15
}
```
---
## Business Logic
### Content Analysis
**Trigger:** User clicks "Analyze"
**Credit Cost:** None (analysis only)
**Checks:**
- Keyword density and distribution
- Meta title and description optimization
- Heading structure (H1, H2, H3)
- Readability score
- Content length
- Internal/external link count
- Image alt text presence
### Content Optimization (AI)
**Trigger:** User clicks "Optimize"
**AI Function:** `OptimizeContentFunction`
**Credit Cost:** Per 200 words
**Flow:**
1. Load content with metadata
2. Analyze current state
3. AIEngine executes `OptimizeContentFunction`:
- Reviews content against SEO best practices
- Suggests improvements
- Rewrites sections if needed
4. Create `OptimizationTask` record
5. Save original and optimized versions
6. Return comparison view
---
## Module Enable Control
### Backend Model
```python
# modules/system/settings_models.py
class ModuleEnableSettings(AccountBaseModel):
optimizer_enabled = models.BooleanField(default=True)
```
### Frontend Check
```typescript
// layout/AppSidebar.tsx
if (isModuleEnabled('optimizer')) {
workflowItems.push({
name: "Optimizer",
path: "/optimizer/content",
});
}
```
### Current Limitation
Direct URL access to `/optimizer/*` routes still works even when module is disabled.
---
## Frontend Pages
### Optimizer Overview (`/optimizer`)
- Module overview
- Quick stats
- Recent optimizations
### Select Content (`/optimizer/content`)
- List of content available for optimization
- Filter by score, status
- Start analysis button
- Batch optimize action
### Analysis Preview (`/optimizer/preview`)
- Side-by-side comparison
- Suggestion list
- Accept/reject changes
- Apply optimization button
---
## Optimization Scoring
| Metric | Weight | Description |
|--------|--------|-------------|
| Keyword Density | 20% | Target 1-3% density |
| Meta Quality | 15% | Title 50-60 chars, desc 150-160 chars |
| Heading Structure | 15% | Proper H1-H6 hierarchy |
| Readability | 15% | Flesch-Kincaid score |
| Content Length | 10% | Meets word count target |
| Internal Links | 10% | 2-5 internal links |
| Image Optimization | 10% | Alt text, sizing |
| Mobile Friendly | 5% | No wide elements |
---
## Integration Points
| From | To | Trigger |
|------|----|---------|
| Content | Optimizer | Manual analysis/optimize |
| WordPress | Optimizer | Synced content optimization |
| Optimizer | Content | Apply changes |
| Automation | Optimizer | Automated optimization (future) |
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Module visible when disabled | Only sidebar hidden | Pending: route protection |
| Optimization not improving | Content already good | Check original score |
| High credit usage | Large content | Optimize sections |
| Changes not saving | Apply not clicked | Click "Apply Changes" |
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| Route-level protection | 🔜 Pending | Block access when module disabled |
| Dashboard card hiding | 🔜 Pending | Hide from dashboard when disabled |
| Automation integration | 🔜 Planned | Add to automation pipeline |
| Partial optimization | 🔜 Planned | Optimize specific sections only |
| Competitor analysis | 🔜 Planned | Compare against top-ranking content |
| A/B testing | 🔜 Planned | Track performance of optimizations |

View File

@@ -0,0 +1,232 @@
# Planner Module
**Last Verified:** January 20, 2026
**Version:** 1.8.4
**Status:** ✅ Active
**Backend Path:** `backend/igny8_core/modules/planner/`
**Frontend Path:** `frontend/src/pages/Planner/`
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Models | `modules/planner/models.py` | `Keywords`, `Clusters`, `ContentIdeas` |
| Views | `modules/planner/views.py` | `KeywordViewSet`, `ClusterViewSet`, `ContentIdeaViewSet` |
| Serializers | `modules/planner/serializers.py` | Keyword/Cluster/Idea serializers |
| AI Functions | `ai/functions/clustering.py` | `AutoClusterFunction` |
| AI Functions | `ai/functions/ideas.py` | `GenerateIdeasFunction` |
| Frontend Pages | `pages/Planner/Keywords.tsx` | Keyword management |
| Frontend Pages | `pages/Planner/Clusters.tsx` | Cluster management |
| Frontend Pages | `pages/Planner/Ideas.tsx` | Content ideas |
---
## Purpose
The Planner module manages the SEO content planning pipeline:
```
SeedKeywords (Global) → Keywords (Site/Sector) → Clusters → ContentIdeas → Tasks
```
---
## Data Models
### Keywords
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| sector | FK | Parent sector |
| seed_keyword | FK | Reference to global SeedKeyword |
| keyword | CharField | Keyword text |
| search_volume | Integer | Monthly search volume |
| difficulty | Integer | SEO difficulty (0-100) |
| cpc | Decimal | Cost per click |
| search_intent | CharField | informational/commercial/transactional/navigational |
| status | CharField | new/mapped/used |
| cluster | FK | Assigned cluster (nullable) |
| created_at | DateTime | Creation date |
### Clusters
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| sector | FK | Parent sector |
| name | CharField | Cluster name |
| description | TextField | Cluster description |
| primary_keyword | FK | Main keyword |
| status | CharField | draft/active/complete |
| keyword_count | Integer | Number of keywords |
| created_at | DateTime | Creation date |
### ContentIdeas
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| sector | FK | Parent sector |
| cluster | FK | Source cluster |
| title | CharField | Content title |
| description | TextField | Content brief |
| target_keywords | JSON | Keywords to target |
| content_type | CharField | blog_post/guide/comparison/etc. |
| word_count_target | Integer | Target word count |
| status | CharField | draft/queued/used |
| priority | Integer | Priority score |
| created_at | DateTime | Creation date |
---
## API Endpoints
### Keywords
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/planner/keywords/` | `KeywordViewSet.list` | List keywords with filtering |
| POST | `/api/v1/planner/keywords/` | `KeywordViewSet.create` | Create single keyword |
| GET | `/api/v1/planner/keywords/{id}/` | `KeywordViewSet.retrieve` | Get keyword detail |
| PUT | `/api/v1/planner/keywords/{id}/` | `KeywordViewSet.update` | Update keyword |
| DELETE | `/api/v1/planner/keywords/{id}/` | `KeywordViewSet.destroy` | Soft delete keyword |
| POST | `/api/v1/planner/keywords/bulk_delete/` | `KeywordViewSet.bulk_delete` | Hard delete multiple |
| POST | `/api/v1/planner/keywords/bulk_status/` | `KeywordViewSet.bulk_status` | Update status for multiple |
| POST | `/api/v1/planner/keywords/add_to_workflow/` | `KeywordViewSet.add_to_workflow` | Add SeedKeywords to workflow |
**Filters:** `?site_id=`, `?sector_id=`, `?status=`, `?cluster_id=`, `?difficulty_min=`, `?difficulty_max=`, `?volume_min=`, `?volume_max=`
### Clusters
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/planner/clusters/` | `ClusterViewSet.list` | List clusters |
| POST | `/api/v1/planner/clusters/` | `ClusterViewSet.create` | Create cluster manually |
| POST | `/api/v1/planner/clusters/auto_cluster/` | `ClusterViewSet.auto_cluster` | AI-powered clustering |
| POST | `/api/v1/planner/clusters/generate_ideas/` | `ClusterViewSet.generate_ideas` | Generate ideas from clusters |
### Content Ideas
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/planner/ideas/` | `ContentIdeaViewSet.list` | List ideas |
| POST | `/api/v1/planner/ideas/` | `ContentIdeaViewSet.create` | Create idea manually |
| POST | `/api/v1/planner/ideas/create_tasks/` | `ContentIdeaViewSet.create_tasks` | Convert ideas to tasks |
---
## Business Logic
### Auto Clustering (AI)
**Trigger:** User clicks "Auto Cluster" button
**AI Function:** `AutoClusterFunction`
**Credit Cost:** Based on AI tokens used (see AIModelConfig)
**Flow:**
1. User selects keywords (or all unclustered)
2. Frontend calls `POST /clusters/auto_cluster/`
3. Backend validates minimum 5 keywords
4. AIEngine executes `AutoClusterFunction`:
- Sends keywords to GPT-4
- AI groups by semantic similarity
- Returns cluster assignments
5. Creates/updates `Clusters` records
6. Assigns keywords to clusters
7. Returns created clusters
### Generate Ideas (AI)
**Trigger:** User clicks "Generate Ideas" on cluster(s)
**AI Function:** `GenerateIdeasFunction`
**Credit Cost:** Based on AI tokens used (see AIModelConfig)
**Flow:**
1. User selects clusters
2. Frontend calls `POST /clusters/generate_ideas/`
3. Backend validates clusters have keywords
4. AIEngine executes `GenerateIdeasFunction`:
- Analyzes cluster keywords
- Generates content titles + briefs
- Suggests word counts and content types
5. Creates `ContentIdeas` records
6. Returns created ideas
### Add Keywords to Workflow
**Trigger:** User selects SeedKeywords in setup
**Credit Cost:** None
**Flow:**
1. User browses global SeedKeywords
2. Selects keywords to add to their sector
3. Frontend calls `POST /keywords/add_to_workflow/`
4. Backend creates `Keywords` records linked to SeedKeywords
5. Keywords appear in Planner for clustering
---
## Frontend Pages
### Keywords Page (`/planner/keywords`)
- Table of all keywords with filtering
- Bulk actions: delete, update status
- Add keywords from SeedKeyword library
- Import keywords from CSV
- Assign to clusters manually
### Clusters Page (`/planner/clusters`)
- Grid/list of clusters
- Auto-cluster action (AI)
- Generate ideas action (AI)
- View keywords in cluster
- Cluster status management
### Ideas Page (`/planner/ideas`)
- Table of content ideas
- Convert to tasks action
- Edit idea details
- Priority management
---
## Integration Points
| From | To | Trigger |
|------|----|---------|
| SeedKeywords | Keywords | Add to workflow |
| Keywords | Clusters | Auto clustering |
| Clusters | ContentIdeas | Generate ideas |
| ContentIdeas | Tasks | Create tasks |
| Automation Stage 1 | Clusters | Automated clustering |
| Automation Stage 2 | ContentIdeas | Automated idea generation |
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Clustering fails | Less than 5 keywords | Ensure minimum keywords |
| Ideas not generating | Cluster has no keywords | Assign keywords to cluster |
| Keywords not showing | Wrong site/sector filter | Check active site/sector |
| Duplicate keywords | Same keyword added twice | Check seed_keyword reference |
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| Keyword import improvements | 🔜 Planned | Better CSV parsing and validation |
| Cluster merging | 🔜 Planned | Merge similar clusters |
| Idea prioritization | 🔜 Planned | AI-based priority scoring |

View File

@@ -0,0 +1,536 @@
# Publisher Module
**Last Verified:** January 20, 2026
**Status:** ✅ Active
**Version:** 1.8.4
**Backend Path:** `backend/igny8_core/modules/publisher/` + `backend/igny8_core/business/publishing/`
**Frontend Path:** `frontend/src/pages/Publisher/`
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Views | `modules/publisher/views.py` | `PublishingRecordViewSet`, `DeploymentViewSet`, `PublishContentViewSet` |
| Models | `business/publishing/models.py` | `PublishingRecord`, `DeploymentRecord` |
| Integration Models | `modules/integration/models.py` | `PublishingSettings` |
| **Unified Settings** | `modules/integration/views/unified_settings.py` | **v1.8.0** Consolidated settings API |
| Services | `business/publishing/services/*.py` | Publishing orchestration |
| Scheduler Tasks | `igny8_core/tasks/publishing_scheduler.py` | Celery beat tasks |
| URLs | `modules/publisher/urls.py` | Publisher endpoints |
| Frontend | `pages/Publisher/ContentCalendar.tsx` | Content calendar view |
| **Site Settings** | `pages/Sites/AIAutomationSettings.tsx` | **v1.8.0** Unified settings UI |
---
## Purpose
The Publisher module manages:
- Content publishing pipeline
- **Publishing scheduler (automated publishing)**
- Publishing record tracking
- Deployment management
- Multi-destination publishing
- **Content calendar visualization**
**Settings Location (v1.8.0):** Site Settings → Automation tab
> ⚠️ **v1.8.0 Change:** The standalone `/publisher/settings` page has been removed. Publishing settings are now configured in Site Settings → Automation tab under "Capacity" and "Schedule" sections.
---
## Data Models
### PublishingRecord
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| content | FK | Source content |
| destination | CharField | wordpress/ghost/webflow |
| external_id | CharField | ID on destination platform |
| external_url | URLField | Published URL |
| status | CharField | pending/published/failed/retracted |
| published_at | DateTime | Publication time |
| metadata | JSON | Additional data |
| created_at | DateTime | Record creation |
### DeploymentRecord
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Target site |
| deployment_type | CharField | full/incremental |
| status | CharField | pending/deploying/completed/failed |
| items_deployed | Integer | Number of items |
| started_at | DateTime | Start time |
| completed_at | DateTime | Completion time |
| error_log | TextField | Errors encountered |
| metadata | JSON | Deployment details |
### PublishingSettings (v1.3.2, updated v1.8.0)
Site-level publishing configuration:
| Field | Type | Purpose |
|-------|------|---------|
| site | OneToOne | Parent site (unique) |
| auto_approval_enabled | Boolean | Auto-approve content |
| auto_publish_enabled | Boolean | Auto-publish approved content |
| daily_publish_limit | Integer | Max publications per day |
| weekly_publish_limit | Integer | Max publications per week |
| monthly_publish_limit | Integer | Max publications per month |
| publish_days | JSON | Days of week for publishing ["mon","wed","fri"] |
| publish_time_slots | JSON | Time slots for publishing [{"start":"09:00","end":"17:00"}] |
| **total_items_per_run** | Integer | **v1.8.0** Computed capacity display |
### Content Site Status Fields (v1.3.2)
Added to Content model for scheduling:
| Field | Type | Values |
|-------|------|--------|
| site_status | CharField | not_published, scheduled, publishing, published, failed |
| scheduled_publish_at | DateTime | Future publication time (nullable) |
| site_status_updated_at | DateTime | Last status change timestamp |
---
## API Endpoints
### Unified Settings API (v1.8.0)
| Method | Path | Purpose |
|--------|------|---------|
| **GET** | `/api/v1/integration/sites/{site_id}/unified-settings/` | Get all automation + publishing settings |
| **PATCH** | `/api/v1/integration/sites/{site_id}/unified-settings/` | Update settings |
### Publishing & Records
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/publisher/records/` | `PublishingRecordViewSet.list` | List publishing records |
| POST | `/api/v1/publisher/records/` | `PublishingRecordViewSet.create` | Create record |
| GET | `/api/v1/publisher/deployments/` | `DeploymentViewSet.list` | List deployments |
| POST | `/api/v1/publisher/publish/` | `PublishContentViewSet.publish` | Publish content immediately |
| GET | `/api/v1/publisher/publish/status/` | `PublishContentViewSet.status` | Get publishing status |
| GET | `/api/v1/publisher/site-definition/` | `SiteDefinitionViewSet.list` | Public site definitions |
### Scheduling Endpoints (v1.3.2+)
| Method | Path | Purpose | Request Body | Response |
|--------|------|---------|--------------|----------|
| **POST** | `/api/v1/writer/content/{id}/schedule/` | Schedule content for future publishing | `{ "scheduled_publish_at": "2025-01-20T09:00:00Z" }` | `{ "success": true, "scheduled_publish_at": "2025-01-20T09:00:00Z" }` |
| **POST** | `/api/v1/writer/content/{id}/reschedule/` | Reschedule existing scheduled content | `{ "scheduled_at": "2025-01-21T10:00:00Z" }` | `{ "success": true, "scheduled_publish_at": "2025-01-21T10:00:00Z" }` |
| **POST** | `/api/v1/writer/content/{id}/unschedule/` | Cancel scheduled publishing | `{}` | `{ "success": true, "message": "Content unscheduled" }` |
| **POST** | `/api/v1/writer/content/bulk_schedule/` | Bulk schedule with site defaults | `{ "content_ids": [1,2,3], "use_site_defaults": true, "site_id": 5 }` | `{ "success": true, "scheduled_count": 3, "schedule_preview": [...] }` |
| **POST** | `/api/v1/writer/content/bulk_schedule_preview/` | Preview bulk schedule times | `{ "content_ids": [1,2,3], "site_id": 5 }` | `{ "schedule_preview": [...], "site_settings": {...} }` |
### Legacy Publishing Settings (deprecated)
> ⚠️ **Deprecated in v1.8.0:** Use unified-settings API instead
| Method | Path | Purpose |
|--------|------|---------|
| ~~GET~~ | ~~`/api/v1/sites/{site_id}/settings?tab=publishing`~~ | ~~Get site publishing settings~~ |
| ~~PUT~~ | ~~`/api/v1/sites/{site_id}/publishing-settings/`~~ | ~~Update publishing settings~~ |
---
## API Usage Examples
### Publish Content Immediately
**Request:**
```bash
POST /api/v1/publisher/publish/
Content-Type: application/json
{
"content_id": 123,
"destinations": ["wordpress"] # or ["shopify"], ["custom"]
}
```
**Success Response:**
```json
{
"success": true,
"data": {
"success": true,
"results": [
{
"destination": "wordpress",
"success": true,
"external_id": "456",
"url": "https://mysite.com/article-title/",
"publishing_record_id": 789,
"platform_type": "wordpress"
}
]
}
}
```
**Error Response:**
```json
{
"success": false,
"error": "Publishing API error: Invalid credentials"
}
```
### Schedule Content for Future Publishing
**Request:**
```bash
POST /api/v1/writer/content/123/schedule/
Content-Type: application/json
{
"scheduled_publish_at": "2025-01-20T09:00:00Z"
}
```
**Response:**
```json
{
"success": true,
"scheduled_publish_at": "2025-01-20T09:00:00Z",
"site_status": "scheduled"
}
```
**Notes:**
- Content `site_status` changes from `not_published``scheduled`
- Celery task `process_scheduled_publications` will publish at scheduled time
- Runs every 5 minutes, so publishing happens within 5 min of scheduled time
### Reschedule Content
**Request:**
```bash
POST /api/v1/writer/content/123/reschedule/
Content-Type: application/json
{
"scheduled_at": "2025-01-21T10:00:00Z"
}
```
**Response:**
```json
{
"success": true,
"scheduled_publish_at": "2025-01-21T10:00:00Z",
"site_status": "scheduled"
}
```
**Use Cases:**
- Reschedule from `site_status='scheduled'` (change time)
- Reschedule from `site_status='failed'` (retry at new time)
### Unschedule Content
**Request:**
```bash
POST /api/v1/writer/content/123/unschedule/
Content-Type: application/json
{}
```
**Response:**
```json
{
"success": true,
"message": "Content unscheduled successfully",
"site_status": "not_published"
}
```
**Notes:**
- Removes content from publishing queue
- Content returns to `site_status='not_published'`
- Can be rescheduled or published immediately later
### Bulk Schedule with Site Defaults
**Request:**
```bash
POST /api/v1/writer/content/bulk_schedule/
Content-Type: application/json
{
"content_ids": [123, 124, 125, 126],
"use_site_defaults": true,
"site_id": 45
}
```
**Response:**
```json
{
"success": true,
"scheduled_count": 4,
"schedule_preview": [
{
"content_id": 123,
"scheduled_at": "2025-01-17T09:00:00Z",
"title": "First Article"
},
{
"content_id": 124,
"scheduled_at": "2025-01-17T09:15:00Z",
"title": "Second Article"
},
{
"content_id": 125,
"scheduled_at": "2025-01-17T09:30:00Z",
"title": "Third Article"
},
{
"content_id": 126,
"scheduled_at": "2025-01-17T09:45:00Z",
"title": "Fourth Article"
}
],
"site_settings": {
"base_time": "09:00 AM",
"stagger_interval": 15,
"timezone": "America/New_York"
}
}
```
**Notes:**
- Uses site's default publishing schedule from Site Settings
- Automatically staggers publications (e.g., 15 min intervals)
- No limit on number of items (unlike direct publish which is limited to 5)
- All items set to `site_status='scheduled'`
### Bulk Schedule Preview (Before Confirming)
**Request:**
```bash
POST /api/v1/writer/content/bulk_schedule_preview/
Content-Type: application/json
{
"content_ids": [123, 124, 125],
"site_id": 45
}
```
**Response:**
```json
{
"schedule_preview": [
{"content_id": 123, "scheduled_at": "2025-01-17T09:00:00Z", "title": "Article 1"},
{"content_id": 124, "scheduled_at": "2025-01-17T09:15:00Z", "title": "Article 2"},
{"content_id": 125, "scheduled_at": "2025-01-17T09:30:00Z", "title": "Article 3"}
],
"site_settings": {
"base_time": "09:00 AM",
"stagger_interval": 15,
"timezone": "America/New_York",
"publish_days": ["mon", "tue", "wed", "thu", "fri"]
}
}
```
**Use Case:**
- Show user what times items will be scheduled before confirming
- Allow user to adjust site settings if needed
- User clicks "Confirm" to execute actual bulk_schedule
---
## Publishing Scheduler (v1.3.2)
### Celery Beat Tasks
Located in `igny8_core/tasks/publishing_scheduler.py`:
| Task | Schedule | Purpose |
|------|----------|---------|
| `schedule_approved_content` | Every 15 minutes | Assigns publish times to approved content |
| `process_scheduled_publications` | Every 5 minutes | Publishes content when scheduled time arrives |
| `cleanup_failed_publications` | Daily at midnight | Retries or cleans up failed publications |
### Scheduling Flow
```
Approved Content → schedule_approved_content (every 15 min)
Check PublishingSettings
Assign scheduled_publish_at based on:
- publish_days configuration
- publish_time_slots configuration
- daily/weekly/monthly limits
Set site_status = "scheduled"
process_scheduled_publications (every 5 min)
If scheduled_publish_at <= now:
- Set site_status = "publishing"
- Execute publication
- Set site_status = "published" or "failed"
```
### Site Status State Machine
```
not_published → scheduled → publishing → published
↓ ↓
↓ → failed
not_published (if unscheduled)
```
---
## Frontend Pages (v1.3.2)
### Content Calendar (`/publisher/content-calendar`)
**Purpose:** Visualize and manage scheduled content
**Features:**
- Calendar view of scheduled publications
- List view alternative
- Filter by site
- Schedule/unschedule actions
- Drag-and-drop rescheduling (planned)
**Components:**
- `ContentCalendar.tsx` - Main page component
- `CalendarItemTooltip` - Hover details for calendar items
- `SiteInfoBar` - Site context header
### Publishing Queue (`/publisher/publishing-queue`)
**Purpose:** Review upcoming publications
**Features:**
- List of content pending publication
- Status indicators (scheduled, publishing, failed)
- Publish now / unschedule actions
---
## Publishing Flow
### Single Content Publish
**Trigger:** User clicks "Publish" or API call
**Flow:**
1. Validate content is in publishable state
2. Get site integration credentials
3. Transform content for destination:
- Format HTML for platform
- Process images
- Map taxonomies
4. Push to destination API
5. Create `PublishingRecord` with external ID
6. Update Content status to `published`
### Batch Deployment
**Trigger:** Deployment action
**Flow:**
1. Create `DeploymentRecord`
2. Gather all pending content
3. Process each item:
- Publish to destination
- Create publishing record
- Update content status
4. Update deployment status
5. Log any errors
---
## Destination Adapters
### WordPress Adapter
- Uses WordPress REST API
- Supports posts, pages, custom post types
- Handles media upload
- Maps categories/tags
### Ghost Adapter (Planned)
- Ghost Admin API integration
- Post publishing
- Image handling
### Webflow Adapter (Planned)
- Webflow CMS API
- Collection item publishing
- Asset management
---
## Publishing States
| Status | Description |
|--------|-------------|
| pending | Ready to publish |
| published | Successfully published |
| failed | Publishing failed |
| retracted | Unpublished/removed |
---
## Site Definition Endpoint
**Purpose:** Public endpoint for headless CMS use cases
Returns site structure for external consumption:
- Site metadata
- Available taxonomies
- Content types
- URL structure
---
## Integration Points
| From | To | Trigger |
|------|----|---------|
| Writer | Publisher | Publish action |
| Automation Stage 7 | Publisher | Auto-publish (future) |
| Publisher | Integrations | Destination APIs |
| Publisher | Content | Status updates |
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Publish failed | Invalid credentials | Check integration settings |
| Duplicate posts | Published twice | Check existing publishing record |
| Images missing | Upload failed | Check media library access |
| Wrong category | Mapping issue | Verify taxonomy sync |
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| Ghost integration | 🔜 Planned | Ghost CMS publishing |
| Webflow integration | 🔜 Planned | Webflow publishing |
| ~~Scheduled publishing~~ | ✅ Implemented (v1.3.2) | Future-date publishing |
| Republish detection | 🔜 Planned | Detect and handle updates |
| ~~Publishing queue~~ | ✅ Implemented (v1.3.2) | Batch publishing with queue |
| Drag-and-drop calendar | 🔜 Planned | Reschedule via drag-and-drop |

View File

@@ -0,0 +1,293 @@
# System Settings Module
**Last Verified:** January 20, 2026
**Version:** 1.8.4
**Status:** ✅ Active
**Backend Path:** `backend/igny8_core/modules/system/`
**Frontend Path:** `frontend/src/pages/Settings/`
> **Note (v1.8.0):** AI & Automation settings have been consolidated into Site Settings → Automation tab. See [AUTOMATION.md](AUTOMATION.md) for the unified settings API.
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Global Models | `modules/system/global_settings_models.py` | `GlobalIntegrationSettings`, `GlobalAIPrompt`, `GlobalAuthorProfile` |
| Account Models | `modules/system/settings_models.py` | `SystemSettings`, `UserSettings` |
| Email Models | `modules/system/email_models.py` | `EmailSettings`, `EmailTemplate`, `EmailLog` |
| AI Settings | `modules/system/ai_settings.py` | `SystemAISettings` |
| Provider Model | `modules/system/models.py` | `IntegrationProvider` |
| Views | `modules/system/settings_views.py` | Settings ViewSets |
| Integration Views | `modules/system/integration_views.py` | AI integration settings |
| **Unified Settings** | `api/unified_settings.py` | **v1.8.0** Site-level consolidated settings |
| Frontend | `pages/Settings/*.tsx` | Settings pages |
---
## Purpose
The System Settings module manages:
- Platform-wide global settings (API keys, defaults)
- Per-account settings overrides
- AI prompts and configurations
- Module enable/disable
- Author profiles and content strategies
---
## Settings Hierarchy
```
Global Settings (Platform-wide)
Account Settings (Per-account overrides)
User Settings (Per-user preferences)
```
**Priority:** User > Account > Global
---
## Global Settings Models
### GlobalIntegrationSettings (Singleton)
**Admin:** `/admin/system/globalintegrationsettings/`
**Purpose:** Platform-wide API keys and defaults
| Field | Type | Purpose |
|-------|------|---------|
| openai_api_key | CharField | OpenAI API key (all accounts) |
| openai_model | CharField | Default model (gpt-4o-mini) |
| openai_temperature | Float | Default temperature (0.7) |
| openai_max_tokens | Integer | Default max tokens (8192) |
| dalle_api_key | CharField | DALL-E API key |
| dalle_model | CharField | Default model (dall-e-3) |
| dalle_size | CharField | Default size (1024x1024) |
| dalle_quality | CharField | Default quality (standard) |
| dalle_style | CharField | Default style (vivid) |
| anthropic_api_key | CharField | Anthropic API key |
| runware_api_key | CharField | Runware API key |
**Critical:** This is a singleton (only 1 record, pk=1). All accounts use these API keys.
### GlobalAIPrompt
**Admin:** `/admin/system/globalaiprompt/`
**Purpose:** Default AI prompt templates
| Field | Type | Purpose |
|-------|------|---------|
| prompt_type | CharField | clustering/ideas/content_generation/etc. |
| prompt_value | TextField | The actual prompt text |
| description | TextField | What this prompt does |
| variables | JSON | Available variables ({keyword}, {industry}) |
| version | Integer | Prompt version |
| is_active | Boolean | Enable/disable |
### GlobalAuthorProfile
**Admin:** `/admin/system/globalauthorprofile/`
**Purpose:** Default author persona templates
| Field | Type | Purpose |
|-------|------|---------|
| name | CharField | Profile name |
| description | TextField | Description |
| tone | CharField | professional/casual/technical |
| language | CharField | en/es/fr |
| structure_template | JSON | Content structure config |
| category | CharField | saas/ecommerce/blog/technical |
| is_active | Boolean | Enable/disable |
---
## Account Settings Models
### IntegrationSettings
**Purpose:** Per-account AI model overrides (NOT API keys)
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| integration_type | CharField | openai/runware/image_generation |
| config | JSON | Model, temperature, max_tokens overrides |
| is_active | Boolean | Enable/disable |
**Important:**
- Free plan cannot create overrides
- Starter/Growth/Scale can override model/settings
- API keys ALWAYS come from GlobalIntegrationSettings
### ModuleEnableSettings
**Purpose:** Enable/disable modules per account
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| planner_enabled | Boolean | Enable Planner |
| writer_enabled | Boolean | Enable Writer |
| thinker_enabled | Boolean | Enable Thinker (AI settings) |
| automation_enabled | Boolean | Enable Automation |
| site_builder_enabled | Boolean | Enable Site Builder |
| linker_enabled | Boolean | Enable Linker |
| optimizer_enabled | Boolean | Enable Optimizer |
| publisher_enabled | Boolean | Enable Publisher |
**Current Implementation:**
- Controls sidebar navigation visibility
- ⚠️ **Pending:** Extend to other pages and references
### AIPrompt (Account-Level)
**Purpose:** Per-account prompt customizations
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| prompt_type | CharField | Prompt type |
| prompt_value | TextField | Current prompt (custom or default) |
| default_prompt | TextField | Original default (for reset) |
| is_customized | Boolean | True if user modified |
---
## API Endpoints
### Integration Settings
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/system/settings/integrations/openai/` | Get OpenAI settings | Get current model/params |
| PUT | `/api/v1/system/settings/integrations/openai/` | Save OpenAI settings | Save overrides |
| GET | `/api/v1/system/settings/integrations/image_generation/` | Get image settings | Get DALL-E/Runware settings |
| PUT | `/api/v1/system/settings/integrations/image_generation/` | Save image settings | Save overrides |
| POST | `/api/v1/system/settings/integrations/test/` | Test connection | Test API connectivity |
### Prompts
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/system/prompts/` | List prompts | Get all prompts |
| GET | `/api/v1/system/prompts/{type}/` | Get prompt | Get specific prompt |
| PUT | `/api/v1/system/prompts/{type}/` | Save prompt | Save customization |
| POST | `/api/v1/system/prompts/{type}/reset/` | Reset prompt | Reset to default |
### Module Settings
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/system/modules/` | Get module settings | Get enable/disable state |
| PUT | `/api/v1/system/modules/` | Save module settings | Update enabled modules |
---
## Settings Flow (Frontend → Backend)
### Getting OpenAI Settings
1. Frontend requests: `GET /system/settings/integrations/openai/`
2. Backend checks account's `IntegrationSettings`
3. Gets global defaults from `GlobalIntegrationSettings`
4. Merges: account overrides > global defaults
5. Returns (NEVER includes API keys):
```json
{
"model": "gpt-4o-mini",
"temperature": 0.7,
"max_tokens": 8192,
"using_global": true
}
```
### Saving OpenAI Settings
1. Frontend sends: `PUT /system/settings/integrations/openai/`
2. Backend STRIPS any API key fields (security)
3. Validates account plan allows overrides
4. Saves to `IntegrationSettings.config`
5. Returns updated settings
---
## Module Enable/Disable
### How It Works (Current)
1. On app load, frontend fetches module settings
2. `useModuleStore.isModuleEnabled(name)` checks state
3. `AppSidebar.tsx` conditionally renders menu items:
```typescript
if (isModuleEnabled('linker')) {
workflowItems.push({
name: "Linker",
path: "/linker/content",
});
}
```
### Current Limitations (⚠️ Pending Implementation)
- Only hides sidebar menu items
- Direct URL access still works
- Other page references still show module links
- Dashboard cards may still show disabled modules
### Required Extension
Need to add `ModuleGuard` component to:
- Route-level protection
- Dashboard cards/widgets
- Cross-module references
- Settings page links
---
## Frontend Pages
### AI Settings (`/settings/ai`)
- OpenAI model selection
- Temperature and max tokens
- Image generation settings
- Test connection button
### Prompts (`/thinker/prompts`)
- List all prompt types
- Edit prompt text
- Reset to default
- Variable reference
### Module Settings (Admin Only)
- Enable/disable modules
- Per-account configuration
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Settings not saving | Plan restriction | Upgrade plan |
| API key exposed | Security flaw | Should never happen - check code |
| Module still visible | Cache stale | Clear cache, reload |
| Prompt reset not working | default_prompt missing | Re-run migration |
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| Module guard extension | 🔜 Pending | Extend disable to all pages, not just sidebar |
| AIModelConfig database | 🔜 Planned | Move model pricing to database |
| Strategy templates | 🔜 Planned | Global strategy library |
| Per-user preferences | 🔜 Planned | User-level setting overrides |

View File

@@ -0,0 +1,295 @@
# Writer Module
**Last Verified:** January 20, 2026
**Version:** 1.8.4
**Status:** ✅ Active
**Backend Path:** `backend/igny8_core/modules/writer/`
**Frontend Path:** `frontend/src/pages/Writer/`
---
## Quick Reference
| What | File | Key Items |
|------|------|-----------|
| Models | `modules/writer/models.py` | `Tasks`, `Content`, `Images`, `ContentTaxonomy` |
| Views | `modules/writer/views.py` | `TaskViewSet`, `ContentViewSet`, `ImageViewSet` |
| AI Functions | `ai/functions/content.py` | `GenerateContentFunction` |
| AI Functions | `ai/functions/images.py` | `GenerateImagesFunction`, `GenerateImagePromptsFunction` |
| Frontend Pages | `pages/Writer/Tasks.tsx` | Task management |
| Frontend Pages | `pages/Writer/Content.tsx` | Content listing |
| Frontend Pages | `pages/Writer/ContentViewer.tsx` | Content preview/edit |
---
## Purpose
The Writer module manages the content creation pipeline:
```
ContentIdeas → Tasks → Content → Images → Review → Publish
```
---
## Data Models
### Tasks
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| sector | FK | Parent sector |
| content_idea | FK | Source idea (nullable) |
| title | CharField | Task/content title |
| description | TextField | Task brief |
| target_keywords | JSON | Keywords to target |
| content_type | CharField | blog_post/guide/comparison/etc. |
| word_count_target | Integer | Target word count |
| status | CharField | pending/in_progress/completed/cancelled |
| assigned_to | FK | Assigned user (nullable) |
| due_date | DateTime | Due date (nullable) |
| priority | Integer | Priority level |
| created_at | DateTime | Creation date |
### Content
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| sector | FK | Parent sector |
| task | FK | Source task (nullable) |
| title | CharField | Content title |
| slug | SlugField | URL slug |
| content_body | TextField | HTML content |
| excerpt | TextField | Short summary |
| meta_title | CharField | SEO meta title |
| meta_description | CharField | SEO meta description |
| word_count | Integer | Actual word count |
| status | CharField | draft/review/approved/published |
| content_type | CharField | post/page/product |
| content_structure | CharField | article/guide/comparison/review/listicle |
| source | CharField | igny8/wordpress/manual |
| wordpress_id | Integer | WP post ID (if synced) |
| published_at | DateTime | Publication date |
| created_at | DateTime | Creation date |
### Images
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| sector | FK | Parent sector |
| content | FK | Parent content |
| image_type | CharField | featured/desktop/mobile/in_article |
| prompt | TextField | Generation prompt |
| image_url | URLField | Image URL |
| alt_text | CharField | Alt text for SEO |
| status | CharField | pending/generating/completed/failed |
| position | Integer | Order for in-article images |
| provider | CharField | dalle/runware |
| model | CharField | dall-e-3/runware:97@1 |
| created_at | DateTime | Creation date |
### ContentTaxonomy
| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| name | CharField | Category/tag name |
| slug | SlugField | URL slug |
| taxonomy_type | CharField | category/tag |
| parent | FK | Parent taxonomy (for hierarchy) |
| description | TextField | Description |
| wordpress_id | Integer | WP term ID (if synced) |
---
## API Endpoints
### Tasks
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/writer/tasks/` | `TaskViewSet.list` | List tasks |
| POST | `/api/v1/writer/tasks/` | `TaskViewSet.create` | Create task |
| POST | `/api/v1/writer/tasks/bulk_create/` | `TaskViewSet.bulk_create` | Create multiple tasks |
| POST | `/api/v1/writer/tasks/{id}/generate_content/` | `TaskViewSet.generate_content` | AI content generation |
### Content
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/writer/content/` | `ContentViewSet.list` | List content |
| POST | `/api/v1/writer/content/` | `ContentViewSet.create` | Create content manually |
| PUT | `/api/v1/writer/content/{id}/` | `ContentViewSet.update` | Update content |
| POST | `/api/v1/writer/content/{id}/update_content/` | `ContentViewSet.update_content` | Update with validation |
| POST | `/api/v1/writer/content/{id}/generate_images/` | `ContentViewSet.generate_images` | Generate images |
| POST | `/api/v1/writer/content/{id}/publish_to_wordpress/` | `ContentViewSet.publish_to_wordpress` | Publish to WP |
### Images
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/writer/images/` | `ImageViewSet.list` | List images |
| POST | `/api/v1/writer/images/generate_for_content/` | `ImageViewSet.generate_for_content` | Generate images |
| POST | `/api/v1/writer/images/regenerate/` | `ImageViewSet.regenerate` | Regenerate image |
---
## Business Logic
### Content Generation (AI)
**Trigger:** User clicks "Generate" on task
**AI Function:** `GenerateContentFunction`
**Credit Cost:** Based on AI tokens used (see AIModelConfig)
**Flow:**
1. User has task with title, keywords, word count target
2. Frontend calls `POST /tasks/{id}/generate_content/`
3. Backend validates task and credits
4. AIEngine executes `GenerateContentFunction`:
- Loads account's AI prompts and strategy
- Sends to GPT-4 with structured output
- Receives HTML content with proper structure
5. Creates `Content` record linked to task
6. Updates task status to `completed`
7. Returns content for review
### Image Prompt Generation (AI)
**Trigger:** Part of content generation or explicit
**AI Function:** `GenerateImagePromptsFunction`
**Credit Cost:** Based on AI tokens used (see AIModelConfig)
**Flow:**
1. Content exists with body
2. AI analyzes content sections
3. Generates prompts for:
- Featured image (1)
- In-article images (configurable count)
4. Creates `Images` records with prompts, status=pending
### Image Generation (AI)
**Trigger:** User clicks "Generate Images" or automation
**AI Function:** `GenerateImagesFunction`
**Credit Cost:** Deducted per image (see AIModelConfig.credits_per_image)
**Flow:**
1. Image record exists with prompt, status=pending
2. Backend calls DALL-E or Runware API
3. Receives image URL
4. Updates `Images` record with URL, status=completed
### WordPress Publishing
**Trigger:** User clicks "Publish to WordPress"
**Credit Cost:** None
**Flow:**
1. Content is in `approved` status
2. Site has WordPress integration configured
3. Backend calls WordPress REST API:
- Creates/updates post
- Uploads featured image
- Sets categories/tags
4. Updates Content with `wordpress_id`
5. Sets status to `published`
---
## Content Structures
| Structure | Purpose | Typical Sections |
|-----------|---------|------------------|
| article | General blog post | Intro, Body, Conclusion |
| guide | How-to content | Steps, Tips, Summary |
| comparison | Product comparison | Features, Pros/Cons, Verdict |
| review | Product review | Overview, Features, Rating |
| listicle | List-based content | Numbered items with details |
| pillar | Long-form authority | Multiple sections with depth |
---
## Frontend Pages
### Tasks Page (`/writer/tasks`)
- Table of all tasks
- Filter by status, assigned user
- Generate content action
- Bulk actions
### Content Page (`/writer/content`)
- Table of all content
- Filter by status, content type
- Quick preview
- Publish actions
### Content Viewer (`/writer/content/{id}`)
- Full content preview
- Edit mode
- Image management
- Publish to WordPress
- Status management
### Draft/Review/Approved Tabs
- Filtered views by status
- Different actions per status
- **v1.2.0**: "Published" tab renamed to "Approved"
---
## Content Status Flow
```
draft → review → approved → published
(rejected) → draft (revision)
```
---
## Integration Points
| From | To | Trigger |
|------|----|---------|
| ContentIdeas | Tasks | Create tasks |
| Tasks | Content | Generate content |
| Content | Images | Generate images |
| Content | WordPress | Publish |
| WordPress | Content | Sync imports |
| Automation Stage 4 | Content | Automated generation |
| Automation Stage 5-6 | Images | Automated image generation |
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Content too short | Low word count target | Increase target in task |
| Images not generating | No prompts created | Run image prompt generation first |
| WordPress publish fails | Invalid credentials | Check integration settings |
| Content stuck in draft | No manual status update | Update status via UI or API |
| Duplicate content | Re-running generation | Check if content already exists |
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| Content revisions | 🔜 Planned | Track content version history |
| Multi-language | 🔜 Planned | Generate content in different languages |
| Batch generation | 🔜 Planned | Generate multiple content pieces at once |
| Regenerate sections | 🔜 Planned | AI regenerate specific sections |