Mejoras de sesion: estabilizacion, IA, Nexus Yonke y marketplace B2B
- 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
This commit is contained in:
101
scripts/rollup_tenant_metrics.py
Normal file
101
scripts/rollup_tenant_metrics.py
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user