#!/usr/bin/env python3 """pub.py — Interactive menu entry point for the Publishing Sales Accounting Run. Usage: python pub.py At the menu prompt enter a number (0–10) or 'x' to exit. """ import logging import os import sys # --------------------------------------------------------------------------- # Logging — INFO to stdout; adjust level here or set LOG_LEVEL env var # --------------------------------------------------------------------------- logging.basicConfig( level=os.environ.get("LOG_LEVEL", "INFO").upper(), format="%(asctime)s %(levelname)-8s %(message)s", datefmt="%H:%M:%S", stream=sys.stdout, ) import config import pubsalesacc.pipeline as pb from pubsalesacc.utils.excel_fix import fix_sony_excel # --------------------------------------------------------------------------- # Menu definition # --------------------------------------------------------------------------- MENU: list[str] = [ "(0) Read Publishing data file into memory", "(1) Produce Royalty Output from loaded data file", "(2) Perform Song ID reconciliation", "(3) Update Master Lookup from stakeholder-corrected file", "(4) Perform Missing Songwriter Agreement analysis", "(5) Perform Contract Rate Calculations", "(6) Perform Payment Summary Calculations", "(7) [FUTURE] Load sales data to Snowflake", "(8) Utilities - Fix malformed Sony Publishing XLSX file", "(9) Utilities - Clean up and archive files", "(10) Utilities - Reload already reconciled DataFrame from pickle", ] # --------------------------------------------------------------------------- # Startup period-mismatch warning (non-blocking) # --------------------------------------------------------------------------- def _check_period_mismatch() -> None: """Warn if config.PERIOD differs from the latest period in Snowflake.""" try: import pubsalesacc.connectors.snowflake as sf from config import PERIOD sf_schema = os.environ.get("SF_ROYALTY_SCHEMA", "qa") result = sf.sf_df(pb.sql[4].format(SF_ROYALTY_SCHEMA=sf_schema)) max_period = result.iloc[0, 0] if str(max_period) != str(PERIOD): print( f"\n{'!' * 60}\n" f" WARNING: config.PERIOD = {PERIOD}\n" f" Latest Snowflake period = {max_period}\n" f" Update PERIOD in config.py if this is incorrect.\n" f"{'!' * 60}\n" ) except Exception: pass # Non-critical — don't block startup if Snowflake is unreachable # --------------------------------------------------------------------------- # Main loop # --------------------------------------------------------------------------- def main() -> None: _check_period_mismatch() file = config.FILEPATH + config.FILENAME df = None # Loaded on step 0 user_input = " " while user_input.lower() not in ("x", ""): row_count = len(df) if df is not None else 0 print(f"\n{'=' * 15} Quarterly Publishing Run {'=' * 15}") print( f" Period : {config.PERIOD}\n" f" File series : {config.FILESERIES}\n" f" Standard Roy % : {config.STANDARD_ROY_PCT}\n" f" Ingest file : {config.FILENAME}\n" f" Rows in memory : {row_count:,}\n" f" Client lookup : {config.PUBSONG_LOOKUP_LITE}\n" f" Master lookup : {config.PUBSONG_LOOKUP_MASTER}\n" f" James file : {config.JAMES_FILE_PATH}\n" f" Contract rates : {config.CONTRACT_RATES_FILE}\n" ) print("Enter 'x' to exit.\n") for item in MENU: print(f" {item}") user_input = input("\nSelection: ").strip() try: if user_input == "0": df = pb.read_file_to_df(file, drop_head_rows=False, drop_header=True) elif user_input == "1": if df is None: print("No data loaded. Run step 0 first.") else: pb.royalty_output( df, config.FILESERIES, config.PERIOD, reporting_month=config._d, standard_roy_pct=config.STANDARD_ROY_PCT, ) elif user_input == "2": if df is None: print("No data loaded. Run step 0 first.") else: df = pb.reconcile_song_ids( df, config.PUBSONG_LOOKUP_MASTER, config.FILESERIES, config.PERIOD, reporting_month=config._d, ) elif user_input == "3": pb.update_master_lookup( config.PUBSONG_LOOKUP_MASTER, config.PUBSONG_LOOKUP_FULL, config.PUBSONG_LOOKUP_LITE, ) elif user_input == "4": if df is None: print("No data loaded. Run step 0 first.") else: pb.check_missing_songwriter_agreements(df, config.PERIOD) elif user_input == "5": pb.contract_rate_cal( config.JAMES_FILE_PATH, config.LABEL_MAP_FILE, config.FILESERIES, config.PERIOD, reporting_month=config._d, ) elif user_input == "6": pb.payment_summary_cal( config.CONTRACT_RATES_FILE, config.CURRENCY_RATES_FILE, config.FILESERIES, config.PERIOD, ) elif user_input == "7": print( "[FUTURE] This step covers loading the renamed/gzipped sales file\n" "to S3 and running the Snowflake load queries. Not yet automated.\n" "See workflow doc for manual steps." ) elif user_input == "8": print(f"Applying Excel fix to: {file}") fix_sony_excel(file) print("Fix applied. You can now re-run step 0 to load the file.") elif user_input == "9": pb.cleanup_files( 0, config.PERIOD, config.CLEANUP_EXTENSIONS, config.CLEANUP_EXCEPTIONS, ) elif user_input == "10": df = pb.reload_reconciled_file(config.PERIOD) print( f"Loaded {len(df):,} rows from pickle for period {config.PERIOD}." ) elif user_input.lower() == "x": break else: print("Invalid selection. Enter a number 0–10 or 'x' to exit.") except KeyboardInterrupt: print("\nInterrupted.") except Exception as exc: logging.error("Error: %s", exc, exc_info=True) if __name__ == "__main__": main()