Files
Autoparts-DB/scripts/credit_score_report.py
consultoria-as a386ca5b4a
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled
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
2026-10-12 00:57:31 +00:00

124 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Credit risk score report (A9).
Prints rule-based credit scores for a tenant's customers using
``services.credit_scoring.score_customer``. Read-only: never writes to the DB.
Usage (as root/postgres, from /home/Autopartes):
python3 scripts/credit_score_report.py --tenant 31
python3 scripts/credit_score_report.py --tenant 45 --only-credit
python3 scripts/credit_score_report.py --all --tier risk
python3 scripts/credit_score_report.py --tenant 31 --json
"""
import argparse
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'pos'))
from tenant_db import get_tenant_conn, get_master_conn # noqa: E402
from services.credit_scoring import score_customer # noqa: E402
def list_tenants(only_id=None):
conn = get_master_conn()
try:
cur = conn.cursor()
if only_id:
cur.execute(
"SELECT id, name, db_name FROM tenants "
"WHERE is_active = true AND id = %s",
(only_id,),
)
else:
cur.execute(
"SELECT id, name, db_name FROM tenants WHERE is_active = true ORDER BY id"
)
rows = cur.fetchall()
cur.close()
return rows
finally:
conn.close()
def customer_ids(conn, only_credit=False, limit=None):
cur = conn.cursor()
where = "WHERE c.is_active = true"
if only_credit:
where += " AND (c.credit_limit > 0 OR c.credit_balance > 0)"
sql = f"SELECT c.id FROM customers c {where} ORDER BY c.id"
if limit:
sql += " LIMIT %s"
cur.execute(sql, (limit,))
else:
cur.execute(sql)
ids = [r[0] for r in cur.fetchall()]
cur.close()
return ids
def report_tenant(tenant_id, tenant_name, only_credit, limit, tier_filter, as_json):
conn = get_tenant_conn(tenant_id)
try:
ids = customer_ids(conn, only_credit=only_credit, limit=limit)
rows = []
for cid in ids:
try:
rows.append(score_customer(conn, cid))
except Exception as exc:
rows.append({'customer_id': cid, 'error': str(exc)})
finally:
conn.close()
if tier_filter:
rows = [r for r in rows if r.get('tier') == tier_filter]
if as_json:
print(json.dumps({'tenant_id': tenant_id, 'tenant': tenant_name, 'scores': rows},
ensure_ascii=False, indent=2, default=str))
return
print(f"\n=== Tenant {tenant_id} — {tenant_name} ({len(rows)} clientes) ===")
print(f"{'ID':>6} {'score':>5} {'tier':<6} {'lim_sug':>12} {'datos':>5} señal principal")
for r in sorted(rows, key=lambda x: x.get('score', 0)):
if 'error' in r:
print(f"{r['customer_id']:>6} ERROR: {r['error']}")
continue
reasons = r.get('reasons') or []
headline = next(
(x for x in reasons if 'Mora' in x or 'atraso' in x or 'excedido' in x),
reasons[-1] if reasons else '',
)
print(f"{r['customer_id']:>6} {r['score']:>5} {r['tier']:<6} "
f"{r['recommended_limit']:>12,.2f} {r['data_points']:>5} {headline}")
def main():
parser = argparse.ArgumentParser(description="Credit risk score report (A9)")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--tenant', type=int, help='Tenant id')
group.add_argument('--all', action='store_true', help='All active tenants')
parser.add_argument('--only-credit', action='store_true',
help='Only customers with a credit line or balance')
parser.add_argument('--tier', choices=['good', 'medium', 'risk'],
help='Only show customers in this tier')
parser.add_argument('--limit', type=int, help='Max customers per tenant')
parser.add_argument('--json', action='store_true', help='Emit JSON')
args = parser.parse_args()
tenants = list_tenants(None if args.all else args.tenant)
if not tenants:
print("No tenants found", file=sys.stderr)
return 1
for tenant_id, name, _db_name in tenants:
report_tenant(tenant_id, name, args.only_credit, args.limit,
args.tier, args.json)
return 0
if __name__ == '__main__':
sys.exit(main())