--- 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.