# IGNY8 Phase 2: Content Types Extension (02A) ## Type-Specific Content Generation Pipeline **Document Version:** 1.0 **Date:** 2026-03-23 **Phase:** IGNY8 Phase 2 — Feature Expansion **Status:** Build Ready **Source of Truth:** Codebase at `/data/app/igny8/` **Audience:** Claude Code, Backend Developers, Architects --- ## 1. CURRENT STATE ### Content Model Today The `Content` model (app_label=`writer`, db_table=`igny8_content`) already supports multiple content types via two fields: ```python CONTENT_TYPE_CHOICES = [ ('post', 'Post'), ('page', 'Page'), ('product', 'Product'), ('taxonomy', 'Taxonomy'), ] CONTENT_STRUCTURE_CHOICES = [ ('article', 'Article'), ('guide', 'Guide'), ('comparison', 'Comparison'), ('review', 'Review'), ('listicle', 'Listicle'), ('landing_page', 'Landing Page'), ('business_page', 'Business Page'), ('service_page', 'Service Page'), ('general', 'General'), ('cluster_hub', 'Cluster Hub'), ('product_page', 'Product Page'), ('category_archive', 'Category Archive'), ('tag_archive', 'Tag Archive'), ('attribute_archive', 'Attribute Archive'), ] ``` These same choices exist on `Tasks` (db_table=`igny8_tasks`) and `ContentIdeas` (db_table=`igny8_content_ideas`). ### What Works Now - The `generate_content` AI function in `igny8_core/ai/functions/generate_content.py` produces blog-style articles regardless of the `content_type` field - Only `content_type='post'` with `content_structure='article'` is actively used by the automation pipeline - Pipeline Stage 4 (Tasks → Content) does not route to type-specific prompts - No type-specific section layouts, presets, or schema generation exist ### Phase 1 Foundation Available - `SAGCluster.cluster_type` choices: `product_category`, `condition_problem`, `feature`, `brand`, `informational`, `comparison` - `SAGCluster.hub_page_type` (default `cluster_hub`) and `hub_page_structure` (guide_tutorial, product_comparison, category_overview, problem_solution, resource_library) - 01E blueprint-aware pipeline provides `blueprint_context` to each stage with `cluster_type`, `content_structure`, and `content_type` fields - 01E defines 12 content type → template key mappings (e.g., `sag_hub_guide`, `sag_blog_comparison`, `sag_product_page`) ### Gap The template keys from 01E (`sag_hub_guide`, `sag_blog_comparison`, etc.) route to LLM prompt templates — but those templates don't exist yet. The actual type-specific prompt logic, section layouts, field schemas, and generation presets are what this doc delivers. --- ## 2. WHAT TO BUILD ### Overview Extend the content generation pipeline to produce structurally different output for 6 content type categories. Each type gets: - **Section layout templates** — defining the structure of the generated content (sections, order, constraints) - **Type-specific AI prompts** — prompt templates tailored to the content type's purpose - **Generation presets** — default word counts, image counts, schema types, tone - **Structured data fields** — type-specific data (product specs, service steps, comparison items) stored in a JSONField ### Content Type Extensions **Type 1: Pages** (content_type=`page`) | Structure | Purpose | Section Layout | |-----------|---------|----------------| | `landing_page` | Conversion-focused landing page | Hero → Features → Benefits → Social Proof → CTA | | `business_page` | About/company page | Company Intro → History → Values → Team → CTA | | `service_page` | Service offering page | Problem → Solution → Process → Outcomes → Pricing → FAQ → CTA | | `general` | Generic page | Intro → Body Sections → CTA | | `cluster_hub` | Cluster hub/pillar page | Overview → Subtopic Grid → Detailed Guides → Internal Links → FAQ | - **AI prompt tone:** Professional, conversion-focused, concise, benefit-driven - **Schema:** WebPage (default), AboutPage, ContactPage as appropriate - **Default word count:** 1,000–3,000 depending on structure **Type 2: Products** (content_type=`product`) | Structure | Purpose | Section Layout | |-----------|---------|----------------| | `product_page` | Single product review/description | Overview → Features → Specifications → Pros/Cons → Verdict | | `comparison` | A vs B product comparison | Introduction → Feature Matrix → Category Breakdown → Verdict | | `listicle` | Top products roundup | Introduction → Product Cards (ranked) → Comparison Table → Verdict | - **Structured data fields:** `price_range` (JSON), `features` (JSON array), `specifications` (JSON), `pros_cons` (JSON {pros: [], cons: []}) - **AI prompt tone:** Feature-benefit mapping, objective analysis, buyer-persona aware - **Schema:** Product with offers, aggregateRating, review - **Image presets:** Product hero image, feature highlight visuals, comparison table graphic - **Default word count:** 1,500–4,000 **Type 3: Services** (content_type=`page`, content_structure=`service_page`) | Structure | Purpose | Section Layout | |-----------|---------|----------------| | `service_page` | Core service offering page | Problem → Solution → Process Steps → Outcomes → Pricing Tiers → FAQ → CTA | | `landing_page` | Service-specific landing (area variant) | Hero → Service Intro → Benefits → Testimonials → Area Info → CTA | - **Structured data fields:** `process_steps` (JSON array), `outcomes` (JSON array), `pricing_tiers` (JSON), `areas_served` (JSON array), `faqs` (JSON array of {question, answer}) - **AI prompt tone:** Problem-solution, trust-building, process explanation, CTA-heavy - **Schema:** Service, ProfessionalService, LocalBusiness+hasOfferCatalog - **Geographic targeting:** Generate area-specific variations from a base service page - **Default word count:** 1,500–3,500 **Type 4: Company Pages** (content_type=`page`, content_structure=`business_page`) | Structure | Purpose | |-----------|---------| | `business_page` | About company, team, careers, press pages | - **AI prompt tone:** Brand voice emphasis, story-driven, credibility markers - **Schema:** Organization, AboutPage - **Default word count:** 800–2,000 **Type 5: Comparison Pages** (content_type=`post`, content_structure=`comparison`) | Structure | Purpose | Section Layout | |-----------|---------|----------------| | `comparison` | A vs B analysis | Introduction → Feature Matrix → Category-by-Category → Winner → FAQ | | `listicle` | Top N alternatives/roundup | Introduction → Comparison Table → Individual Reviews → Verdict | - **Structured data fields:** `comparison_items` (JSON array of {name, features, pros, cons, rating, verdict}) - **AI prompt tone:** Objective analysis, data-driven, comparison tables, winner selection with reasoning - **Schema:** Article with itemListElement (for multi-comparison) - **Default word count:** 2,000–5,000 **Type 6: Brand Pages** (content_type=`page`, content_structure=`brand_page`) | Structure | Purpose | |-----------|---------| | `brand_page` | Brand overview, review, or alternative recommendation page | - **AI prompt tone:** Brand-focused, factual, company background included - **Schema:** Organization (for overview), Article (for review) - **Default word count:** 1,000–3,000 ### Blueprint-to-Type Mapping When the pipeline executes with SAG context (01E), the `SAGCluster.cluster_type` informs which content types to generate: | SAGCluster.cluster_type | Primary content_type | Primary content_structure | |-------------------------|---------------------|--------------------------| | `informational` | post | article, guide | | `comparison` | post | comparison, listicle | | `product_category` | product | product_page, listicle | | `feature` | page | landing_page, service_page | | `brand` | page | brand_page | | `condition_problem` | post | guide, article | Hub pages for any cluster type use `content_type=page`, `content_structure=cluster_hub`. --- ## 3. DATA MODELS & APIs ### New Choices (add to existing choice lists) ```python # Add to CONTENT_STRUCTURE_CHOICES on Content, Tasks, ContentIdeas ('brand_page', 'Brand Page'), ``` Note: Most structures already exist in the codebase. Only `brand_page` is new. ### Modified Models **Content** (db_table=`igny8_content`) — add fields: ```python sections = models.JSONField( default=list, blank=True, help_text="Ordered section data for structured content types" ) # Structure: [{"type": "hero", "heading": "...", "body": "...", "cta": "..."}, ...] structured_data = models.JSONField( default=dict, blank=True, help_text="Type-specific data: product specs, service steps, comparison items" ) # Structure varies by content_type — see type definitions above ``` **Tasks** (db_table=`igny8_tasks`) — add fields: ```python structure_template = models.JSONField( default=dict, blank=True, help_text="Section layout template for content generation" ) # Structure: {"sections": [{"type": "hero", "required": true, "max_words": 200}, ...]} type_presets = models.JSONField( default=dict, blank=True, help_text="Type-specific generation parameters" ) # Structure: {"tone": "professional", "schema_type": "Product", "image_count": 3, ...} ``` ### New Model ```python class ContentTypeTemplate(AccountBaseModel): """ Defines section layout and AI prompt templates per content_type + content_structure combination. System-provided defaults (is_system=True) plus per-account custom templates. """ content_type = models.CharField(max_length=50, choices=CONTENT_TYPE_CHOICES, db_index=True) content_structure = models.CharField(max_length=50, choices=CONTENT_STRUCTURE_CHOICES, db_index=True) template_name = models.CharField(max_length=200) section_layout = models.JSONField( default=list, help_text="Ordered sections: [{type, label, required, max_words, guidance}]" ) ai_prompt_template = models.TextField( help_text="Base AI prompt template for this type. Supports {variables}." ) default_schema_type = models.CharField(max_length=100, blank=True, default='') default_word_count_min = models.IntegerField(default=1000) default_word_count_max = models.IntegerField(default=3000) default_image_count = models.IntegerField(default=2) tone = models.CharField(max_length=100, default='professional') is_system = models.BooleanField(default=False, help_text="System-provided template (not editable by users)") is_active = models.BooleanField(default=True) class Meta: app_label = 'writer' db_table = 'igny8_content_type_templates' unique_together = [['account', 'content_type', 'content_structure', 'template_name']] ordering = ['content_type', 'content_structure'] ``` ### Migration ``` igny8_core/migrations/XXXX_content_types_extension.py ``` Fields added: 1. `Content.sections` — JSONField, default=list 2. `Content.structured_data` — JSONField, default=dict 3. `Tasks.structure_template` — JSONField, default=dict 4. `Tasks.type_presets` — JSONField, default=dict 5. `ContentTypeTemplate` new table 6. Add `('brand_page', 'Brand Page')` to CONTENT_STRUCTURE_CHOICES ### API Endpoints **Content Type Templates:** ``` GET /api/v1/writer/content-type-templates/ # List templates (filtered by content_type, content_structure) POST /api/v1/writer/content-type-templates/ # Create custom template GET /api/v1/writer/content-type-templates/{id}/ # Template detail PUT /api/v1/writer/content-type-templates/{id}/ # Update custom template DELETE /api/v1/writer/content-type-templates/{id}/ # Delete custom template (system templates cannot be deleted) GET /api/v1/writer/content-type-templates/{id}/preview/ # Preview: generate sample section layout ``` **Modified Endpoints:** ``` POST /api/v1/writer/tasks/ # Extend: accepts structure_template + type_presets POST /api/v1/writer/generate/ # Extend: routes to type-specific AI prompt GET /api/v1/writer/content/{id}/ # Response now includes sections + structured_data ``` **ViewSet:** ```python # igny8_core/modules/writer/views/content_type_template_views.py class ContentTypeTemplateViewSet(AccountModelViewSet): serializer_class = ContentTypeTemplateSerializer queryset = ContentTypeTemplate.objects.all() filterset_fields = ['content_type', 'content_structure', 'is_system', 'is_active'] def get_queryset(self): # Return system templates + account's custom templates return ContentTypeTemplate.objects.filter( models.Q(is_system=True) | models.Q(account=self.request.account) ) @action(detail=True, methods=['get']) def preview(self, request, pk=None): template = self.get_object() # Return rendered section layout with placeholder content return Response({'sections': template.section_layout}) ``` **URL Registration:** ```python # igny8_core/modules/writer/urls.py — add to existing router router.register('content-type-templates', ContentTypeTemplateViewSet, basename='content-type-template') ``` ### AI Function Extension Extend `GenerateContentFunction` in `igny8_core/ai/functions/generate_content.py`: ```python class GenerateContentFunction(BaseAIFunction): def prepare(self, payload: dict, account=None) -> Any: tasks = super().prepare(payload, account) for task in tasks: # Load template if not already set on task if not task.structure_template: template = ContentTypeTemplate.objects.filter( models.Q(account=account) | models.Q(is_system=True), content_type=task.content_type, content_structure=task.content_structure, is_active=True ).order_by('-is_system').first() # Prefer account-specific over system if template: task._template = template return tasks def build_prompt(self, data: Any, account=None) -> str: task = data # Single task (batch_size=1) template = getattr(task, '_template', None) if template: prompt = template.ai_prompt_template.format( title=task.title, keywords=task.keywords or '', word_count=task.word_count, content_type=task.content_type, content_structure=task.content_structure, sections=json.dumps(template.section_layout), schema_type=template.default_schema_type, tone=template.tone, ) else: # Fallback to existing blog-style prompt prompt = self._build_default_prompt(task) # Inject blueprint context if available (from 01E) blueprint_context = getattr(task, 'blueprint_context', None) if blueprint_context: prompt += f"\n\nCluster Context: {json.dumps(blueprint_context)}" return prompt def parse_response(self, response: str, step_tracker=None) -> Any: parsed = super().parse_response(response, step_tracker) # Extract sections array and structured_data if present in AI response if isinstance(parsed, dict): parsed.setdefault('sections', []) parsed.setdefault('structured_data', {}) return parsed def save_output(self, parsed, original_data, account=None, **kwargs) -> Dict: result = super().save_output(parsed, original_data, account, **kwargs) # Persist sections and structured_data on Content if 'content_id' in result: Content.objects.filter(id=result['content_id']).update( sections=parsed.get('sections', []), structured_data=parsed.get('structured_data', {}), ) return result ``` ### System Template Seed Data Create a management command to seed default templates: ```python # igny8_core/management/commands/seed_content_type_templates.py ``` Seed templates (is_system=True, account=None): | content_type | content_structure | template_name | default_schema_type | word_count_range | |---|---|---|---|---| | post | article | Blog Article | Article | 1000–2500 | | post | guide | Comprehensive Guide | Article | 2000–4000 | | post | comparison | Comparison Article | Article | 2000–5000 | | post | review | Product Review | Review | 1500–3000 | | post | listicle | Listicle | Article | 1500–3500 | | page | landing_page | Landing Page | WebPage | 1000–2500 | | page | business_page | Business Page | AboutPage | 800–2000 | | page | service_page | Service Page | Service | 1500–3500 | | page | general | General Page | WebPage | 500–2000 | | page | cluster_hub | Cluster Hub Page | CollectionPage | 2000–5000 | | page | brand_page | Brand Page | Organization | 1000–3000 | | product | product_page | Product Page | Product | 1500–4000 | | taxonomy | category_archive | Category Archive | CollectionPage | 500–1500 | | taxonomy | tag_archive | Tag Archive | CollectionPage | 500–1500 | | taxonomy | attribute_archive | Attribute Archive | CollectionPage | 500–1500 | ### Credit Costs No change to existing credit costs. Type routing changes the prompt structure but not the token volume — still 4–8 credits per content generation via `CreditCostConfig(operation_type='content_generation')`. --- ## 4. IMPLEMENTATION STEPS ### Step 1: Add New Fields to Existing Models ```bash # Add fields to Content, Tasks models # Add 'brand_page' to CONTENT_STRUCTURE_CHOICES ``` Files to modify: - `backend/igny8_core/business/content/models.py` — add `sections`, `structured_data` to Content; add `structure_template`, `type_presets` to Tasks; add `brand_page` to CONTENT_STRUCTURE_CHOICES - `backend/igny8_core/business/planning/models.py` — add `brand_page` to ContentIdeas CONTENT_STRUCTURE_CHOICES ### Step 2: Create ContentTypeTemplate Model ```bash # Create new model in writer app ``` File to create: - `backend/igny8_core/business/content/content_type_template.py` (or add to existing `models.py`) ### Step 3: Create and Run Migration ```bash cd /data/app/igny8/backend python manage.py makemigrations --name content_types_extension python manage.py migrate ``` ### Step 4: Create Serializers Files to create/modify: - `backend/igny8_core/modules/writer/serializers/content_type_template_serializer.py` - Modify existing content serializer to include `sections` and `structured_data` - Modify existing task serializer to include `structure_template` and `type_presets` ### Step 5: Create ViewSet and URLs Files to create: - `backend/igny8_core/modules/writer/views/content_type_template_views.py` - Modify `backend/igny8_core/modules/writer/urls.py` — register new ViewSet ### Step 6: Extend GenerateContentFunction File to modify: - `backend/igny8_core/ai/functions/generate_content.py` — add type routing logic ### Step 7: Create System Template Seed Command File to create: - `backend/igny8_core/management/commands/seed_content_type_templates.py` ```bash python manage.py seed_content_type_templates ``` ### Step 8: Create Type-Specific Prompt Templates Files to create in `backend/igny8_core/ai/prompts/`: - `page_prompts.py` — landing_page, business_page, service_page, general, cluster_hub - `product_prompts.py` — product_page, product comparison, product roundup - `comparison_prompts.py` — versus, multi-comparison, alternatives - `brand_prompts.py` — brand overview, brand review ### Step 9: Frontend Updates Files to create/modify in `frontend/src/`: - `pages/Writer/ContentTypeTemplates.tsx` — template management page - `stores/contentTypeTemplateStore.ts` — Zustand store - `api/contentTypeTemplates.ts` — API client - Modify task creation form to show type-specific template selection - Modify content viewer to render section-based content ### Step 10: Tests ```bash cd /data/app/igny8/backend python manage.py test igny8_core.business.content.tests.test_content_type_templates python manage.py test igny8_core.ai.tests.test_generate_content_types ``` --- ## 5. ACCEPTANCE CRITERIA - [ ] `Content.sections` and `Content.structured_data` fields exist and migrate successfully - [ ] `Tasks.structure_template` and `Tasks.type_presets` fields exist and migrate successfully - [ ] `ContentTypeTemplate` model created with all fields, `igny8_content_type_templates` table exists - [ ] `brand_page` added to CONTENT_STRUCTURE_CHOICES on Content, Tasks, ContentIdeas - [ ] 15 system templates seeded via management command - [ ] `GET /api/v1/writer/content-type-templates/` returns system + account templates - [ ] `POST /api/v1/writer/content-type-templates/` creates custom template (is_system=False) - [ ] System templates cannot be modified or deleted by account users - [ ] `GenerateContentFunction` routes to type-specific prompt when template exists - [ ] `GenerateContentFunction` falls back to existing blog-style prompt when no template found - [ ] Content generated with type template populates `sections` and `structured_data` fields - [ ] Blueprint context from 01E is injected into prompts when SAG data available - [ ] Frontend template management page allows CRUD on custom templates - [ ] Task creation form shows template selection filtered by content_type + content_structure - [ ] All new API endpoints require authentication and enforce account isolation - [ ] Existing `content_type='post'` + `content_structure='article'` generation works unchanged (backward compatible) --- ## 6. CLAUDE CODE INSTRUCTIONS ### Execution Order 1. Read `backend/igny8_core/business/content/models.py` — understand existing Content and Tasks models 2. Read `backend/igny8_core/ai/functions/generate_content.py` — understand current generation logic 3. Read `backend/igny8_core/ai/base.py` and `backend/igny8_core/ai/registry.py` — understand base pattern 4. Add new fields + ContentTypeTemplate model 5. Create migration, run `makemigrations` + `migrate` 6. Build serializers, ViewSet, URLs 7. Extend GenerateContentFunction with type routing 8. Create seed command and run it 9. Build prompt templates per type 10. Build frontend components ### Key Constraints - ALL primary keys are `BigAutoField` (integer). No UUIDs anywhere. - Model class names are PLURAL where applicable: `Tasks`, `ContentIdeas`, `Clusters`, `Keywords`, `Images`. `Content` stays singular. - Frontend files use `.tsx` extension, Zustand for state management, Vitest for testing - Celery app name is `igny8_core` - All new tables use `igny8_` prefix - Follow existing ViewSet pattern: `AccountModelViewSet` for account-scoped resources - Follow existing serializer pattern: `ModelSerializer` with explicit `fields` - Follow existing URL pattern: register on `DefaultRouter` in `igny8_core/modules/writer/urls.py` ### File Tree (New/Modified) ``` backend/igny8_core/ ├── business/content/ │ └── models.py # MODIFY: add sections, structured_data, structure_template, type_presets, brand_page choice │ └── content_type_template.py # NEW: ContentTypeTemplate model (or add to models.py) ├── business/planning/ │ └── models.py # MODIFY: add brand_page to ContentIdeas CONTENT_STRUCTURE_CHOICES ├── ai/functions/ │ └── generate_content.py # MODIFY: type routing + template-aware prompt building ├── ai/prompts/ │ ├── page_prompts.py # NEW: landing page, business page, service page prompts │ ├── product_prompts.py # NEW: product page, comparison, roundup prompts │ ├── comparison_prompts.py # NEW: versus, multi-comparison prompts │ └── brand_prompts.py # NEW: brand overview/review prompts ├── management/commands/ │ └── seed_content_type_templates.py # NEW: seed system templates ├── modules/writer/ │ ├── serializers/ │ │ └── content_type_template_serializer.py # NEW │ ├── views/ │ │ └── content_type_template_views.py # NEW │ └── urls.py # MODIFY: register new ViewSet ├── migrations/ │ └── XXXX_content_types_extension.py # NEW: auto-generated frontend/src/ ├── pages/Writer/ │ └── ContentTypeTemplates.tsx # NEW: template management ├── stores/ │ └── contentTypeTemplateStore.ts # NEW: Zustand store ├── api/ │ └── contentTypeTemplates.ts # NEW: API client ``` ### Cross-References - **01E** (blueprint-aware pipeline): blueprint_context injection, cluster_type → content_type mapping - **01A** (SAG data foundation): SAGCluster.cluster_type, hub_page_type, hub_page_structure - **02B** (taxonomy term content): uses content_type=taxonomy with ContentTypeTemplate - **02G** (rich schema): schema_type from ContentTypeTemplate.default_schema_type - **03B** (WP plugin connected): content sync maps content_type to WordPress post types