Módulo 6: Accumulating And Cumulative Patterns

Mini-proyecto: funnel y actividad acumulativa de Kiosko

Descripción

Este proyecto cierra el módulo integrando las seis piezas anteriores: el mecanismo mínimo de accumulating snapshot verificado en una sola sesión (lección 2), fact_sessions completo con el mapeo sesión-tienda declarado (lección 3), el mismo hecho reconstruido evento por evento con cero diferencias (lección 4), el cumulative table design verificado en tres días de una tienda (lección 5), fact_store_activity completo con recorte de arreglos y doble verificación (lección 6), y el análisis de activos de 7 y 30 días (lección 7). Lo que falta es reunir todo en un solo flujo, verificado con assert en cada paso, sobre los mismos fact_orders (40 filas) y events (32 filas, canónicos de dbt-analytics-engineering-guide) que este módulo usó desde la lección 1.

El proyecto tiene cinco partes. Primero, reconstruyes fact_orders y events, heredados sin cambios. Segundo, construyes fact_sessions con el mapeo sesión-tienda declarado explícitamente. Tercero, analizas el funnel de conversión, por etapa y por tienda. Cuarto, construyes fact_store_activity con las ventanas de 7 y 30 días. Quinto, documentas todo en ACCUMULATING_AND_CUMULATIVE_SUMMARY, la estructura formal que cierra el módulo.

Conexión con el módulo. Este proyecto no introduce ningún concepto nuevo — es la integración final de las siete lecciones anteriores, empaquetada como ACCUMULATING_AND_CUMULATIVE_SUMMARY, la estructura que el módulo 7 de esta guía puede citar sin volver a reconstruir la evidencia desde cero.

Una analogía: el reporte semanal completo, dos hechos distintos, un solo cierre

Cada lección de este módulo resolvió una pieza por separado: cómo sellar una guía de envío sin reimprimirla, cómo escalar ese sello a diecisiete sesiones, cómo verificar que el mecanismo real (evento por evento) coincide con el atajo agregado, cómo hacer crecer un resumen un día a la vez sin releer el año completo, y cómo confirmar que ese resumen dice la verdad. Este proyecto es el cierre semanal: los dos hechos —fact_sessions y fact_store_activity— construidos de punta a punta, con cada número verificado por un assert antes de pasar al siguiente, exactamente el rigor que un equipo de datos real aplicaría antes de entregarle a un gerente de Kiosko un reporte que mezcla "cómo convierten nuestras sesiones" con "qué tan activa está cada tienda".

El material: todo lo que este módulo construyó, en un solo flujo

Necesitas, en la misma carpeta: kiosko.py, raw_orders.py (idénticos a los módulos anteriores) y events.py (los 32 eventos canónicos, introducidos en la lección 3 de este módulo). No necesitas ningún archivo adicional — el mapeo sesión-tienda y el cálculo de actividad por tienda se definen directamente en el script de este proyecto, igual que en los proyectos anteriores.

La solución de referencia, verificada

Parte 1 — Reconstruir fact_orders y events, heredados sin cambios

# kiosko_accumulating_cumulative_project.py -- mini-proyecto de cierre del modulo 6
from datetime import datetime
import duckdb

from kiosko import DIM_PRODUCT, DIM_STORE, Order, transform_fact_orders
from raw_orders import RAW_ORDERS
from events import RAW_EVENTS

print("=== Kiosko: funnel y actividad acumulativa, entrega final del modulo 6 ===\n")

con = duckdb.connect()

orders = [
    Order(order_id=r[0], store_id=r[1], product_id=r[2], quantity=r[3],
          unit_price=r[4], order_ts=datetime.fromisoformat(r[5]))
    for r in RAW_ORDERS
]
fact_orders = transform_fact_orders(orders, DIM_STORE, DIM_PRODUCT)
con.execute("""
    CREATE TABLE fact_orders (
        order_id VARCHAR, store_id VARCHAR, product_id VARCHAR,
        quantity INTEGER, unit_price DOUBLE, revenue DOUBLE, order_ts TIMESTAMP
    )
""")
con.executemany(
    "INSERT INTO fact_orders VALUES (?, ?, ?, ?, ?, ?, ?)",
    [(r["order_id"], r["store_id"], r["product_id"], r["quantity"],
      r["unit_price"], r["revenue"], r["order_ts"]) for r in fact_orders],
)

con.execute("CREATE TABLE events (event_id VARCHAR, event_type VARCHAR, session_id VARCHAR, event_ts TIMESTAMP)")
con.executemany(
    "INSERT INTO events VALUES (?, ?, ?, ?)",
    [(r[0], r[1], r[2], datetime.fromisoformat(r[3])) for r in RAW_EVENTS],
)

total_orders = con.sql("SELECT COUNT(*) FROM fact_orders").fetchone()[0]
total_events = con.sql("SELECT COUNT(*) FROM events").fetchone()[0]

print("Parte 1 -- fact_orders y events, heredados sin cambios")
print(f"  fact_orders   {total_orders:3} filas")
print(f"  events        {total_events:3} filas (canonicos de dbt-analytics-engineering-guide)")
assert total_orders == 40 and total_events == 32

Esta primera parte no construye nada nuevo — reconstruye, exactamente como en cada lección de este módulo, las dos tablas de entrada fijas que sostienen todo lo que sigue.

Parte 2 — fact_sessions, con el mapeo sesión-tienda declarado

STORE_ROTATION = ["S01", "S02", "S03"]


def store_for_session(session_id: str) -> str:
    """Mapeo fijo, deterministico, aditivo: rota S01/S02/S03 segun el numero
    de secuencia de la sesion. events (source canonico de dbt-analytics-
    engineering-guide) nunca declara store_id -- esta guia lo agrega."""
    session_number = int(session_id.split("-")[1])
    return STORE_ROTATION[(session_number - 1) % 3]


ALL_SESSIONS = [f"SESS-{n:02d}" for n in range(1, 18)]
con.execute("CREATE TABLE session_store_map (session_id VARCHAR, store_id VARCHAR)")
con.executemany("INSERT INTO session_store_map VALUES (?, ?)", [(sid, store_for_session(sid)) for sid in ALL_SESSIONS])

con.execute("""
    CREATE TABLE fact_sessions AS
    SELECT
        e.session_id, m.store_id, MIN(CAST(e.event_ts AS DATE)) AS session_date,
        MAX(CASE WHEN e.event_type = 'page_view'   THEN e.event_ts END) AS view_ts,
        MAX(CASE WHEN e.event_type = 'add_to_cart' THEN e.event_ts END) AS add_to_cart_ts,
        MAX(CASE WHEN e.event_type = 'purchase'    THEN e.event_ts END) AS purchase_ts,
        MAX(CASE WHEN e.event_type = 'purchase' THEN true ELSE false END) AS is_converted
    FROM events e JOIN session_store_map m ON e.session_id = m.session_id
    GROUP BY e.session_id, m.store_id
""")

total_sessions = con.sql("SELECT COUNT(*) FROM fact_sessions").fetchone()[0]
print("\nParte 2 -- fact_sessions, con el mapeo sesion-tienda declarado")
print(f"  fact_sessions {total_sessions:3} filas (17 sesiones distintas de events)")
print(f"  mapeo: SESS-01->S01, SESS-02->S02, SESS-03->S03, SESS-04->S01, ... (rotacion S01/S02/S03)")
assert total_sessions == 17

Parte 3 — El funnel de conversión, por etapa y por tienda

funnel = con.sql("""
    SELECT COUNT(*) AS total, COUNT(view_ts) AS viewed,
           COUNT(add_to_cart_ts) AS added_to_cart, COUNT(purchase_ts) AS purchased
    FROM fact_sessions
""").fetchone()

conversion_pct = con.sql("SELECT ROUND(100.0 * COUNT(purchase_ts) / COUNT(*), 1) FROM fact_sessions").fetchone()[0]

print("\nParte 3 -- el funnel de conversion de Kiosko")
print(f"  total sesiones: {funnel[0]}, vieron: {funnel[1]}, agregaron al carrito: {funnel[2]}, compraron: {funnel[3]}")
print(f"  conversion total: {conversion_pct}%")
assert funnel == (17, 17, 9, 6) and conversion_pct == 35.3

by_store = con.sql("""
    SELECT store_id, COUNT(*) AS sessions, COUNT(purchase_ts) AS purchases,
           ROUND(100.0 * COUNT(purchase_ts) / COUNT(*), 1) AS conversion_pct
    FROM fact_sessions GROUP BY store_id ORDER BY store_id
""").fetchall()
print("  por tienda:")
for store_id, sessions, purchases, pct in by_store:
    print(f"    {store_id}: {sessions} sesiones, {purchases} compras, {pct}% conversion")

Qué esperar (Partes 1 a 3).

=== Kiosko: funnel y actividad acumulativa, entrega final del modulo 6 ===

Parte 1 -- fact_orders y events, heredados sin cambios
  fact_orders    40 filas
  events         32 filas (canonicos de dbt-analytics-engineering-guide)

Parte 2 -- fact_sessions, con el mapeo sesion-tienda declarado
  fact_sessions  17 filas (17 sesiones distintas de events)
  mapeo: SESS-01->S01, SESS-02->S02, SESS-03->S03, SESS-04->S01, ... (rotacion S01/S02/S03)

Parte 3 -- el funnel de conversion de Kiosko
  total sesiones: 17, vieron: 17, agregaron al carrito: 9, compraron: 6
  conversion total: 35.3%
  por tienda:
    S01: 6 sesiones, 3 compras, 50.0% conversion
    S02: 6 sesiones, 2 compras, 33.3% conversion
    S03: 5 sesiones, 1 compras, 20.0% conversion

Parte 4 — fact_store_activity, con las ventanas de 7 y 30 días

con.execute("""
    CREATE TABLE fact_store_activity (
        store_id VARCHAR, activity_date DATE, daily_revenue DOUBLE,
        revenue_array_7d DOUBLE[], active_days_7d INTEGER,
        revenue_array_30d DOUBLE[], active_days_30d INTEGER
    )
""")

DAYS = ["2026-08-03", "2026-08-04", "2026-08-05", "2026-08-06", "2026-08-07", "2026-08-08", "2026-08-09"]

for day in DAYS:
    for store_id in STORE_ROTATION:
        daily_revenue = con.sql(f"""
            SELECT COALESCE(ROUND(SUM(revenue), 2), 0.0) FROM fact_orders
            WHERE store_id = '{store_id}' AND CAST(order_ts AS DATE) = DATE '{day}'
        """).fetchone()[0]
        prev = con.sql(f"""
            SELECT revenue_array_7d, revenue_array_30d FROM fact_store_activity
            WHERE store_id = '{store_id}' ORDER BY activity_date DESC LIMIT 1
        """).fetchone()
        if prev is None:
            new_7d, new_30d = [daily_revenue], [daily_revenue]
        else:
            new_7d = ([daily_revenue] + list(prev[0]))[:7]
            new_30d = ([daily_revenue] + list(prev[1]))[:30]
        active_7d = sum(1 for v in new_7d if v > 0)
        active_30d = sum(1 for v in new_30d if v > 0)
        con.execute(
            "INSERT INTO fact_store_activity VALUES (?, ?, ?, ?, ?, ?, ?)",
            (store_id, day, daily_revenue, new_7d, active_7d, new_30d, active_30d),
        )

total_activity_rows = con.sql("SELECT COUNT(*) FROM fact_store_activity").fetchone()[0]
print(f"\nParte 4 -- fact_store_activity, 3 tiendas x 7 dias")
print(f"  fact_store_activity {total_activity_rows:3} filas")
assert total_activity_rows == 21

snapshot = con.sql("""
    SELECT store_id, ROUND(list_sum(revenue_array_7d), 2) AS revenue_7d, active_days_7d, active_days_30d
    FROM fact_store_activity WHERE activity_date = DATE '2026-08-09' ORDER BY store_id
""").fetchall()
print("  snapshot del 2026-08-09 (ultimo dia disponible):")
for store_id, revenue_7d, active_7d, active_30d in snapshot:
    print(f"    {store_id}: revenue_7d={revenue_7d}, active_days_7d={active_7d}, active_days_30d={active_30d}")

weekly_revenue = con.sql("SELECT store_id, ROUND(SUM(revenue), 2) FROM fact_orders GROUP BY store_id ORDER BY store_id").fetchall()
for (store_id, revenue_7d, _, _), (_, weekly) in zip(snapshot, weekly_revenue):
    assert revenue_7d == weekly, f"revenue_7d de {store_id} no coincide con el revenue semanal conocido"
print("  Verificacion OK: list_sum(revenue_array_7d) == revenue semanal de fact_orders, para las 3 tiendas")

Qué esperar (Parte 4).

Parte 4 -- fact_store_activity, 3 tiendas x 7 dias
  fact_store_activity  21 filas
  snapshot del 2026-08-09 (ultimo dia disponible):
    S01: revenue_7d=38.3, active_days_7d=7, active_days_30d=7
    S02: revenue_7d=38.8, active_days_7d=7, active_days_30d=7
    S03: revenue_7d=29.05, active_days_7d=6, active_days_30d=6
  Verificacion OK: list_sum(revenue_array_7d) == revenue semanal de fact_orders, para las 3 tiendas

Parte 5 — Documentar como una estructura formal

ACCUMULATING_AND_CUMULATIVE_SUMMARY = {
    "fact_orders_rows": total_orders,
    "events_rows": total_events,
    "session_to_store_mapping": "rotacion fija S01/S02/S03 por numero de secuencia de sesion",
    "fact_sessions_rows": total_sessions,
    "funnel_total_viewed_cart_purchased": list(funnel),
    "funnel_conversion_pct": conversion_pct,
    "funnel_conversion_by_store": {store_id: pct for store_id, _, _, pct in by_store},
    "fact_store_activity_rows": total_activity_rows,
    "snapshot_2026_08_09": {
        store_id: {"revenue_7d": revenue_7d, "active_days_7d": active_7d, "active_days_30d": active_30d}
        for store_id, revenue_7d, active_7d, active_30d in snapshot
    },
    "revenue_7d_matches_weekly_revenue": True,
}
print("\nParte 5 -- la declaracion formal: ACCUMULATING_AND_CUMULATIVE_SUMMARY")
for key, value in ACCUMULATING_AND_CUMULATIVE_SUMMARY.items():
    print(f"  {key}: {value}")

Qué esperar. Al correr python3 kiosko_accumulating_cumulative_project.py completo (las cinco partes juntas), la salida termina exactamente así:

Parte 5 -- la declaracion formal: ACCUMULATING_AND_CUMULATIVE_SUMMARY
  fact_orders_rows: 40
  events_rows: 32
  session_to_store_mapping: rotacion fija S01/S02/S03 por numero de secuencia de sesion
  fact_sessions_rows: 17
  funnel_total_viewed_cart_purchased: [17, 17, 9, 6]
  funnel_conversion_pct: 35.3
  funnel_conversion_by_store: {'S01': 50.0, 'S02': 33.3, 'S03': 20.0}
  fact_store_activity_rows: 21
  snapshot_2026_08_09: {'S01': {'revenue_7d': 38.3, 'active_days_7d': 7, 'active_days_30d': 7}, 'S02': {'revenue_7d': 38.8, 'active_days_7d': 7, 'active_days_30d': 7}, 'S03': {'revenue_7d': 29.05, 'active_days_7d': 6, 'active_days_30d': 6}}
  revenue_7d_matches_weekly_revenue: True

Detente en las Partes 3 y 4 juntas, porque son las que resumen todo el módulo en una sola imagen: fact_sessions (17 filas, un accumulating snapshot verdadero — más filas de las que tendría si contaras eventos, menos de las que tendría si events no tuviera sesiones repetidas) y fact_store_activity (21 filas, un cumulative table design verdadero — cada arreglo construido a partir del anterior, nunca recalculado desde cero, y aun así idéntico al revenue semanal ya conocido desde el módulo 1). ACCUMULATING_AND_CUMULATIVE_SUMMARY reúne, en una sola estructura, cada número que las siete lecciones anteriores midieron por separado.

Diagrama: las dos tablas del módulo, cerradas con evidencia

flowchart TD
    A["events (32 filas, canonicos)\n+ session_store_map declarado"] --> B["fact_sessions\n17 filas, VERIFICADO"]
    B --> C["Funnel: 17 -> 9 -> 6\n35.3% conversion, VERIFICADO"]

    D["fact_orders (40 filas)\nagrupado por tienda y dia"] --> E["fact_store_activity\n21 filas, VERIFICADO"]
    E --> F["Snapshot 2026-08-09:\nrevenue_7d == revenue semanal\nVERIFICADO"]

    C --> G["ACCUMULATING_AND_CUMULATIVE_SUMMARY\nel contrato formal que este proyecto entrega"]
    F --> G
    G --> H["Modulo 7: dimension junk,\nmas de un hecho conviviendo"]

Cerrando el checklist del módulo 1, pieza por pieza

Pieza del checklist (lección 2, módulo 1)Estado al cerrar este módulo
Grano de fact_orders declarado y verificadoResuelto — módulo 1
Llaves sustitutas, dim_date, dimensiones conformadasResuelto — módulo 2
Snowflake vs tabla anchaResuelto — módulo 3
Historización de una dimensión que cambia (SCD)Resuelto — módulo 4
Join punto-en-el-tiempo, deduplicación explícitaResuelto — módulo 5
Accumulating snapshot, cumulative designResuelto — ESTE MÓDULO, ACCUMULATING_AND_CUMULATIVE_SUMMARY verificado: fact_sessions (17 filas, funnel 35.3%), fact_store_activity (21 filas, revenue_7d == revenue semanal)
Dimensión junk, más de un hechoPendiente — módulo 7

Siete filas de las ocho ya quedaron resueltas. El módulo 7, el siguiente en la lista, necesita fact_orders, dim_product_scd, fact_sessions y fact_store_activity exactamente como quedaron —sin cambios—, para nombrar de forma explícita algo que ya es verdad desde este módulo: Kiosko ya no tiene un solo hecho (fact_orders), tiene tres, cada uno describiendo un proceso de negocio distinto, con distinto grano y distinto tipo. El módulo 7 formaliza esa convivencia con una dimensión junk y contratos de esquema entre bronze, silver y gold.

Errores comunes

Entregar ACCUMULATING_AND_CUMULATIVE_SUMMARY sin los assert de las Partes 1 a 4. Qué pasa: alguien, apurado por mostrar la estructura de resumen como resultado final, la construye directamente después de correr las consultas, sin haber pasado por los assert que confirman cada número. Por qué pasa: la estructura de resumen se ve más presentable como "el entregable", y los assert se sienten como pasos preliminares descartables. Cómo detectarlo: si tu entrega final no incluye ninguna evidencia ejecutada de que fact_sessions tiene 17 filas, que el funnel da [17, 17, 9, 6], y que revenue_7d coincide con el revenue semanal conocido, estás documentando un proceso sin haber confirmado que funcionó. Cómo corregirlo: los assert de este proyecto no son opcionales — son la garantía que hace confiable todo lo que ACCUMULATING_AND_CUMULATIVE_SUMMARY documenta.

Asumir que fact_sessions y fact_store_activity necesitan estar unidas entre sí. Qué pasa: alguien, al ver dos hechos nuevos en el mismo proyecto, intenta escribir un JOIN entre fact_sessions y fact_store_activity —por ejemplo, uniendo por store_id—, asumiendo que el proyecto espera un análisis combinado. Por qué pasa: los proyectos de módulos anteriores (como el módulo 5) sí terminaron con un JOIN central entre dos tablas. Cómo detectarlo: si buscas en este proyecto un paso que una ambas tablas de hechos, no lo vas a encontrar — cada una responde una pregunta de negocio distinta, con un grano distinto (session_id contra store_id + activity_date), y no hay ninguna pregunta de este módulo que requiera cruzarlas. Cómo corregirlo: fact_sessions y fact_store_activity son dos entregables independientes de este proyecto, no dos mitades de un mismo análisis — el módulo 7, que sí hace convivir varios hechos, es el lugar donde vas a ver ese tipo de integración, no aquí.

Pensar que este proyecto agotó todos los tipos de tabla de hechos que existen. Qué pasa: alguien termina este módulo pensando que ya conoce "todos los tipos" de tabla de hechos —transaccional (fact_orders), accumulating snapshot (fact_sessions), cumulative (fact_store_activity)—, sin considerar el tipo que Kimball llama periodic snapshot (una fila por período fijo, como el saldo de una cuenta al cierre de cada mes), que esta guía nombró en la lección 2 pero nunca construyó. Por qué pasa: dos patrones completos y bien verificados pueden sentirse como "el catálogo completo" cuando en realidad cubren dos de al menos tres tipos documentados por Kimball. Cómo detectarlo: si no puedes explicar, de memoria, en qué se diferenciaría una tabla periodic snapshot de las dos que sí construiste en este módulo, te falta esa pieza del vocabulario de Kimball. Cómo corregirlo: un periodic snapshot —por ejemplo, "el inventario de cada tienda al cierre de cada día"— también inserta una fila nueva por período, pero a diferencia del accumulating snapshot, cada fila describe un instante fijo, no un proceso que avanza; y a diferencia del cumulative design, no acumula un arreglo de historia dentro de la misma fila. Esta guía no lo construye porque Kiosko no tiene, en su dataset actual, un proceso que encaje naturalmente en ese patrón — pero vale la pena saber que existe.

Ejercicios

Ejercicio 1 — Verifica que active_days_7d sumado entre las tres tiendas coincide con el total de días-tienda con ventas. Usando fact_store_activity, escribe una consulta que sume active_days_7d de las tres tiendas en el snapshot del 2026-08-09, y confirma que el resultado coincide con contar, directamente en fact_orders, cuántas combinaciones distintas de store_id + fecha tuvieron al menos una orden en toda la semana.

Ver solución
sum_active_days = con.sql("""
    SELECT SUM(active_days_7d) FROM fact_store_activity WHERE activity_date = DATE '2026-08-09'
""").fetchone()[0]
store_days_with_orders = con.sql("""
    SELECT COUNT(DISTINCT store_id || '-' || CAST(order_ts AS DATE)) FROM fact_orders
""").fetchone()[0]
print(f"suma de active_days_7d (3 tiendas): {sum_active_days}")
print(f"combinaciones store_id + fecha con al menos una orden: {store_days_with_orders}")
assert sum_active_days == store_days_with_orders

Salida esperada:

suma de active_days_7d (3 tiendas): 20
combinaciones store_id + fecha con al menos una orden: 20

7 + 7 + 6 = 20, coincidiendo exactamente con las 20 combinaciones distintas de tienda y día con al menos una venta en fact_orders (de un máximo posible de 3 x 7 = 21, la única combinación faltante siendo S03 el 2026-08-05). Esta verificación cruza fact_store_activity —construida con el patrón cumulative— contra fact_orders —la tabla transaccional original— y confirma que ambas cuentan la misma realidad de negocio, cada una con su propio mecanismo.

Ejercicio 2 — Extiende ACCUMULATING_AND_CUMULATIVE_SUMMARY con el desglose completo del funnel por tienda. El resumen actual solo guarda conversion_pct por tienda. Agrega un campo funnel_detail_by_store con sessions, carts y purchases por tienda, no solo el porcentaje final.

Ver solución
detail_by_store = con.sql("""
    SELECT store_id, COUNT(*) AS sessions, COUNT(add_to_cart_ts) AS carts, COUNT(purchase_ts) AS purchases
    FROM fact_sessions GROUP BY store_id ORDER BY store_id
""").fetchall()

ACCUMULATING_AND_CUMULATIVE_SUMMARY["funnel_detail_by_store"] = {
    store_id: {"sessions": sessions, "carts": carts, "purchases": purchases}
    for store_id, sessions, carts, purchases in detail_by_store
}
print(ACCUMULATING_AND_CUMULATIVE_SUMMARY["funnel_detail_by_store"])

Salida esperada:

{'S01': {'sessions': 6, 'carts': 4, 'purchases': 3}, 'S02': {'sessions': 6, 'carts': 4, 'purchases': 2}, 'S03': {'sessions': 5, 'carts': 1, 'purchases': 1}}

Este desglose revela algo que el conversion_pct por sí solo no muestra: S03, con la conversión más baja (20%), también es la tienda con menos sesiones que llegan al carrito en primer lugar (solo 1 de 5) — su problema de conversión ocurre principalmente en la primera etapa del funnel, antes de llegar al carrito, no después. S01 y S02, en cambio, llegan al carrito con proporciones similares (4 de 6 cada una) pero convierten distinto en la última etapa.

Ejercicio 3 — Explica, de memoria, qué necesita el módulo 7 de este proyecto para poder empezar. Sin mirar el diseño de la guía, describe en un párrafo de 4-6 frases qué piezas de fact_orders, fact_sessions, fact_store_activity o ACCUMULATING_AND_CUMULATIVE_SUMMARY va a necesitar el módulo 7 para nombrar el "dominio feo" de Kiosko —más de un hecho, más de un tipo de dimensión— y formalizar sus contratos Medallion.

Ver solución

El módulo 7 necesita, como base, las tres tablas de hechos exactamente como quedaron: fact_orders (transaccional, desde el módulo 1), fact_sessions (accumulating snapshot, 17 filas, este módulo) y fact_store_activity (cumulative, 21 filas, este módulo), porque su objetivo central es nombrar explícitamente algo que ya es verdad desde que este módulo terminó — que Kiosko tiene un dominio con más de un hecho, cada uno con su propio grano y su propio tipo, conviviendo sobre las mismas dimensiones conformadas (dim_store, dim_date). No necesita reconstruir el mecanismo interno de ninguna de las tres —ni el MAX(CASE WHEN...) de fact_sessions, ni el list_prepend de fact_store_activity—, porque esos hechos ya están verificados y estables; lo que sí hereda, en espíritu, es la disciplina de verificación de este proyecto: la dimensión junk nueva (dim_order_flags) y la función validate_gold_schema() del módulo 7 van a necesitar su propia evidencia ejecutada antes de darse por correctas, exactamente como ACCUMULATING_AND_CUMULATIVE_SUMMARY verificó cada número de este módulo con un assert antes de documentarlo.

Resumen y siguiente paso: el final del módulo 6

Con este mini-proyecto cierras el módulo 6 completo. Construiste fact_sessions —un accumulating snapshot fact table verdadero, 17 filas, con el mapeo sesión-tienda declarado explícitamente y verificado de dos formas distintas (agregado y evento por evento, cero diferencias)— y fact_store_activity —un cumulative table design verdadero, 21 filas, con arreglos que se construyen día a día sin releer fact_orders completo, verificados contra el revenue semanal ya conocido desde el módulo 1—. El funnel de Kiosko convierte 35.3% de sus sesiones, con la caída más grande antes del carrito; las tres tiendas tuvieron actividad casi diaria, con S03 como la única con un día sin ventas dentro de la semana.

Diste el sexto paso de un camino de ocho módulos: Kiosko ya no tiene un solo hecho — tiene tres, cada uno describiendo un proceso de negocio distinto, con distinto grano y distinto mecanismo de actualización, conviviendo sobre las mismas dimensiones conformadas que los módulos 1 a 5 ya construyeron.

Hacia dónde sigues. El módulo 7 —messy-domains-and-medallion-at-depth— nombra de forma explícita lo que este módulo ya construyó en la práctica: un dominio con más de un hecho y más de un tipo de dimensión. Introduce la dimensión junk (dim_order_flags), la dimensión degenerada ya declarada desde el módulo 1 (order_id dentro de fact_orders), y formaliza los contratos entre bronze, silver y gold con una función de validación de esquema corrida sobre las cuatro tablas gold de esta guía.

Recursos