ferrero-opentext/Python-Version/MARKDOWN_DOCS/CUTOVER.md

32 KiB

Ferrero Automation - Production Cutover Plan

Overview

This document outlines the complete cutover process from development/staging to production environment for the Ferrero DAM Content Scaling automation system.

Current State: Running on staging/dev environment Target State: Full production deployment with live DAM integration


Pre-Cutover Checklist

1. Verify Current System Health

Development Environment:

# Check all workflows are working
python scripts/a1_to_a2_download.py
python scripts/a5_to_a6_download.py
python scripts/b1_to_b2_download.py
python scripts/a2_to_a3_upload_polling.py
python scripts/creativex_scoring_storing.py

# Verify database connectivity
python scripts/test_connection.py

# Check recent logs for errors
grep -i error logs/*.log | tail -50

Expected Results:

  • All scripts execute without errors
  • Database connections successful
  • Box authentication working
  • DAM OAuth2 authentication working
  • Email notifications sending

2. Backup Current Configuration

# Backup current .env file
cp .env .env.backup.$(date +%Y%m%d)

# Backup database
./database/backup.sh --daily

# Backup Box-config.json
cp ../Box-config.json ../Box-config.json.backup.$(date +%Y%m%d)

# Backup current cron jobs
crontab -l > cron_backup_$(date +%Y%m%d).txt

3. Document Current URLs and IDs

Create reference document with current settings:

# Extract current settings
grep "^DAM_BASE_URL" .env
grep "^BOX_ROOT_FOLDER" .env
grep "WEBHOOK" .env

Production Credentials & Configuration

Required Production Values

1. DAM Production Endpoints

Current (Dev/Staging):

DAM_BASE_URL=https://ppr.dam.ferrero.com/otmmapi
DAM_AUTH_URL=https://ppr.dam.ferrero.com/otdsws/oauth2/token

Production:

DAM_BASE_URL=https://dam.ferrero.com/otmmapi  # VERIFY actual production URL
DAM_AUTH_URL=https://dam.ferrero.com/otdsws/oauth2/token  # VERIFY

ACTION REQUIRED:

  • Confirm production DAM URLs with Ferrero IT team
  • Obtain production OAuth2 credentials
  • Test authentication against production endpoint

2. DAM OAuth2 Credentials

Current (Dev):

DAM_CLIENT_ID=otds-OLV
DAM_CLIENT_SECRET=hs28LZ9ZzQ5I9rlW3P7Wwyw85oOatlC1

Production:

DAM_CLIENT_ID=<production_client_id>
DAM_CLIENT_SECRET=<production_client_secret>

ACTION REQUIRED:

  • Request production OAuth2 client credentials from Ferrero
  • Test credentials against production DAM
  • Verify token refresh works

3. Box Folder IDs

Current (Dev/Staging):

BOX_ROOT_FOLDER_A1_A2=348304357505      # Local master downloads
BOX_ROOT_FOLDER_A2_A3=348526703108      # Agency upload processing
BOX_ROOT_FOLDER_B1_B2=349261192115      # Global master downloads
BOX_ROOT_FOLDER_CREATIVEX=350605024645  # CreativeX PDF scoring

Production:

BOX_ROOT_FOLDER_A1_A2=<production_folder_id>
BOX_ROOT_FOLDER_A2_A3=<production_folder_id>
BOX_ROOT_FOLDER_B1_B2=<production_folder_id>
BOX_ROOT_FOLDER_CREATIVEX=<production_folder_id>

ACTION REQUIRED:

  • Create production Box folders with correct structure
  • Document folder IDs
  • Set proper permissions for Box service account
  • Test upload/download to each folder

4. Box Authentication

Current (Dev):

BOX_CLIENT_ID=l2atwxxq4xna7phcjr2uifm4mbah69qp
BOX_CLIENT_SECRET=6XcuCQ6akpk9daE0UHaGSv3mSxWaER4l
BOX_JWT_KEY_ID=n1izyn3l
BOX_PASSPHRASE=971585f5fd6171428c14a7c8899af5ab
BOX_ENTERPRISE_ID=43984435

Production:

BOX_CLIENT_ID=<production_value>
BOX_CLIENT_SECRET=<production_value>
BOX_JWT_KEY_ID=<production_value>
BOX_PASSPHRASE=<production_value>
BOX_ENTERPRISE_ID=<production_value>

File: ../Box-config.json (one level up from Python-Version)

ACTION REQUIRED:

  • Request production Box JWT credentials
  • Generate new Box-config.json for production
  • Place at correct location (../Box-config.json)
  • Set permissions: chmod 600 ../Box-config.json
  • Test Box authentication

5. Database

Current (Dev):

DB_HOST=localhost
DB_PORT=5437
DB_USER=ferrero_user
DB_PASSWORD=ferrero_pass_2025

Production Options:

Option A: Keep local PostgreSQL (recommended for start)

DB_HOST=localhost
DB_PORT=5437
DB_USER=ferrero_user
DB_PASSWORD=<new_strong_production_password>

Option B: Use managed database service

DB_HOST=<rds_or_managed_db_hostname>
DB_PORT=5432
DB_USER=ferrero_prod_user
DB_PASSWORD=<production_password>

ACTION REQUIRED:

  • Decide: Local PostgreSQL or managed service
  • Change default password (ferrero_pass_2025)
  • Update .env with production credentials
  • Initialize production database with init.sql
  • Set up automated backups (cron jobs)

6. Email Notifications

Current (Dev):

SMTP_SERVER=smtp.mailgun.org
SMTP_USER=twist@mail.dev.oliver.solutions
SENDER_EMAIL=TWIST-UK-SERVER@oliver.agency
ERROR_EMAIL=daveporter@oliver.agency
REPORT_EMAILS=daveporter@oliver.agency

Production:

SMTP_SERVER=smtp.mailgun.org
SMTP_USER=<production_mailgun_user>
SENDER_EMAIL=ferrero-automation@ferrero.com  # Or oliver.agency
ERROR_EMAIL=ferrero-it@ferrero.com,daveporter@oliver.agency
REPORT_EMAILS=ferrero-team@ferrero.com,operations@oliver.agency

ACTION REQUIRED:

  • Set up production email domain
  • Update recipient lists for production team
  • Test email delivery
  • Verify spam filters don't block automated emails

7. Webhooks

Current (Dev):

CAMPAIGN_STATUS_WEBHOOK_URL=https://hook.us1.make.celonis.com/3f9ztwl8qnljufo0l65utfv5wvvnt9m5

Production:

CAMPAIGN_STATUS_WEBHOOK_URL=<production_make_scenario_url>

ACTION REQUIRED:

  • Create production Make.com scenario
  • Update webhook URL
  • Test webhook delivery
  • Verify webhook signature validation (if enabled)

8. CreativeX / LlamaCloud

Current (Dev):

LLAMA_CLOUD_API_KEY=llx-EDmfh0ZReUbXUbaa5i5275TAP2LznNDqc3skJRL3HY4RUDcf
CREATIVEX_AGENT_NAME=Creativex-Extract

Production:

LLAMA_CLOUD_API_KEY=<production_api_key>
CREATIVEX_AGENT_NAME=Creativex-Extract  # Same agent or production-specific?

ACTION REQUIRED:

  • Confirm same LlamaCloud API key for production or get new one
  • Verify agent "Creativex-Extract" exists and works in production
  • Test extraction with production data

9. mTLS Certificate (Optional)

Current (Dev):

DAM_MTLS_BASE_URL=https://dev-auth.app-api.ferrero.com/00003/mm
DAM_MTLS_CERT_PATH=config/certificates/dam-mtls-dev.pfx
DAM_MTLS_CERT_PASSWORD=fnJ8xrnh!54NE&2HR62=2P3YEy+hy9RajZ7v5&=y

Production:

DAM_MTLS_BASE_URL=https://prod-auth.app-api.ferrero.com/00003/mm  # VERIFY
DAM_MTLS_CERT_PATH=config/certificates/dam-mtls-prod.pfx
DAM_MTLS_CERT_PASSWORD=<production_cert_password>

ACTION REQUIRED:

  • Obtain production mTLS certificate
  • Place in config/certificates/
  • Set permissions: chmod 600 config/certificates/*.pfx
  • Whitelist production server IP
  • Test mTLS authentication: python scripts/test_connection.py --auth-pfx

Cutover Steps

Phase 1: Preparation (1-2 days before)

Step 1.1: Update Production Server Code

# SSH to production server
ssh user@production-server

# Navigate to deployment directory
cd /opt/ferrero-automation/Python-Version

# Pull latest code from Bitbucket
git pull origin main

# Verify all new files present
ls -la scripts/creativex_scoring_storing.py
ls -la database/backup.sh
ls -la DATABASE_BACKUP_GUIDE.md

Step 1.2: Install Dependencies

# Activate virtual environment
source venv/bin/activate

# Install new dependencies (llama-cloud-services)
sudo venv/bin/pip install llama-cloud-services

# Verify installation
pip show llama-cloud-services

# Or reinstall all dependencies
sudo venv/bin/pip install -r requirements.txt

Step 1.3: Database Updates

# Create creativex_scores table
PGPASSWORD=ferrero_pass_2025 psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -f database/init.sql

# Or just add the new table
PGPASSWORD=ferrero_pass_2025 psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "
CREATE TABLE IF NOT EXISTS creativex_scores (
    id SERIAL PRIMARY KEY,
    filename VARCHAR(500) NOT NULL,
    box_file_id VARCHAR(255),
    creativex_id VARCHAR(255),
    creativex_url TEXT,
    quality_score VARCHAR(50),
    full_extraction_data JSONB,
    extracted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status VARCHAR(50) DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_creativex_filename ON creativex_scores(filename);
CREATE INDEX IF NOT EXISTS idx_creativex_box_file ON creativex_scores(box_file_id);
CREATE INDEX IF NOT EXISTS idx_creativex_status ON creativex_scores(status);
"

# Verify table created
PGPASSWORD=ferrero_pass_2025 psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "\d creativex_scores"

Step 1.4: Update .env with Production Credentials

# Backup current .env
cp .env .env.staging.backup

# Edit .env with production values
nano .env

Update these sections:

# DAM Production URLs
DAM_BASE_URL=<production_dam_url>
DAM_AUTH_URL=<production_auth_url>
DAM_CLIENT_ID=<production_client_id>
DAM_CLIENT_SECRET=<production_client_secret>

# Box Production Folder IDs
BOX_ROOT_FOLDER_A1_A2=<production_folder>
BOX_ROOT_FOLDER_A2_A3=<production_folder>
BOX_ROOT_FOLDER_B1_B2=<production_folder>
BOX_ROOT_FOLDER_CREATIVEX=<production_folder>

# Database Production Password
DB_PASSWORD=<new_strong_password>

# Email Production Recipients
ERROR_EMAIL=ferrero-it@ferrero.com,operations@oliver.agency
REPORT_EMAILS=ferrero-team@ferrero.com,operations@oliver.agency

# Webhooks Production URL
CAMPAIGN_STATUS_WEBHOOK_URL=<production_webhook>

# CreativeX Production
LLAMA_CLOUD_API_KEY=<production_key>

Save and exit.

Step 1.5: Update Box-config.json

# Backup current Box config
cp ../Box-config.json ../Box-config.json.staging.backup

# Replace with production Box JWT config
nano ../Box-config.json

Paste production Box JWT credentials (JSON format from Box Developer Console).

# Set secure permissions
chmod 600 ../Box-config.json

Step 1.6: Add mTLS Production Certificate (If Using)

# Copy production certificate
cp /path/to/dam-mtls-prod.pfx config/certificates/

# Set permissions
chmod 600 config/certificates/dam-mtls-prod.pfx

# Update .env
nano .env
# Update: DAM_MTLS_CERT_PATH=config/certificates/dam-mtls-prod.pfx
# Update: DAM_MTLS_CERT_PASSWORD=<production_password>

Phase 2: Testing (Before Cutover)

Step 2.1: Test Database Connection

# Test PostgreSQL
PGPASSWORD=<new_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "SELECT COUNT(*) FROM master_assets;"

# Test via Python
python scripts/test_connection.py

Expected: Connection successful, tables accessible

Step 2.2: Test DAM Authentication

# Test OAuth2 (production credentials)
python scripts/test_connection.py

# Test mTLS (if using)
python scripts/test_connection.py --auth-pfx

Expected: DAM connection successful, can retrieve campaigns

Step 2.3: Test Box Authentication

# Run Box connection test
python -c "
from shared.config_loader import load_config
from shared.box_client import BoxClient
config = load_config('config/config.yaml')
box = BoxClient(config, root_folder_id=config['box']['root_folder_a1_a2'])
print('Box test:', box.test_connection())
"

Expected: Box connection OK

Step 2.4: Test Email Notifications

# Send test email
python -c "
from shared.config_loader import load_config
from shared.notifier import Notifier
config = load_config('config/config.yaml')
notifier = Notifier(config)
notifier.send_email(
    template_name='a1_to_a2_no_assets',
    recipients=config['notifications']['recipients']['success'],
    data={
        'campaign_name': 'TEST CAMPAIGN',
        'campaign_id': 'TEST123',
        'campaign_number': 'C000000999'
    }
)
print('Test email sent')
"

Expected: Email received by production recipients

Step 2.5: Test Webhook

# Test webhook delivery
python -c "
from shared.config_loader import load_config
from shared.notifier import Notifier
config = load_config('config/config.yaml')
notifier = Notifier(config)
result = notifier.send_webhook(
    url=config['webhooks']['campaign_status_update']['url'],
    payload={'test': 'cutover_test', 'campaign': 'TEST'}
)
print('Webhook test:', result)
"

Expected: Webhook received in production Make.com scenario

Step 2.6: Test CreativeX Extraction

# Upload test PDF to production CreativeX folder
# Then run extraction
python scripts/creativex_scoring_storing.py

# Verify in database
PGPASSWORD=<new_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "
SELECT filename, quality_score FROM creativex_scores WHERE status = 'active' LIMIT 5;
"

Expected: Scores extracted and stored successfully

Step 2.7: Dry Run Workflows (Production Data, No Status Updates)

Important: Test with real production campaigns but DON'T update statuses yet

# Test A1→A2 download (will process but not update status without all assets)
# Manually select a small test campaign first
python scripts/a1_to_a2_download.py

# Check logs
tail -100 logs/a1_to_a2.log

# Verify files in Box production folder
# Verify records in database

# Test A2→A3 upload with test file
# Upload one file with tracking ID to A2→A3 folder
python scripts/a2_to_a3_upload_polling.py

# Verify upload to DAM
# Check email notification

CRITICAL: During dry run testing:

  • Use real production endpoints
  • Use real production Box folders
  • ⚠️ Process SMALL test campaigns only
  • ⚠️ Verify you're comfortable with status updates before going live
  • ⚠️ Have rollback plan ready

Phase 3: Go-Live Decision Point

Go/No-Go Criteria

GO if all true:

  • All connection tests passed
  • Email notifications working
  • Webhooks delivering successfully
  • Test campaign processed without errors
  • Box uploads/downloads working
  • DAM uploads working with correct metadata
  • CreativeX scores extracting and integrating
  • Database backups configured and tested
  • Team trained on monitoring/support
  • Rollback plan documented

NO-GO if any true:

  • Authentication failing
  • Production credentials incomplete
  • Email delivery issues
  • Webhook not reaching Make.com
  • Test uploads corrupted or failed
  • Metadata mapping incorrect
  • No backup system in place

Go-Live Execution

Phase 4: Cutover Day

Step 4.1: Final Preparation (Morning)

# 1. Stop all cron jobs temporarily
crontab -e
# Comment out all Ferrero automation lines (add # at start)

# 2. Create final backup of staging data
./database/backup.sh --daily

# 3. Verify no workflows are running
ps aux | grep python | grep scripts

# 4. Clear any pending Box files
# Manually verify Box folders are empty or in known state

Step 4.2: Update Environment Variable

# Update environment setting
nano .env

# Change:
ENV=staging
# To:
ENV=production

# Save and exit

Step 4.3: Enable Production Cron Jobs

crontab -e

Production Cron Schedule:

# A1→A2: Download master assets from DAM (every 5 minutes)
*/5 * * * * cd /opt/ferrero-automation/Python-Version && venv/bin/python scripts/a1_to_a2_download.py >> logs/cron_a1_a2.log 2>&1

# A5→A6: Download rejected assets for rework (every 5 minutes)
*/5 * * * * cd /opt/ferrero-automation/Python-Version && venv/bin/python scripts/a5_to_a6_download.py >> logs/cron_a5_a6.log 2>&1

# B1→B2: Download global master campaigns (every 5 minutes)
*/5 * * * * cd /opt/ferrero-automation/Python-Version && venv/bin/python scripts/b1_to_b2_download.py >> logs/cron_b1_b2.log 2>&1

# A2→A3: Process agency uploads from Box (every 5 minutes)
*/5 * * * * cd /opt/ferrero-automation/Python-Version && venv/bin/python scripts/a2_to_a3_upload_polling.py >> logs/cron_a2_a3.log 2>&1

# Daily Report: Send summary email (7:00 PM daily)
0 19 * * * cd /opt/ferrero-automation/Python-Version && venv/bin/python scripts/daily_report.py >> logs/daily_report.log 2>&1

# Database Backup: Daily at 2:00 AM
0 2 * * * cd /opt/ferrero-automation/Python-Version && ./database/backup.sh --daily >> logs/backup.log 2>&1

# Database Backup: Weekly on Sundays at 3:00 AM
0 3 * * 0 cd /opt/ferrero-automation/Python-Version && ./database/backup.sh --weekly >> logs/backup.log 2>&1

# Backup Health Check: Daily at 8:00 AM
0 8 * * * cd /opt/ferrero-automation/Python-Version && ./database/check_backups.sh --quiet >> logs/backup_check.log 2>&1

Save and exit.

Step 4.4: Monitor Initial Runs

# Watch logs in real-time
tail -f logs/cron_a1_a2.log

# In another terminal, watch all workflow logs
tail -f logs/*.log

# Monitor for 1-2 hours
# Look for:
# - ✅ "No A1 campaigns found" (normal if no campaigns ready)
# - ✅ Successful processing messages
# - ⚠️ Any errors or authentication failures

Step 4.5: Verify First Campaign Processing

When first A1 campaign appears:

# Monitor A1→A2 processing
tail -f logs/a1_to_a2.log

# Expected flow:
# - Campaign detected
# - Assets downloaded from DAM
# - Uploaded to Box with tracking IDs
# - Stored in database
# - Status updated A1→A2
# - Email sent

# Verify in database
PGPASSWORD=<prod_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "
SELECT tracking_id, original_filename, created_at FROM master_assets ORDER BY created_at DESC LIMIT 10;
"

# Verify in Box folder
# Check production Box folder contains files with tracking IDs

Post-Cutover Monitoring

First 24 Hours

Hourly Checks:

# Check cron execution
tail -100 logs/cron_*.log

# Check for errors
grep -i error logs/*.log | grep "$(date +%Y-%m-%d)"

# Check database growth
PGPASSWORD=<prod_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "
SELECT
    (SELECT COUNT(*) FROM master_assets) as masters,
    (SELECT COUNT(*) FROM derivative_assets) as derivatives,
    (SELECT COUNT(*) FROM creativex_scores) as creativex,
    (SELECT COUNT(*) FROM campaign_status) as campaigns;
"

# Check Box folders
# Verify files are being created/deleted as expected

Email Monitoring:

  • Success emails arriving
  • ⚠️ No error emails (or expected errors only)
  • 📊 Daily report at 7 PM contains production data

Workflow Health:

# Check all workflows completed recent runs
ls -lt logs/*.log | head -10

# Verify campaigns progressing through stages
PGPASSWORD=<prod_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "
SELECT campaign_number, status, webhook_sent, updated_at
FROM campaign_status
ORDER BY updated_at DESC
LIMIT 10;
"

First Week

Daily Checks:

  1. Review daily report email (7 PM)

    • Verify statistics are accurate
    • Check success rates
    • Review any errors
  2. Check backup health

    ./database/check_backups.sh
    
  3. Monitor disk space

    df -h /opt/ferrero-automation
    du -sh Python-Version/backups/
    du -sh Python-Version/logs/
    du -sh Python-Version/temp/
    
  4. Check cron logs

    # Verify cron is executing
    grep CRON /var/log/syslog | grep ferrero | tail -20
    

Weekly Tasks:

  • Review workflow success rates
  • Check for any recurring errors
  • Verify webhook delivery to Make.com
  • Test restore from backup (monthly, but good to test early)
  • Review with Ferrero team

Rollback Plan

If Critical Issues Found

Quick Rollback (Revert to Staging)

# 1. Stop production cron jobs
crontab -e
# Comment out all lines

# 2. Restore staging .env
cp .env.staging.backup .env

# 3. Restore staging Box-config.json
cp ../Box-config.json.staging.backup ../Box-config.json

# 4. Update environment
nano .env
# Change: ENV=production → ENV=staging

# 5. Restart staging cron jobs
crontab -e
# Restore original cron jobs

Database Rollback

# If database needs rollback
./database/restore.sh backups/dumps/<pre_cutover_backup>.sql.gz

Code Rollback

# If code changes need rollback
git log --oneline | head -10  # Find commit before cutover
git revert <commit_hash>
git push origin main

Known Production Differences

Development vs Production

Component Development Production Notes
DAM URL ppr.dam.ferrero.com dam.ferrero.com (TBD) Confirm with Ferrero IT
Box Folders Dev folder IDs Prod folder IDs Create new folders
OAuth2 Creds Dev credentials Prod credentials Request from Ferrero
Email Recipients daveporter@oliver.agency Ferrero team + ops Update recipient lists
Webhook URL Dev Make scenario Prod Make scenario Create new scenario
DB Password ferrero_pass_2025 Strong production password Change for security
mTLS Cert dev-mtls-dev.pfx dam-mtls-prod.pfx Get prod certificate
Server IP Dev IP Prod IP Whitelist for mTLS

Cutover Communication Plan

Stakeholders to Notify

Before Cutover (48 hours):

  • Ferrero DAM team
  • Ferrero IT team
  • Oliver Agency operations
  • Content team users

Email Template:

Subject: Ferrero DAM Automation - Production Cutover [Date]

The automated content scaling system will be going live on [DATE] at [TIME].

What's changing:
- Automation moves from staging to production DAM environment
- Campaigns will automatically progress through workflow stages
- Email notifications will come from production system

What to expect:
- A1→A2: Master assets auto-downloaded to Box
- A2→A3: Localized assets auto-uploaded to DAM
- A5→A6: Rejected assets flagged for rework
- CreativeX scores automatically attached to assets

Support during cutover:
- [Contact person]: [Email/Phone]
- Monitoring: First 24 hours continuous
- Rollback available if issues found

Please report any issues immediately to [ERROR_EMAIL]

During Cutover:

  • Status updates every 2 hours
  • Available for immediate response

After Cutover (24 hours):

  • Summary email with statistics
  • Known issues (if any)
  • Next steps

Post-Cutover Validation

Validation Checklist (First 24 Hours)

Workflow Validations:

A1→A2 (Master Download):

  • Campaign detected with status A1
  • All assets downloaded from DAM
  • Uploaded to Box with tracking IDs
  • Stored in database with full metadata
  • Status updated to A2
  • Success email sent
  • Tracking IDs are unique
  • Subfolder structure preserved

A5→A6 (Rejections/Rework):

  • Only "NOT APPROVED" assets downloaded
  • Rejection comments extracted
  • Tracking IDs reused correctly
  • Email lists rejection reasons
  • Approved assets skipped

B1→B2 (Global Masters):

  • Only "Global comm" campaigns processed
  • Uploaded to separate Box folder
  • MASTERS_ prefix in folder names
  • Email sent (no webhook)

A2→A3 (Agency Uploads):

  • Files detected in Box A2→A3 folder
  • Tracking IDs parsed from filenames
  • Master metadata loaded from database
  • CreativeX scores looked up from database
  • Default values used when scores missing
  • Uploaded to correct DAM folder
  • Files deleted from Box after success
  • Email shows CreativeX status

CreativeX Scoring:

  • PDFs processed from Box folder 350605024645
  • Scores extracted via LlamaExtract
  • Stored in database with version tracking
  • Decimals removed from IDs and scores
  • PDFs deleted after successful extraction
  • Email sent with version badges

Database Validations:

# Check all tables have data
PGPASSWORD=<prod_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "
SELECT
    'master_assets' as table_name, COUNT(*) as records FROM master_assets
UNION ALL SELECT
    'derivative_assets', COUNT(*) FROM derivative_assets
UNION ALL SELECT
    'creativex_scores', COUNT(*) FROM creativex_scores
UNION ALL SELECT
    'campaign_status', COUNT(*) FROM campaign_status
UNION ALL SELECT
    'asset_events', COUNT(*) FROM asset_events;
"

# Check for any errors in logs
grep -i error logs/*.log | grep "$(date +%Y-%m-%d)"

Backup Validations:

# Verify first production backup
./database/check_backups.sh

# Expected:
# - Backup created at 2:00 AM
# - Backup file size reasonable
# - No errors in backup.log

Production Support Procedures

Monitoring Dashboard Commands

Create monitoring script: scripts/production_status.sh

#!/bin/bash
echo "========================================="
echo "Ferrero Automation Production Status"
echo "========================================="
echo ""

# Database stats
echo "Database Records:"
PGPASSWORD=<prod_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "
SELECT
    (SELECT COUNT(*) FROM master_assets WHERE status = 'active') as active_masters,
    (SELECT COUNT(*) FROM creativex_scores WHERE status = 'active') as active_scores,
    (SELECT COUNT(*) FROM campaign_status WHERE webhook_sent = true) as completed_campaigns;
"

echo ""
echo "Recent Activity (Last 24 hours):"
PGPASSWORD=<prod_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "
SELECT COUNT(*) as new_assets_today
FROM master_assets
WHERE created_at > NOW() - INTERVAL '24 hours';
"

echo ""
echo "Backup Status:"
./database/check_backups.sh --quiet

echo ""
echo "Recent Errors:"
grep -i error logs/*.log | grep "$(date +%Y-%m-%d)" | tail -5

Emergency Contacts

Escalation Path:

  1. Level 1: Operations team (monitors daily)
  2. Level 2: Technical lead (Dave Porter)
  3. Level 3: Ferrero IT team

Contact Information:


Configuration Checklist Summary

Files to Update:

  • Python-Version/.env - All production credentials
  • ../Box-config.json - Production Box JWT
  • config/certificates/dam-mtls-prod.pfx - Production mTLS cert (if using)
  • Crontab - Production schedule
  • /etc/hosts - Production hostname if needed

Credentials to Obtain:

  • Production DAM OAuth2 client ID and secret
  • Production DAM base URL and auth URL
  • Production Box folder IDs (4 folders)
  • Production Box JWT credentials
  • Production mTLS certificate (if using)
  • Production webhook URL (Make.com)
  • Production email recipients list
  • Production LlamaCloud API key (or confirm dev key works)

Testing to Complete:

  • DAM connection test
  • Box connection test
  • Database connection test
  • Email delivery test
  • Webhook delivery test
  • CreativeX extraction test
  • End-to-end workflow test with small campaign
  • Backup and restore test

Success Metrics

Week 1 Targets:

  • Uptime: > 99% (workflows running without crashes)
  • Success Rate: > 95% (assets processed successfully)
  • Email Delivery: 100% (all notifications received)
  • Backup Success: 7/7 daily backups completed
  • Response Time: < 10 minutes (cron cycle)

Monitor These KPIs:

# Check success rate from daily report emails
# Or query database:
PGPASSWORD=<prod_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "
SELECT
    COUNT(*) FILTER (WHERE webhook_sent = true) as successful_campaigns,
    COUNT(*) as total_campaigns,
    ROUND(100.0 * COUNT(*) FILTER (WHERE webhook_sent = true) / NULLIF(COUNT(*), 0), 1) as success_rate
FROM campaign_status
WHERE created_at > NOW() - INTERVAL '7 days';
"

Troubleshooting Production Issues

Issue: Workflows Not Running

# Check cron service
systemctl status cron

# Check cron logs
grep CRON /var/log/syslog | tail -50

# Manually run workflow
cd /opt/ferrero-automation/Python-Version
source venv/bin/activate
python scripts/a1_to_a2_download.py

Issue: Authentication Failing

# Test OAuth2
python scripts/test_connection.py

# Test mTLS
python scripts/test_connection.py --auth-pfx

# Check credentials in .env
grep "DAM_" .env | grep -v PASSWORD

Issue: Database Not Accessible

# Check PostgreSQL container
docker ps | grep ferrero-tracking-db

# Check logs
docker logs ferrero-tracking-db | tail -50

# Test connection
PGPASSWORD=<prod_password> psql -h localhost -p 5437 -U ferrero_user -d ferrero_tracking -c "SELECT 1;"

Issue: Box Upload/Download Failing

# Test Box connection
python -c "
from shared.config_loader import load_config
from shared.box_client import BoxClient
config = load_config('config/config.yaml')
box = BoxClient(config)
print(box.test_connection())
"

# Check Box-config.json location
ls -la ../Box-config.json

# Verify Box folder IDs
grep "BOX_ROOT_FOLDER" .env

Issue: Emails Not Sending

# Check SMTP settings
grep "SMTP" .env

# Test email manually
python -c "
from shared.config_loader import load_config
from shared.notifier import Notifier
config = load_config('config/config.yaml')
notifier = Notifier(config)
# Send test email...
"

# Check Mailgun logs (if using Mailgun)

Appendix: Production Readiness Checklist

Infrastructure:

  • Production server provisioned and accessible
  • Docker installed and running
  • PostgreSQL container running on port 5437
  • Sufficient disk space (minimum 10 GB)
  • Network access to DAM, Box, email servers
  • Firewall rules configured (if using mTLS)
  • SSL certificates valid (if applicable)

Code Deployment:

  • Latest code pulled from Bitbucket
  • Virtual environment created (Python 3.10+)
  • All dependencies installed (requirements.txt)
  • llama-cloud-services installed
  • Scripts executable (chmod +x)
  • Database schema initialized (init.sql)
  • creativex_scores table created

Configuration:

  • .env file configured with production values
  • Box-config.json in correct location (../Box-config.json)
  • mTLS certificate in place (if using)
  • All folder IDs updated
  • Email recipients updated
  • Webhook URL updated

Testing:

  • All connection tests passed
  • Test campaign processed successfully
  • Email notifications received
  • Webhook delivery confirmed
  • CreativeX extraction working
  • Backup system tested

Automation:

  • Cron jobs configured
  • Cron service running
  • Log rotation configured
  • Backup schedule active
  • Monitoring scripts in place

Documentation:

  • Team trained on system
  • Support procedures documented
  • Rollback plan understood
  • Escalation path defined
  • Emergency contacts listed

Sign-off:

  • Technical lead approval: _________________ Date: _______
  • Operations approval: _________________ Date: _______
  • Ferrero stakeholder approval: _________________ Date: _______

Post-Cutover: Continuous Improvement

Month 1 Review:

  • Analyze success rates
  • Identify recurring errors
  • Optimize cron schedule if needed
  • Tune email notifications
  • Adjust backup retention if needed

Potential Enhancements:

  • Add Grafana/Prometheus monitoring
  • Implement alerting thresholds
  • Add Slack notifications
  • Remote backup to S3/cloud storage
  • Point-in-time recovery (WAL archiving)
  • High availability setup (if needed)
  • Load balancing (if volume increases)

Document Version: 1.0 Last Updated: November 11, 2025 Owner: Dave Porter (daveporter@oliver.agency) Review Date: [After cutover + 1 week]