Enhancement: Treat empty A1 folders as expected workflow

Campaign managers often create the campaign in DAM before assets are
uploaded, so an empty Master Assets folder is the normal pre-asset state
rather than a failure. Stop marking these as permanently failed and stop
emailing on every poll.

- increment_a1_retry() gains mark_failed_at_max param; empty-folder path
  passes False so the campaign keeps polling indefinitely until assets
  appear (or the DAM status changes).
- Empty-folder branch now skips silently on every poll and sends a single
  warning email at poll 20 (~1 hour at the 3-min cadence) so genuinely
  stuck campaigns still surface.
- New a1_to_a2_no_assets_warning email template — one-time soft warning,
  no permanent-failure language.
- Existing reset_a1_retry() on successful A1→A2 still clears the counter
  when assets eventually appear.
- Other folder-error paths (folder not found, etc.) keep the original
  3-retry-then-fail behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nickviljoen 2026-04-28 15:20:41 +02:00
parent ab557b78de
commit 90f326aecb
3 changed files with 65 additions and 30 deletions

View file

@ -50,6 +50,11 @@ logging.basicConfig(
logger = logging.getLogger('A1toA2Box')
# Empty A1 folders are an expected client workflow (folder created before assets uploaded).
# Skip silently and send a single warning email at this poll count to flag genuinely-stuck
# campaigns without spamming. At ~3-min poll cadence, 20 polls ≈ 1 hour.
EMPTY_FOLDER_WARNING_THRESHOLD = 20
def extract_creativex_from_dam_metadata(asset_metadata):
"""
Extract CreativeX score and URL from DAM asset metadata if present
@ -189,45 +194,37 @@ def process_campaign(campaign, dam, box, db, notifier, config):
logger.info("Found {} master assets".format(total_assets))
if total_assets == 0:
logger.warning("No master assets found in Master Assets folder")
# Increment retry counter
# Empty folders are expected when a campaign manager creates the campaign
# before uploading assets. Track the count for visibility but never auto-fail
# — keep retrying every poll until assets appear (or status changes in DAM).
retry_result = db.increment_a1_retry(
campaign_id=campaign_id,
campaign_number=campaign_number,
campaign_name=campaign_name,
reason="No master assets found in Master Assets folder"
reason="No master assets found in Master Assets folder",
mark_failed_at_max=False
)
if not retry_result['success']:
logger.error("Failed to update retry counter")
is_permanently_failed = retry_result.get('permanently_failed', False)
retry_count = retry_result.get('retry_count', 0)
logger.info("No master assets yet (poll {}) - skipping until assets appear".format(retry_count))
# Determine which email template to use
if is_permanently_failed:
# Send FINAL failure email (after 3 attempts)
template_name = 'a1_to_a2_permanently_failed'
recipients = config['notifications']['recipients']['errors']
else:
# Send standard retry notification
template_name = 'a1_to_a2_no_assets_retry'
recipients = config['notifications']['recipients']['errors']
# Send email notification
notifier.send_email(
template_name=template_name,
recipients=recipients,
data={
'campaign_name': campaign_name,
'campaign_id': campaign_id,
'campaign_number': campaign_number,
'retry_count': retry_count,
'max_retries': 3,
'is_permanently_failed': is_permanently_failed
}
)
# Send a single warning email when the campaign has been empty for ~1 hour
# so genuinely-stuck campaigns still surface, without spamming on every poll.
if retry_count == EMPTY_FOLDER_WARNING_THRESHOLD:
logger.warning("Campaign has been empty for {} polls - sending one-time warning".format(retry_count))
notifier.send_email(
template_name='a1_to_a2_no_assets_warning',
recipients=config['notifications']['recipients']['errors'],
data={
'campaign_name': campaign_name,
'campaign_id': campaign_id,
'campaign_number': campaign_number,
'poll_count': retry_count
}
)
return {'success': False, 'processed': 0, 'failed': 0}

View file

@ -630,7 +630,7 @@ class Database:
cursor.close()
self.put_connection(conn)
def increment_a1_retry(self, campaign_id, campaign_number, campaign_name, reason):
def increment_a1_retry(self, campaign_id, campaign_number, campaign_name, reason, mark_failed_at_max=True):
"""
Increment A1 retry counter and mark as permanently failed if max attempts reached
@ -639,6 +639,9 @@ class Database:
campaign_number: Campaign number (e.g., C000000078)
campaign_name: Campaign name
reason: Description of failure (e.g., "No master assets found")
mark_failed_at_max: If True (default), set a1_permanently_failed=True at MAX_RETRIES.
Set False for empty-folder polling where the campaign is expected
to eventually receive assets and should keep retrying silently.
Returns:
dict with success, retry_count, permanently_failed
@ -659,7 +662,7 @@ class Database:
row = cursor.fetchone()
current_count = (row[0] or 0) if row else 0
new_count = current_count + 1
is_permanently_failed = new_count >= MAX_RETRIES
is_permanently_failed = mark_failed_at_max and new_count >= MAX_RETRIES
# Insert or update campaign status with retry tracking
cursor.execute("""

View file

@ -705,6 +705,41 @@ class Notifier:
</div>
"""
},
'a1_to_a2_no_assets_warning': {
'subject': "⚠️ Campaign in A1 with no assets yet - {campaign_name}",
'html': """
<div style="font-family: Arial, sans-serif; max-width: 900px; margin: 0 auto;">
<div style="background-color: #ff9800; color: white; padding: 20px; text-align: center; border-radius: 8px 8px 0 0;">
<h1 style="margin: 0;"> Campaign in A1 with No Assets Yet</h1>
</div>
<div style="background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0;">
<p style="margin: 0;"><strong>Campaign:</strong> {{ campaign_name }} ({{ campaign_number }})</p>
<p style="margin: 5px 0 0 0;"><strong>Campaign ID:</strong> {{ campaign_id }}</p>
<p style="margin: 5px 0 0 0;"><strong>Status:</strong> A1</p>
<p style="margin: 5px 0 0 0;"><strong>Polls with empty folder:</strong> {{ poll_count }}</p>
</div>
<div style="padding: 20px; background-color: #f8f9fa; border-radius: 4px; margin: 20px 0;">
<h3 style="color: #ff9800; margin-top: 0;">Master Assets Folder Has Been Empty for ~1 Hour</h3>
<p>This campaign has been at status A1 for roughly an hour with no master assets in the folder.</p>
<p>This is often expected the folder may have been created before assets were uploaded and the system will keep checking automatically.</p>
<p>This is a <strong>one-time warning</strong>; no further emails will be sent for this campaign.</p>
</div>
<div style="background-color: #e3f2fd; border-left: 4px solid #1976d2; padding: 15px; margin: 20px 0;">
<p style="margin: 0;"><strong>📌 Action only needed if:</strong></p>
<ul style="margin: 10px 0;">
<li>You expected assets to be uploaded already</li>
<li>The campaign was set to A1 by mistake (change the status in DAM)</li>
</ul>
<p style="margin: 10px 0 0 0;">Otherwise no action needed processing will start automatically as soon as assets appear in the Master Assets folder.</p>
</div>
<p style="color: #666; font-size: 12px; margin-top: 20px;">A1A2 script will continue to check silently every 3 minutes.</p>
</div>
"""
},
'a1_to_a2_permanently_failed': {
'subject': "❌ PERMANENTLY FAILED - Campaign {campaign_name} (No Assets After 3 Attempts)",
'html': """