Commit graph

12 commits

Author SHA1 Message Date
DJP
5f6d24c550 Fix timestamp bug in campaign status recording
- Fixed database.py line 479: Changed 'CURRENT_TIMESTAMP' string to actual datetime
- Added datetime import for proper UTC timestamp generation
- This fixes the PostgreSQL error: invalid input syntax for type timestamp
- Added migration file for campaign_status table

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 16:34:46 -05:00
DJP
6b49ee5e9e Add A4 webhook monitor and Live Campaign field to webhooks
New A4 monitoring script + updated A1→A2 webhook with Live Campaign indicator.

NEW SCRIPT: a4_webhook_monitor.py
- Monitors campaigns with status A4
- Sends webhook when A4 campaigns found
- A4 = Campaign NOT going live
- Webhook payload includes: "live_campaign": "NO"

A4 WEBHOOK PAYLOAD:
{
  "campaign_id": "abc123...",
  "campaign_number": "C000000078",
  "campaign_name": "Campaign Name",
  "status": "A4",
  "live_campaign": "NO",     ← NEW FIELD!
  "timestamp": 1234567890,
  "message": "Campaign marked A4 - Not going live"
}

A1→A2 WEBHOOK UPDATE:
Added "live_campaign": "YES" to existing webhook
- A1→A2 campaigns ARE going live
- Webhook now includes live campaign indicator

A1→A2 WEBHOOK PAYLOAD (UPDATED):
{
  "campaign_id": "abc123...",
  "campaign_number": "C000000078",
  "campaign_name": "Campaign Name",
  "old_status": "A1",
  "new_status": "A2",
  "live_campaign": "YES",    ← NEW FIELD!
  "asset_count": 5,
  "processed_assets": [...],
  "timestamp": 1234567890
}

EMAIL NOTIFICATION:
NEW: a4_webhook_sent template
- Gray theme for A4 status
- Shows "Live Campaign: NO" prominently
- Lists webhook URL and payload details

USAGE:
  Default (OAuth2):
    python scripts/a4_webhook_monitor.py

  With mTLS:
    python scripts/a4_webhook_monitor.py --auth-pfx

CRON SCHEDULE (same as other workflows):
  */5 * * * * cd ~/Python-Version && venv/bin/python scripts/a4_webhook_monitor.py >> logs/cron_a4.log 2>&1

WORKFLOW SUMMARY:
- A1→A2: Webhook with "live_campaign": "YES" (going live)
- A4: Webhook with "live_campaign": "NO" (not going live)
- A5→A6: No webhook (rework workflow)
- B1→B2: No webhook (global masters)

Changes:
- NEW: scripts/a4_webhook_monitor.py (200 lines)
- EDIT: scripts/a1_to_a2_download.py (added live_campaign: YES)
- EDIT: scripts/shared/notifier.py (added a4_webhook_sent template)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 16:34:46 -05:00
DJP
8e7ae7e2d2 Add optional mTLS certificate authentication with --auth-pfx flag
Implements dual authentication system: OAuth2 (default) + mTLS (opt-in).
Zero-risk implementation - existing OAuth2 workflows unchanged.

NEW FEATURE: mTLS Certificate Authentication
- PFX/P12 certificate support for enhanced security
- Activated ONLY with --auth-pfx command-line flag
- OAuth2 remains default (no flag = OAuth2 as before)
- Perfect for testing new auth without breaking production

USAGE:
  Default (OAuth2):
    python scripts/a1_to_a2_download.py

  With mTLS:
    python scripts/a1_to_a2_download.py --auth-pfx

IMPLEMENTATION:

1. Certificate Storage (SECURE):
   - NEW: config/certificates/ folder (gitignored)
   - Moved PFX file to secure location
   - File permissions: 600 (owner read/write only)
   - Password stored in .env (already gitignored)

2. Configuration:
   - .env: Added DAM_MTLS_CERT_PATH and DAM_MTLS_CERT_PASSWORD
   - config.yaml: Added mtls_cert_path and mtls_cert_password
   - .gitignore: Added config/certificates/, *.pfx, *.p12

3. DAM Client Dual Auth:
   - NEW: pfx_to_pem() - Converts PFX to temporary PEM for requests
   - UPDATED: __init__() - Accepts use_mtls flag
   - NEW: _make_api_request() - Unified request wrapper
   - Auto-selects auth method based on flag
   - Updated ALL 8 API calls to use wrapper

4. Scripts Updated (argparse):
   - test_connection.py - Added --auth-pfx flag
   - a1_to_a2_download.py - Added --auth-pfx flag
   - a5_to_a6_download.py - Added --auth-pfx flag
   - b1_to_b2_download.py - Added --auth-pfx flag

5. Test Script:
   - NEW: test_mtls_cert.py - Standalone cert loading test
   - Tests PFX→PEM conversion without API calls
   - Verifies certificate format and cleanup

TESTING RESULTS:
✓ Certificate loads successfully (10930 bytes)
✓ PFX→PEM conversion works (13520 bytes)
✓ Temp file cleanup working
✓ OAuth2 connection test: PASS
✓ mTLS connection test: PASS
✓ Both auth methods working independently

SECURITY:
✓ Certificate file gitignored
✓ Password in .env (gitignored)
✓ File permissions: 600
✓ Temp PEM files auto-deleted
✓ No secrets in code or config

MIGRATION PATH:
- Dev: Use dam-mtls-dev.pfx (current)
- Prod: Replace cert file, update password, same code

BACKWARD COMPATIBILITY:
✓ OAuth2 still default (100% backward compatible)
✓ Existing cron jobs unchanged
✓ No breaking changes
✓ Easy rollback (just don't use --auth-pfx)

Changes:
- .gitignore (+3 lines)
- Python-Version/.env (+3 lines)
- Python-Version/config/config.yaml (+3 lines)
- Python-Version/scripts/shared/dam_client.py (+100 lines dual auth)
- Python-Version/scripts/a1_to_a2_download.py (+14 lines argparse)
- Python-Version/scripts/a5_to_a6_download.py (+14 lines argparse)
- Python-Version/scripts/b1_to_b2_download.py (+14 lines argparse)
- Python-Version/scripts/test_connection.py (+15 lines argparse)
- NEW: Python-Version/scripts/test_mtls_cert.py (92 lines)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 18:01:23 -05:00
DJP
1081cfd9ca Add 'no assets found' email notifications for A1→A2 and B1→B2
Ensures all workflows send email notifications even when no assets are found.

NEW EMAIL TEMPLATES:
1. a1_to_a2_no_assets - Warns when A1 campaign has no master assets
2. b1_to_b2_no_assets - Warns when B1 campaign has no global master assets

Both templates use orange warning theme and explain:
- Campaign is set to A1/B1 but no assets found
- Status NOT updated (remains at current status)
- Script will retry on next run
- Suggests verifying assets exist in folder

Changes:
- a1_to_a2_download.py: Send email when total_assets == 0
- b1_to_b2_download.py: Send email when total_assets == 0
- notifier.py: Add 2 new warning templates

This completes email coverage for all scenarios:
✓ Success (all assets processed)
✓ Partial (some failed)
✓ No assets found (campaign empty)
✓ No rejections (A5→A6 specific)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 15:33:25 -05:00
DJP
055fc9ad16 Add recursive folder search, NOT APPROVED filtering, and rejection details for A5→A6
Major enhancements to all workflow scripts with recursive search and detailed rejection tracking.

NEW FEATURES:
1. Recursive Folder Search (ALL workflows: A1→A2, A5→A6, B1→B2)
   - Searches subfolders within Master/Final Assets folders
   - Preserves folder structure in Box
   - Adds 'folder_path' attribute to each asset

2. NOT APPROVED Filtering (A5→A6 ONLY)
   - Only downloads assets with ECOMMERCE STATUS = "NOT APPROVED"
   - Skips approved/other status assets
   - Logs rejected vs skipped counts

3. Rejection Details Extraction (A5→A6)
   - Extracts comments from 3 reviewers: Approver, Legal, IA&CC
   - Includes certifier names and dates
   - Displays in detailed email notifications

CHANGES BY FILE:

dam_client.py:
- NEW: _get_assets_recursive() - Recursively searches folders
- UPDATED: get_master_assets() - Now uses recursive search, adds folder_path to assets
- NEW: is_asset_not_approved() - Checks FERRERO.FIELD.ECOMMERCE STATUS
- NEW: extract_rejection_details() - Extracts all rejection comments from 10 fields

box_client.py:
- UPDATED: upload_with_tracking_id() - Added subfolder_path parameter
- NEW: _get_or_create_subfolder_path() - Creates/navigates Box subfolders
- Preserves DAM folder structure in Box uploads

a1_to_a2_download.py:
- Added folder_path extraction from assets
- Pass subfolder_path to Box upload
- Logs subfolder info during processing

b1_to_b2_download.py:
- Added folder_path extraction from assets
- Pass subfolder_path to Box upload
- Logs subfolder info during processing

a5_to_a6_download.py:
- Filter assets for NOT APPROVED status ONLY
- Extract rejection details for each asset
- Pass subfolder_path to Box upload
- Updated email data with rejection_details
- Handle "no rejections" scenario with email
- Updated logging to show rejected vs skipped counts

notifier.py:
- REPLACED: a5_to_a6_complete → a5_to_a6_rejections
- Detailed HTML template with rejection sections
- Shows Approver, Legal, and IA&CC rejections
- Styled with red warnings and bordered sections
- NEW: a5_to_a6_no_rejections template
- Green success message when no rejected assets found
- UPDATED: a5_to_a6_partial - Now uses rejected_assets

FIELD IDs EXTRACTED (A5→A6):
- FERRERO.FIELD.ECOMMERCE STATUS (primary check)
- FERRERO.MARKETING.FIELD.CERTIFIER COMMENT
- FERRERO.FIELD.ECOMMERCE CERTIFIER
- FERRERO.MARKETING.FIELD.APPROVAL DATE
- FERRERO.MARKETING.FIELD.LEGAL COMMENT
- FERRERO.FIELD.LEGAL CERTIFER (typo in field ID)
- FERRERO.MARKETING.FIELD.LEGAL APPROVAL DATE
- FERRERO.MARKETING.FIELD.IA CC COMMENT
- FERRERO.MARKETING.FIELD.IA CERTIFIER
- FERRERO.MARKETING.FIELD.IA CC APPROVAL DATE

TESTING:
✓ All connections working (DAM, Box, Database)
✓ A5→A6 script executes correctly
✓ Recursive search working
✓ NOT APPROVED filtering working
✓ "No rejections" email sent successfully
✓ Folder structure preserved in logs

WORKFLOW IMPACTS:
- A1→A2: Now searches recursively, preserves folder structure
- A5→A6: Filters for NOT APPROVED only, shows rejection details
- B1→B2: Now searches recursively, preserves folder structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 14:49:19 -05:00
DJP
eebfc8f189 Add local_campaign_id extraction and storage for complete campaign tracking
Database Schema:
 Added local_campaign_id VARCHAR(50) column
 Stores the immediate campaign the asset belongs to (C000000551)

Enhanced Extraction:
 extract_global_campaign_reference() now returns 3 values:
   - global_master_campaign_id (C000000068)
   - global_master_folder_id (676f2bcde4c7...)
   - local_campaign_id (C000000551)

 Extracts FERRERO.FIELD.CAMPAIGN ID from same collection
 Only sets local_campaign_id if that collection has global reference
 Logs all three IDs when found

A1→A2 Script:
 Passes local_campaign_id to store_master_asset()
 Stores complete campaign relationship

Database Now Stores:
- tracking_id: ABC123 (unique 6-char)
- opentext_id: 0008a50... (DAM asset ID)
- local_campaign_id: C000000551 (immediate campaign)
- global_master_campaign_id: C000000068 (global master)
- global_master_folder_id: 676f2bcde4c7... (global folder)

Example Relationship:
- Asset downloaded from Local Campaign C000000551
- That campaign references Global Master C000000068
- All three IDs stored for complete traceability

Database schema now complete with full campaign relationship tracking!

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 10:31:19 -05:00
DJP
c5165db46a Add Global Campaign Reference extraction and storage for A1→A2 workflow
Database Schema:
 Added global_master_campaign_id VARCHAR(50) column
 Added global_master_folder_id VARCHAR(255) column
 Stores relationship between local campaigns and their global masters

Database Module:
 Added extract_global_campaign_reference() method
 Searches inherited_metadata_collections for L7+ - CAMPAIGN containers
 Extracts FERRERO.FIELD.GLOBAL CAMPAIGN REFERENCE field
 Extracts container_id as global_master_folder_id
 Returns dict with both IDs

A1→A2 Script:
 Calls db.extract_global_campaign_reference() for each asset
 Passes global_master_campaign_id to store_master_asset()
 Passes global_master_folder_id to store_master_asset()
 Logs when Global Campaign Reference found

Example Data Stored:
- Local Campaign C000000551 asset
- global_master_campaign_id: C000000068
- global_master_folder_id: 676f2bcde4c7bcf7ef783e97f7495069bf50b6bc

Usage:
This data enables tracking which Global Master a local asset came from.
Can query all local assets for a specific Global Master campaign.
Foundation for future cross-campaign features.

Based on EXTRACTION_GUIDE.md implementation pattern.

Note: B1→B2 workflow NOT updated (those ARE the global masters)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 10:19:50 -05:00
DJP
ec372576e8 Make email notifications verbose with detailed asset lists
Email Template Enhancements:

1. A1→A2 Complete Email - Now Shows:
    Campaign name and number
    Asset count
    List of ALL processed assets with:
      - Asset name
      - Tracking ID
      - Box file ID
      - Box URL (clickable link)

2. A1→A2 Partial Email - Now Shows:
    Campaign details
    Total/successful/failed counts
    List of SUCCESSFUL assets with:
      - Asset name
      - Tracking ID
      - Box URL
    List of FAILED assets with:
      - Asset name
      - Specific error message
    Note about automatic retry

3. A2→A3 File Uploaded Email - Shows:
    Original filename (with Job# and Tracking ID)
    Clean filename (stripped)
    DAM Asset ID
    Tracking ID
    Note that file was deleted from Box

Benefits:
- Know exactly which assets succeeded/failed
- Can click Box URLs to verify files
- Can track specific errors per asset
- Don't need to check logs for details
- Full visibility into automation status

Example Partial Email:
━━━━━━━━━━━━━━━━━━━━━━━
Campaign Partially Processed
Campaign: KSURPRISE LOCAL (C000000123)
Total: 3 | Successful: 1 | Failed: 2

 Successfully Processed (1):
• asset1.mp4 (Tracking ID: ABC123)
  Box URL: https://app.box.com/file/123

 Failed Assets (2):
• asset2.mp4 (Error: Network timeout)
• asset3.mp4 (Error: Invalid metadata)
━━━━━━━━━━━━━━━━━━━━━━━

Also Updated DEPLOYMENT.md:
- Added Key Features section
- Documented log rotation (28 files, 10MB each)
- Documented Box file deletion
- Documented per-file email notifications

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 08:27:05 -04:00
DJP
65f2c9c68e Add Box file deletion, email notifications, and log rotation for A2→A3
Major Features Added:

1. Delete Files from Box After Upload
   - After successful DAM upload, delete file from Box
   - Prevents reprocessing same files
   - Keeps Box folder clean
   - Only deletes on success (keeps on failure for retry)

2. Email Notification for Each Upload
   - New template: a2_to_a3_file_uploaded
   - Sends email immediately after each successful upload
   - Includes: filename, clean filename, asset ID, tracking ID
   - Don't wait for "all done" - notify per file
   - Recipients: configured in .env (REPORT_EMAILS)

3. Log Rotation for Both Scripts
   - Uses RotatingFileHandler
   - Max file size: 10MB per log file
   - Backup count: 28 files (approximately 1 month)
   - Auto-rotates when log reaches 10MB
   - Keeps logs/a1_to_a2.log (current)
   - Backups: logs/a1_to_a2.log.1, .2, .3, etc.
   - Automatically deletes logs older than 28 rotations
   - Applied to both A1→A2 and A2→A3 scripts

Flow Changes:
A2→A3 now:
1. Poll Box folder
2. Find V2 files
3. Download from Box
4. Upload to DAM
5. Delete from Box  NEW
6. Send email notification  NEW
7. Store derivative record
8. Exit

Log Management:
- Active logs: ~10MB max
- Rotated backups: 28 files = ~280MB total
- Automatic cleanup (no manual intervention needed)
- 1 week of detailed logs + 3 weeks of backups

Database:
- Added dam_asset_id and upload_status columns to derivative_assets
- Fixed store_derivative_asset() to use existing schema columns

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 19:41:02 -04:00
DJP
e95988e2bc Fix Box folder naming - use campaign ID (C000000078) not asset ID
Issue: Box folders were named with hex asset ID instead of campaign ID
Example Wrong: 7e2f7c97b003f91f8b2a162b9f62ccab51586fa9_Local_adaptation_test_2
Example Correct: C000000078-Local_adaptation_test_2

Fixes:
1. Use campaign_number (C000000078) instead of campaign_id (hex) for Box folder
2. Change separator from underscore to dash (C000000078-Campaign_Name)

The confusion:
- campaign_id variable = DAM asset_id (hex string for API calls)
- campaign_number variable = actual campaign ID (C000000078)

Now Box folders will be named correctly: C000000078-Local_adaptation_test_2

🤖 Generated with Claude Code
2025-10-30 18:19:06 -04:00
DJP
40ebc56585 Change A1→A2 script to single-run mode (process one campaign and exit)
Changed from continuous loop to single-run mode:
- Processes ONLY the first A1 campaign found
- Exits after processing (success=0, failure=1)
- Cron will run every 5 minutes, naturally processing one at a time

Benefits:
 Controlled processing (one campaign at a time)
 Easy to test manually
 Predictable resource usage
 Failed campaigns retry on next run
 Can stop/start easily

How it works:
1. Cron triggers script every 5 minutes
2. Script finds A1 campaigns
3. Processes first one only
4. If success → Updates to A2, exits
5. If failure → Stays A1, exits
6. Next run processes next A1 (or retries failed)

Also fixed requirements.txt to use >= versions for Python 3.10+ compatibility

🤖 Generated with Claude Code
2025-10-30 17:01:45 -04:00
DJP
b4e004c822 Complete Python automation implementation - All components built
MAJOR MILESTONE: Complete Python automation system created!

Components Implemented:
 Box Client (box_client.py)
   - JWT authentication via boxsdk
   - Upload with tracking ID suffix
   - Download files
   - Campaign folder creation
   - Connection testing

 Database Client (database.py)
   - PostgreSQL connection pooling
   - generate_unique_tracking_id()
   - store_master_asset() with full_metadata JSONB
   - get_master_asset(tracking_id)
   - check_campaign_upload_complete() - ALL-DONE CHECK!
   - store_derivative_asset()
   - Connection testing

 Filename Parser (filename_parser.py)
   - V2 naming convention parser (ported from PHP)
   - parse_filename() - 10 components
   - strip_upload_components() - Remove Job# and Tracking ID
   - Strict validation with detailed errors

 Metadata Extractor MVP (metadata_extractor_mvp.py)
   - Extract 28 MVP fields from master
   - Update fields from V2 filename (Description, Language, State)
   - Add missing fields with defaults
   - Build asset representation for upload

 Notifier (notifier.py)
   - Mailgun email integration
   - Outgoing webhook sender
   - Email templates (success, error, partial, critical)
   - Configurable recipients

Main Scripts:
 A1→A2 Download (a1_to_a2_download.py)
   - Poll DAM every 5 minutes for A1 campaigns
   - Download all master assets
   - Upload to Box with tracking IDs
   - Store in DB with full metadata
   - ALL-DONE CHECK before status update
   - Update A1→A2 only if all assets successful
   - Send webhook with campaign ID/number
   - Email notifications

 A2→A3 Upload (a2_to_a3_upload.py)
   - Flask webhook receiver for Box uploads
   - Signature validation
   - Async task queue processing
   - Parse V2 filenames
   - Load master metadata
   - Extract MVP fields
   - Upload to DAM
   - ALL-DONE CHECK for campaign
   - Update A2→A3 when all assets uploaded
   - Send webhook notifications

 Test Connection Script (test_connection.py)
   - Verify DAM, Box, Database connectivity
   - Quick health check

 README.md
   - Complete setup guide
   - Usage instructions
   - Configuration examples
   - Troubleshooting

Key Features:
- Python 3.6+ compatible (server requirement)
- Virtual environment isolated
- Configuration-driven (YAML files)
- Easy field updates (no code changes)
- Environment switching (staging/production)
- Comprehensive error handling
- Email + webhook notifications
- Retry logic
- All-done checks before status updates
- Campaign webhook notifications

Ready for testing locally with Python 3.10!

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 16:49:14 -04:00