import os import sys import argparse from typing import Literal from datetime import date from dotenv import load_dotenv from loguru import logger load_dotenv() Mode = Literal["dev", "prod"] def setup_logging(log_file: str | None = None) -> None: logger.remove() logger.add(sys.stderr, level="INFO", format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}") if log_file: log_dir = os.getenv("LOGDIR") if log_dir: logger.add( f"{log_dir}/{log_file}", rotation="7 days", retention="30 days", compression="zip", level="DEBUG", ) def run(mode: Mode = "prod", target_date: date | None = None, email_only: bool = False, data_only: bool = False, affiliate: str | None = None, to: str | None = None) -> None: from .services.fill import FillService from .services.data import DataService from .services.dashboard import DashboardService from .services.distro import DistroService from .services.email_service import EmailService fill_service = FillService() data_service = DataService() dashboard_service = DashboardService() distro_service = DistroService() email_service = EmailService() if email_only: logger.info("Email-only mode, skipping data generation") distro_service.update_distro() email_service.send_all(mode, affiliate=affiliate, to=to, cleanup=False) logger.success("Completed successfully") return if mode == "dev": logger.info("Running in dev mode") run_data = True run_email = True else: status = fill_service.check_availability() if status.run_data: if not fill_service.fill_dates(status.dates_to_fill): logger.warning("Fill failed, aborting run") return run_data = status.run_data run_email = status.run_email if not run_data: logger.info("No data to process") return logger.info("Generating report data") data_generated = data_service.generate(target_date) if not data_generated: logger.warning("Data generation failed") return logger.info("Updating dashboard") dashboard_service.update() if run_email and not data_only: logger.info("Updating distribution list") distro_service.update_distro() logger.info("Sending emails") email_service.send_all(mode, affiliate=affiliate, to=to, cleanup=(mode != "dev")) logger.success("Completed successfully") def main() -> None: parser = argparse.ArgumentParser( prog="whats-cookin", description="Generate and send What's Cookin' email reports", ) parser.add_argument( "mode", nargs="?", default="prod", choices=["dev", "prod"], help="Execution mode (default: prod)", ) parser.add_argument( "--date", "-d", type=str, default=None, help="Target date for report (YYYY-MM-DD), defaults to latest available", ) parser.add_argument( "--log-file", "-l", type=str, default="whats-cookin-cea.log", help="Log file name (stored in LOGDIR)", ) parser.add_argument( "--email-only", "-e", action="store_true", help="Skip data generation, only send emails from existing CSV", ) parser.add_argument( "--data-only", action="store_true", help="Generate data only, skip sending emails", ) parser.add_argument( "--to", "-t", type=str, default=None, help="Override recipients with comma-separated email address(es)", ) parser.add_argument( "--affiliate", "-a", type=str, default=None, help="Only send for specific affiliate (country code like DE, label like D:W, or CEA)", ) parser.add_argument( "--markets", "-m", type=str, default=None, help="Comma-separated country codes for a multi-market report (e.g., DE,FR,IT). Requires N>1 markets.", ) args = parser.parse_args() setup_logging(args.log_file) target_date = None if args.date: try: target_date = date.fromisoformat(args.date) except ValueError: logger.error(f"Invalid date format: {args.date}, expected YYYY-MM-DD") sys.exit(1) try: if args.markets: markets = [m.strip().upper() for m in args.markets.split(",") if m.strip()] if len(markets) < 2: logger.error("--markets requires at least 2 country codes") sys.exit(1) from .services.email_service import EmailService email_service = EmailService() email_service.send_multi_market(markets=markets, mode=args.mode, to=args.to) else: run(mode=args.mode, target_date=target_date, email_only=args.email_only, data_only=args.data_only, affiliate=args.affiliate, to=args.to) except KeyboardInterrupt: logger.info("Interrupted by user") sys.exit(130) except Exception as e: logger.exception(f"Fatal error: {e}") sys.exit(1) if __name__ == "__main__": main()