From 4ca85ae0e58f08f0ce2ee9167172b2e66300f0ba Mon Sep 17 00:00:00 2001 From: alorig <220087330+alorig@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:53:29 +0500 Subject: [PATCH] remaining stage 1 --- .../planning/services/clustering_service.py | 14 ++++++ .../igny8_core/modules/planner/serializers.py | 48 +++++++++++-------- .../0012_metadata_mapping_tables.py | 27 +++++++++++ .../igny8_core/modules/writer/serializers.py | 29 ++++++----- 4 files changed, 86 insertions(+), 32 deletions(-) diff --git a/backend/igny8_core/business/planning/services/clustering_service.py b/backend/igny8_core/business/planning/services/clustering_service.py index 9ad8798e..49838c3e 100644 --- a/backend/igny8_core/business/planning/services/clustering_service.py +++ b/backend/igny8_core/business/planning/services/clustering_service.py @@ -52,12 +52,26 @@ class ClusteringService: # Delegate to AI task from igny8_core.ai.tasks import run_ai_task + from django.conf import settings payload = { 'ids': keyword_ids, 'sector_id': sector_id } + # Stage 1: When USE_SITE_BUILDER_REFACTOR is enabled, payload can include + # taxonomy hints and dimension metadata for enhanced clustering. + # TODO (Stage 2/3): Enhance clustering to collect and use: + # - Taxonomy hints from SiteBlueprintTaxonomy + # - Dimension metadata (context_type, dimension_meta) for clusters + # - Attribute values from Keywords.attribute_values + if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False): + logger.info( + f"Clustering with refactor enabled: {len(keyword_ids)} keywords, " + f"sector_id={sector_id}, account_id={account.id}" + ) + # Future: Add taxonomy hints and dimension metadata to payload + try: if hasattr(run_ai_task, 'delay'): # Celery available - queue async diff --git a/backend/igny8_core/modules/planner/serializers.py b/backend/igny8_core/modules/planner/serializers.py index 92c1915d..d1f6a711 100644 --- a/backend/igny8_core/modules/planner/serializers.py +++ b/backend/igny8_core/modules/planner/serializers.py @@ -1,4 +1,5 @@ from rest_framework import serializers +from django.conf import settings from .models import Keywords, Clusters, ContentIdeas from igny8_core.auth.models import SeedKeyword @@ -28,14 +29,6 @@ class KeywordSerializer(serializers.ModelSerializer): sector_name = serializers.SerializerMethodField() site_id = serializers.IntegerField(write_only=True, required=False) sector_id = serializers.IntegerField(write_only=True, required=False) - - attribute_values = serializers.ListField( - child=serializers.CharField(), - required=False, - allow_empty=True, - default=list, - help_text="Optional attribute metadata (e.g., product specs, service modifiers)", - ) class Meta: model = Keywords @@ -53,7 +46,6 @@ class KeywordSerializer(serializers.ModelSerializer): 'cluster_name', 'sector_name', 'status', - 'attribute_values', 'created_at', 'updated_at', 'site_id', @@ -62,6 +54,12 @@ class KeywordSerializer(serializers.ModelSerializer): ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id', 'keyword', 'volume', 'difficulty', 'intent'] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Only include Stage 1 fields when feature flag is enabled + if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False): + self.fields['attribute_values'] = serializers.JSONField(read_only=True) + def validate(self, attrs): """Validate that seed_keyword_id is provided for create operations""" # For create operations, seed_keyword_id is required @@ -123,8 +121,6 @@ class ClusterSerializer(serializers.ModelSerializer): sector_name = serializers.SerializerMethodField() site_id = serializers.IntegerField(write_only=True, required=False) sector_id = serializers.IntegerField(write_only=True, required=False) - - context_type_display = serializers.SerializerMethodField() class Meta: model = Clusters @@ -136,9 +132,6 @@ class ClusterSerializer(serializers.ModelSerializer): 'volume', 'mapped_pages', 'status', - 'context_type', - 'context_type_display', - 'dimension_meta', 'sector_name', 'created_at', 'updated_at', @@ -148,6 +141,14 @@ class ClusterSerializer(serializers.ModelSerializer): ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id', 'keywords_count', 'volume', 'mapped_pages'] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Only include Stage 1 fields when feature flag is enabled + if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False): + self.fields['context_type'] = serializers.CharField(read_only=True) + self.fields['context_type_display'] = serializers.SerializerMethodField() + self.fields['dimension_meta'] = serializers.JSONField(read_only=True) + def get_sector_name(self, obj): """Get sector name from Sector model""" if obj.sector_id: @@ -160,7 +161,10 @@ class ClusterSerializer(serializers.ModelSerializer): return None def get_context_type_display(self, obj): - return obj.get_context_type_display() + """Get context type display name (only when feature flag enabled)""" + if hasattr(obj, 'get_context_type_display'): + return obj.get_context_type_display() + return None def validate_name(self, value): """Ensure cluster name is unique within account""" @@ -172,7 +176,6 @@ class ContentIdeasSerializer(serializers.ModelSerializer): """Serializer for ContentIdeas model""" keyword_cluster_name = serializers.SerializerMethodField() sector_name = serializers.SerializerMethodField() - taxonomy_name = serializers.SerializerMethodField() site_id = serializers.IntegerField(write_only=True, required=False) sector_id = serializers.IntegerField(write_only=True, required=False) @@ -187,13 +190,9 @@ class ContentIdeasSerializer(serializers.ModelSerializer): 'target_keywords', 'keyword_cluster_id', 'keyword_cluster_name', - 'taxonomy_id', - 'taxonomy_name', 'sector_name', 'status', 'estimated_word_count', - 'site_entity_type', - 'cluster_role', 'created_at', 'updated_at', 'site_id', @@ -202,6 +201,15 @@ class ContentIdeasSerializer(serializers.ModelSerializer): ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Only include Stage 1 fields when feature flag is enabled + if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False): + self.fields['taxonomy_id'] = serializers.IntegerField(read_only=True, allow_null=True) + self.fields['taxonomy_name'] = serializers.SerializerMethodField() + self.fields['site_entity_type'] = serializers.CharField(read_only=True) + self.fields['cluster_role'] = serializers.CharField(read_only=True) + def get_keyword_cluster_name(self, obj): """Get cluster name from Clusters model""" if obj.keyword_cluster_id: diff --git a/backend/igny8_core/modules/writer/migrations/0012_metadata_mapping_tables.py b/backend/igny8_core/modules/writer/migrations/0012_metadata_mapping_tables.py index 321cae4e..ec198b74 100644 --- a/backend/igny8_core/modules/writer/migrations/0012_metadata_mapping_tables.py +++ b/backend/igny8_core/modules/writer/migrations/0012_metadata_mapping_tables.py @@ -2,6 +2,28 @@ from django.db import migrations, models import django.db.models.deletion +def backfill_metadata_mappings_stub(apps, schema_editor): + """ + Stage 1: Placeholder for Stage 3 metadata backfill. + + This function will be extended in Stage 3 to backfill: + - ContentClusterMap records from existing Content/Task -> Cluster relationships + - ContentTaxonomyMap records from existing taxonomy associations + - ContentAttributeMap records from existing attribute data + + For now, this is a no-op to establish the migration hook. + """ + # Stage 1: No-op - tables created, ready for Stage 3 backfill + pass + + +def reverse_backfill_metadata_mappings_stub(apps, schema_editor): + """ + Reverse operation for metadata backfill (no-op for Stage 1). + """ + pass + + class Migration(migrations.Migration): dependencies = [ @@ -117,5 +139,10 @@ class Migration(migrations.Migration): model_name='contentattributemap', index=models.Index(fields=['task', 'name'], name='writer_con_task__name_fa4a4e_idx'), ), + # Stage 1: Data migration stub for Stage 3 backfill + migrations.RunPython( + backfill_metadata_mappings_stub, + reverse_backfill_metadata_mappings_stub, + ), ] diff --git a/backend/igny8_core/modules/writer/serializers.py b/backend/igny8_core/modules/writer/serializers.py index 80ea8309..37c3f811 100644 --- a/backend/igny8_core/modules/writer/serializers.py +++ b/backend/igny8_core/modules/writer/serializers.py @@ -1,6 +1,7 @@ from rest_framework import serializers from django.db import models from django.core.exceptions import ObjectDoesNotExist +from django.conf import settings from .models import Tasks, Images, Content from igny8_core.business.planning.models import Clusters, ContentIdeas from igny8_core.business.content.models import ( @@ -22,9 +23,6 @@ class TasksSerializer(serializers.ModelSerializer): content_secondary_keywords = serializers.SerializerMethodField() content_tags = serializers.SerializerMethodField() content_categories = serializers.SerializerMethodField() - cluster_mappings = serializers.SerializerMethodField() - taxonomy_mappings = serializers.SerializerMethodField() - attribute_mappings = serializers.SerializerMethodField() class Meta: model = Tasks @@ -50,9 +48,6 @@ class TasksSerializer(serializers.ModelSerializer): 'content_secondary_keywords', 'content_tags', 'content_categories', - 'cluster_mappings', - 'taxonomy_mappings', - 'attribute_mappings', 'assigned_post_id', 'post_url', 'created_at', @@ -63,6 +58,14 @@ class TasksSerializer(serializers.ModelSerializer): ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Only include Stage 1 fields when feature flag is enabled + if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False): + self.fields['cluster_mappings'] = serializers.SerializerMethodField() + self.fields['taxonomy_mappings'] = serializers.SerializerMethodField() + self.fields['attribute_mappings'] = serializers.SerializerMethodField() + def get_cluster_name(self, obj): """Get cluster name from Clusters model""" if obj.cluster_id: @@ -246,9 +249,6 @@ class ContentSerializer(serializers.ModelSerializer): sector_name = serializers.SerializerMethodField() has_image_prompts = serializers.SerializerMethodField() has_generated_images = serializers.SerializerMethodField() - cluster_mappings = serializers.SerializerMethodField() - taxonomy_mappings = serializers.SerializerMethodField() - attribute_mappings = serializers.SerializerMethodField() class Meta: model = Content @@ -277,12 +277,17 @@ class ContentSerializer(serializers.ModelSerializer): 'entity_type', 'json_blocks', 'structure_data', - 'cluster_mappings', - 'taxonomy_mappings', - 'attribute_mappings', ] read_only_fields = ['id', 'generated_at', 'updated_at', 'account_id'] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Only include Stage 1 fields when feature flag is enabled + if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False): + self.fields['cluster_mappings'] = serializers.SerializerMethodField() + self.fields['taxonomy_mappings'] = serializers.SerializerMethodField() + self.fields['attribute_mappings'] = serializers.SerializerMethodField() + def get_task_title(self, obj): """Get task title""" if obj.task_id: