chore: sync from fn-registry agent
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2b3d1ae8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 00 — Resultados ejecutados (vía Metabase, sin ADC)\n",
|
||||
"\n",
|
||||
"Resultados de la ejecución del script `run_via_metabase.py` (BigQuery `autingo-159109.psql_dcpublic`).\n",
|
||||
"\n",
|
||||
"Ventana: 90 días Q0, 60 días para detectar regeneración.\n",
|
||||
"Centros call_center excluidos del cómputo: 159 (CALL CENTER AURGI), 162 (CALL CENTER)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "c39f6e2c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'window_days': 90,\n",
|
||||
" 'A_quote_cc_eur': 6392965.08,\n",
|
||||
" 'B_mismo_cliente_eur': 6923203.81,\n",
|
||||
" 'C_total_centros_eur': 29635811.36,\n",
|
||||
" 'A_sobre_C': 0.2157,\n",
|
||||
" 'B_sobre_C': 0.2336,\n",
|
||||
" 'lift_B_vs_A': 1.08,\n",
|
||||
" 'centros_activos': 139}"
|
||||
]
|
||||
},
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"from pathlib import Path\n",
|
||||
"import pandas as pd\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"\n",
|
||||
"BASE = Path('../data/results').resolve()\n",
|
||||
"\n",
|
||||
"def load(name):\n",
|
||||
" return pd.read_csv(BASE / f'{name}.csv')\n",
|
||||
"\n",
|
||||
"totales = json.loads((BASE / 'totales_globales.json').read_text())\n",
|
||||
"totales"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "33adaf14",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Q1 — Tasa de conversión por origen del usuario que generó la quote"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "24471a70",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"load('01_conversion_origen')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5847ffda",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Lectura:** \n",
|
||||
"- Call center genera 62.8K quotes / 90d, convierte 47.1% (29.6K facturas con mismo `order_id`).\n",
|
||||
"- Otros usuarios generan 477K quotes / 90d, convierten 57.4% (273.8K facturas).\n",
|
||||
"- Brecha de ~10pp es esperable: el call_center genera quote en frío (cliente no presente), el TPV de centro genera quote casi siempre con el cliente ya en mostrador."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3484eeb6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Q2 — 3 KPI por centro\n",
|
||||
"\n",
|
||||
"- **A** = € facturados desde quotes creados por call_center (mismo order_id).\n",
|
||||
"- **B** = € facturados a los mismos clientes (`customer_id` + `vehicle_id`) en centros físicos NO call_center.\n",
|
||||
"- **C** = € facturados totales del centro (todos los clientes).\n",
|
||||
"- `lift_B_vs_A`: 1.0 = solo factura el quote inicial; >1 = el centro factura más al cliente que sólo el quote."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f2bc0458",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df = load('02_kpi_3_por_centro')\n",
|
||||
"df.head(15)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "201ac95f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Top 15 centros por valor A (más facturado vía call_center)\n",
|
||||
"top = df.sort_values('A_quote_cc_eur', ascending=False).head(15).iloc[::-1]\n",
|
||||
"fig, ax = plt.subplots(figsize=(10, 7))\n",
|
||||
"y = range(len(top))\n",
|
||||
"ax.barh(y, top.A_quote_cc_eur, label='A (cc -> factura)')\n",
|
||||
"ax.barh(y, (top.B_mismo_cliente_eur - top.A_quote_cc_eur).clip(lower=0),\n",
|
||||
" left=top.A_quote_cc_eur, label='B-A (mismo cliente extra)')\n",
|
||||
"ax.set_yticks(list(y)); ax.set_yticklabels(top.center_name)\n",
|
||||
"ax.set_xlabel('€ facturados (90d)')\n",
|
||||
"ax.legend(); ax.set_title('Top 15 centros — quotes call_center -> factura')\n",
|
||||
"plt.tight_layout(); plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "291fa4af",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print('TOTALES 90d (excluye centros call_center 159/162):')\n",
|
||||
"for k, v in totales.items():\n",
|
||||
" print(f' {k:25} {v}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3d161868",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Q3 — Centros que MÁS regeneran el presupuesto\n",
|
||||
"\n",
|
||||
"Definición operativa: para un par `(customer_id, vehicle_id)` cuyo primer presupuesto (Q0) lo abrió el call_center, hay un Q1+ posterior con **distinto `order_id`** abierto en un terminal del centro físico dentro de 60 días."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7831e9fe",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"regen = load('03_regen_por_centro')\n",
|
||||
"regen"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "83758dc9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"top_regen = regen.head(15).iloc[::-1]\n",
|
||||
"fig, ax = plt.subplots(figsize=(10, 6))\n",
|
||||
"y = range(len(top_regen))\n",
|
||||
"ax.barh(y, top_regen.q0_regenerados_aqui)\n",
|
||||
"ax.set_yticks(list(y)); ax.set_yticklabels(top_regen.center_name)\n",
|
||||
"ax.set_xlabel('# Q0 (de call_center) regenerados en este centro')\n",
|
||||
"ax.set_title('Top centros regeneradores de presupuesto call_center (90d Q0, 60d window)')\n",
|
||||
"plt.tight_layout(); plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b0f6c55c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Q4 — ¿Regenerar perjudica la conversión propia del Q0?\n",
|
||||
"\n",
|
||||
"Conversión del Q0 = el invoice se genera contra el MISMO order_id del Q0 (no contra el order_id regenerado en centro). Si regeneran, ese flujo cae."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "423b68cd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"load('04_regen_vs_conversion')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4956e2d1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Lectura:**\n",
|
||||
"- 35.5K Q0 sin regeneración convierten al **63.1%** (sobre el order_id original).\n",
|
||||
"- 18.5K Q0 con regeneración convierten al **38.7%** sobre el order_id original.\n",
|
||||
"- Los 'regenerados' no se 'pierden' necesariamente — el cliente puede haberse facturado vía un order_id distinto (el del centro). Esa parte está capturada en el KPI **B** del cuadro anterior.\n",
|
||||
"- Aprox **34.2% de los Q0 call_center** entran en patrón de regeneración (18488 / 53995)."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "07306d98",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 01 — Exploración: quotes ↔ call_center ↔ factura\n",
|
||||
"\n",
|
||||
"Mapa de tablas y joins en `psql_dcpublic` (BigQuery `autingo-159109`).\n",
|
||||
"\n",
|
||||
"## Cadena de joins\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"tpv_authorization_tpvuser_centers (dccenter_id ∈ {159 CALL_CENTER_AURGI, 162 CALL_CENTER})\n",
|
||||
" │ tpvuser_id\n",
|
||||
" ▼\n",
|
||||
"tpv_orders_quote.created_by_id ──► quote por agente call_center\n",
|
||||
" │ order_id\n",
|
||||
" ▼\n",
|
||||
"tpv_orders_order ─► terminal_id ─► tpv_terminals.center_id ─► centers (centro real de facturación)\n",
|
||||
" │ │ customer_id │ vehicle_id\n",
|
||||
" ▼ ▼ ▼\n",
|
||||
"tpv_orders_invoice (status convertido) tpv_customers (tlf) tpv_vehicles_vehicle (matrícula)\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Identidad cliente = `(customer_id, vehicle_id)` o (tlf, matrícula) según necesite normalización."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d2b6d545",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os, sys\n",
|
||||
"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",
|
||||
"def q(sql):\n",
|
||||
" return bq.query(sql).to_dataframe()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3238b92e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Usuarios call_center"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "34d656ad",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"cc_users = q(f\"\"\"\n",
|
||||
"SELECT u.id, u.name, u.email, u.is_active, u.role_id,\n",
|
||||
" STRING_AGG(CAST(uc.dccenter_id AS STRING)) AS centers\n",
|
||||
"FROM `{PROJECT}.{DATASET}.tpv_authorization_tpvuser` u\n",
|
||||
"JOIN `{PROJECT}.{DATASET}.tpv_authorization_tpvuser_centers` uc\n",
|
||||
" ON u.id = uc.tpvuser_id\n",
|
||||
"WHERE uc.dccenter_id IN (159, 162)\n",
|
||||
"GROUP BY 1,2,3,4,5\n",
|
||||
"ORDER BY u.is_active DESC, u.id\n",
|
||||
"\"\"\")\n",
|
||||
"print(f\"Total usuarios call_center: {len(cc_users)} (activos: {cc_users.is_active.sum()})\")\n",
|
||||
"cc_users.head(20)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9ec102fb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Schema quote — campos clave"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e5f94b52",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"q(f\"\"\"\n",
|
||||
"SELECT column_name, data_type\n",
|
||||
"FROM `{PROJECT}.{DATASET}.INFORMATION_SCHEMA.COLUMNS`\n",
|
||||
"WHERE table_name='tpv_orders_quote'\n",
|
||||
"ORDER BY ordinal_position\n",
|
||||
"\"\"\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "32ffc1d2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Distribución `status` y `accepted` (últimos 90d)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "609022a9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"q(f\"\"\"\n",
|
||||
"SELECT status, accepted, COUNT(*) n\n",
|
||||
"FROM `{PROJECT}.{DATASET}.tpv_orders_quote`\n",
|
||||
"WHERE created_at >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)\n",
|
||||
" AND deleted_at IS NULL\n",
|
||||
"GROUP BY status, accepted\n",
|
||||
"ORDER BY n DESC\n",
|
||||
"\"\"\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "73c85198",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Conversion quote → invoice (mismo order_id)\n",
|
||||
"\n",
|
||||
"Una quote convierte cuando existe `tpv_orders_invoice` con el mismo `order_id`. Ese invoice fija la facturación real (NAV-sync via `nav_id`)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "46378f84",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"q(f\"\"\"\n",
|
||||
"SELECT\n",
|
||||
" COUNT(*) AS quotes,\n",
|
||||
" COUNT(DISTINCT q.order_id) AS distinct_orders,\n",
|
||||
" SUM(CASE WHEN i.id IS NOT NULL THEN 1 ELSE 0 END) AS quotes_con_invoice,\n",
|
||||
" SAFE_DIVIDE(SUM(CASE WHEN i.id IS NOT NULL THEN 1 ELSE 0 END), COUNT(*)) AS conversion_rate\n",
|
||||
"FROM `{PROJECT}.{DATASET}.tpv_orders_quote` q\n",
|
||||
"LEFT JOIN `{PROJECT}.{DATASET}.tpv_orders_invoice` i\n",
|
||||
" ON q.order_id = i.order_id\n",
|
||||
"WHERE q.created_at >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)\n",
|
||||
" AND q.deleted_at IS NULL\n",
|
||||
"\"\"\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "df8b402c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Sanity: quote por call_center vs otro\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "506744d3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"q(f\"\"\"\n",
|
||||
"WITH 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",
|
||||
"SELECT\n",
|
||||
" CASE WHEN cc.user_id IS NOT NULL THEN 'call_center' ELSE 'otro' END AS origen_user,\n",
|
||||
" COUNT(*) AS quotes,\n",
|
||||
" SUM(CASE WHEN i.id IS NOT NULL THEN 1 ELSE 0 END) AS convertidos,\n",
|
||||
" ROUND(SAFE_DIVIDE(SUM(CASE WHEN i.id IS NOT NULL THEN 1 ELSE 0 END), COUNT(*)), 3) AS conv_rate\n",
|
||||
"FROM `{PROJECT}.{DATASET}.tpv_orders_quote` q\n",
|
||||
"LEFT JOIN cc_users cc ON q.created_by_id = cc.user_id\n",
|
||||
"LEFT JOIN `{PROJECT}.{DATASET}.tpv_orders_invoice` i ON q.order_id = i.order_id\n",
|
||||
"WHERE q.created_at >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)\n",
|
||||
" AND q.deleted_at IS NULL\n",
|
||||
"GROUP BY 1\n",
|
||||
"\"\"\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
{
|
||||
"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=\"B−A (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
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b090f346",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 03 — Regeneración de presupuestos\n",
|
||||
"\n",
|
||||
"**Hipótesis:** un mismo cliente (`customer_id` + `vehicle_id`) recibe N quotes antes de convertir. El centro \"regenera\" el presupuesto cuando descarta el de call_center y abre uno nuevo en TPV local.\n",
|
||||
"\n",
|
||||
"Definición operativa de regeneración:\n",
|
||||
"- Existe quote call_center previa (Q0) para el par cliente+vehículo.\n",
|
||||
"- Existe quote posterior (Q1...Qn) en un terminal de centro NO call_center, dentro de ventana D días.\n",
|
||||
"- Q1 puede tener distinto `order_id` que Q0 (regenera de cero) o mismo (reescribe — menos común).\n",
|
||||
"\n",
|
||||
"Métricas pedidas:\n",
|
||||
"1. Centros que MÁS regeneran (cuentan regeneraciones absolutas y % sobre quotes call_center recibidos).\n",
|
||||
"2. Quotes call_center con regeneración vs sin regeneración."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5626c2cd",
|
||||
"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 # ventana de análisis sobre quote call_center\n",
|
||||
"REGEN_WINDOW_DAYS = 60 # ventana para detectar regeneración posterior\n",
|
||||
"\n",
|
||||
"def q(sql):\n",
|
||||
" return bq.query(sql).to_dataframe()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "17cff6ce",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"SQL_REGEN = f\"\"\"\n",
|
||||
"DECLARE win INT64 DEFAULT {WINDOW_DAYS};\n",
|
||||
"DECLARE regen_win INT64 DEFAULT {REGEN_WINDOW_DAYS};\n",
|
||||
"DECLARE t_start TIMESTAMP DEFAULT TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL win 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",
|
||||
"-- Q0: quotes generados por call_center\n",
|
||||
"q0 AS (\n",
|
||||
" SELECT\n",
|
||||
" q.id AS q0_id,\n",
|
||||
" q.order_id AS q0_order,\n",
|
||||
" q.created_at AS q0_ts,\n",
|
||||
" o.customer_id,\n",
|
||||
" o.vehicle_id\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",
|
||||
" WHERE q.created_at >= t_start\n",
|
||||
" AND q.deleted_at IS NULL\n",
|
||||
" AND o.customer_id IS NOT NULL\n",
|
||||
" AND o.vehicle_id IS NOT NULL\n",
|
||||
"),\n",
|
||||
"-- Q1..Qn: quotes posteriores para mismo cliente+vehículo, en centro NO call_center\n",
|
||||
"qN AS (\n",
|
||||
" SELECT\n",
|
||||
" q.id AS qn_id,\n",
|
||||
" q.order_id AS qn_order,\n",
|
||||
" q.created_at AS qn_ts,\n",
|
||||
" q.created_by_id,\n",
|
||||
" o.customer_id,\n",
|
||||
" o.vehicle_id,\n",
|
||||
" o.terminal_id,\n",
|
||||
" t.center_id\n",
|
||||
" FROM `{PROJECT}.{DATASET}.tpv_orders_quote` q\n",
|
||||
" JOIN `{PROJECT}.{DATASET}.tpv_orders_order` o ON q.order_id = o.id\n",
|
||||
" LEFT JOIN `{PROJECT}.{DATASET}.tpv_terminals` t ON o.terminal_id = t.id\n",
|
||||
" WHERE q.deleted_at IS NULL\n",
|
||||
" AND t.center_id IS NOT NULL\n",
|
||||
" AND t.center_id NOT IN (159, 162)\n",
|
||||
"),\n",
|
||||
"-- Empareja Q0 con Q1+ dentro de regen_win días\n",
|
||||
"regen AS (\n",
|
||||
" SELECT\n",
|
||||
" q0.q0_id,\n",
|
||||
" q0.q0_order,\n",
|
||||
" q0.customer_id,\n",
|
||||
" q0.vehicle_id,\n",
|
||||
" qN.qn_id,\n",
|
||||
" qN.qn_order,\n",
|
||||
" qN.center_id AS regen_center,\n",
|
||||
" TIMESTAMP_DIFF(qN.qn_ts, q0.q0_ts, HOUR) / 24 AS dias_entre\n",
|
||||
" FROM q0\n",
|
||||
" JOIN qN\n",
|
||||
" ON q0.customer_id = qN.customer_id\n",
|
||||
" AND q0.vehicle_id = qN.vehicle_id\n",
|
||||
" AND qN.qn_ts > q0.q0_ts\n",
|
||||
" AND qN.qn_ts <= TIMESTAMP_ADD(q0.q0_ts, INTERVAL regen_win DAY)\n",
|
||||
" AND qN.qn_order != q0.q0_order\n",
|
||||
"),\n",
|
||||
"-- Para cada Q0, ¿hay al menos UNA regeneración?\n",
|
||||
"q0_has_regen AS (\n",
|
||||
" SELECT q0_id, COUNT(*) AS regen_count,\n",
|
||||
" MIN(dias_entre) AS dias_a_regen,\n",
|
||||
" APPROX_TOP_COUNT(regen_center, 1)[OFFSET(0)].value AS first_regen_center\n",
|
||||
" FROM regen\n",
|
||||
" GROUP BY q0_id\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"-- Vista por centro: cuántos Q0 regenera cada centro\n",
|
||||
"SELECT\n",
|
||||
" c.id AS center_id,\n",
|
||||
" c.name AS center_name,\n",
|
||||
" COUNT(DISTINCT r.q0_id) AS q0_regenerados_aqui,\n",
|
||||
" COUNT(*) AS regen_events,\n",
|
||||
" ROUND(AVG(r.dias_entre), 1) AS dias_avg_regen\n",
|
||||
"FROM regen r\n",
|
||||
"JOIN `{PROJECT}.{DATASET}.centers` c ON r.regen_center = c.id\n",
|
||||
"GROUP BY c.id, c.name\n",
|
||||
"ORDER BY q0_regenerados_aqui DESC\n",
|
||||
"LIMIT 30\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"df_centros = q(SQL_REGEN)\n",
|
||||
"print(f\"Centros con eventos de regeneración: {len(df_centros)}\")\n",
|
||||
"df_centros.head(30)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "43add847",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Totales: Q0 con regeneración vs sin regeneración"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "736158ba",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"SQL_TOT = f\"\"\"\n",
|
||||
"DECLARE win INT64 DEFAULT {WINDOW_DAYS};\n",
|
||||
"DECLARE regen_win INT64 DEFAULT {REGEN_WINDOW_DAYS};\n",
|
||||
"DECLARE t_start TIMESTAMP DEFAULT TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL win 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",
|
||||
"q0 AS (\n",
|
||||
" SELECT q.id AS q0_id, q.order_id AS q0_order, q.created_at AS q0_ts,\n",
|
||||
" o.customer_id, o.vehicle_id\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",
|
||||
" WHERE q.created_at >= t_start AND q.deleted_at IS NULL\n",
|
||||
" AND o.customer_id IS NOT NULL AND o.vehicle_id IS NOT NULL\n",
|
||||
"),\n",
|
||||
"qN AS (\n",
|
||||
" SELECT q.order_id AS qn_order, q.created_at AS qn_ts,\n",
|
||||
" o.customer_id, o.vehicle_id, t.center_id\n",
|
||||
" FROM `{PROJECT}.{DATASET}.tpv_orders_quote` q\n",
|
||||
" JOIN `{PROJECT}.{DATASET}.tpv_orders_order` o ON q.order_id = o.id\n",
|
||||
" LEFT JOIN `{PROJECT}.{DATASET}.tpv_terminals` t ON o.terminal_id = t.id\n",
|
||||
" WHERE q.deleted_at IS NULL\n",
|
||||
" AND t.center_id IS NOT NULL AND t.center_id NOT IN (159,162)\n",
|
||||
"),\n",
|
||||
"regen AS (\n",
|
||||
" SELECT DISTINCT q0.q0_id\n",
|
||||
" FROM q0\n",
|
||||
" JOIN qN\n",
|
||||
" ON q0.customer_id = qN.customer_id\n",
|
||||
" AND q0.vehicle_id = qN.vehicle_id\n",
|
||||
" AND qN.qn_ts > q0.q0_ts\n",
|
||||
" AND qN.qn_ts <= TIMESTAMP_ADD(q0.q0_ts, INTERVAL regen_win DAY)\n",
|
||||
" AND qN.qn_order != q0.q0_order\n",
|
||||
")\n",
|
||||
"SELECT\n",
|
||||
" COUNT(*) AS q0_total,\n",
|
||||
" COUNT(DISTINCT r.q0_id) AS q0_regenerados,\n",
|
||||
" COUNT(*) - COUNT(DISTINCT r.q0_id) AS q0_no_regenerados,\n",
|
||||
" ROUND(SAFE_DIVIDE(COUNT(DISTINCT r.q0_id), COUNT(*)), 4) AS pct_regenerados\n",
|
||||
"FROM q0\n",
|
||||
"LEFT JOIN regen r USING (q0_id)\n",
|
||||
"\"\"\"\n",
|
||||
"q(SQL_TOT)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c183a653",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Distribución días hasta regeneración"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "aab452ca",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"SQL_DIAS = f\"\"\"\n",
|
||||
"DECLARE win INT64 DEFAULT {WINDOW_DAYS};\n",
|
||||
"DECLARE regen_win INT64 DEFAULT {REGEN_WINDOW_DAYS};\n",
|
||||
"DECLARE t_start TIMESTAMP DEFAULT TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL win 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",
|
||||
"q0 AS (\n",
|
||||
" SELECT q.id AS q0_id, q.order_id AS q0_order, q.created_at AS q0_ts,\n",
|
||||
" o.customer_id, o.vehicle_id\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",
|
||||
" WHERE q.created_at >= t_start AND q.deleted_at IS NULL\n",
|
||||
" AND o.customer_id IS NOT NULL AND o.vehicle_id IS NOT NULL\n",
|
||||
"),\n",
|
||||
"qN AS (\n",
|
||||
" SELECT q.order_id AS qn_order, q.created_at AS qn_ts,\n",
|
||||
" o.customer_id, o.vehicle_id, t.center_id\n",
|
||||
" FROM `{PROJECT}.{DATASET}.tpv_orders_quote` q\n",
|
||||
" JOIN `{PROJECT}.{DATASET}.tpv_orders_order` o ON q.order_id = o.id\n",
|
||||
" LEFT JOIN `{PROJECT}.{DATASET}.tpv_terminals` t ON o.terminal_id = t.id\n",
|
||||
" WHERE q.deleted_at IS NULL\n",
|
||||
" AND t.center_id IS NOT NULL AND t.center_id NOT IN (159,162)\n",
|
||||
")\n",
|
||||
"SELECT\n",
|
||||
" TIMESTAMP_DIFF(qN.qn_ts, q0.q0_ts, HOUR)/24 AS dias_entre\n",
|
||||
"FROM q0\n",
|
||||
"JOIN qN\n",
|
||||
" ON q0.customer_id = qN.customer_id\n",
|
||||
" AND q0.vehicle_id = qN.vehicle_id\n",
|
||||
" AND qN.qn_ts > q0.q0_ts\n",
|
||||
" AND qN.qn_ts <= TIMESTAMP_ADD(q0.q0_ts, INTERVAL regen_win DAY)\n",
|
||||
" AND qN.qn_order != q0.q0_order\n",
|
||||
"\"\"\"\n",
|
||||
"dias = q(SQL_DIAS)\n",
|
||||
"print(dias.describe())\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"dias[\"dias_entre\"].clip(upper=60).hist(bins=30)\n",
|
||||
"plt.xlabel(\"Días entre Q0 (call_center) y Q1 (centro)\")\n",
|
||||
"plt.ylabel(\"# eventos\")\n",
|
||||
"plt.title(\"Distribución de regeneración temporal\")\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0feaa9c1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Cruzar regeneración con conversión a factura\n",
|
||||
"\n",
|
||||
"¿Los Q0 regenerados convierten MENOS que los Q0 no regenerados? (Hipótesis: el cliente prefiere lo que negocia el centro)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "376502d8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"SQL_CONV = f\"\"\"\n",
|
||||
"DECLARE win INT64 DEFAULT {WINDOW_DAYS};\n",
|
||||
"DECLARE regen_win INT64 DEFAULT {REGEN_WINDOW_DAYS};\n",
|
||||
"DECLARE t_start TIMESTAMP DEFAULT TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL win 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",
|
||||
"q0 AS (\n",
|
||||
" SELECT q.id AS q0_id, q.order_id AS q0_order, q.created_at AS q0_ts,\n",
|
||||
" o.customer_id, o.vehicle_id\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",
|
||||
" WHERE q.created_at >= t_start AND q.deleted_at IS NULL\n",
|
||||
" AND o.customer_id IS NOT NULL AND o.vehicle_id IS NOT NULL\n",
|
||||
"),\n",
|
||||
"qN AS (\n",
|
||||
" SELECT q.order_id AS qn_order, q.created_at AS qn_ts,\n",
|
||||
" o.customer_id, o.vehicle_id, t.center_id\n",
|
||||
" FROM `{PROJECT}.{DATASET}.tpv_orders_quote` q\n",
|
||||
" JOIN `{PROJECT}.{DATASET}.tpv_orders_order` o ON q.order_id = o.id\n",
|
||||
" LEFT JOIN `{PROJECT}.{DATASET}.tpv_terminals` t ON o.terminal_id = t.id\n",
|
||||
" WHERE q.deleted_at IS NULL\n",
|
||||
" AND t.center_id IS NOT NULL AND t.center_id NOT IN (159,162)\n",
|
||||
"),\n",
|
||||
"regen AS (\n",
|
||||
" SELECT DISTINCT q0.q0_id\n",
|
||||
" FROM q0\n",
|
||||
" JOIN qN\n",
|
||||
" ON q0.customer_id = qN.customer_id\n",
|
||||
" AND q0.vehicle_id = qN.vehicle_id\n",
|
||||
" AND qN.qn_ts > q0.q0_ts\n",
|
||||
" AND qN.qn_ts <= TIMESTAMP_ADD(q0.q0_ts, INTERVAL regen_win DAY)\n",
|
||||
" AND qN.qn_order != q0.q0_order\n",
|
||||
"),\n",
|
||||
"q0_inv AS (\n",
|
||||
" SELECT q0.q0_id,\n",
|
||||
" CASE WHEN i.id IS NOT NULL THEN 1 ELSE 0 END AS q0_factura\n",
|
||||
" FROM q0\n",
|
||||
" LEFT JOIN `{PROJECT}.{DATASET}.tpv_orders_invoice` i ON i.order_id = q0.q0_order\n",
|
||||
")\n",
|
||||
"SELECT\n",
|
||||
" CASE WHEN r.q0_id IS NOT NULL THEN 'regenerado' ELSE 'no_regenerado' END AS bucket,\n",
|
||||
" COUNT(*) AS q0_total,\n",
|
||||
" SUM(qi.q0_factura) AS q0_convertido_propio,\n",
|
||||
" ROUND(SAFE_DIVIDE(SUM(qi.q0_factura), COUNT(*)), 4) AS conv_q0_propio\n",
|
||||
"FROM q0_inv qi\n",
|
||||
"LEFT JOIN regen r USING (q0_id)\n",
|
||||
"GROUP BY bucket\n",
|
||||
"\"\"\"\n",
|
||||
"q(SQL_CONV)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
Reference in New Issue
Block a user