Files
presupuestos_callcenter/notebooks/.ipynb_checkpoints/02_metricas_3kpi-checkpoint.ipynb
T
2026-05-21 18:26:30 +02:00

245 lines
8.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"id": "36ae91c9",
"metadata": {},
"source": [
"# 02 — 3 KPI principales\n",
"\n",
"Por **centro real de facturación** (`tpv_terminals.center_id` del invoice) y ventana temporal:\n",
"\n",
"1. **A — Valor facturado de quotes call_center que CONVIRTIERON** \n",
" Quote creado por usuario call_center + existe `tpv_orders_invoice` con el mismo `order_id`. Sumamos `tpv_orders_order.total_cost` o líneas. Centro = centro del invoice.\n",
"\n",
"2. **B — Valor facturado total a esos mismos clientes en centros** \n",
" Misma identidad cliente (`customer_id` y/o `vehicle_id` y/o `tlf`+`matricula` normalizados). Todas las facturas del cliente en ese centro en la misma ventana. Debe ser ≥ A.\n",
"\n",
"3. **C — Facturación total del centro** \n",
" Suma de invoices del centro en la ventana, todos los clientes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7938921f",
"metadata": {},
"outputs": [],
"source": [
"from google.cloud import bigquery\n",
"import pandas as pd\n",
"\n",
"PROJECT = \"autingo-159109\"\n",
"DATASET = \"psql_dcpublic\"\n",
"bq = bigquery.Client(project=PROJECT)\n",
"\n",
"WINDOW_DAYS = 90\n",
"\n",
"def q(sql):\n",
" return bq.query(sql).to_dataframe()"
]
},
{
"cell_type": "markdown",
"id": "c7cfa785",
"metadata": {},
"source": [
"## Setup: CTEs base reutilizables\n",
"\n",
"Construimos una query maestra con CTEs para A, B, C juntos por centro."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e06df33",
"metadata": {},
"outputs": [],
"source": [
"SQL_KPI = f\"\"\"\n",
"DECLARE window_days INT64 DEFAULT {WINDOW_DAYS};\n",
"DECLARE t_start TIMESTAMP DEFAULT TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL window_days DAY);\n",
"\n",
"WITH\n",
"cc_users AS (\n",
" SELECT DISTINCT tpvuser_id AS user_id\n",
" FROM `{PROJECT}.{DATASET}.tpv_authorization_tpvuser_centers`\n",
" WHERE dccenter_id IN (159, 162)\n",
"),\n",
"-- Quotes creados por call_center que TIENEN invoice (convertidos)\n",
"cc_converted AS (\n",
" SELECT\n",
" q.id AS quote_id,\n",
" q.order_id,\n",
" q.created_at AS quote_ts,\n",
" o.customer_id,\n",
" o.vehicle_id,\n",
" o.terminal_id,\n",
" t.center_id,\n",
" o.total_cost,\n",
" i.id AS invoice_id,\n",
" i.created_at AS invoice_ts\n",
" FROM `{PROJECT}.{DATASET}.tpv_orders_quote` q\n",
" JOIN cc_users cc ON q.created_by_id = cc.user_id\n",
" JOIN `{PROJECT}.{DATASET}.tpv_orders_order` o ON q.order_id = o.id\n",
" JOIN `{PROJECT}.{DATASET}.tpv_orders_invoice` i ON i.order_id = o.id\n",
" LEFT JOIN `{PROJECT}.{DATASET}.tpv_terminals` t ON o.terminal_id = t.id\n",
" WHERE q.created_at >= t_start\n",
" AND q.deleted_at IS NULL\n",
"),\n",
"-- Clientes \"tocados\" por call_center (customer_id + vehicle_id)\n",
"cc_clients AS (\n",
" SELECT DISTINCT center_id, customer_id, vehicle_id\n",
" FROM cc_converted\n",
" WHERE customer_id IS NOT NULL\n",
"),\n",
"-- Todas las facturas en la ventana, con centro real\n",
"all_invoices AS (\n",
" SELECT\n",
" i.id AS invoice_id,\n",
" i.order_id,\n",
" i.created_at AS invoice_ts,\n",
" o.customer_id,\n",
" o.vehicle_id,\n",
" o.terminal_id,\n",
" t.center_id,\n",
" o.total_cost\n",
" FROM `{PROJECT}.{DATASET}.tpv_orders_invoice` i\n",
" JOIN `{PROJECT}.{DATASET}.tpv_orders_order` o ON i.order_id = o.id\n",
" LEFT JOIN `{PROJECT}.{DATASET}.tpv_terminals` t ON o.terminal_id = t.id\n",
" WHERE i.created_at >= t_start\n",
"),\n",
"-- B: facturas de los mismos clientes en cualquier centro NO call_center\n",
"client_invoices_in_centers AS (\n",
" SELECT ai.*\n",
" FROM all_invoices ai\n",
" JOIN cc_clients cc ON ai.customer_id = cc.customer_id\n",
" WHERE ai.center_id NOT IN (159, 162) -- excluye los propios centros call_center\n",
"),\n",
"kpi_a AS (\n",
" SELECT center_id,\n",
" COUNT(DISTINCT quote_id) AS quotes_cc_convertidos,\n",
" COUNT(DISTINCT invoice_id) AS invoices_a,\n",
" SUM(total_cost) AS valor_a\n",
" FROM cc_converted\n",
" WHERE center_id IS NOT NULL\n",
" GROUP BY center_id\n",
"),\n",
"kpi_b AS (\n",
" SELECT center_id,\n",
" COUNT(DISTINCT invoice_id) AS invoices_b,\n",
" SUM(total_cost) AS valor_b\n",
" FROM client_invoices_in_centers\n",
" GROUP BY center_id\n",
"),\n",
"kpi_c AS (\n",
" SELECT center_id,\n",
" COUNT(DISTINCT invoice_id) AS invoices_c,\n",
" SUM(total_cost) AS valor_c\n",
" FROM all_invoices\n",
" WHERE center_id IS NOT NULL\n",
" GROUP BY center_id\n",
")\n",
"SELECT\n",
" c.id AS center_id,\n",
" c.name AS center_name,\n",
" COALESCE(a.quotes_cc_convertidos, 0) AS quotes_cc_convertidos,\n",
" ROUND(COALESCE(a.valor_a, 0), 2) AS A_valor_quote_cc_convertido,\n",
" ROUND(COALESCE(b.valor_b, 0), 2) AS B_valor_mismo_cliente_centro,\n",
" ROUND(COALESCE(c2.valor_c, 0), 2) AS C_valor_total_centro,\n",
" ROUND(SAFE_DIVIDE(COALESCE(a.valor_a, 0), c2.valor_c), 4) AS A_sobre_C,\n",
" ROUND(SAFE_DIVIDE(COALESCE(b.valor_b, 0), c2.valor_c), 4) AS B_sobre_C\n",
"FROM `{PROJECT}.{DATASET}.centers` c\n",
"LEFT JOIN kpi_a a ON c.id = a.center_id\n",
"LEFT JOIN kpi_b b ON c.id = b.center_id\n",
"LEFT JOIN kpi_c c2 ON c.id = c2.center_id\n",
"WHERE c.id NOT IN (159, 162)\n",
" AND COALESCE(c2.valor_c, 0) > 0\n",
"ORDER BY C_valor_total_centro DESC\n",
"\"\"\"\n",
"\n",
"df = q(SQL_KPI)\n",
"print(f\"Centros con actividad ({WINDOW_DAYS}d): {len(df)}\")\n",
"df.head(30)"
]
},
{
"cell_type": "markdown",
"id": "724baf5c",
"metadata": {},
"source": [
"## Totales globales"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "75c3297e",
"metadata": {},
"outputs": [],
"source": [
"totals = df[[\"A_valor_quote_cc_convertido\", \"B_valor_mismo_cliente_centro\", \"C_valor_total_centro\"]].sum()\n",
"print(totals.to_string())\n",
"print()\n",
"print(f\"A/C global: {totals.A_valor_quote_cc_convertido / totals.C_valor_total_centro:.4f}\")\n",
"print(f\"B/C global: {totals.B_valor_mismo_cliente_centro / totals.C_valor_total_centro:.4f}\")\n",
"print(f\"Lift B vs A: {totals.B_valor_mismo_cliente_centro / totals.A_valor_quote_cc_convertido:.2f}x\")"
]
},
{
"cell_type": "markdown",
"id": "0fba60db",
"metadata": {},
"source": [
"## Top 15 centros por A (valor traído por call_center)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f90abe7e",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"top = df.sort_values(\"A_valor_quote_cc_convertido\", ascending=False).head(15)\n",
"fig, ax = plt.subplots(figsize=(10, 6))\n",
"x = range(len(top))\n",
"ax.barh(x, top.A_valor_quote_cc_convertido, label=\"A (cc → factura)\")\n",
"ax.barh(x, top.B_valor_mismo_cliente_centro - top.A_valor_quote_cc_convertido,\n",
" left=top.A_valor_quote_cc_convertido, label=\"BA (mismo cliente extra)\")\n",
"ax.set_yticks(x)\n",
"ax.set_yticklabels(top.center_name)\n",
"ax.invert_yaxis()\n",
"ax.set_xlabel(\"€ facturados\")\n",
"ax.legend()\n",
"ax.set_title(f\"Top 15 centros — quotes call_center ({WINDOW_DAYS}d)\")\n",
"plt.tight_layout()\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}