#!/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())