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:
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user