Module 7: Messy Domains And Medallion At Depth
Mini-project: Kiosko's multi-fact gold layer
Description
This project closes the module by integrating the six previous pieces: the domain inventory with three facts and four dimensions (lesson 2), the degenerate dimension developed in depth (lesson 3), dim_order_flags built end to end (lesson 4), validate_gold_schema() tested and run (lesson 5), the three facts joined against the same dim_date (lesson 6), and safe vs. unsafe schema evolution (lesson 7). What's left is to bring it all together in a single flow: rebuild the inherited warehouse with no change at all, build dim_order_flags from scratch, run validate_gold_schema() over the guide's four gold tables, and document the complete result in MEDALLION_SUMMARY — the formal structure module 8, the whole guide's capstone, is going to inherit without repeating the work.
The project has five parts. First, you rebuild the inherited warehouse — fact_orders, dim_date, fact_sessions, fact_store_activity — exactly as it stood since modules 1 through 6. Second, you build dim_order_flags and resolve the flag_key for Kiosko's forty orders. Third, you analyze revenue by payment method and channel, the first real business question the junk dimension enables. Fourth, you run validate_gold_schema() over the guide's four gold tables, with zero discrepancies as the result. Fifth, you document everything in MEDALLION_SUMMARY, the formal structure that closes the module.
Connection to the module. This project introduces no new concept — it's the final integration of the seven previous lessons, packaged as MEDALLION_SUMMARY, the structure module 8 of this guide can cite without rebuilding the evidence from scratch.
An analogy: the quarter-close audit, not just another report
Each lesson in this module solved one piece separately: naming the messy domain, developing the degenerate dimension, building the junk dimension, writing the schema contract, joining three facts against a shared calendar, and distinguishing safe evolution from unsafe. This project is the closing audit: all those pieces, verified together in a single flow, with every number confirmed by an assert before moving to the next — exactly the rigor a real data team would apply before declaring their gold layer ready for other teams — BI, the next guide in this series — to build on with confidence.
The material you need
You need, in the same folder: kiosko.py, raw_orders.py, and events.py (identical to the previous modules). You don't need any additional file — dim_order_flags, order_flags_staging, and validate_gold_schema() are defined directly in this project's script, just like in the previous projects.
The reference solution, verified
Part 1 — Rebuilding the inherited warehouse, unchanged
# kiosko_medallion_project.py -- Kiosko's multi-fact gold layer, module 7's closing mini-project
from datetime import date, datetime, timedelta
import duckdb
from kiosko import DIM_PRODUCT, DIM_STORE, Order, transform_fact_orders
from raw_orders import RAW_ORDERS
from events import RAW_EVENTS
DAY_NAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
def generate_date_dim(start_date: str, end_date: str) -> list[dict]:
start = date.fromisoformat(start_date)
end = date.fromisoformat(end_date)
rows = []
current = start
while current <= end:
weekday_index = current.weekday()
rows.append({
"date_key": int(current.strftime("%Y%m%d")), "calendar_date": current,
"day_of_week": DAY_NAMES[weekday_index], "month": current.month,
"quarter": (current.month - 1) // 3 + 1, "year": current.year,
"is_weekend": weekday_index >= 5,
})
current += timedelta(days=1)
return rows
print("=== Kiosko: multi-fact gold layer, module 7's final delivery ===\n")
print(f"DuckDB version: {duckdb.__version__}\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])
dim_date_rows = generate_date_dim("2026-08-01", "2026-08-31")
con.execute("""
CREATE TABLE dim_date (
date_key INTEGER, calendar_date DATE, day_of_week VARCHAR,
month INTEGER, quarter INTEGER, year INTEGER, is_weekend BOOLEAN
)
""")
con.executemany("INSERT INTO dim_date VALUES (?, ?, ?, ?, ?, ?, ?)",
[(r["date_key"], r["calendar_date"], r["day_of_week"], r["month"],
r["quarter"], r["year"], r["is_weekend"]) for r in dim_date_rows])
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])
STORE_ROTATION = ["S01", "S02", "S03"]
def store_for_session(session_id: str) -> str:
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
""")
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))
print("Part 1 -- inherited warehouse, rebuilt unchanged (M1-M6)")
for table in ["fact_orders", "dim_date", "fact_sessions", "fact_store_activity"]:
count = con.sql(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
print(f" {table:20} {count:3} rows")
What to expect (Part 1).
=== Kiosko: multi-fact gold layer, module 7's final delivery ===
DuckDB version: 1.5.5
Part 1 -- inherited warehouse, rebuilt unchanged (M1-M6)
fact_orders 40 rows
dim_date 31 rows
fact_sessions 17 rows
fact_store_activity 21 rows
This first part builds nothing new — it rebuilds, exactly as in every previous project of this guide, the four inherited tables that everything that follows rests on.
Part 2 — Building dim_order_flags and resolving the 40 orders
PAYMENT_METHODS = ["cash", "card", "wallet"]
CHANNELS = ["in_store", "app"]
def payment_method_for_order(order_index: int) -> str:
return PAYMENT_METHODS[(order_index - 1) % len(PAYMENT_METHODS)]
def channel_for_order(order_index: int) -> str:
return CHANNELS[(order_index - 1) % len(CHANNELS)]
order_flags_rows = [
(r[0], payment_method_for_order(i), channel_for_order(i))
for i, r in enumerate(RAW_ORDERS, start=1)
]
con.execute("CREATE TABLE order_flags_staging (order_id VARCHAR, payment_method VARCHAR, channel VARCHAR)")
con.executemany("INSERT INTO order_flags_staging VALUES (?, ?, ?)", order_flags_rows)
DIM_ORDER_FLAGS_ROWS = []
next_flag_key = 1
for payment_method in PAYMENT_METHODS:
for channel in CHANNELS:
DIM_ORDER_FLAGS_ROWS.append((next_flag_key, payment_method, channel))
next_flag_key += 1
con.execute("CREATE TABLE dim_order_flags (flag_key INTEGER, payment_method VARCHAR, channel VARCHAR)")
con.executemany("INSERT INTO dim_order_flags VALUES (?, ?, ?)", DIM_ORDER_FLAGS_ROWS)
flag_count = con.sql("SELECT COUNT(*) FROM dim_order_flags").fetchone()[0]
assert flag_count == 6, "dim_order_flags doesn't have the complete cartesian product"
con.execute("""
CREATE TABLE order_flags_resolved AS
SELECT s.order_id, f.flag_key, s.payment_method, s.channel
FROM order_flags_staging s
JOIN dim_order_flags f ON s.payment_method = f.payment_method AND s.channel = f.channel
""")
resolved_count = con.sql("SELECT COUNT(*) FROM order_flags_resolved").fetchone()[0]
assert resolved_count == 40, "not all orders resolved their flag_key"
print("\nPart 2 -- dim_order_flags built and the 40 orders resolved")
print(f" dim_order_flags {flag_count:3} rows (3 payment_method x 2 channel)")
print(f" order_flags_resolved {resolved_count:3} rows (40 orders, each with its flag_key)")
What to expect (Part 2).
Part 2 -- dim_order_flags built and the 40 orders resolved
dim_order_flags 6 rows (3 payment_method x 2 channel)
order_flags_resolved 40 rows (40 orders, each with its flag_key)
Part 3 — Revenue by payment method and channel
revenue_by_flag = con.sql("""
SELECT f.flag_key, f.payment_method, f.channel, COUNT(*) AS orders, ROUND(SUM(o.revenue), 2) AS revenue
FROM fact_orders o
JOIN order_flags_resolved r ON o.order_id = r.order_id
JOIN dim_order_flags f ON r.flag_key = f.flag_key
GROUP BY f.flag_key, f.payment_method, f.channel
ORDER BY f.flag_key
""").fetchall()
print("\nPart 3 -- revenue by payment method and channel")
for flag_key, payment_method, channel, orders_count, revenue in revenue_by_flag:
print(f" flag_key={flag_key} {payment_method:6} {channel:8} {orders_count:2} orders revenue={revenue}")
total_via_flags = round(sum(row[4] for row in revenue_by_flag), 2)
assert total_via_flags == 106.15, "revenue broken down by flag doesn't match the known total"
print(f" TOTAL (via flags): {total_via_flags}")
What to expect (Part 3).
Part 3 -- revenue by payment method and channel
flag_key=1 cash in_store 7 orders revenue=14.2
flag_key=2 cash app 7 orders revenue=14.8
flag_key=3 card in_store 6 orders revenue=15.7
flag_key=4 card app 7 orders revenue=23.8
flag_key=5 wallet in_store 7 orders revenue=19.55
flag_key=6 wallet app 6 orders revenue=18.1
TOTAL (via flags): 106.15
Part 4 — validate_gold_schema() over the 4 gold tables
def validate_gold_schema(con: duckdb.DuckDBPyConnection, table: str, expected_columns: dict[str, str]) -> list[str]:
actual_rows = con.sql(f"DESCRIBE {table}").fetchall()
actual_columns = {row[0]: row[1] for row in actual_rows}
discrepancies = []
for column_name, expected_type in expected_columns.items():
if column_name not in actual_columns:
discrepancies.append(f"{table}: missing column '{column_name}' (expected type {expected_type})")
elif actual_columns[column_name] != expected_type:
discrepancies.append(
f"{table}: '{column_name}' has type {actual_columns[column_name]}, expected {expected_type}"
)
for column_name in actual_columns:
if column_name not in expected_columns:
discrepancies.append(f"{table}: unexpected column '{column_name}', not declared in the contract")
return discrepancies
GOLD_CONTRACTS = {
"fact_orders": {
"order_id": "VARCHAR", "store_id": "VARCHAR", "product_id": "VARCHAR",
"quantity": "INTEGER", "unit_price": "DOUBLE", "revenue": "DOUBLE", "order_ts": "TIMESTAMP",
},
"fact_sessions": {
"session_id": "VARCHAR", "store_id": "VARCHAR", "session_date": "DATE",
"view_ts": "TIMESTAMP", "add_to_cart_ts": "TIMESTAMP", "purchase_ts": "TIMESTAMP",
"is_converted": "BOOLEAN",
},
"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",
},
"dim_date": {
"date_key": "INTEGER", "calendar_date": "DATE", "day_of_week": "VARCHAR",
"month": "INTEGER", "quarter": "INTEGER", "year": "INTEGER", "is_weekend": "BOOLEAN",
},
}
print("\nPart 4 -- validate_gold_schema() over the guide's 4 gold tables")
total_discrepancies = 0
for table, expected_columns in GOLD_CONTRACTS.items():
discrepancies = validate_gold_schema(con, table, expected_columns)
total_discrepancies += len(discrepancies)
status = "OK, 0 discrepancies" if not discrepancies else f"{len(discrepancies)} discrepancies"
print(f" {table:20} {status}")
assert total_discrepancies == 0, "the Medallion contract broke"
print(f" TOTAL: {len(GOLD_CONTRACTS)} gold tables, {total_discrepancies} discrepancies -- OK")
What to expect (Part 4).
Part 4 -- validate_gold_schema() over the guide's 4 gold tables
fact_orders OK, 0 discrepancies
fact_sessions OK, 0 discrepancies
fact_store_activity OK, 0 discrepancies
dim_date OK, 0 discrepancies
TOTAL: 4 gold tables, 0 discrepancies -- OK
Part 5 — Documenting it as a formal structure
MEDALLION_SUMMARY = {
"warehouse_tables": {
"fact_orders": 40, "dim_date": 31, "fact_sessions": 17, "fact_store_activity": 21,
},
"degenerate_dimension": "order_id, lives inside fact_orders, no table of its own (modules 1 and 7)",
"junk_dimension": {
"table": "dim_order_flags", "rows": flag_count,
"domains": {"payment_method": PAYMENT_METHODS, "channel": CHANNELS},
"orders_resolved": resolved_count,
"revenue_verified": total_via_flags,
},
"gold_contract": {
"tables_validated": list(GOLD_CONTRACTS.keys()),
"total_columns": sum(len(cols) for cols in GOLD_CONTRACTS.values()),
"discrepancies": total_discrepancies,
},
"conformed_dimensions": {"dim_store": 3, "dim_date": 3},
}
print("\nPart 5 -- the formal declaration: MEDALLION_SUMMARY")
for key, value in MEDALLION_SUMMARY.items():
print(f" {key}: {value}")
What to expect. Running python3 kiosko_medallion_project.py in full (all five parts together), the output ends exactly like this:
Part 5 -- the formal declaration: MEDALLION_SUMMARY
warehouse_tables: {'fact_orders': 40, 'dim_date': 31, 'fact_sessions': 17, 'fact_store_activity': 21}
degenerate_dimension: order_id, lives inside fact_orders, no table of its own (modules 1 and 7)
junk_dimension: {'table': 'dim_order_flags', 'rows': 6, 'domains': {'payment_method': ['cash', 'card', 'wallet'], 'channel': ['in_store', 'app']}, 'orders_resolved': 40, 'revenue_verified': 106.15}
gold_contract: {'tables_validated': ['fact_orders', 'fact_sessions', 'fact_store_activity', 'dim_date'], 'total_columns': 28, 'discrepancies': 0}
conformed_dimensions: {'dim_store': 3, 'dim_date': 3}
Stop on gold_contract and junk_dimension together, because they summarize the whole module in a single picture. discrepancies: 0 over twenty-eight columns in four gold tables is the executed confirmation that Kiosko's Medallion contract holds; revenue_verified: 106.15 is proof that the new junk dimension didn't alter a single cent of the business you already knew since module 1 — it only added a new way to break it down. conformed_dimensions closes with the number lesson 2 started measuring and lesson 6 completed: both dim_store and dim_date now serve all three of Kiosko's facts.
Diagram: the module's seven pieces, closed with evidence
flowchart TD
A["L2: Domain inventory\nVERIFIED -- 3 facts, 4 dimensions"] --> B
B["L3: Degenerate dimension in depth\nVERIFIED -- dim_order_bad, 0 added value"] --> C
C["L4: dim_order_flags\nVERIFIED -- 6 rows, 40 orders resolved"] --> D
D["L5: validate_gold_schema()\nVERIFIED -- tested and drift detected"] --> E
E["L6: 3 facts, 1 calendar\nVERIFIED -- totals match via dim_date"] --> F
F["L7: Safe vs unsafe evolution\nVERIFIED -- additive first"] --> G
G["MEDALLION_SUMMARY\nthe formal contract this project delivers"]
G --> H["Module 8: capstone,\nKiosko's first complete warehouse"]
Closing module 1's lesson 2 checklist, piece by piece
| Checklist item (lesson 2, module 1) | Status at the close of this module |
|---|---|
Grain of fact_orders declared and verified | Resolved — module 1 |
Surrogate keys, dim_date, conformed dimensions | Resolved — module 2 |
| Snowflake vs wide table | Resolved — module 3 |
| Historizing a changing dimension (SCD) | Resolved — module 4 |
| Point-in-time join, deduplication | Resolved — module 5 |
| Accumulating snapshot, cumulative design | Resolved — module 6 |
| Junk dimension, more than one fact | Resolved — THIS MODULE, MEDALLION_SUMMARY verified: dim_order_flags (6 rows), 4-gold-table contract (0 discrepancies) |
The seven rows of the checklist that opened this guide — in module 1's lesson 2 — are now resolved. Module 8, the whole guide's capstone, doesn't need to resolve any new dimensional-modeling piece: it needs to integrate the seven pieces modules 1 through 7 already built and verified, into a single end-to-end warehouse, with a final business report as the output.
Common mistakes
Delivering MEDALLION_SUMMARY without Parts 2 through 4's asserts. What happens: someone, in a hurry to show the summary structure as the final result, builds it directly after running the queries, without having gone through the asserts that confirm each number. Why it happens: the summary structure looks more presentable as "the deliverable," and the asserts feel like discardable preliminary steps. How to spot it: if your final delivery doesn't include any executed evidence that dim_order_flags has 6 rows, that the 40 orders resolved their flag_key, and that the 4 gold tables pass the contract with 0 discrepancies, you're documenting a process without having confirmed it worked. How to fix it: this project's asserts aren't optional — they're the guarantee that makes everything MEDALLION_SUMMARY documents trustworthy.
Thinking dim_order_flags needs to be added to GOLD_CONTRACTS for the project to be complete. What happens: someone, after building dim_order_flags in Part 2, expects Part 4 to include it as a fifth table in GOLD_CONTRACTS, and worries seeing the project only validates four. Why it happens: dim_order_flags is the module's newest table, and it seems natural for it to also be part of the main contract. How to spot it: if you expect to see dim_order_flags in Part 4's output, check this guide's design — the four explicitly verified gold tables are fact_orders, fact_sessions, fact_store_activity, and dim_date, with dim_order_flags as a supporting dimension. How to fix it: nothing stops you from extending GOLD_CONTRACTS with dim_order_flags in a project of your own — lesson 5's exercise 1 already did — but this project's four tables are, specifically, the ones the guide's design names as "the guide's 4 gold tables."
Confusing this module's close with the whole guide's close. What happens: someone, seeing the complete checklist with all seven rows resolved, concludes there's nothing left to build in this guide. Why it happens: a complete checklist naturally feels like an ending. How to spot it: if you can't name what module 8 does, you missed that module 1's lesson 2 checklist was a list of pending concepts — all resolved now — not a list of complete-warehouse deliverables. How to fix it: module 8 — the capstone — still has real work: integrating the seven pieces into a single end-to-end flow, rebuilding bronze and silver from foundations, publishing the star with historized dim_product, fact_sessions, and fact_store_activity, and module 3's wide table for BI — all together, for the first time, in a single script.
Exercises
Exercise 1 — Verify no dim_order_flags combination was left unused by any order. Using order_flags_resolved and dim_order_flags, write a query with LEFT JOIN that confirms the six precomputed combinations were used at least once by the forty orders.
See solution
unused_flags = con.sql("""
SELECT f.flag_key, f.payment_method, f.channel
FROM dim_order_flags f
LEFT JOIN order_flags_resolved r ON f.flag_key = r.flag_key
WHERE r.order_id IS NULL
""").fetchall()
print(f"Unused dim_order_flags combinations: {len(unused_flags)}")
assert len(unused_flags) == 0
Expected output:
Unused dim_order_flags combinations: 0
Zero unused combinations — with only forty orders spread across six possible combinations, each received at least six orders (as you already saw in lesson 4), so none stayed empty in this specific week of data.
Exercise 2 — Extend MEDALLION_SUMMARY with the revenue breakdown by payment_method only (without channel). Add a revenue_by_payment_method field that groups only by payment method.
See solution
revenue_by_payment = con.sql("""
SELECT f.payment_method, ROUND(SUM(o.revenue), 2) AS revenue
FROM fact_orders o
JOIN order_flags_resolved r ON o.order_id = r.order_id
JOIN dim_order_flags f ON r.flag_key = f.flag_key
GROUP BY f.payment_method
ORDER BY f.payment_method
""").fetchall()
MEDALLION_SUMMARY["revenue_by_payment_method"] = {pm: rev for pm, rev in revenue_by_payment}
print(MEDALLION_SUMMARY["revenue_by_payment_method"])
Expected output:
{'card': 39.5, 'cash': 29.0, 'wallet': 37.65}
39.5 + 29.0 + 37.65 = 106.15, the same total as always. card is the payment method with the highest revenue, followed by wallet and cash — a breakdown dim_order_flags enables in a single GROUP BY line, with no change to fact_orders at all.
Exercise 3 — Explain, from memory, what module 8 needs from this project to be able to start. Without looking at the guide's design, describe in a 4-6 sentence paragraph which pieces of MEDALLION_SUMMARY — and of the tables built in this project — module 8 is going to need to build Kiosko's first complete analytical warehouse.
See solution
Module 8 needs, as a foundation, exactly the tables this project left verified: fact_orders, dim_date, fact_sessions, and fact_store_activity — the four gold tables with a confirmed contract (gold_contract.discrepancies: 0) — plus dim_order_flags and the degenerate dimension order_id already declared within the fact itself. It also needs, even though this project didn't explicitly rebuild them, dim_store and dim_product_scd — module 4's historized dimension — because the capstone integrates the complete star, not just this module's new pieces. What module 8 doesn't need to repeat is any of the verifications already closed here: it doesn't rebuild dim_order_flags from scratch again, it doesn't retest validate_gold_schema() against a drift scenario — that evidence already exists, documented in MEDALLION_SUMMARY. What module 8 does add, which no previous module did, is rebuilding bronze and silver from foundations (not just assuming fact_orders already exists) and publishing mart_daily_sales_obt, module 3's wide table, as the final deliverable for the BI team.
Summary and next step: the end of module 7
With this mini-project you close module 7 in full. You built dim_order_flags — six rows, the complete cartesian product of three payment methods and two channels — resolved Kiosko's forty orders to their corresponding flag_key, and confirmed that revenue broken down by payment method and channel still sums to 106.15, the same total known since module 1. You ran validate_gold_schema() over the guide's four gold tables — twenty-eight columns in total — with zero discrepancies, and documented everything in MEDALLION_SUMMARY, the formal structure that summarizes, in a single picture, a domain with three facts, a degenerate dimension, a junk dimension, and a verified schema contract.
You took the seventh step of an eight-module path: Kiosko is no longer the flat fact_orders/dim_store/dim_product foundations left behind — it's a complete dimensional warehouse, with a declared grain, a star schema, preserved history, correct point-in-time joins, two non-transactional fact patterns, and now a named messy domain, with non-standard dimensions and a verifiable contract between bronze, silver, and gold.
Where you're headed next. Module 8 — project-kioskos-analytics-warehouse, this guide's capstone — integrates the seven pieces from modules 1 through 7 into a single end-to-end flow: bronze and silver rebuilt from foundations, the complete star with historized dim_product, fact_sessions and fact_store_activity published, and mart_daily_sales_obt served for the BI team — Kiosko's first real analytical warehouse, closed with the map toward this ecosystem's sibling guides.
Resources
- Kimball Group — "Star Schema / OLAP Cube" — the complete vocabulary of degenerate and junk dimensions this project integrated alongside the rest of the dimensional model. kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/star-schema-olap-cube. In English.
- Kimball Group — "Junk Dimensions" — the formal definition of
dim_order_flags, verified end to end in this project. kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/junk-dimension. In English. - Databricks — "What is the medallion lakehouse architecture?" — the bronze/silver/gold framework backing the schema contract verified in Part 4 of this project. docs.databricks.com/aws/en/lakehouse/medallion. In English.
- DuckDB — official Python client documentation, the interface that ran every verification in this project. duckdb.org/docs/current/clients/python/overview. In English.