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>
421 lines
No EOL
10 KiB
Markdown
421 lines
No EOL
10 KiB
Markdown
# Creative X Mappings Guide
|
|
|
|
## Overview
|
|
|
|
The Ferrero naming convention and Creative X API use different codes and naming for brands, channels, and markets. This mapping system bridges the two systems to enable seamless uploads.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Ferrero Filename
|
|
↓
|
|
[Filename Parser] → Extracts Ferrero codes
|
|
↓
|
|
[Mapping Resolver] → Translates to Creative X format
|
|
↓
|
|
[API Client] → Uploads with Creative X metadata
|
|
```
|
|
|
|
## Files
|
|
|
|
- **`mappings.json`** - Complete mapping tables (brands, channels, countries)
|
|
- **`core/mapping_resolver.py`** - Python module for resolving mappings
|
|
- **`scripts/upload.py`** - Upload script (uses mapping resolver)
|
|
|
|
---
|
|
|
|
## Mapping Tables
|
|
|
|
### 1. Brand Mappings
|
|
|
|
Maps Ferrero brand codes to Creative X brand names and IDs.
|
|
|
|
**Example:**
|
|
```json
|
|
{
|
|
"NUT": {
|
|
"creativex_name": "Nutella",
|
|
"creativex_id": 423,
|
|
"ferrero_name": "NUTELLA"
|
|
},
|
|
"RAF": {
|
|
"creativex_name": "Rafalleo",
|
|
"creativex_id": 422,
|
|
"ferrero_name": "RAFFAELLO"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Current Status:**
|
|
- ✅ Nutella (NUT)
|
|
- ✅ Raffaello (RAF) - Note: Creative X spells it "Rafalleo"
|
|
- ❌ 103 other Ferrero brands not yet added to Creative X
|
|
|
|
### 2. Channel Mappings
|
|
|
|
Maps Ferrero social media codes to Creative X channel + publisher + placement combinations.
|
|
|
|
**Structure:**
|
|
```json
|
|
{
|
|
"FERRERO_CODE": {
|
|
"ferrero_name": "Human-readable name",
|
|
"creativex_channel": "channel_name",
|
|
"creativex_publisher": "publisher_name (optional)",
|
|
"creativex_placement": "placement_name (optional)",
|
|
"creativex_ad_format": "ad_format (optional for YouTube/DV360)"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Examples:**
|
|
|
|
#### Facebook (requires channel + publisher + placement)
|
|
```
|
|
FBS → facebook_paid / facebook / facebook_stories
|
|
IGF → instagram_paid / instagram / feed
|
|
```
|
|
|
|
#### YouTube (requires channel + publisher + ad_format)
|
|
```
|
|
YTB → google_ads / youtube / Bumper
|
|
YTS → google_ads / youtube / Shorts
|
|
```
|
|
|
|
#### Simple channels (only need channel name)
|
|
```
|
|
TIK → tiktok_paid
|
|
PIN → pinterest
|
|
```
|
|
|
|
**Supported Channels:**
|
|
| Ferrero Code | Creative X Channel | Notes |
|
|
|--------------|-------------------|-------|
|
|
| FBS, FBF, etc. | facebook_paid | 15 Facebook placements |
|
|
| IGF, IGR, IST, etc. | instagram_paid | 11 Instagram placements |
|
|
| MSI, MSS | facebook_paid | 2 Messenger placements |
|
|
| ANC, ANI, ANR | facebook_paid | 3 Audience Network placements |
|
|
| YTB, YTI, YTS, etc. | google_ads | 5 YouTube ad formats |
|
|
| DV3 | dv360 | Display & YouTube |
|
|
| TIK | tiktok_paid | TikTok |
|
|
| SNA | snapchat_paid | Snapchat |
|
|
| PIN | pinterest | Pinterest |
|
|
| TWI | twitter_paid | Twitter/X |
|
|
| AMZ | amazon_paid | Amazon |
|
|
| GOO | google_ads | Generic Google |
|
|
|
|
### 3. Country Mappings
|
|
|
|
Most country codes use standard ISO 3166-1 alpha-2 codes and map directly.
|
|
|
|
**Special Cases:**
|
|
- **GL** = "Global" (not Greenland) - Creative X expects `market_name: "Global"` with no ISO code
|
|
- **CZ** = Czechia (Ferrero may use "Czech Republic")
|
|
- **US** = "United States of America" (not just "United States")
|
|
- **GB** = "United Kingdom"
|
|
- **KR** = "South Korea"
|
|
|
|
All other countries use standard ISO codes (DE, IT, FR, ES, etc.)
|
|
|
|
---
|
|
|
|
## How It Works
|
|
|
|
### 1. Parsing Phase
|
|
|
|
```python
|
|
from core.filename_parser import FerreroFilenameParser
|
|
|
|
parser = FerreroFilenameParser(data_loader)
|
|
parsed = parser.parse("NUT_GL_en_MOMENT_OLV_6S_1x1_FBS_xyz.mp4")
|
|
|
|
# Result:
|
|
{
|
|
'brand_code': 'NUT',
|
|
'brand_name': 'NUTELLA',
|
|
'country_code': 'GL',
|
|
'country_name': 'GLOBAL',
|
|
'language_code': 'en',
|
|
'social_media': 'FBS',
|
|
'channel': 'FB - Stories',
|
|
...
|
|
}
|
|
```
|
|
|
|
### 2. Mapping Phase
|
|
|
|
```python
|
|
from core.mapping_resolver import MappingResolver
|
|
|
|
resolver = MappingResolver('mappings.json')
|
|
creativex_payload = resolver.build_creativex_payload(parsed)
|
|
|
|
# Result:
|
|
{
|
|
'brand_name': 'Nutella',
|
|
'channel': 'facebook_paid',
|
|
'publisher': 'facebook',
|
|
'placement': 'facebook_stories',
|
|
'market_name': 'Global',
|
|
'language': 'en',
|
|
'dimensions': '1x1',
|
|
'duration': '6',
|
|
...
|
|
}
|
|
```
|
|
|
|
### 3. Upload Phase
|
|
|
|
```python
|
|
api_client.create_preflight({
|
|
'name': 'NUT_GL_en_MOMENT_OLV_6S_1x1_FBS_xyz.mp4',
|
|
'brand_name': 'Nutella',
|
|
'market_name': 'Global',
|
|
'channel': 'facebook_paid',
|
|
'publisher': 'facebook',
|
|
'placement': 'facebook_stories',
|
|
'language': 'en',
|
|
'dimensions': '1x1',
|
|
'duration': '6',
|
|
'creatives': [
|
|
{'source_url': 'https://s3.../file.mp4'}
|
|
]
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Validation
|
|
|
|
The system validates at multiple levels:
|
|
|
|
### 1. Brand Validation
|
|
```python
|
|
resolver.is_brand_supported('NUT') # True
|
|
resolver.is_brand_supported('ROC') # False - not in Creative X yet
|
|
```
|
|
|
|
### 2. Channel Validation
|
|
```python
|
|
resolver.is_channel_supported('FBS') # True
|
|
resolver.is_channel_supported('XXX') # False - not mapped
|
|
```
|
|
|
|
### 3. Full Metadata Validation
|
|
```python
|
|
is_valid, errors = resolver.validate_metadata_for_upload(parsed_data)
|
|
if not is_valid:
|
|
print("Errors:", errors)
|
|
```
|
|
|
|
---
|
|
|
|
## Adding New Mappings
|
|
|
|
### Add a New Brand
|
|
|
|
Edit `mappings.json`:
|
|
|
|
```json
|
|
{
|
|
"brand_mappings": {
|
|
"ROC": {
|
|
"creativex_name": "Ferrero Rocher",
|
|
"creativex_id": 424,
|
|
"ferrero_name": "ROCHER"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Steps:**
|
|
1. Get Creative X brand ID from `/dimensions` endpoint
|
|
2. Add mapping to `mappings.json`
|
|
3. Test upload: `python scripts/upload.py --dry-run test_file.mp4`
|
|
|
|
### Add a New Channel
|
|
|
|
Edit `mappings.json`:
|
|
|
|
```json
|
|
{
|
|
"channel_mappings": {
|
|
"NEW_PLATFORM_CODES": {
|
|
"NPF": {
|
|
"ferrero_name": "New Platform - Feed",
|
|
"creativex_channel": "new_platform_paid",
|
|
"creativex_publisher": "new_platform",
|
|
"creativex_placement": "feed"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Steps:**
|
|
1. Query `/dimensions` to see Creative X channel structure
|
|
2. Determine if channel needs publisher/placement/ad_format
|
|
3. Add mapping following existing patterns
|
|
4. Test validation
|
|
|
|
---
|
|
|
|
## Common Issues & Solutions
|
|
|
|
### Issue: "Brand 'XXX' is not supported in Creative X"
|
|
|
|
**Cause:** Brand not yet added to Creative X system
|
|
|
|
**Solutions:**
|
|
1. Check if brand exists: `python get_dimensions.py | grep -i brandname`
|
|
2. If exists, add mapping to `mappings.json`
|
|
3. If doesn't exist, contact Creative X to add brand
|
|
|
|
### Issue: "Social media code 'XXX' not mapped"
|
|
|
|
**Cause:** Ferrero social code not in `mappings.json`
|
|
|
|
**Solutions:**
|
|
1. Check Creative X channels: `python get_dimensions.py`
|
|
2. Identify correct channel/publisher/placement
|
|
3. Add mapping to `mappings.json`
|
|
|
|
### Issue: "Invalid placement for channel"
|
|
|
|
**Cause:** Mismatch between channel and placement
|
|
|
|
**Solutions:**
|
|
1. Verify placement exists: `python get_dimensions.py`
|
|
2. Check channel structure (some channels need publisher+placement, others don't)
|
|
3. Update mapping with correct structure
|
|
|
|
### Issue: "Market not found"
|
|
|
|
**Cause:** Country code mapping issue (usually GL = Global)
|
|
|
|
**Solutions:**
|
|
1. Check if country exists: `python get_dimensions.py | grep -i country`
|
|
2. For "Global" uploads, ensure GL maps to market_name="Global" (no ISO code)
|
|
3. Update `country_mappings.SPECIAL_CASES` if needed
|
|
|
|
---
|
|
|
|
## Testing Mappings
|
|
|
|
### 1. Dry Run Test
|
|
|
|
Test mapping without uploading:
|
|
|
|
```bash
|
|
python scripts/upload.py --dry-run /path/to/test_file.mp4
|
|
```
|
|
|
|
**Expected output:**
|
|
```
|
|
✓ File valid
|
|
✓ Brand: NUTELLA
|
|
✓ Market: GLOBAL
|
|
✓ Channel: FB - Stories
|
|
✓ Metadata valid
|
|
✓ Mappings valid
|
|
→ Brand: Nutella
|
|
→ Channel: facebook_paid
|
|
→ Publisher: facebook
|
|
→ Placement: facebook_stories
|
|
✓ DRY RUN: Would upload file
|
|
```
|
|
|
|
### 2. Query Available Dimensions
|
|
|
|
Check what's available in Creative X:
|
|
|
|
```bash
|
|
source venv/bin/activate
|
|
python get_dimensions.py > dimensions_output.txt
|
|
```
|
|
|
|
Review:
|
|
- Available brands
|
|
- Available channels (with publishers/placements)
|
|
- Available markets
|
|
|
|
### 3. Interactive Python Test
|
|
|
|
```python
|
|
from core.mapping_resolver import MappingResolver
|
|
|
|
resolver = MappingResolver('mappings.json')
|
|
|
|
# Test brand
|
|
brand = resolver.get_creativex_brand('NUT')
|
|
print(f"Brand: {brand}")
|
|
|
|
# Test channel
|
|
channel = resolver.get_creativex_channel_mapping('FBS')
|
|
print(f"Channel: {channel}")
|
|
|
|
# Test market
|
|
market = resolver.get_creativex_market('GL')
|
|
print(f"Market: {market}")
|
|
|
|
# List all supported
|
|
print(f"Supported brands: {resolver.get_all_supported_brands()}")
|
|
print(f"Supported channels: {resolver.get_all_supported_channels()}")
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
### Filename Format
|
|
```
|
|
BRAND_COUNTRY_LANG_SUBJECT_ASSETTYPE_DURATION_ASPECTRATIO_SOCIAL_hash.ext
|
|
NUT_GL_en_MOMENT_OLV_6S_1x1_FBS_xyz.mp4
|
|
│ │ │ │ │ │ │ │ └── Hash/random
|
|
│ │ │ │ │ │ │ └────── Social media code (FBS)
|
|
│ │ │ │ │ │ └────────── Aspect ratio (1x1)
|
|
│ │ │ │ │ └───────────── Duration (6S)
|
|
│ │ │ │ └───────────────── Asset type (OLV)
|
|
│ │ │ └──────────────────────── Subject (MOMENT)
|
|
│ │ └─────────────────────────── Language (en)
|
|
│ └────────────────────────────── Country (GL)
|
|
└────────────────────────────────── Brand (NUT)
|
|
```
|
|
|
|
### Upload Workflow
|
|
```
|
|
1. Parse filename → Extract Ferrero codes
|
|
2. Validate file → Check format/size
|
|
3. Validate Ferrero metadata → Check against data.json
|
|
4. Validate mappings → Check brand/channel exist in Creative X
|
|
5. Get presigned URL → From Creative X API
|
|
6. Upload to S3 → Direct to S3
|
|
7. Create preflight → With mapped metadata
|
|
```
|
|
|
|
---
|
|
|
|
## Support
|
|
|
|
**Issues:**
|
|
- Brand not mapped → Add to `brand_mappings` in `mappings.json`
|
|
- Channel not mapped → Add to `channel_mappings` in `mappings.json`
|
|
- Country not found → Check `country_mappings.SPECIAL_CASES`
|
|
|
|
**Testing:**
|
|
```bash
|
|
# Test with dry run
|
|
python scripts/upload.py --dry-run file.mp4
|
|
|
|
# Check dimensions
|
|
python get_dimensions.py
|
|
|
|
# Check logs
|
|
cat data/logs/creativex_upload_*.log
|
|
```
|
|
|
|
**Documentation:**
|
|
- Main README: `README.md`
|
|
- Project status: `STATUS.md`
|
|
- This guide: `MAPPINGS_GUIDE.md`
|
|
|