chore: auto-commit (26 archivos)

- python/functions/bigquery/bq_auth.md
- python/functions/bigquery/bq_load_from_file.md
- python/functions/bigquery/bq_load_from_gcs.md
- python/functions/bigquery/client.py
- python/functions/bigquery/queries.py
- python/functions/datascience/__init__.py
- python/functions/datascience/decode_qr_image.py
- python/functions/datascience/load_bq_table_to_duckdb.md
- python/functions/datascience/load_bq_table_to_duckdb.py
- python/functions/pipelines/profile_bq_table.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 19:00:13 +02:00
parent 2ebc9efeb2
commit 5a4f82cf76
26 changed files with 2573 additions and 94 deletions
@@ -0,0 +1,103 @@
---
name: run_sales_forecast
kind: pipeline
lang: py
domain: pipelines
purity: impure
version: "1.1.0"
signature: "def run_sales_forecast(as_of: str = '', horizon: int = 7, model: str = 'baseline_v1', author: str = 'egutierrez', dry_run: bool = False) -> dict"
description: "Forecast diario de ventas Aurgi (dia x centro x subcategoria CGQ) escrito en BigQuery autingo-159109.sales_forecast.predictions, en una sola llamada. Compone funciones del registry: bq_auth(drop_quota_project=True) para el cliente sin quota project ajeno, bq_query para leer la historia agregada del mart bi_ventas_mart.base_margenes_aa (18 semanas, venta_n saneado) y ejecutar el DELETE de idempotencia, forecast_seasonal_median (modelo PURO mediana estacional + tendencia acotada) para generar todas las predicciones, y bq_load_from_file para cargar el JSONL a la tabla de predicciones. Historia utilizable hasta as_of-1 (el dia as_of esta parcial cuando corre el cron a las 21:00); predice as_of+1..as_of+horizon; run_date=as_of. Solo predice series activas (venta>0 en las ultimas 8 semanas). Idempotente por (run_date, model, author). --dry-run no escribe."
tags: [forecast, bigquery, sales, aurgi, pipeline, launcher]
uses_functions:
- forecast_seasonal_median_py_datascience
- bq_auth_py_infra
- bq_query_py_infra
- bq_load_from_file_py_infra
uses_types: []
returns: []
returns_optional: false
error_type: error_go_core
imports: [google-cloud-bigquery]
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/pipelines/run_sales_forecast.py"
params:
- name: as_of
desc: "fecha de corte 'YYYY-MM-DD' (dia de la corrida). Vacio (DEFAULT) = hoy. La historia utilizable llega hasta as_of-1 dia (el dia as_of esta parcial en el cron 21:00); se predice as_of+1..as_of+horizon; run_date=as_of"
- name: horizon
desc: "numero de dias futuros a predecir a partir de as_of+1. Default 7"
- name: model
desc: "etiqueta del modelo escrita en la columna model de cada fila. Default 'baseline_v1'. Forma parte de la clave de idempotencia"
- name: author
desc: "autor de la corrida (columna author). Default 'egutierrez'. Forma parte de la clave de idempotencia"
- name: dry_run
desc: "si True no escribe en BigQuery (ni DELETE ni load): devuelve el resumen + una muestra de 5 filas. Default False"
output: "dict dict-no-throw. En exito {status:'ok', run_date, series:N (series activas), rows:N (filas predichas), model, author, rows_loaded, job_id}; con dry_run=True incluye sample:[5 filas] y omite rows_loaded/job_id. En error {status:'error', error, stage}. Por stdout imprime el JSON del resumen; exit 0 si ok, 1 si error"
---
## Ejemplo
```bash
# Corrida real (cron 21:00): predice los 7 dias siguientes a hoy y carga a BigQuery.
./fn run run_sales_forecast
# Fecha de corte y horizonte explicitos, sin escribir (revisar la muestra):
./fn run run_sales_forecast --as-of 2026-07-01 --horizon 7 --dry-run
# Modelo alternativo (clave de idempotencia distinta: no pisa baseline_v1):
./fn run run_sales_forecast --model baseline_v2 --author egutierrez
```
```python
# Uso programatico (venv del proyecto, PYTHONPATH=python/functions):
from pipelines.run_sales_forecast import run_sales_forecast
r = run_sales_forecast(as_of="2026-07-01", horizon=7, dry_run=True)
print(r["series"], "series activas,", r["rows"], "filas")
for row in r["sample"]:
print(row["forecast_date"], row["center_id"], row["subcat_cgq"], row["y_pred"])
```
## Cuando usarla
Cuando quieras (re)generar el forecast diario de ventas Aurgi por centro y
subcategoria CGQ y dejarlo en `autingo-159109.sales_forecast.predictions` en una
sola llamada. Es el pipeline que dispara el cron nocturno (21:00): lee la historia
del mart, aplica el baseline estacional, y carga las predicciones de forma
idempotente. Usa `--dry-run` para inspeccionar la muestra antes de escribir, o
para probar tras un cambio en el mart o en el modelo. Cambia `--model` para probar
una variante sin pisar las predicciones del modelo actual (la clave de
idempotencia es run_date + model + author).
## Gotchas
- Impura: requiere ADC de BigQuery configurado (`gcloud auth application-default
login`) con acceso a `autingo-159109`. Usa `bq_auth(drop_quota_project=True)`
para descartar el quota project del ADC del usuario `egutierrez` y evitar el
`403 USER_PROJECT_DENIED` (gotcha conocido del repo).
- Escribe en produccion: en modo real hace `DELETE` de las predicciones previas de
`(run_date, model, author)` y luego carga (WRITE_APPEND). Es idempotente para esa
combinacion: re-ejecutar la misma corrida no duplica. Cambiar `model` o `author`
crea un conjunto de predicciones paralelo. Usa `--dry-run` si solo quieres mirar.
- La tabla `sales_forecast.predictions` debe existir con schema fijo y con las
columnas exactas que emite el pipeline: `run_ts` (TIMESTAMP), `run_date` (DATE),
`forecast_date` (DATE), `lag_days` (INT64), `center_id` (STRING), `center_name`
(STRING), `ambito` (STRING), `subcat_cgq` (STRING), `model` (STRING), `author`
(STRING), `y_pred` (FLOAT64). El load usa `autodetect=False`: los nombres del
JSONL deben coincidir con los de la tabla o el load falla.
- `center_id` se emite como STRING (str(idCentro)); `subcat_cgq` toma el valor de
la columna `subcat_cqq` del mart (el nombre difiere entre origen y destino a
proposito). center_name/ambito son los ultimos conocidos por serie (fecha maxima).
- Historia hasta as_of-1: el dia `as_of` NO entra en la historia (esta parcial en
el cron de las 21:00). Si necesitas incluir el dia en curso, pasa `--as-of` con
el dia siguiente.
- Solo predice series con venta > 0 en las ultimas 8 semanas: las series muertas se
omiten (no aparecen en la tabla). `series` en la salida cuenta las activas.
- Guarda 18 semanas de historia del mart: cubre la ventana estacional (8 semanas
del mismo dia) mas la tendencia (4+4 semanas) con margen. venta_n se filtra
`ABS < 1e9` para descartar las filas veneno del mart.
## Capability growth log
- v1.1.0 (2026-07-02) — añade paso 9: refresh de `sales_forecast.actuals_daily` (tabla física de venta real, ventana móvil de 10 días) tras cargar las predicciones; `forecast_eval` y el dashboard de competición comparan contra ella.