#!/usr/bin/env python3 """Daily business-metrics rollup for every tenant, into the master table ``tenant_metrics_daily``. Creates the table idempotently (no tenant migration runner involved) and upserts one row per tenant per day, so it is safe to re-run for the same date. Usage: python3 scripts/rollup_tenant_metrics.py # today, active tenants python3 scripts/rollup_tenant_metrics.py --date 2026-10-03 python3 scripts/rollup_tenant_metrics.py --all # include inactive tenants python3 scripts/rollup_tenant_metrics.py --days 30 # backfill last 30 days python3 scripts/rollup_tenant_metrics.py --tenant tenant_capi_autopartes python3 scripts/rollup_tenant_metrics.py --json Exit code 0 on success, 2 when one or more tenants failed to roll up. """ import argparse import json import os import sys from datetime import date as date_cls MANAGER_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "manager") if MANAGER_DIR not in sys.path: sys.path.insert(0, MANAGER_DIR) from services import portfolio_service # noqa: E402 def _print_report(result, health_by_tenant): print(f"Rollup {result['date_from']} → {result['date_to']} " f"({result['days']} día(s)) — {result['rows_written']} filas, " f"{len(result['tenants'])} tenants") print(f"{'tenant':<34} {'rows':>5} {'ventas(hoy)':>11} {'MX$ hoy':>10} " f"{'items':>8} {'fitment%':>8} {'health':>6}") for t in result["tenants"]: h = health_by_tenant.get(t["tenant_id"], {}) last = t.get("last") or {} print(f"{t['db_name']:<34} {t['rows']:>5} " f"{last.get('ventas_count', ''):>11} " f"{last.get('ventas_total', ''):>10} " f"{last.get('items_activos', ''):>8} " f"{str(last.get('fitment_pct', '')):>8} " f"{str(h.get('health_score', '')):>6}") for e in result["errors"]: print(f" ERROR {e['db_name']}: {e['error']}", file=sys.stderr) print("\nHealth (último día):") for t in result["tenants"]: h = health_by_tenant.get(t["tenant_id"], {}) codes = ", ".join(s["code"] for s in h.get("signals", [])) or "-" print(f" {t['db_name']:<34} {str(h.get('health_score')):>5} " f"{h.get('band', ''):<9} [{codes}]") def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--date", help="last day of the range (YYYY-MM-DD, default today)") parser.add_argument("--days", type=int, default=1, help="number of days to roll up ending at --date (default 1)") parser.add_argument("--all", action="store_true", help="include inactive tenants (default: active only)") parser.add_argument("--tenant", action="append", default=None, help="restrict to a db_name or tenant id (repeatable)") parser.add_argument("--json", action="store_true", help="print raw JSON") args = parser.parse_args() target = None if args.date: try: target = date_cls.fromisoformat(args.date) except ValueError: parser.error(f"fecha inválida: {args.date} (usa YYYY-MM-DD)") result = portfolio_service.run_rollup( date=target, days=max(1, args.days), tenant_ids=args.tenant, include_inactive=args.all, ) # Attach the latest collected row + health per tenant for the report/JSON. metrics = portfolio_service.get_tenant_metrics(days=14) health_by_tenant = {t["tenant_id"]: t["health"] for t in metrics["tenants"]} latest_metrics = {t["tenant_id"]: t.get("metrics") for t in metrics["tenants"]} for t in result["tenants"]: t["last"] = latest_metrics.get(t["tenant_id"]) t["health"] = health_by_tenant.get(t["tenant_id"]) if args.json: print(json.dumps(result, indent=2, default=str)) else: _print_report(result, health_by_tenant) return 2 if result["errors"] else 0 if __name__ == "__main__": sys.exit(main())