diff --git a/Python-Version/scripts/a1_to_a2_box_uploader.py b/Python-Version/scripts/a1_to_a2_box_uploader.py index 6fc9212..9cb76ae 100644 --- a/Python-Version/scripts/a1_to_a2_box_uploader.py +++ b/Python-Version/scripts/a1_to_a2_box_uploader.py @@ -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} diff --git a/Python-Version/scripts/shared/database.py b/Python-Version/scripts/shared/database.py index 5e32d23..192da51 100644 --- a/Python-Version/scripts/shared/database.py +++ b/Python-Version/scripts/shared/database.py @@ -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(""" diff --git a/Python-Version/scripts/shared/notifier.py b/Python-Version/scripts/shared/notifier.py index 705c377..6005bad 100644 --- a/Python-Version/scripts/shared/notifier.py +++ b/Python-Version/scripts/shared/notifier.py @@ -705,6 +705,41 @@ class Notifier: """ }, + 'a1_to_a2_no_assets_warning': { + 'subject': "⚠️ Campaign in A1 with no assets yet - {campaign_name}", + 'html': """ +
Campaign: {{ campaign_name }} ({{ campaign_number }})
+Campaign ID: {{ campaign_id }}
+Status: A1
+Polls with empty folder: {{ poll_count }}
+This campaign has been at status A1 for roughly an hour with no master assets in the folder.
+This is often expected — the folder may have been created before assets were uploaded — and the system will keep checking automatically.
+This is a one-time warning; no further emails will be sent for this campaign.
+📌 Action only needed if:
+Otherwise no action needed — processing will start automatically as soon as assets appear in the Master Assets folder.
+A1→A2 script will continue to check silently every 3 minutes.
+