creative-x-ferrero/scripts/validate_mappings.py
DJP b20119b383 Add complete mapping system and automated Box.com monitoring service
Major Features:
- Complete Ferrero ↔ CreativeX mapping system with 93 brands
- Automated Box.com folder monitoring service
- Email notifications with score breakdowns
- Database integration for result storage

Mapping System (v2.0.0):
- mappings.json: 93 brand mappings, 44+ channel mappings
- core/mapping_resolver.py: Translates Ferrero codes to CreativeX format
- scripts/validate_mappings.py: Validation tool for brand/channel support
- scripts/generate_brand_mappings.py: Auto-mapping tool
- scripts/download_reports.py: Scorecard PDF download tool
- Updated scripts/upload.py: Integrated mapping validation
- Updated scripts/check_status.py: Added detailed score display with guidelines

Documentation:
- Updated README.md: Complete user guide with mapping system
- Updated STATUS.md: Production-ready status with test results
- MAPPINGS_GUIDE.md: Complete mapping documentation
- MAPPING_IMPLEMENTATION.md: Implementation summary
- BRAND_MAPPINGS_REVIEW.md: Brand mapping validation guide
- PRODUCTION_BRANDS_SUMMARY.md: Production brand catalog
- PRODUCTION_MAPPING_COMPLETE.md: Mapping completion summary

Automation Service (New):
- creativex-automation/: Complete automated Box monitoring service
- Monitors Box Ferrero-In folder (363284027140) for new files
- Automatically uploads to CreativeX
- Polls for completion (30 min intervals)
- Extracts scores and stores in PostgreSQL creativex_scores table
- Sends formatted emails to file uploader + daveporter@oliver.agency
- Moves processed files to Processed subfolder

Service Components:
- automation/box_monitor.py: Box folder monitoring with uploader detection
- automation/upload_processor.py: CreativeX upload integration
- automation/status_poller.py: CreativeX status polling
- automation/result_handler.py: Score extraction and email sending
- automation/orchestrator.py: Service coordination
- automation/processing_queue.py: JSON-based processing queue
- service.py: Main service entry point
- config.py: Service configuration loader
- requirements.txt: All dependencies
- deployment/systemd/: Systemd service unit file
- Updated shared/notifier.py: Added creativex_upload_complete and creativex_upload_failed templates

Testing:
- Supports --dry-run mode for configuration testing
- Supports --scan-once mode for Box folder testing
- Manual run mode for development/testing
- Comprehensive logging with rotation (10MB, 28 backups)

Database Integration:
- Uses existing creativex_scores table (no migrations needed)
- Compatible with existing Ferrero-Opentext workflows
- Stores full CreativeX API responses in JSONB

Email Templates:
- Matches Ferrero-Opentext styling (#9c27b0 purple for CreativeX)
- Includes score, tier, guidelines breakdown, scorecard URL
- Recipients: Box uploader + CC to daveporter@oliver.agency

Deployment:
- Runs locally for dev/testing
- Systemd service for production
- Auto-restart on failure
- Complete documentation in creativex-automation/README.md

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
2026-01-29 09:51:16 -05:00

314 lines
9.9 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Validate mappings between Ferrero and Creative X systems
Usage:
python validate_mappings.py
python validate_mappings.py --test-file /path/to/test_file.mp4
python validate_mappings.py --show-supported
"""
import argparse
import sys
from pathlib import Path
from typing import Dict, Any
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from config import load_config
from core.filename_parser import FerreroFilenameParser
from core.data_loader import DataLoader
from core.mapping_resolver import MappingResolver
from utils.file_handler import FileHandler
def print_section(title: str):
"""Print formatted section header"""
print("\n" + "=" * 70)
print(f" {title}")
print("=" * 70)
def print_subsection(title: str):
"""Print formatted subsection header"""
print(f"\n{title}")
print("-" * 70)
def show_supported_values(resolver: MappingResolver):
"""Display all supported brands and channels"""
print_section("SUPPORTED VALUES IN CREATIVE X")
# Brands
print_subsection("Brands")
brands = resolver.get_all_supported_brands()
if brands:
for code, name in brands.items():
print(f" {code:10s}{name}")
else:
print(" (none configured)")
print(f"\n Total: {len(brands)} brands")
# Channels
print_subsection("Social Media Channels")
channels = resolver.get_all_supported_channels()
# Group by channel
channel_groups = {}
for code, channel in channels.items():
if channel not in channel_groups:
channel_groups[channel] = []
channel_groups[channel].append(code)
for channel, codes in sorted(channel_groups.items()):
print(f"\n {channel}:")
for code in sorted(codes):
mapping = resolver.get_creativex_channel_mapping(code)
details = []
if mapping.get('publisher'):
details.append(f"publisher={mapping['publisher']}")
if mapping.get('placement'):
details.append(f"placement={mapping['placement']}")
if mapping.get('ad_format'):
details.append(f"ad_format={mapping['ad_format']}")
detail_str = f" ({', '.join(details)})" if details else ""
print(f" {code:10s}{mapping.get('ferrero_name', '')}{detail_str}")
print(f"\n Total: {len(channels)} social media codes")
def validate_filename(filename: str, parser: FerreroFilenameParser,
resolver: MappingResolver) -> Dict[str, Any]:
"""
Validate a filename through complete mapping process
Returns:
dict: Validation results
"""
results = {
'filename': filename,
'parsed': False,
'parse_errors': [],
'mapped': False,
'mapping_errors': [],
'parsed_data': None,
'creativex_payload': None
}
# Step 1: Parse filename
print_subsection(f"Parsing: {filename}")
try:
parsed_data = parser.parse(filename)
results['parsed'] = True
results['parsed_data'] = parsed_data
print(f" ✓ Parsed successfully")
print(f" Brand: {parsed_data.get('brand_code')}{parsed_data.get('brand_name')}")
print(f" Country: {parsed_data.get('country_code')}{parsed_data.get('country_name')}")
print(f" Language: {parsed_data.get('language_code')}")
print(f" Channel: {parsed_data.get('social_media')}{parsed_data.get('channel')}")
print(f" Subject: {parsed_data.get('subject')}")
print(f" Duration: {parsed_data.get('seconds')}")
print(f" Ratio: {parsed_data.get('aspect_ratio')}")
except Exception as e:
results['parse_errors'].append(str(e))
print(f" ✗ Parse failed: {e}")
return results
# Step 2: Validate mappings
print_subsection("Validating Creative X Mappings")
is_valid, errors = resolver.validate_metadata_for_upload(parsed_data)
if is_valid:
print(f" ✓ All mappings valid")
results['mapped'] = True
else:
results['mapping_errors'] = errors
for error in errors:
print(f"{error}")
return results
# Step 3: Build Creative X payload
print_subsection("Creative X Payload")
try:
payload = resolver.build_creativex_payload(parsed_data)
results['creativex_payload'] = payload
print(f" brand_name: {payload.get('brand_name')}")
print(f" channel: {payload.get('channel')}")
if payload.get('publisher'):
print(f" publisher: {payload.get('publisher')}")
if payload.get('placement'):
print(f" placement: {payload.get('placement')}")
if payload.get('ad_format'):
print(f" ad_format: {payload.get('ad_format')}")
if payload.get('market_name'):
print(f" market_name: {payload.get('market_name')}")
if payload.get('language'):
print(f" language: {payload.get('language')}")
if payload.get('dimensions'):
print(f" dimensions: {payload.get('dimensions')}")
if payload.get('duration'):
print(f" duration: {payload.get('duration')}")
if payload.get('subject'):
print(f" subject: {payload.get('subject')}")
print(f"\n ✓ Ready for upload to Creative X")
except Exception as e:
results['mapping_errors'].append(str(e))
print(f" ✗ Payload build failed: {e}")
return results
def validate_test_file(file_path: str, parser: FerreroFilenameParser,
resolver: MappingResolver):
"""Validate a test file"""
print_section(f"VALIDATING TEST FILE")
filename = FileHandler.get_filename(file_path)
# Check file exists
if not FileHandler.file_exists(file_path):
print(f"\n✗ File not found: {file_path}")
return False
# Validate
results = validate_filename(filename, parser, resolver)
# Summary
print_subsection("Summary")
success = results['parsed'] and results['mapped']
if success:
print(f" ✓ File is valid and ready for upload")
print(f" ✓ Filename parsed successfully")
print(f" ✓ All Creative X mappings valid")
return True
else:
print(f" ✗ File validation failed")
if not results['parsed']:
print(f"\n Parse Errors:")
for error in results['parse_errors']:
print(f" - {error}")
if not results['mapped'] and results['parsed']:
print(f"\n Mapping Errors:")
for error in results['mapping_errors']:
print(f" - {error}")
return False
def validate_example_filenames(parser: FerreroFilenameParser,
resolver: MappingResolver):
"""Validate example filenames"""
print_section("VALIDATING EXAMPLE FILENAMES")
examples = [
"1234567_NUT_MOMENT_OLV_6S_1x1_REF_GL_en_FBS_abcdef.mp4",
"2345678_RAF_LUXURY_OLV_15S_16x9_MST_IT_it_IGF_xyz123.mp4",
"3456789_NUT_BREAKFAST_OLV_10S_9x16_REF_DE_de_YTS_qwerty.mp4",
]
results = []
for filename in examples:
result = validate_filename(filename, parser, resolver)
results.append(result)
# Summary
print_section("VALIDATION SUMMARY")
total = len(results)
passed = sum(1 for r in results if r['parsed'] and r['mapped'])
failed = total - passed
print(f"\n Total: {total}")
print(f" Passed: {passed}")
print(f" Failed: {failed}")
if failed > 0:
print("\n Failed files:")
for result in results:
if not (result['parsed'] and result['mapped']):
print(f" - {result['filename']}")
if result['parse_errors']:
for error in result['parse_errors']:
print(f" Parse: {error}")
if result['mapping_errors']:
for error in result['mapping_errors']:
print(f" Mapping: {error}")
return failed == 0
def main():
"""CLI entry point"""
parser = argparse.ArgumentParser(
description='Validate mappings between Ferrero and Creative X'
)
parser.add_argument('--test-file', help='Test a specific file')
parser.add_argument('--show-supported', action='store_true',
help='Show all supported brands and channels')
parser.add_argument('--test-examples', action='store_true',
help='Test example filenames')
args = parser.parse_args()
# Load configuration
try:
config = load_config()
except Exception as e:
print(f"Error loading configuration: {e}")
sys.exit(1)
# Initialize components
try:
data_loader = DataLoader(str(config.data_json_path))
mappings_path = config.project_root / 'mappings.json'
mapping_resolver = MappingResolver(str(mappings_path))
filename_parser = FerreroFilenameParser(data_loader)
except Exception as e:
print(f"Error initializing components: {e}")
sys.exit(1)
print_section("CREATIVE X MAPPING VALIDATOR")
print(f"\nConfiguration:")
print(f" Data file: {config.data_json_path}")
print(f" Mappings file: {mappings_path}")
success = True
# Show supported values
if args.show_supported or (not args.test_file and not args.test_examples):
show_supported_values(mapping_resolver)
# Test specific file
if args.test_file:
file_success = validate_test_file(args.test_file, filename_parser, mapping_resolver)
success = success and file_success
# Test examples
if args.test_examples:
examples_success = validate_example_filenames(filename_parser, mapping_resolver)
success = success and examples_success
print()
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()