25 KiB
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:
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_contentAI function inigny8_core/ai/functions/generate_content.pyproduces blog-style articles regardless of thecontent_typefield - Only
content_type='post'withcontent_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_typechoices:product_category,condition_problem,feature,brand,informational,comparisonSAGCluster.hub_page_type(defaultcluster_hub) andhub_page_structure(guide_tutorial, product_comparison, category_overview, problem_solution, resource_library)- 01E blueprint-aware pipeline provides
blueprint_contextto each stage withcluster_type,content_structure, andcontent_typefields - 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)
# 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:
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:
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
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:
Content.sections— JSONField, default=listContent.structured_data— JSONField, default=dictTasks.structure_template— JSONField, default=dictTasks.type_presets— JSONField, default=dictContentTypeTemplatenew table- 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:
# 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:
# 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:
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:
# 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
# Add fields to Content, Tasks models
# Add 'brand_page' to CONTENT_STRUCTURE_CHOICES
Files to modify:
backend/igny8_core/business/content/models.py— addsections,structured_datato Content; addstructure_template,type_presetsto Tasks; addbrand_pageto CONTENT_STRUCTURE_CHOICESbackend/igny8_core/business/planning/models.py— addbrand_pageto ContentIdeas CONTENT_STRUCTURE_CHOICES
Step 2: Create ContentTypeTemplate Model
# Create new model in writer app
File to create:
backend/igny8_core/business/content/content_type_template.py(or add to existingmodels.py)
Step 3: Create and Run Migration
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
sectionsandstructured_data - Modify existing task serializer to include
structure_templateandtype_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
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_hubproduct_prompts.py— product_page, product comparison, product roundupcomparison_prompts.py— versus, multi-comparison, alternativesbrand_prompts.py— brand overview, brand review
Step 9: Frontend Updates
Files to create/modify in frontend/src/:
pages/Writer/ContentTypeTemplates.tsx— template management pagestores/contentTypeTemplateStore.ts— Zustand storeapi/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
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.sectionsandContent.structured_datafields exist and migrate successfullyTasks.structure_templateandTasks.type_presetsfields exist and migrate successfullyContentTypeTemplatemodel created with all fields,igny8_content_type_templatestable existsbrand_pageadded 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 templatesPOST /api/v1/writer/content-type-templates/creates custom template (is_system=False)- System templates cannot be modified or deleted by account users
GenerateContentFunctionroutes to type-specific prompt when template existsGenerateContentFunctionfalls back to existing blog-style prompt when no template found- Content generated with type template populates
sectionsandstructured_datafields - 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
- Read
backend/igny8_core/business/content/models.py— understand existing Content and Tasks models - Read
backend/igny8_core/ai/functions/generate_content.py— understand current generation logic - Read
backend/igny8_core/ai/base.pyandbackend/igny8_core/ai/registry.py— understand base pattern - Add new fields + ContentTypeTemplate model
- Create migration, run
makemigrations+migrate - Build serializers, ViewSet, URLs
- Extend GenerateContentFunction with type routing
- Create seed command and run it
- Build prompt templates per type
- 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.Contentstays singular. - Frontend files use
.tsxextension, Zustand for state management, Vitest for testing - Celery app name is
igny8_core - All new tables use
igny8_prefix - Follow existing ViewSet pattern:
AccountModelViewSetfor account-scoped resources - Follow existing serializer pattern:
ModelSerializerwith explicitfields - Follow existing URL pattern: register on
DefaultRouterinigny8_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