Implement Stage 3: Enhance content metadata and validation features

- Added entity metadata fields to the Tasks model, including entity_type, taxonomy, and cluster_role.
- Updated CandidateEngine to prioritize content relevance based on cluster mappings.
- Introduced metadata completeness scoring in ContentAnalyzer.
- Enhanced validation services to check for entity type and mapping completeness.
- Updated frontend components to display and validate new metadata fields.
- Implemented API endpoints for content validation and metadata persistence.
- Migrated existing data to populate new metadata fields for Tasks and Content.
This commit is contained in:
IGNY8 VPS (Salman)
2025-11-19 19:21:30 +00:00
parent 38f6026e73
commit bae9ea47d8
33 changed files with 2388 additions and 73 deletions

View File

@@ -13,6 +13,8 @@ from igny8_core.api.permissions import IsAuthenticatedAndActive, IsViewerOrAbove
from .models import Tasks, Images, Content
from .serializers import TasksSerializer, ImagesSerializer, ContentSerializer
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
from igny8_core.business.content.services.validation_service import ContentValidationService
from igny8_core.business.content.services.metadata_mapping_service import MetadataMappingService
from igny8_core.business.billing.exceptions import InsufficientCreditsError
@@ -668,6 +670,74 @@ class ImagesViewSet(SiteSectorModelViewSet):
request=request
)
@action(detail=True, methods=['get'], url_path='validation', url_name='validation')
def validation(self, request, pk=None):
"""
Stage 3: Get validation checklist for content.
GET /api/v1/writer/content/{id}/validation/
Returns aggregated validation checklist for Writer UI.
"""
content = self.get_object()
validation_service = ContentValidationService()
errors = validation_service.validate_content(content)
publish_errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'ready_to_publish': len(publish_errors) == 0,
'validation_errors': errors,
'publish_errors': publish_errors,
'metadata': {
'has_entity_type': bool(content.entity_type),
'entity_type': content.entity_type,
'has_cluster_mapping': self._has_cluster_mapping(content),
'has_taxonomy_mapping': self._has_taxonomy_mapping(content),
}
},
request=request
)
@action(detail=True, methods=['post'], url_path='validate', url_name='validate')
def validate(self, request, pk=None):
"""
Stage 3: Re-run validators and return actionable errors.
POST /api/v1/writer/content/{id}/validate/
Re-validates content and returns structured errors.
"""
content = self.get_object()
validation_service = ContentValidationService()
# Persist metadata mappings if task exists
if content.task:
mapping_service = MetadataMappingService()
mapping_service.persist_task_metadata_to_content(content)
errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'errors': errors,
},
request=request
)
def _has_cluster_mapping(self, content):
"""Helper to check if content has cluster mapping"""
from igny8_core.business.content.models import ContentClusterMap
return ContentClusterMap.objects.filter(content=content).exists()
def _has_taxonomy_mapping(self, content):
"""Helper to check if content has taxonomy mapping"""
from igny8_core.business.content.models import ContentTaxonomyMap
return ContentTaxonomyMap.objects.filter(content=content).exists()
@extend_schema_view(
list=extend_schema(tags=['Writer']),
create=extend_schema(tags=['Writer']),
@@ -758,6 +828,74 @@ class ContentViewSet(SiteSectorModelViewSet):
request=request
)
@action(detail=True, methods=['get'], url_path='validation', url_name='validation')
def validation(self, request, pk=None):
"""
Stage 3: Get validation checklist for content.
GET /api/v1/writer/content/{id}/validation/
Returns aggregated validation checklist for Writer UI.
"""
content = self.get_object()
validation_service = ContentValidationService()
errors = validation_service.validate_content(content)
publish_errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'ready_to_publish': len(publish_errors) == 0,
'validation_errors': errors,
'publish_errors': publish_errors,
'metadata': {
'has_entity_type': bool(content.entity_type),
'entity_type': content.entity_type,
'has_cluster_mapping': self._has_cluster_mapping(content),
'has_taxonomy_mapping': self._has_taxonomy_mapping(content),
}
},
request=request
)
@action(detail=True, methods=['post'], url_path='validate', url_name='validate')
def validate(self, request, pk=None):
"""
Stage 3: Re-run validators and return actionable errors.
POST /api/v1/writer/content/{id}/validate/
Re-validates content and returns structured errors.
"""
content = self.get_object()
validation_service = ContentValidationService()
# Persist metadata mappings if task exists
if content.task:
mapping_service = MetadataMappingService()
mapping_service.persist_task_metadata_to_content(content)
errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'errors': errors,
},
request=request
)
def _has_cluster_mapping(self, content):
"""Helper to check if content has cluster mapping"""
from igny8_core.business.content.models import ContentClusterMap
return ContentClusterMap.objects.filter(content=content).exists()
def _has_taxonomy_mapping(self, content):
"""Helper to check if content has taxonomy mapping"""
from igny8_core.business.content.models import ContentTaxonomyMap
return ContentTaxonomyMap.objects.filter(content=content).exists()
@action(detail=False, methods=['post'], url_path='generate_product', url_name='generate_product')
def generate_product(self, request):
"""
@@ -841,6 +979,74 @@ class ContentViewSet(SiteSectorModelViewSet):
request=request
)
@action(detail=True, methods=['get'], url_path='validation', url_name='validation')
def validation(self, request, pk=None):
"""
Stage 3: Get validation checklist for content.
GET /api/v1/writer/content/{id}/validation/
Returns aggregated validation checklist for Writer UI.
"""
content = self.get_object()
validation_service = ContentValidationService()
errors = validation_service.validate_content(content)
publish_errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'ready_to_publish': len(publish_errors) == 0,
'validation_errors': errors,
'publish_errors': publish_errors,
'metadata': {
'has_entity_type': bool(content.entity_type),
'entity_type': content.entity_type,
'has_cluster_mapping': self._has_cluster_mapping(content),
'has_taxonomy_mapping': self._has_taxonomy_mapping(content),
}
},
request=request
)
@action(detail=True, methods=['post'], url_path='validate', url_name='validate')
def validate(self, request, pk=None):
"""
Stage 3: Re-run validators and return actionable errors.
POST /api/v1/writer/content/{id}/validate/
Re-validates content and returns structured errors.
"""
content = self.get_object()
validation_service = ContentValidationService()
# Persist metadata mappings if task exists
if content.task:
mapping_service = MetadataMappingService()
mapping_service.persist_task_metadata_to_content(content)
errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'errors': errors,
},
request=request
)
def _has_cluster_mapping(self, content):
"""Helper to check if content has cluster mapping"""
from igny8_core.business.content.models import ContentClusterMap
return ContentClusterMap.objects.filter(content=content).exists()
def _has_taxonomy_mapping(self, content):
"""Helper to check if content has taxonomy mapping"""
from igny8_core.business.content.models import ContentTaxonomyMap
return ContentTaxonomyMap.objects.filter(content=content).exists()
@action(detail=False, methods=['post'], url_path='generate_service', url_name='generate_service')
def generate_service(self, request):
"""
@@ -924,6 +1130,74 @@ class ContentViewSet(SiteSectorModelViewSet):
request=request
)
@action(detail=True, methods=['get'], url_path='validation', url_name='validation')
def validation(self, request, pk=None):
"""
Stage 3: Get validation checklist for content.
GET /api/v1/writer/content/{id}/validation/
Returns aggregated validation checklist for Writer UI.
"""
content = self.get_object()
validation_service = ContentValidationService()
errors = validation_service.validate_content(content)
publish_errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'ready_to_publish': len(publish_errors) == 0,
'validation_errors': errors,
'publish_errors': publish_errors,
'metadata': {
'has_entity_type': bool(content.entity_type),
'entity_type': content.entity_type,
'has_cluster_mapping': self._has_cluster_mapping(content),
'has_taxonomy_mapping': self._has_taxonomy_mapping(content),
}
},
request=request
)
@action(detail=True, methods=['post'], url_path='validate', url_name='validate')
def validate(self, request, pk=None):
"""
Stage 3: Re-run validators and return actionable errors.
POST /api/v1/writer/content/{id}/validate/
Re-validates content and returns structured errors.
"""
content = self.get_object()
validation_service = ContentValidationService()
# Persist metadata mappings if task exists
if content.task:
mapping_service = MetadataMappingService()
mapping_service.persist_task_metadata_to_content(content)
errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'errors': errors,
},
request=request
)
def _has_cluster_mapping(self, content):
"""Helper to check if content has cluster mapping"""
from igny8_core.business.content.models import ContentClusterMap
return ContentClusterMap.objects.filter(content=content).exists()
def _has_taxonomy_mapping(self, content):
"""Helper to check if content has taxonomy mapping"""
from igny8_core.business.content.models import ContentTaxonomyMap
return ContentTaxonomyMap.objects.filter(content=content).exists()
@action(detail=False, methods=['post'], url_path='generate_taxonomy', url_name='generate_taxonomy')
def generate_taxonomy(self, request):
"""
@@ -1005,4 +1279,72 @@ class ContentViewSet(SiteSectorModelViewSet):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
@action(detail=True, methods=['get'], url_path='validation', url_name='validation')
def validation(self, request, pk=None):
"""
Stage 3: Get validation checklist for content.
GET /api/v1/writer/content/{id}/validation/
Returns aggregated validation checklist for Writer UI.
"""
content = self.get_object()
validation_service = ContentValidationService()
errors = validation_service.validate_content(content)
publish_errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'ready_to_publish': len(publish_errors) == 0,
'validation_errors': errors,
'publish_errors': publish_errors,
'metadata': {
'has_entity_type': bool(content.entity_type),
'entity_type': content.entity_type,
'has_cluster_mapping': self._has_cluster_mapping(content),
'has_taxonomy_mapping': self._has_taxonomy_mapping(content),
}
},
request=request
)
@action(detail=True, methods=['post'], url_path='validate', url_name='validate')
def validate(self, request, pk=None):
"""
Stage 3: Re-run validators and return actionable errors.
POST /api/v1/writer/content/{id}/validate/
Re-validates content and returns structured errors.
"""
content = self.get_object()
validation_service = ContentValidationService()
# Persist metadata mappings if task exists
if content.task:
mapping_service = MetadataMappingService()
mapping_service.persist_task_metadata_to_content(content)
errors = validation_service.validate_for_publish(content)
return success_response(
data={
'content_id': content.id,
'is_valid': len(errors) == 0,
'errors': errors,
},
request=request
)
def _has_cluster_mapping(self, content):
"""Helper to check if content has cluster mapping"""
from igny8_core.business.content.models import ContentClusterMap
return ContentClusterMap.objects.filter(content=content).exists()
def _has_taxonomy_mapping(self, content):
"""Helper to check if content has taxonomy mapping"""
from igny8_core.business.content.models import ContentTaxonomyMap
return ContentTaxonomyMap.objects.filter(content=content).exists()