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,94 @@
---
name: forecast_seasonal_median
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: pure
signature: "def forecast_seasonal_median(history: list[dict], horizon_dates: list[str], as_of: str, dow_weeks: int = 8, trend_recent_weeks: int = 4, trend_clip: tuple = (0.5, 2.0)) -> list[dict]"
description: "Forecast diario por mediana estacional (mismo dia de semana) mas factor de tendencia acotado, para una o varias series temporales. Base estacional = mediana del valor en las ultimas dow_weeks fechas con el mismo dia de semana que la fecha objetivo (dias ausentes = 0, para series intermitentes). Factor de tendencia por serie = razon de la suma de las ultimas trend_recent_weeks semanas frente a las trend_recent_weeks anteriores, clipped a trend_clip. y_pred = max(0, base * factor). Funcion pura y determinista (solo stdlib, sin I/O ni datetime.now). Nucleo del forecast de ventas diarias Aurgi (dia x centro x subcategoria CGQ)."
tags: [forecast, bigquery, timeseries, seasonal, median, baseline, sales, aurgi, python]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
- name: history
desc: "lista de observaciones {series_id: str, date: 'YYYY-MM-DD', value: float}. Filas duplicadas (misma serie+fecha) se suman. Los dias sin fila dentro de las ventanas cuentan como valor 0 (series intermitentes: sin fila = sin venta)"
- name: horizon_dates
desc: "fechas futuras a predecir, strings ISO 'YYYY-MM-DD'. Tipicamente as_of+1..as_of+horizon"
- name: as_of
desc: "fecha de corte 'YYYY-MM-DD': ultimo dia de historia utilizable, inclusive. Todas las ventanas se calculan hacia atras desde aqui"
- name: dow_weeks
desc: "numero de fechas del mismo dia de semana que la objetivo a promediar (mediana) para la base estacional. Default 8 (8 semanas)"
- name: trend_recent_weeks
desc: "tamano en semanas de cada una de las dos ventanas de tendencia (reciente y anterior). Default 4: compara 4 semanas recientes vs las 4 previas"
- name: trend_clip
desc: "tupla (min, max) al que se acota el factor de tendencia. Default (0.5, 2.0): la prediccion no puede caer a menos de la mitad ni superar el doble por tendencia"
output: "list[dict]: una fila {series_id: str, date: str, y_pred: float} por cada serie presente en history y cada fecha de horizon_dates. Ordenada por series_id (asc) y luego por el orden de horizon_dates. y_pred siempre >= 0.0"
tested: true
tests:
- "serie regular con patron semanal claro da la mediana correcta"
- "serie intermitente: los dias ausentes cuentan como 0 en la mediana"
- "serie con tendencia creciente aplica factor >1 acotado a trend_clip"
- "sin datos en la ventana anterior, el factor de tendencia es 1.0"
- "horizon de 7 dias produce una fila por serie y fecha, ordenadas"
test_file_path: "python/functions/datascience/forecast_seasonal_median_test.py"
file_path: "python/functions/datascience/forecast_seasonal_median.py"
---
## Ejemplo
```python
from datascience import forecast_seasonal_median
# Historia diaria por serie (centro|subcategoria). Sin fila = sin venta = 0.
history = [
{"series_id": "12|NEUMATICOS", "date": "2026-06-23", "value": 1450.0},
{"series_id": "12|NEUMATICOS", "date": "2026-06-16", "value": 1380.0},
{"series_id": "12|NEUMATICOS", "date": "2026-06-09", "value": 1500.0},
# ... mas historia (idealmente >= 8 semanas para la base estacional) ...
]
# as_of = ultimo dia cerrado; predice los 7 dias siguientes.
horizon = ["2026-06-30", "2026-07-01", "2026-07-02", "2026-07-03",
"2026-07-04", "2026-07-05", "2026-07-06"]
preds = forecast_seasonal_median(history, horizon, as_of="2026-06-29")
for p in preds:
print(p["series_id"], p["date"], round(p["y_pred"], 2))
```
## Cuando usarla
Cuando necesites un baseline de forecast diario robusto y explicable para series
con estacionalidad semanal fuerte (ventas por dia de la semana) y posibles huecos
(dias sin venta). Es el nucleo puro del pipeline `run_sales_forecast`: se llama una
vez con toda la historia agregada y devuelve todas las predicciones de golpe.
Usala como punto de partida antes de modelos mas pesados (Prophet, ARIMA, gradient
boosting): captura el patron dia-de-semana + una correccion de tendencia acotada
sin dependencias externas ni entrenamiento. Ideal para muchas series a la vez
(miles de pares centro x subcategoria) donde entrenar un modelo por serie no
compensa.
## Notas
- Funcion pura y determinista: no hace I/O, no llama `datetime.now()`; el corte
temporal siempre es el argumento `as_of` explicito. Solo stdlib (datetime,
statistics), sin numpy ni pandas.
- La base estacional toma las fechas EXACTAS del calendario: la mas reciente
<= as_of con el mismo dia de semana que la objetivo, y de ahi 7 dias hacia atras
por punto (hasta `dow_weeks` puntos). Una fecha ausente en `history` cuenta como
0, por lo que la mediana refleja bien las series intermitentes.
- El factor de tendencia se calcula UNA vez por serie (no depende de la fecha
objetivo) como razon de sumas de dos ventanas contiguas de `trend_recent_weeks`
semanas. Denominador 0 => factor 1.0 (evita division por cero y no infla series
que arrancan). El clip a `trend_clip` evita que un pico reciente dispare la
prediccion.
- `y_pred = max(0.0, base * factor)`: nunca negativo. No modela festivos ni eventos
puntuales; para eso se necesitaria una capa de calendario adicional.
- Para que la base estacional sea fiable conviene aportar >= `dow_weeks` semanas de
historia. Con menos historia, los puntos ausentes (=0) empujan la mediana hacia
abajo.