diff --git a/Python-Version/scripts/a4_box_uploader.py b/Python-Version/scripts/a4_box_uploader.py index 9a6bc94..fcb01ef 100644 --- a/Python-Version/scripts/a4_box_uploader.py +++ b/Python-Version/scripts/a4_box_uploader.py @@ -52,23 +52,20 @@ logger = logging.getLogger('A4Box') def generate_and_upload_csv(db, box, config): """ - Generate CSV of all live LOCAL campaigns and upload to Box. - Filename prefix `live_campaigns_` is what OMG's Box automation watches for. + Generate the combined live-campaigns CSV (A-series + B-series) and upload + to Box. OMG's automation treats each new file as a full replacement of + its live list, so we always emit the complete list under one filename. """ try: logger.info("Generating live campaigns CSV...") - # 1. Get all live campaigns from DB campaigns = db.get_all_live_campaigns() if not campaigns: logger.warning("No live campaigns found to report") - # Even if empty, we might want to upload an empty CSV to clear the list? - # For now, let's upload it even if empty to reflect that no campaigns are live. logger.info("Found {} live campaigns".format(len(campaigns))) - # 2. Generate CSV file timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%d_%H%M%S_UTC') csv_filename = 'live_campaigns_{}.csv'.format(timestamp) csv_path = os.path.join('temp', csv_filename) @@ -88,7 +85,6 @@ def generate_and_upload_csv(db, box, config): logger.info("Generated CSV: {}".format(csv_path)) - # 3. Upload to Box folder_id = config['box'].get('live_campaigns_folder_id') if not folder_id: logger.error("Box live_campaigns_folder_id not configured") @@ -104,7 +100,6 @@ def generate_and_upload_csv(db, box, config): csv_filename, upload_result['file_id'] )) - # Clean up os.remove(csv_path) return True @@ -112,63 +107,6 @@ def generate_and_upload_csv(db, box, config): logger.error("Failed to generate/upload CSV: {}".format(str(e))) return False -def generate_and_upload_global_csv(db, box, config): - """ - Generate CSV of all live GLOBAL campaigns (B-series) and upload to Box. - Goes to the same Box folder as the local CSV; filename keeps the - `live_campaigns_` prefix so OMG's Box automation picks it up too. - """ - try: - logger.info("Generating live GLOBAL campaigns CSV...") - - campaigns = db.get_all_live_global_campaigns() - - if not campaigns: - logger.warning("No live global campaigns found to report") - - logger.info("Found {} live global campaigns".format(len(campaigns))) - - timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%d_%H%M%S_UTC') - csv_filename = 'live_campaigns_global_{}.csv'.format(timestamp) - csv_path = os.path.join('temp', csv_filename) - - os.makedirs('temp', exist_ok=True) - - with open(csv_path, 'w', newline='') as csvfile: - fieldnames = ['code', 'description'] - writer = csv.DictWriter(csvfile, fieldnames=fieldnames) - - writer.writeheader() - for camp in campaigns: - writer.writerow({ - 'code': "{}-{}".format(camp['campaign_number'], camp['campaign_name']), - 'description': camp['campaign_name'] - }) - - logger.info("Generated global CSV: {}".format(csv_path)) - - folder_id = config['box'].get('live_campaigns_folder_id') - if not folder_id: - logger.error("Box live_campaigns_folder_id not configured") - return False - - upload_result = box.upload_file( - file_path=csv_path, - folder_id=folder_id, - target_filename=csv_filename - ) - - logger.info("Uploaded global CSV to Box: {} (File ID: {})".format( - csv_filename, upload_result['file_id'] - )) - - os.remove(csv_path) - return True - - except Exception as e: - logger.error("Failed to generate/upload global CSV: {}".format(str(e))) - return False - def process_campaign(campaign, dam, box, db, notifier, config): """ Process A4 campaign - update status and upload CSV @@ -195,15 +133,9 @@ def process_campaign(campaign, dam, box, db, notifier, config): logger.info("Skipping to avoid duplicate processing") return {'success': True, 'processed': False, 'already_processed': True} - # Look up the campaign's PRIOR status before we overwrite it. - # If it was B-series (B2), this is a global campaign that a manager - # closed via A4 — route the CSV regen to the global CSV instead. - prior_status = (campaign_check.get('status') or '') if campaign_check.get('exists') else '' - is_global = prior_status.startswith('B') - # Record campaign status in database # This marks it as NOT LIVE - logger.info("Recording campaign status in database (Live: NO, prior status: {})...".format(prior_status or 'unknown')) + logger.info("Recording campaign status in database (Live: NO)...") db.record_campaign_status( campaign_id=campaign_id, campaign_number=campaign_number, @@ -213,14 +145,8 @@ def process_campaign(campaign, dam, box, db, notifier, config): webhook_sent=True # Mark as processed ) - # Regenerate the CSV that this campaign was on. Global if prior was - # B-series, local otherwise. - if is_global: - logger.info("Prior status was {} - regenerating GLOBAL live campaigns CSV...".format(prior_status)) - csv_success = generate_and_upload_global_csv(db, box, config) - else: - logger.info("Generating and uploading updated live campaigns CSV...") - csv_success = generate_and_upload_csv(db, box, config) + logger.info("Generating and uploading updated live campaigns CSV...") + csv_success = generate_and_upload_csv(db, box, config) if csv_success: logger.info("✓ CSV report uploaded successfully") diff --git a/Python-Version/scripts/b1_to_b2_download.py b/Python-Version/scripts/b1_to_b2_download.py index 912de0a..01f7369 100755 --- a/Python-Version/scripts/b1_to_b2_download.py +++ b/Python-Version/scripts/b1_to_b2_download.py @@ -110,25 +110,24 @@ def extract_creativex_from_dam_metadata(asset_metadata): return {'score': None, 'url': None} -def generate_and_upload_global_csv(db, box, config): +def generate_and_upload_csv(db, box, config): """ - Generate CSV of all live GLOBAL campaigns (B-series) and upload to Box. - Same Box folder as the local CSV; filename keeps the `live_campaigns_` - prefix so OMG's Box automation picks it up. - Mirrors the helper of the same name in a4_box_uploader.py. + Generate the combined live-campaigns CSV (A-series + B-series) and upload + to Box. OMG's automation treats each new file as a full replacement of + its live list, so we always emit the complete list under one filename. """ try: - logger.info("Generating live GLOBAL campaigns CSV...") + logger.info("Generating live campaigns CSV...") - campaigns = db.get_all_live_global_campaigns() + campaigns = db.get_all_live_campaigns() if not campaigns: - logger.warning("No live global campaigns found to report") + logger.warning("No live campaigns found to report") - logger.info("Found {} live global campaigns".format(len(campaigns))) + logger.info("Found {} live campaigns".format(len(campaigns))) timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%d_%H%M%S_UTC') - csv_filename = 'live_campaigns_global_{}.csv'.format(timestamp) + csv_filename = 'live_campaigns_{}.csv'.format(timestamp) csv_path = os.path.join('temp', csv_filename) os.makedirs('temp', exist_ok=True) @@ -144,7 +143,7 @@ def generate_and_upload_global_csv(db, box, config): 'description': camp['campaign_name'] }) - logger.info("Generated global CSV: {}".format(csv_path)) + logger.info("Generated CSV: {}".format(csv_path)) folder_id = config['box'].get('live_campaigns_folder_id') if not folder_id: @@ -157,7 +156,7 @@ def generate_and_upload_global_csv(db, box, config): target_filename=csv_filename ) - logger.info("Uploaded global CSV to Box: {} (File ID: {})".format( + logger.info("Uploaded CSV to Box: {} (File ID: {})".format( csv_filename, upload_result['file_id'] )) @@ -165,7 +164,7 @@ def generate_and_upload_global_csv(db, box, config): return True except Exception as e: - logger.error("Failed to generate/upload global CSV: {}".format(str(e))) + logger.error("Failed to generate/upload CSV: {}".format(str(e))) return False @@ -404,14 +403,14 @@ def process_campaign(campaign, dam, box, db, notifier, config): webhook_sent=False # B-series workflow doesn't send a webhook ) - # Regenerate and upload the global live campaigns CSV to Box. - # Box automation forwards it to OMG. - logger.info("Generating and uploading global live campaigns CSV...") - csv_success = generate_and_upload_global_csv(db, box, config) + # Regenerate and upload the combined live campaigns CSV to Box. + # Box automation forwards it to OMG as a full-list replacement. + logger.info("Generating and uploading live campaigns CSV...") + csv_success = generate_and_upload_csv(db, box, config) if csv_success: - logger.info("✓ Global CSV report uploaded successfully") + logger.info("✓ CSV report uploaded successfully") else: - logger.error("✗ Global CSV report generation/upload failed") + logger.error("✗ CSV report generation/upload failed") # NOTE: B1→B2 workflow does NOT send webhook (only email notification) # Webhook is only used for A1→A2 workflow diff --git a/Python-Version/scripts/b4_box_uploader.py b/Python-Version/scripts/b4_box_uploader.py index 0a4b24a..326279c 100644 --- a/Python-Version/scripts/b4_box_uploader.py +++ b/Python-Version/scripts/b4_box_uploader.py @@ -55,9 +55,9 @@ logger = logging.getLogger('B4Box') def generate_and_upload_csv(db, box, config): """ - Generate CSV of all live LOCAL campaigns and upload to Box. - Used here only as a fallback when a campaign closed via B4 was - actually a local one (defensive — DAM likely prevents this). + Generate the combined live-campaigns CSV (A-series + B-series) and upload + to Box. OMG's automation treats each new file as a full replacement of + its live list, so we always emit the complete list under one filename. """ try: logger.info("Generating live campaigns CSV...") @@ -111,64 +111,6 @@ def generate_and_upload_csv(db, box, config): return False -def generate_and_upload_global_csv(db, box, config): - """ - Generate CSV of all live GLOBAL campaigns (B-series) and upload to Box. - Same Box folder as the local CSV; filename keeps the `live_campaigns_` - prefix so OMG's Box automation picks it up. - """ - try: - logger.info("Generating live GLOBAL campaigns CSV...") - - campaigns = db.get_all_live_global_campaigns() - - if not campaigns: - logger.warning("No live global campaigns found to report") - - logger.info("Found {} live global campaigns".format(len(campaigns))) - - timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%d_%H%M%S_UTC') - csv_filename = 'live_campaigns_global_{}.csv'.format(timestamp) - csv_path = os.path.join('temp', csv_filename) - - os.makedirs('temp', exist_ok=True) - - with open(csv_path, 'w', newline='') as csvfile: - fieldnames = ['code', 'description'] - writer = csv.DictWriter(csvfile, fieldnames=fieldnames) - - writer.writeheader() - for camp in campaigns: - writer.writerow({ - 'code': "{}-{}".format(camp['campaign_number'], camp['campaign_name']), - 'description': camp['campaign_name'] - }) - - logger.info("Generated global CSV: {}".format(csv_path)) - - folder_id = config['box'].get('live_campaigns_folder_id') - if not folder_id: - logger.error("Box live_campaigns_folder_id not configured") - return False - - upload_result = box.upload_file( - file_path=csv_path, - folder_id=folder_id, - target_filename=csv_filename - ) - - logger.info("Uploaded global CSV to Box: {} (File ID: {})".format( - csv_filename, upload_result['file_id'] - )) - - os.remove(csv_path) - return True - - except Exception as e: - logger.error("Failed to generate/upload global CSV: {}".format(str(e))) - return False - - def process_campaign(campaign, dam, box, db, notifier, config): """ Process B4 campaign - mark not-live and regenerate the global CSV. @@ -192,13 +134,7 @@ def process_campaign(campaign, dam, box, db, notifier, config): logger.info("Skipping to avoid duplicate processing") return {'success': True, 'processed': False, 'already_processed': True} - # Symmetric prior-status routing: if a campaign reaches B4 but its prior - # status was A-series, regenerate the LOCAL CSV instead. Defensive — - # DAM workflows likely prevent this cross-type transition. - prior_status = (campaign_check.get('status') or '') if campaign_check.get('exists') else '' - is_local = prior_status.startswith('A') - - logger.info("Recording campaign status in database (Live: NO, prior status: {})...".format(prior_status or 'unknown')) + logger.info("Recording campaign status in database (Live: NO)...") db.record_campaign_status( campaign_id=campaign_id, campaign_number=campaign_number, @@ -208,12 +144,8 @@ def process_campaign(campaign, dam, box, db, notifier, config): webhook_sent=True ) - if is_local: - logger.info("Prior status was {} - regenerating LOCAL live campaigns CSV...".format(prior_status)) - csv_success = generate_and_upload_csv(db, box, config) - else: - logger.info("Generating and uploading updated GLOBAL live campaigns CSV...") - csv_success = generate_and_upload_global_csv(db, box, config) + logger.info("Generating and uploading updated live campaigns CSV...") + csv_success = generate_and_upload_csv(db, box, config) if csv_success: logger.info("✓ CSV report uploaded successfully") diff --git a/Python-Version/scripts/shared/database.py b/Python-Version/scripts/shared/database.py index 06b320a..a836d0f 100644 --- a/Python-Version/scripts/shared/database.py +++ b/Python-Version/scripts/shared/database.py @@ -1074,9 +1074,8 @@ class Database: def get_all_live_campaigns(self): """ - Get all live LOCAL campaigns (A-series) for the local CSV report. - Filters by status starting with 'A' to keep this CSV scoped to - local campaigns even if a B-series row is ever marked live. + Get all live campaigns (A-series local + B-series global) for the + single combined CSV that OMG ingests as a full replacement list. """ conn = self.get_connection() try: @@ -1086,38 +1085,7 @@ class Database: SELECT campaign_number, campaign_name FROM campaign_status WHERE live_campaign = 'YES' - AND status LIKE 'A%' - ORDER BY campaign_number DESC - """) - - rows = cursor.fetchall() - - campaigns = [] - for row in rows: - campaigns.append({ - 'campaign_number': row[0], - 'campaign_name': row[1] - }) - - return campaigns - - finally: - cursor.close() - self.put_connection(conn) - - def get_all_live_global_campaigns(self): - """ - Get all live GLOBAL campaigns (B-series) for the global CSV report. - """ - conn = self.get_connection() - try: - cursor = conn.cursor() - - cursor.execute(""" - SELECT campaign_number, campaign_name - FROM campaign_status - WHERE live_campaign = 'YES' - AND status LIKE 'B%' + AND (status LIKE 'A%' OR status LIKE 'B%') ORDER BY campaign_number DESC """)