- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos - F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado - F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment - F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings) - F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant - Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas - Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant) - Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant - Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
102 lines
4.1 KiB
Python
102 lines
4.1 KiB
Python
#!/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())
|