From 197ccd8489bc855d41cee2e01fdb37424e4934e9 Mon Sep 17 00:00:00 2001 From: vidit choudhary Date: Thu, 24 Sep 2026 16:44:28 +0530 Subject: [PATCH 1/8] Run the SQL layer on PostgreSQL as well as SQLite The staging views, mart and analytical queries are still written once. A small translator rewrites the three SQLite constructs they use (week start, year-month, REAL) and refuses anything it does not know, so a new query fails at translation rather than on the server. The warehouse loads the cleaned model with COPY inside one transaction, so a load that breaks a foreign key leaves the previous warehouse untouched. Tests load both engines from the same tables and require every staging view and every query to match row for row. That found two things in the queries: four sorted on a column that can tie, so the engines returned tied rows in different orders, and SQLite and PostgreSQL place NULLs at opposite ends. Each now sorts on a full key with NULL placement stated. Two edits make the queries portable to Redshift as well: AVG of an integer column is cast first, since Redshift returns an integer there, and the named WINDOW clause is written out. aov can differ by one cent on exact half-cent values: pandas rounds half to even, SQLite rounds the binary float, PostgreSQL rounds the decimal half up. Every other column matches exactly. --- docker-compose.yml | 35 +++++ sql/analytics/daily_sales_tracker.sql | 4 +- sql/analytics/kam_scorecard.sql | 5 +- sql/analytics/supply_watchlist.sql | 21 +-- sql/analytics/target_variance.sql | 5 +- sql/analytics/weekly_movers.sql | 2 +- src/rootsignal/sql/dialect.py | 125 +++++++++++++++++ src/rootsignal/sql/warehouse.py | 195 ++++++++++++++++++++++++++ tests/test_warehouse.py | 167 ++++++++++++++++++++++ 9 files changed, 545 insertions(+), 14 deletions(-) create mode 100644 src/rootsignal/sql/dialect.py create mode 100644 src/rootsignal/sql/warehouse.py create mode 100644 tests/test_warehouse.py diff --git a/docker-compose.yml b/docker-compose.yml index 3bbedb9..bbe36a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,3 +24,38 @@ services: image: rootsignal profiles: ["tools"] command: pytest + + # Source to warehouse: land the published data, conform it in Spark, load + # it into the warehouse below. The lake is ./data/lake on the host. + pipeline: + build: . + image: rootsignal + profiles: ["tools"] + depends_on: + warehouse: + condition: service_healthy + environment: + - ROOTSIGNAL_WAREHOUSE_URL=postgresql://rootsignal:rootsignal@warehouse:5432/rootsignal + volumes: + - ./data:/app/data + command: python scripts/run_pipeline.py + + # The warehouse the pipeline loads into. Credentials are for a local + # container only; a deployed warehouse takes its URL from the environment. + warehouse: + image: postgres:16 + environment: + - POSTGRES_USER=rootsignal + - POSTGRES_PASSWORD=rootsignal + - POSTGRES_DB=rootsignal + ports: + - "5433:5432" + volumes: + - warehouse-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U rootsignal"] + interval: 5s + retries: 10 + +volumes: + warehouse-data: diff --git a/sql/analytics/daily_sales_tracker.sql b/sql/analytics/daily_sales_tracker.sql index ed384ce..8ce6acf 100644 --- a/sql/analytics/daily_sales_tracker.sql +++ b/sql/analytics/daily_sales_tracker.sql @@ -32,7 +32,7 @@ SELECT d.date, d.net_sales, d.units, - do.orders, + od.orders, CASE WHEN d.ordered_units <> 0 THEN ROUND(CAST(d.fulfilled_units AS REAL) / d.ordered_units, 4) END AS fill_rate, p.sales_target, @@ -42,6 +42,6 @@ SELECT LAG(d.net_sales) OVER (ORDER BY d.date) AS prior_day_net_sales, ROUND(d.net_sales - LAG(d.net_sales) OVER (ORDER BY d.date), 2) AS day_over_day_change FROM daily AS d -JOIN daily_orders AS do ON do.date = d.date +JOIN daily_orders AS od ON od.date = d.date LEFT JOIN plan AS p ON p.date = d.date ORDER BY d.date; diff --git a/sql/analytics/kam_scorecard.sql b/sql/analytics/kam_scorecard.sql index 150db3d..5dc9979 100644 --- a/sql/analytics/kam_scorecard.sql +++ b/sql/analytics/kam_scorecard.sql @@ -44,4 +44,7 @@ FROM dim_kam AS k LEFT JOIN sales AS s ON s.kam_id = k.kam_id LEFT JOIN orders AS o ON o.kam_id = k.kam_id LEFT JOIN quota AS q ON q.kam_id = k.kam_id -ORDER BY sales_attainment; +-- Ties are broken on the segment key, and NULLs placed explicitly: SQLite +-- sorts them first and PostgreSQL last, so leaving either implicit gives the +-- two engines different row orders for the same result. +ORDER BY sales_attainment NULLS FIRST, k.kam_id; diff --git a/sql/analytics/supply_watchlist.sql b/sql/analytics/supply_watchlist.sql index bd23253..2acd8d0 100644 --- a/sql/analytics/supply_watchlist.sql +++ b/sql/analytics/supply_watchlist.sql @@ -23,12 +23,14 @@ WITH weekly AS ( stock AS ( -- Stock is a level and is averaged; stockout_flag is a rate over SKU-days. -- Summing a level would report a week's stock as seven times its own size. + -- Both are cast before averaging: Redshift's AVG of an integer column is an + -- integer, which would report every stockout rate below 1 as 0. SELECT - DATE(i.date, 'weekday 0', '-6 days') AS week_start, + DATE(i.date, 'weekday 0', '-6 days') AS week_start, i.region_code, i.category, - ROUND(AVG(i.available_stock), 2) AS available_stock, - ROUND(AVG(i.stockout_flag), 4) AS stockout_rate + ROUND(AVG(CAST(i.available_stock AS REAL)), 2) AS available_stock, + ROUND(AVG(CAST(i.stockout_flag AS REAL)), 4) AS stockout_rate FROM stg_inventory AS i GROUP BY week_start, i.region_code, i.category ), @@ -51,15 +53,16 @@ joined AS ( AND s.region_code = w.region_code AND s.category = w.category ), +-- The window is written out on each LAG because Redshift has no named WINDOW +-- clause. movement AS ( SELECT *, - LAG(fill_rate) OVER segment AS prior_fill_rate, - LAG(ordered_units) OVER segment AS prior_ordered_units, - LAG(available_stock) OVER segment AS prior_available_stock, - LAG(stockout_rate) OVER segment AS prior_stockout_rate + LAG(fill_rate) OVER (PARTITION BY region_code, category ORDER BY week_start) AS prior_fill_rate, + LAG(ordered_units) OVER (PARTITION BY region_code, category ORDER BY week_start) AS prior_ordered_units, + LAG(available_stock) OVER (PARTITION BY region_code, category ORDER BY week_start) AS prior_available_stock, + LAG(stockout_rate) OVER (PARTITION BY region_code, category ORDER BY week_start) AS prior_stockout_rate FROM joined - WINDOW segment AS (PARTITION BY region_code, category ORDER BY week_start) ) SELECT week_start, @@ -82,4 +85,4 @@ WHERE prior_fill_rate IS NOT NULL AND fill_rate - prior_fill_rate <= -0.05 -- ...while demand did not, so this is not simply a shrinking segment. AND CAST(ordered_units AS REAL) / prior_ordered_units >= 0.95 -ORDER BY fill_rate_change; +ORDER BY fill_rate_change, week_start, region_code, category; diff --git a/sql/analytics/target_variance.sql b/sql/analytics/target_variance.sql index f500840..94fd515 100644 --- a/sql/analytics/target_variance.sql +++ b/sql/analytics/target_variance.sql @@ -38,4 +38,7 @@ LEFT JOIN actual AS a ON a.region_code = p.region_code AND a.category = p.category AND a.channel = p.channel -ORDER BY variance_pct; +-- Ties are broken on the segment key, and NULLs placed explicitly: SQLite +-- sorts them first and PostgreSQL last, so leaving either implicit gives the +-- two engines different row orders for the same result. +ORDER BY variance_pct NULLS FIRST, p.region_code, p.category, p.channel; diff --git a/sql/analytics/weekly_movers.sql b/sql/analytics/weekly_movers.sql index 6a8591d..522c7ed 100644 --- a/sql/analytics/weekly_movers.sql +++ b/sql/analytics/weekly_movers.sql @@ -52,4 +52,4 @@ SELECT AS share_of_absolute_movement, c.days_observed FROM contribution AS c -ORDER BY c.week_start, c.contribution; +ORDER BY c.week_start, c.contribution, c.region_code, c.category; diff --git a/src/rootsignal/sql/dialect.py b/src/rootsignal/sql/dialect.py new file mode 100644 index 0000000..3bcff16 --- /dev/null +++ b/src/rootsignal/sql/dialect.py @@ -0,0 +1,125 @@ +"""Run the one set of SQL files on PostgreSQL as well as SQLite. + +The staging views, the mart and the analytical queries are written once. Where +the two engines disagree, this module rewrites the SQLite form into the +PostgreSQL one, and nothing else: every rule below exists because a specific +construct in sql/ needs it, and a construct with no rule is refused rather than +passed through to fail somewhere less obvious. + +Rounding is the one difference not handled by rewriting. PostgreSQL has no +ROUND for double precision with a number of places, so the warehouse defines +one (see ROUND_FUNCTION) rather than every ROUND call in the repository being +wrapped in a cast. +""" + +from __future__ import annotations + +import re + +# Week starting Monday. SQLite moves forward to the next Sunday (or stays on a +# Sunday) and steps back six days; DATE_TRUNC('week') is the ISO Monday, which +# is the same day. +_WEEK_START = re.compile( + r"DATE\(\s*([\w.]+)\s*,\s*'weekday 0'\s*,\s*'-6 days'\s*\)", re.IGNORECASE +) +_YEAR_MONTH = re.compile(r"STRFTIME\(\s*'%Y-%m'\s*,\s*([\w.]+)\s*\)", re.IGNORECASE) +_PRAGMA = re.compile(r"^\s*PRAGMA\b[^;]*;\s*$", re.IGNORECASE | re.MULTILINE) +# SQLite's REAL is an 8-byte float. PostgreSQL's REAL is 4 bytes, which would +# lose cents on large revenue figures, so it becomes DOUBLE PRECISION. +_REAL = re.compile(r"\bREAL\b") + +# Anything still SQLite-specific after translation. +_UNTRANSLATED = ( + re.compile(r"\bSTRFTIME\s*\(", re.IGNORECASE), + re.compile(r"'weekday\s+\d'", re.IGNORECASE), + re.compile(r"\bPRAGMA\b", re.IGNORECASE), + re.compile(r"\bJULIANDAY\s*\(", re.IGNORECASE), + re.compile(r"\bIFNULL\s*\(", re.IGNORECASE), +) + +ROUND_FUNCTION = """ +CREATE OR REPLACE FUNCTION round(value DOUBLE PRECISION, places INTEGER) +RETURNS DOUBLE PRECISION +LANGUAGE SQL IMMUTABLE STRICT +AS $$ SELECT round(value::numeric, places)::double precision $$; +""" + + +def _strip_comments(sql: str) -> str: + return re.sub(r"--[^\n]*", "", sql) + + +_CREATE_TABLE = re.compile(r"CREATE TABLE\s+(\w+)\s*\((.*?)\n\);", re.IGNORECASE | re.DOTALL) +_COLUMN = re.compile(r"^\s*(\w+)\s+(TEXT|INTEGER|REAL|DATE)\b", re.IGNORECASE) + + +def schema_columns(schema_sql: str) -> dict[str, list[tuple[str, str]]]: + """Each table's columns and declared types, in order, from schema.sql. + + Used to write Parquet whose types match the tables exactly, which Redshift's + COPY requires: it will not narrow a 64-bit integer into an INTEGER column. + """ + tables = {} + for name, body in _CREATE_TABLE.findall(_strip_comments(schema_sql)): + columns = [] + for line in body.splitlines(): + match = _COLUMN.match(line) + if match: + columns.append((match.group(1), match.group(2).upper())) + tables[name] = columns + return tables + + +def _strip_checks(sql: str) -> str: + """Remove CHECK (...) clauses, following parentheses to their close.""" + out, index = [], 0 + pattern = re.compile(r"\s+CHECK\s*\(", re.IGNORECASE) + while True: + match = pattern.search(sql, index) + if not match: + out.append(sql[index:]) + return "".join(out) + out.append(sql[index:match.start()]) + depth, position = 1, match.end() + while depth: + depth += {"(": 1, ")": -1}.get(sql[position], 0) + position += 1 + index = position + + +def to_redshift(sql: str) -> str: + """Rewrite SQL from this repository for Amazon Redshift. + + The PostgreSQL translation applies, plus what Redshift does not support: + CHECK constraints and indexes are removed (validation enforces the checks + before anything is loaded, and Redshift sorts and distributes instead of + indexing), and TEXT becomes VARCHAR(1024), because Redshift's TEXT is + VARCHAR(256) and would truncate a long product description. + """ + out = to_postgres(sql) + out = _strip_checks(out) + out = re.sub(r"^\s*CREATE INDEX\b[^;]*;\s*$", "", out, flags=re.IGNORECASE | re.MULTILINE) + out = re.sub(r"\bTEXT\b", "VARCHAR(1024)", out) + return out + + +def to_postgres(sql: str) -> str: + """Rewrite SQLite SQL from this repository into PostgreSQL. + + Raises ValueError if a SQLite-only construct survives, so a new query using + one fails at translation rather than as a syntax error from the server. + """ + out = _PRAGMA.sub("", sql) + out = _WEEK_START.sub(r"CAST(DATE_TRUNC('week', \1) AS DATE)", out) + out = _YEAR_MONTH.sub(r"TO_CHAR(\1, 'YYYY-MM')", out) + out = _REAL.sub("DOUBLE PRECISION", out) + + code = _strip_comments(out) + for pattern in _UNTRANSLATED: + match = pattern.search(code) + if match: + raise ValueError( + f"No PostgreSQL translation for {match.group(0)!r}. " + "Add a rule to rootsignal.sql.dialect or write it portably." + ) + return out diff --git a/src/rootsignal/sql/warehouse.py b/src/rootsignal/sql/warehouse.py new file mode 100644 index 0000000..d506c52 --- /dev/null +++ b/src/rootsignal/sql/warehouse.py @@ -0,0 +1,195 @@ +"""Load the cleaned business model into PostgreSQL and query it. + +The SQLite database in database.py is what the test suite builds in memory. +This is the same schema, the same views and the same queries on a PostgreSQL +server: the warehouse the pipeline loads into. Nothing is written twice. The +files in sql/ are translated by dialect.to_postgres, and tests assert both +engines return the same rows for every query. + +Each load replaces one schema wholesale, so a run is repeatable and a failed +load never leaves a half-updated warehouse behind: the new tables are built in +a transaction and the old schema is only dropped inside the same one. +""" + +from __future__ import annotations + +import datetime as dt +import io +import os +from collections.abc import Sequence +from decimal import Decimal +from pathlib import Path + +import pandas as pd + +from .database import ( + ANALYTICS_DIR, + LOAD_ORDER, + MARTS_DIR, + SCHEMA_PATH, + STAGING_DIR, + _sql_files, + to_sqlite_types, +) +from .dialect import ROUND_FUNCTION, to_postgres + +URL_VARIABLE = "ROOTSIGNAL_WAREHOUSE_URL" +DEFAULT_SCHEMA = "rootsignal" + + +def warehouse_url(url: str | None = None) -> str: + """The connection string, from the argument or ROOTSIGNAL_WAREHOUSE_URL.""" + resolved = url or os.environ.get(URL_VARIABLE, "") + if not resolved: + raise ValueError( + f"No warehouse configured. Pass a URL or set {URL_VARIABLE}, for example " + "postgresql://rootsignal:rootsignal@localhost:5433/rootsignal " + "(docker compose up -d warehouse)." + ) + return resolved + + +def connect(url: str | None = None, schema: str = DEFAULT_SCHEMA): + """Open a connection whose unqualified names resolve inside `schema`.""" + import psycopg + + conn = psycopg.connect(warehouse_url(url), autocommit=False) + conn.execute(f'SET search_path TO "{schema}"') + conn.commit() + return conn + + +def _copy_frame(cursor, table: str, frame: pd.DataFrame) -> None: + """Bulk-load a frame with COPY, which is what makes a million rows cheap. + + CSV is used as the wire format. An empty unquoted field is NULL in COPY's + CSV mode, which is how pandas writes a missing value. + """ + buffer = io.StringIO() + to_sqlite_types(frame).to_csv(buffer, index=False, header=False, na_rep="") + columns = ", ".join(f'"{column}"' for column in frame.columns) + with cursor.copy(f'COPY "{table}" ({columns}) FROM STDIN WITH (FORMAT csv)') as copy: + copy.write(buffer.getvalue()) + + +def build_warehouse( + tables: dict[str, pd.DataFrame], + url: str | None = None, + schema: str = DEFAULT_SCHEMA, + with_views: bool = True, +): + """Replace `schema` with the cleaned tables, and register the views. + + Foreign keys are enforced by the server as rows arrive, so a load that + violates the declared model fails here, and the transaction leaves the + previous warehouse as it was. + """ + missing = [name for name in LOAD_ORDER if name not in tables] + if missing: + raise ValueError(f"Cannot build the warehouse; missing tables: {missing}") + + import psycopg + + conn = psycopg.connect(warehouse_url(url), autocommit=False) + try: + with conn.cursor() as cursor: + cursor.execute(f'DROP SCHEMA IF EXISTS "{schema}" CASCADE') + cursor.execute(f'CREATE SCHEMA "{schema}"') + cursor.execute(f'SET LOCAL search_path TO "{schema}"') + cursor.execute(ROUND_FUNCTION) + cursor.execute(to_postgres(SCHEMA_PATH.read_text(encoding="utf-8"))) + for name in LOAD_ORDER: + _copy_frame(cursor, name, tables[name]) + if with_views: + for directory in (STAGING_DIR, MARTS_DIR): + for path in _sql_files(directory): + cursor.execute(to_postgres(path.read_text(encoding="utf-8"))) + cursor.execute("ANALYZE") + conn.commit() + except Exception: + conn.rollback() + conn.close() + raise + conn.close() + return connect(url, schema) + + +def _column_type(series: pd.Series) -> str: + if pd.api.types.is_bool_dtype(series): + return "BOOLEAN" + if pd.api.types.is_integer_dtype(series): + return "BIGINT" + if pd.api.types.is_numeric_dtype(series): + return "DOUBLE PRECISION" + if series.map(lambda value: isinstance(value, dt.date)).any(): + return "DATE" + return "TEXT" + + +def write_report_table(conn, name: str, frame: pd.DataFrame) -> int: + """Replace one report table with a frame, typing columns from its dtypes. + + Report tables are outputs of the analysis layer rather than part of the + declared model, so they carry no keys and are rebuilt on every run. + """ + columns = ", ".join(f'"{column}" {_column_type(frame[column])}' for column in frame.columns) + with conn.cursor() as cursor: + cursor.execute(f'DROP TABLE IF EXISTS "{name}"') + cursor.execute(f'CREATE TABLE "{name}" ({columns})') + buffer = io.StringIO() + frame.to_csv(buffer, index=False, header=False, na_rep="") + names = ", ".join(f'"{column}"' for column in frame.columns) + with cursor.copy(f'COPY "{name}" ({names}) FROM STDIN WITH (FORMAT csv)') as copy: + copy.write(buffer.getvalue()) + conn.commit() + return len(frame) + + +def _plain(value): + """Server types as the plain Python values the SQLite path returns.""" + if isinstance(value, Decimal): + return float(value) + return value + + +def query_warehouse(conn, sql: str, params: Sequence | None = None) -> pd.DataFrame: + """Run a SELECT against the warehouse and return the result as a frame. + + NUMERIC results (AVG of an integer column, for instance) arrive as Decimal + and are returned as float, and dates as ISO strings, so a frame from either + engine can be compared with the other directly. + """ + statement = to_postgres(sql) + if params: + statement = statement.replace("?", "%s") + with conn.cursor() as cursor: + cursor.execute(statement, list(params) if params else None) + columns = [column.name for column in cursor.description] + rows = [[_plain(value) for value in row] for row in cursor.fetchall()] + conn.rollback() + frame = pd.DataFrame(rows, columns=columns) + for column in frame.columns: + if frame[column].map(lambda value: isinstance(value, dt.date)).any(): + frame[column] = frame[column].map( + lambda value: value.isoformat() if isinstance(value, dt.date) else value + ) + elif frame[column].dtype == object and frame[column].map( + lambda value: value is None or isinstance(value, float) + ).all(): + frame[column] = frame[column].astype(float) + return frame + + +def run_warehouse_query_file( + conn, + name: str, + directory: Path | None = None, + params: Sequence | None = None, +) -> pd.DataFrame: + """Run a named analytical query from sql/analytics on the warehouse.""" + folder = directory or ANALYTICS_DIR + path = folder / f"{name}.sql" + if not path.exists(): + available = [candidate.stem for candidate in _sql_files(folder)] + raise ValueError(f"No query named '{name}' in {folder}; available: {available}") + return query_warehouse(conn, path.read_text(encoding="utf-8"), params) diff --git a/tests/test_warehouse.py b/tests/test_warehouse.py new file mode 100644 index 0000000..c1e7764 --- /dev/null +++ b/tests/test_warehouse.py @@ -0,0 +1,167 @@ +"""The PostgreSQL warehouse returns what the SQLite database returns. + +The translation tests run everywhere. The warehouse tests need a server and run +when ROOTSIGNAL_WAREHOUSE_URL is set, which CI does with a PostgreSQL service; +locally, `docker compose up -d warehouse` provides one. +""" + +from __future__ import annotations + +import os +import uuid + +import numpy as np +import pandas as pd +import pytest + +from rootsignal.modeling import build_commercial_mart +from rootsignal.sql import available_queries, build_database, query, run_query_file +from rootsignal.sql.database import ANALYTICS_DIR, MARTS_DIR, SCHEMA_PATH, STAGING_DIR +from rootsignal.sql.dialect import to_postgres +from rootsignal.sql.warehouse import ( + URL_VARIABLE, + build_warehouse, + query_warehouse, + run_warehouse_query_file, +) + +MART_KEY = ["date", "region_code", "category", "channel", "sales_type"] +STAGING_VIEWS = ["stg_sales", "stg_orders", "stg_inventory", "stg_targets", "stg_kam_targets"] + + +# -------------------------------------------------------------------------- +# Translation +# -------------------------------------------------------------------------- + + +def test_week_start_becomes_the_iso_monday() -> None: + sql = "SELECT DATE(m.date, 'weekday 0', '-6 days') AS week_start FROM t" + assert to_postgres(sql) == "SELECT CAST(DATE_TRUNC('week', m.date) AS DATE) AS week_start FROM t" + + +def test_year_month_and_real_are_translated() -> None: + sql = "SELECT STRFTIME('%Y-%m', date), CAST(x AS REAL) FROM t" + assert to_postgres(sql) == "SELECT TO_CHAR(date, 'YYYY-MM'), CAST(x AS DOUBLE PRECISION) FROM t" + + +def test_pragma_is_dropped() -> None: + assert "PRAGMA" not in to_postgres("PRAGMA foreign_keys = ON;\nCREATE TABLE t (a INTEGER);") + + +def test_an_untranslatable_construct_is_refused_not_passed_through() -> None: + with pytest.raises(ValueError, match="JULIANDAY"): + to_postgres("SELECT JULIANDAY(date) FROM t") + + +def test_a_construct_mentioned_only_in_a_comment_is_not_refused() -> None: + assert to_postgres("-- STRFTIME( is SQLite only\nSELECT 1") == "-- STRFTIME( is SQLite only\nSELECT 1" + + +@pytest.mark.parametrize( + "path", + [SCHEMA_PATH, *sorted(STAGING_DIR.glob("*.sql")), *sorted(MARTS_DIR.glob("*.sql")), + *sorted(ANALYTICS_DIR.glob("*.sql"))], + ids=lambda path: path.stem, +) +def test_every_sql_file_in_the_repository_translates(path) -> None: + """A new query using an unhandled construct fails here, not on the server.""" + to_postgres(path.read_text(encoding="utf-8")) + + +# -------------------------------------------------------------------------- +# The warehouse against the SQLite database +# -------------------------------------------------------------------------- + +needs_warehouse = pytest.mark.skipif( + not os.environ.get(URL_VARIABLE), + reason=f"{URL_VARIABLE} is not set; start one with `docker compose up -d warehouse`", +) + + +@pytest.fixture(scope="module") +def engines(tmp_path_factory, generated_dataset_dir): + """Both engines loaded from the same cleaned tables, in a throwaway schema.""" + from rootsignal.cleaning import clean_dataset + from rootsignal.ingestion import load_dataset + + tables = clean_dataset(load_dataset(generated_dataset_dir)).tables + schema = f"test_{uuid.uuid4().hex[:10]}" + warehouse = build_warehouse(tables, schema=schema) + yield tables, build_database(tables), warehouse + warehouse.rollback() + warehouse.execute(f'DROP SCHEMA "{schema}" CASCADE') + warehouse.commit() + warehouse.close() + + +def _assert_same(left: pd.DataFrame, right: pd.DataFrame, tolerance: dict[str, float] | None = None) -> None: + """Same columns, same row count, same values in the same order.""" + tolerance = tolerance or {} + assert list(left.columns) == list(right.columns) + assert len(left) == len(right) + left, right = left.reset_index(drop=True), right.reset_index(drop=True) + for column in left.columns: + a, b = left[column], right[column] + if pd.api.types.is_numeric_dtype(a) and pd.api.types.is_numeric_dtype(b): + np.testing.assert_allclose( + a.astype(float).to_numpy(), + b.astype(float).to_numpy(), + rtol=0, + atol=tolerance.get(column, 1e-9), + equal_nan=True, + err_msg=column, + ) + else: + assert a.astype(str).tolist() == b.astype(str).tolist(), column + + +@needs_warehouse +@pytest.mark.parametrize("view", STAGING_VIEWS) +def test_staging_views_match_sqlite(engines, view) -> None: + _, sqlite, warehouse = engines + lite = query(sqlite, f"SELECT * FROM {view}") + pg = query_warehouse(warehouse, f"SELECT * FROM {view}") + key = list(lite.columns) + _assert_same(lite.sort_values(key).reset_index(drop=True), pg.sort_values(key).reset_index(drop=True)) + + +@needs_warehouse +def test_warehouse_mart_matches_the_python_mart(engines) -> None: + """The warehouse is held to the same definition as the SQLite mart. + + aov may differ by one cent on exact half-cent values. The Python mart rounds + half to even, SQLite rounds the binary float, and PostgreSQL rounds the + decimal value half up, so 79.065 is 79.06, 79.06 and 79.07. Every other + column must agree exactly. + """ + tables, _, warehouse = engines + pg = query_warehouse(warehouse, "SELECT * FROM mart_commercial_daily") + python = build_commercial_mart(tables) + python["date"] = python["date"].astype(str) + pg = pg.sort_values(MART_KEY).reset_index(drop=True) + python = python.sort_values(MART_KEY).reset_index(drop=True)[pg.columns] + _assert_same(pg, python, tolerance={"aov": 0.01 + 1e-9}) + + +@needs_warehouse +@pytest.mark.parametrize("name", available_queries()) +def test_every_analytical_query_matches_sqlite_row_for_row(engines, name) -> None: + """Same rows in the same order: the queries sort on a full key.""" + _, sqlite, warehouse = engines + _assert_same(run_query_file(sqlite, name), run_warehouse_query_file(warehouse, name)) + + +@needs_warehouse +def test_a_load_that_breaks_a_foreign_key_leaves_the_warehouse_untouched(engines) -> None: + tables, _, warehouse = engines + schema = warehouse.execute("SELECT current_schema()").fetchone()[0] + before = warehouse.execute("SELECT COUNT(*) FROM fact_sales").fetchone()[0] + warehouse.rollback() + + broken = {name: frame.copy() for name, frame in tables.items()} + broken["fact_sales"].loc[broken["fact_sales"].index[0], "sku_id"] = "NO-SUCH-SKU" + with pytest.raises(Exception, match="foreign key"): + build_warehouse(broken, schema=schema) + + assert warehouse.execute("SELECT COUNT(*) FROM fact_sales").fetchone()[0] == before + warehouse.rollback() From 1d155110d09eece4d9af1050e2b7de078de48741 Mon Sep 17 00:00:00 2001 From: vidit choudhary Date: Thu, 24 Sep 2026 16:44:29 +0530 Subject: [PATCH 2/8] Add a pipeline: land to a lake, conform in Spark, load and publish land writes the published workbook and a CSV rendering of it to the raw zone. The lake is a local folder or an S3 bucket, laid out the same way. conform is jobs/conform_online_retail.py, a PySpark version of the Online Retail adapter. It imports nothing from the package, so the same file runs under spark-submit, Glue and EMR Serverless. On all 1,067,371 published lines it produces the same six tables as the pandas adapter, column by column (scripts/compare_spark_to_pandas.py); tests hold the two to each other on a fixture planting every defect the adapter handles. load runs the existing cleaning and validation on Spark's output and replaces the PostgreSQL warehouse only if no error remains: 3 errors before cleaning, 2,590 rows quarantined, 996,531 sales lines loaded. It also writes the cleaned model as Parquet typed to schema.sql, for Redshift. publish writes the analysis results to the warehouse as report tables, so a BI tool reads the project's findings rather than re-deriving them. docs/power_bi.md describes the report built on them. Spark runs in the project image, which now carries a Java runtime, because Spark cannot write files on Windows without a Hadoop helper binary. CI installs Java and runs a PostgreSQL service, so the Spark and warehouse tests run there instead of skipping. --- .github/workflows/ci.yml | 31 +++- .gitignore | 3 + Dockerfile | 8 +- docs/power_bi.md | 108 +++++++++++ jobs/conform_online_retail.py | 270 ++++++++++++++++++++++++++++ pyproject.toml | 15 ++ scripts/compare_spark_to_pandas.py | 126 +++++++++++++ scripts/run_pipeline.py | 74 ++++++++ src/rootsignal/pipeline/__init__.py | 35 ++++ src/rootsignal/pipeline/lake.py | 103 +++++++++++ src/rootsignal/pipeline/publish.py | 80 +++++++++ src/rootsignal/pipeline/stages.py | 238 ++++++++++++++++++++++++ tests/test_lake.py | 81 +++++++++ tests/test_spark_conform.py | 180 +++++++++++++++++++ 14 files changed, 1349 insertions(+), 3 deletions(-) create mode 100644 docs/power_bi.md create mode 100644 jobs/conform_online_retail.py create mode 100644 scripts/compare_spark_to_pandas.py create mode 100644 scripts/run_pipeline.py create mode 100644 src/rootsignal/pipeline/__init__.py create mode 100644 src/rootsignal/pipeline/lake.py create mode 100644 src/rootsignal/pipeline/publish.py create mode 100644 src/rootsignal/pipeline/stages.py create mode 100644 tests/test_lake.py create mode 100644 tests/test_spark_conform.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0453875..b63a7dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,26 @@ jobs: # the floor was the only version anyone had ever run. python-version: ["3.12", "3.13"] + # The warehouse tests compare every query on PostgreSQL with SQLite. Without + # a server they skip, so CI provides one rather than letting them pass by + # not running. + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: rootsignal + POSTGRES_PASSWORD: rootsignal + POSTGRES_DB: rootsignal + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U rootsignal" + --health-interval 5s + --health-retries 10 + + env: + ROOTSIGNAL_WAREHOUSE_URL: postgresql://rootsignal:rootsignal@localhost:5432/rootsignal + steps: - name: Checkout repository uses: actions/checkout@v5 @@ -29,14 +49,21 @@ jobs: python-version: ${{ matrix.python-version }} cache: pip + # Spark runs on the JVM; the conform job's tests need one. + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: "17" + - name: Install project and development dependencies - run: python -m pip install -e ".[dev]" + run: python -m pip install -e ".[dev,pipeline]" - name: Generate controlled sample dataset run: python scripts/generate_sample_data.py --output-dir data/raw/generated - name: Run test suite - run: pytest + run: pytest -rs - name: Run Ruff checks run: ruff check . diff --git a/.gitignore b/.gitignore index be3f4e1..5c2dc9b 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ mutants/ .vscode/ .idea/ .DS_Store + +# The pipeline's local lake (rebuilt by scripts/run_pipeline.py) +data/lake/ diff --git a/Dockerfile b/Dockerfile index c1440b5..62ce430 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,13 +22,19 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ WORKDIR /app +# Spark runs on the JVM. The conform job is tested in here, on Linux, which is +# also what Glue and EMR run it on. +RUN apt-get update \ + && apt-get install -y --no-install-recommends openjdk-17-jre-headless \ + && rm -rf /var/lib/apt/lists/* + # Dependencies are installed from pyproject.toml alone, against a placeholder # package, so that editing source code does not invalidate the layer holding # pandas, numpy and streamlit. COPY pyproject.toml README.md ./ RUN mkdir -p src/rootsignal \ && touch src/rootsignal/__init__.py \ - && pip install --no-cache-dir ".[dev,dashboard,ai]" \ + && pip install --no-cache-dir ".[dev,dashboard,ai,pipeline]" \ && rm -rf src COPY . . diff --git a/docs/power_bi.md b/docs/power_bi.md new file mode 100644 index 0000000..d48831e --- /dev/null +++ b/docs/power_bi.md @@ -0,0 +1,108 @@ +# Power BI on the RootSignal warehouse + +The pipeline loads the cleaned model and the analysis results into PostgreSQL. +This report reads them from there. It computes nothing that the project has +not already computed and tested: revenue comes from the fact table, and the +country contributions, the rate and mix split and the forecast accuracy come +from report tables that `scripts/run_pipeline.py` writes. + +## Connect + +1. Start the warehouse and fill it: + ```bash + docker compose up -d warehouse + docker compose run --rm pipeline + ``` +2. In Power BI Desktop: **Get data** → **PostgreSQL database**. + - Server: `localhost:5433` + - Database: `rootsignal` + - Data connectivity mode: **Import** +3. Credentials: **Database**, user `rootsignal`, password `rootsignal`. These + are the local container's, set in `docker-compose.yml`. +4. The local container has no TLS certificate. Power BI asks whether to connect + without encryption; for this local warehouse, **OK**. +5. Tick these, all in the `rootsignal` schema: + + | Table | What it is | + | --- | --- | + | `fact_sales` | one row per invoice line, after cleaning | + | `dim_date`, `dim_sku`, `dim_customer`, `dim_region` | the dimensions | + | `rpt_monthly_trade` | revenue, units, invoices and AOV per month | + | `rpt_country_contribution` | how much each country moved the latest month's revenue | + | `rpt_return_rate_components` | the return rate's movement split into rate, mix and interaction | + | `rpt_return_rate_split` | the same split, country by country | + | `rpt_returns_monthly` | units sold and returned per country and month | + | `rpt_forecast_accuracy` | backtested forecast error for each model against the naive baseline | + +## Model + +In **Model view**, relate the fact to its dimensions, many to one, single +direction: + +| From | To | +| --- | --- | +| `fact_sales[date]` | `dim_date[date]` | +| `fact_sales[sku_id]` | `dim_sku[sku_id]` | +| `fact_sales[customer_id]` | `dim_customer[customer_id]` | +| `fact_sales[region_code]` | `dim_region[region_code]` | + +Mark `dim_date` as the date table (**Table tools** → **Mark as date table**, +column `date`). The `rpt_` tables stand alone and need no relationships. + +## Measures + +Add these to `fact_sales`: + +```DAX +Net Sales = SUM ( fact_sales[net_sales] ) + +Units = SUM ( fact_sales[units] ) + +Invoices = DISTINCTCOUNT ( fact_sales[order_id] ) + +AOV = DIVIDE ( [Net Sales], [Invoices] ) + +Net Sales Previous Month = + CALCULATE ( [Net Sales], DATEADD ( dim_date[date], -1, MONTH ) ) + +Net Sales MoM % = + DIVIDE ( [Net Sales] - [Net Sales Previous Month], [Net Sales Previous Month] ) +``` + +`Invoices` is a distinct count on purpose. An invoice lists several products, +so summing invoice counts across products counts one invoice several times; +the SQL mart makes the same point. + +## Pages + +**Overview.** Cards for Net Sales, Invoices, AOV and Net Sales MoM %. A line +chart of Net Sales by `dim_date[date]` at month level. A slicer on +`dim_region[region_code]`. + +**What moved revenue.** A bar chart of `rpt_country_contribution`, axis +`segment`, value `contribution`, sorted by value. The United Kingdom accounts +for most of the movement, and the smaller bars show which other markets pulled +the other way. + +**Why the return rate moved.** A bar chart of `rpt_return_rate_components`, +axis `component`, value `net`. Most of the movement is `rate_effect`: countries +genuinely returning a different share, rather than demand shifting toward +countries that always return more (`mix_effect`). Beside it, a table of +`rpt_return_rate_split` with `segment`, `rate_before`, `rate_after`, +`rate_effect` and `mix_effect`. + +**Forecast accuracy.** A bar chart of `rpt_forecast_accuracy`, axis `model`, +value `wape`, with `wape_improvement_vs_baseline` as a tooltip. Lower WAPE is +better; `naive` is the baseline every model is measured against. + +## Things to keep in mind + +- `rpt_monthly_trade` and `rpt_returns_monthly` include an `is_complete` flag. + The dataset stops on 9 December 2011, so the last month is partial: filter on + `is_complete = TRUE` before comparing months, or December 2011 reads as a + collapse. +- Order, fulfilment, inventory and target columns are empty for this dataset. + It records sales only, and the project reports those analyses as unavailable + rather than approximating them. +- A return rate is not a fill rate. Returns are customers sending goods back, + not a warehouse failing to ship. diff --git a/jobs/conform_online_retail.py b/jobs/conform_online_retail.py new file mode 100644 index 0000000..d872840 --- /dev/null +++ b/jobs/conform_online_retail.py @@ -0,0 +1,270 @@ +"""Conform raw Online Retail II invoice lines into the RootSignal model, in Spark. + +This is the Spark form of rootsignal.adapters.online_retail.adapt. It reads the +raw zone, applies the same mapping, and writes one Parquet dataset per table to +the curated zone. Cleaning, validation and the warehouse load run after it, on +its output, exactly as they run on the pandas adapter's. + +It is deliberately a single file that imports nothing from the rootsignal +package, so the same script runs under spark-submit on a laptop, as an AWS Glue +job and on EMR Serverless without packaging the project. It also keeps to +Python 3.9 syntax, which is what EMR Serverless runs. + +Every rule matches the pandas adapter, down to how numbers are rounded: +pandas rounds by scaling, rounding half to even and scaling back, so that is +what rint(x * 100) / 100 does here. Spark's own round() rounds the decimal +value half up, and gives a different cent on an exact half cent. On Online +Retail II that never happens: prices carry at most two decimals and quantities +are whole, and scripts/compare_spark_to_pandas.py counts 0 of 1,044,420 sales +lines affected. The job rounds the pandas way regardless, so the two stay +equal on data where it does happen; the test fixture includes such a line. +tests/test_spark_conform.py asserts the two produce the same tables. + + spark-submit jobs/conform_online_retail.py --input --output +""" + +from __future__ import annotations + +import argparse +import json +import sys + +from pyspark.sql import DataFrame, SparkSession +from pyspark.sql import functions as F +from pyspark.sql import types as T + +RAW_SCHEMA = T.StructType( + [ + T.StructField("Invoice", T.StringType()), + T.StructField("StockCode", T.StringType()), + T.StructField("Description", T.StringType()), + T.StructField("Quantity", T.StringType()), + T.StructField("InvoiceDate", T.StringType()), + T.StructField("Price", T.StringType()), + T.StructField("Customer ID", T.StringType()), + T.StructField("Country", T.StringType()), + ] +) + +FACT_SALES_COLUMNS = [ + "order_id", "date", "customer_id", "region_code", "channel", "kam_id", + "sku_id", "category", "sales_type", "units", "unit_price", "discount_pct", "net_sales", +] +SALES_KEY = ["order_id", "sku_id", "sales_type"] + +# Files written per table. Left alone, Spark writes one file per shuffle +# partition, 200 by default: 200 files of about 100 KB for a million sales lines, +# which every reader, Redshift's COPY included, then has to open one by one. +OUTPUT_FILES = {"fact_sales": 4} + + +def round_half_even(column, places: int): + """NumPy's rounding: scale, round half to even, scale back.""" + scale = float(10**places) + return F.rint(column * F.lit(scale)) / F.lit(scale) + + +def first_in_file_order(column: str): + """pandas groupby 'first': the first non-null value, in source row order.""" + return F.min_by(F.col(column), F.when(F.col(column).isNotNull(), F.col("_row"))) + + +def read_raw(spark: SparkSession, path: str) -> DataFrame: + """Invoice lines as published, every value read as text and typed explicitly. + + multiLine keeps a quoted description containing a line break as one row, and + reads the file in one pass, so _row follows the order of lines in the file. + """ + raw = ( + spark.read.option("header", True) + .option("multiLine", True) + .option("escape", '"') + .schema(RAW_SCHEMA) + .csv(path) + .withColumn("_row", F.monotonically_increasing_id()) + ) + return raw.select( + F.col("_row"), + F.col("Invoice").alias("order_id"), + F.col("StockCode").alias("sku_id"), + F.col("Description").alias("sku_name"), + F.col("Quantity").cast("long").alias("units"), + F.to_timestamp("InvoiceDate").alias("timestamp"), + F.col("Price").cast("double").alias("unit_price"), + F.col("Customer ID").alias("customer_id_raw"), + F.col("Country").alias("region_code"), + ) + + +def conform_lines(raw: DataFrame) -> DataFrame: + """Derived columns on every line, before sales and returns are separated.""" + first_word = F.regexp_extract(F.upper(F.coalesce(F.col("sku_name"), F.lit("UNKNOWN"))), r"(?U)^\s*(\S+)", 1) + return ( + raw.withColumn("date", F.to_date("timestamp")) + .withColumn("category", F.when(first_word == "", "UNKNOWN").otherwise(first_word)) + # A fifth of lines have no customer. They are named rather than dropped. + .withColumn( + "customer_id", + F.coalesce(F.regexp_replace("customer_id_raw", r"\.0$", ""), F.lit("UNKNOWN")), + ) + .withColumn("channel", F.lit("Online")) + .withColumn("sales_type", F.lit("PRIMARY")) + .withColumn("kam_id", F.lit("UNASSIGNED")) + .withColumn( + "is_return", + (F.col("units") < 0) | F.upper(F.col("order_id")).startswith("C"), + ) + ) + + +def consolidate_repeated_lines(sales: DataFrame) -> tuple[DataFrame, int]: + """Combine lines repeating a product on the same invoice. + + Units and revenue are added and the price follows from them, so volume and + revenue are both unchanged. As in the pandas adapter, the price is + recomputed on every line whenever any line was combined. + """ + total = sales.count() + distinct = sales.select(*SALES_KEY).distinct().count() + repeated = total - distinct + if repeated == 0: + return sales.select(*FACT_SALES_COLUMNS), 0 + + grouped = sales.groupBy(*SALES_KEY).agg( + *(first_in_file_order(name).alias(name) + for name in ("date", "customer_id", "region_code", "channel", "kam_id", "category", "discount_pct")), + F.sum("units").alias("units"), + F.sum("net_sales").alias("net_sales"), + ) + grouped = grouped.withColumn( + "unit_price", + round_half_even( + F.col("net_sales") / F.when(F.col("units") != 0, F.col("units").cast("double")), 4 + ), + ) + return grouped.select(*FACT_SALES_COLUMNS), repeated + + +def build_dim_date(fact_sales: DataFrame) -> DataFrame: + return ( + fact_sales.select("date").where(F.col("date").isNotNull()).distinct() + .select( + "date", + F.weekofyear("date").alias("week"), + F.month("date").alias("month"), + F.concat(F.lit("Q"), F.quarter("date").cast("string")).alias("quarter"), + F.year("date").alias("year"), + ) + ) + + +def build_dim_sku(sales: DataFrame) -> DataFrame: + # Price comes from lines with a real one; samples and adjustments sit at or + # below zero and would drag a median negative. + firsts = sales.groupBy("sku_id").agg( + first_in_file_order("sku_name").alias("sku_name"), + first_in_file_order("category").alias("category"), + ) + medians = ( + sales.where(F.col("unit_price") > 0) + .groupBy("sku_id") + .agg(F.expr("percentile(unit_price, 0.5)").alias("list_price")) + ) + sku = firsts.join(medians, "sku_id", "left").fillna({"list_price": 0.0}) + return sku.select( + "sku_id", + F.coalesce("sku_name", F.lit("Not recorded")).alias("sku_name"), + "category", + F.lit("Standard").alias("sub_category"), + F.lit(1.0).alias("pack_size_kg"), + round_half_even(F.col("list_price") * F.lit(0.7), 4).alias("unit_cost"), + "list_price", + F.lit(True).alias("active_flag"), + ) + + +def build_dim_customer(sales: DataFrame) -> DataFrame: + customer = sales.groupBy("customer_id").agg(first_in_file_order("region_code").alias("region_code")) + return customer.select( + "customer_id", + F.when(F.col("customer_id") == "UNKNOWN", F.lit("Unidentified customer")) + .otherwise(F.concat(F.lit("Customer "), F.col("customer_id"))) + .alias("customer_name"), + F.lit("Retail").alias("customer_type"), + F.lit("Online").alias("channel"), + "region_code", + F.col("region_code").alias("city"), + F.col("region_code").alias("zone"), + F.lit("UNASSIGNED").alias("kam_id"), + ) + + +def build_dim_region(sales: DataFrame) -> DataFrame: + regions = sales.select("region_code").where(F.col("region_code").isNotNull()).distinct() + return regions.select("region_code", F.col("region_code").alias("city"), F.col("region_code").alias("zone")) + + +def conform(spark: SparkSession, input_path: str) -> tuple[dict, dict]: + """Every curated table, plus a summary of what the run did.""" + raw = read_raw(spark, input_path) + missing_keys = raw.where(F.col("order_id").isNull() | F.col("sku_id").isNull()).count() + if missing_keys: + raise ValueError(f"{missing_keys} raw line(s) have no invoice or stock code.") + + lines = conform_lines(raw).cache() + sales = ( + lines.where(~F.col("is_return")) + .withColumn("discount_pct", F.lit(0.0)) + .withColumn("net_sales", round_half_even(F.col("units") * F.col("unit_price"), 2)) + .cache() + ) + returns = lines.where(F.col("is_return")).select( + "order_id", "sku_id", "region_code", "customer_id", "date", "units", + F.abs("units").alias("returned_units"), + ) + + fact_sales, repeated = consolidate_repeated_lines(sales) + fact_sales = fact_sales.cache() + + tables = { + "fact_sales": fact_sales, + "dim_date": build_dim_date(fact_sales), + "dim_sku": build_dim_sku(sales), + "dim_customer": build_dim_customer(sales), + "dim_region": build_dim_region(sales), + "dim_kam": spark.createDataFrame([("UNASSIGNED", "Not recorded")], "kam_id string, kam_name string"), + "returns": returns, + } + summary = { + "source_rows": lines.count(), + "returned_lines": returns.count(), + "consolidated_lines": repeated, + "tables": {name: frame.count() for name, frame in tables.items()}, + } + return tables, summary + + +def main(argv: list[str] | None = None) -> None: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--input", required=True, help="Raw invoice CSV file or prefix.") + parser.add_argument("--output", required=True, help="Curated prefix; one Parquet dataset per table.") + # Glue passes its own arguments alongside these. + args, _ = parser.parse_known_args(argv) + + spark = ( + SparkSession.builder.appName("rootsignal-conform-online-retail") + .config("spark.sql.session.timeZone", "UTC") + .getOrCreate() + ) + tables, summary = conform(spark, args.input) + output = args.output.rstrip("/") + for name, frame in tables.items(): + frame.coalesce(OUTPUT_FILES.get(name, 1)).write.mode("overwrite").parquet(f"{output}/{name}") + spark.createDataFrame([(json.dumps(summary),)], "summary string").coalesce(1).write.mode( + "overwrite" + ).text(f"{output}/_summary") + print(json.dumps(summary, indent=2)) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/pyproject.toml b/pyproject.toml index 5bbd337..285bcce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ dev = [ "pytest>=8,<9", "ruff>=0.6,<1", + "moto[s3]>=5,<6", ] dashboard = [ @@ -30,6 +31,20 @@ ai = [ "openai>=1.50,<2", ] +# The warehouse: PostgreSQL through psycopg. +warehouse = [ + "psycopg[binary]>=3.2,<4", +] + +# The data pipeline: Spark for the conform step, Parquet for the curated zone, +# boto3 for S3, Glue and Redshift. Spark needs a Java runtime as well. +pipeline = [ + "pyspark>=3.5,<4", + "pyarrow>=15", + "boto3>=1.34,<2", + "psycopg[binary]>=3.2,<4", +] + [tool.pytest.ini_options] pythonpath = ["src"] testpaths = ["tests"] diff --git a/scripts/compare_spark_to_pandas.py b/scripts/compare_spark_to_pandas.py new file mode 100644 index 0000000..09c23f8 --- /dev/null +++ b/scripts/compare_spark_to_pandas.py @@ -0,0 +1,126 @@ +"""Compare the Spark conform job with the pandas adapter on the whole dataset. + +tests/test_spark_conform.py holds the two to each other on a planted fixture. +This does the same on all 1,067,371 published lines, table by table, and +reports every column that differs. Run it after the conform stage: + + docker compose run --rm pipeline python scripts/run_pipeline.py --stages land,conform + docker compose run --rm pipeline python scripts/compare_spark_to_pandas.py + +It also counts the lines where Spark's built-in round() would have disagreed +with the pandas-style rounding the job uses. On this dataset the count is 0. +""" + +from __future__ import annotations + +import argparse +import time + +import numpy as np +import pandas as pd + +from rootsignal.adapters.online_retail import adapt, read_raw +from rootsignal.console import use_utf8_output +from rootsignal.pipeline import RAW, Lake, read_curated +from rootsignal.pipeline.stages import RAW_CSV + +KEYS = { + "fact_sales": ["order_id", "sku_id", "sales_type"], + "dim_date": ["date"], + "dim_sku": ["sku_id"], + "dim_customer": ["customer_id"], + "dim_region": ["region_code"], + "dim_kam": ["kam_id"], +} + + +def compare(spark_table: pd.DataFrame, pandas_table: pd.DataFrame, key: list[str]) -> list[str]: + """Every difference between two tables, as readable lines. Empty when equal.""" + problems = [] + if sorted(spark_table.columns) != sorted(pandas_table.columns): + return [f"columns differ: {sorted(spark_table.columns)} vs {sorted(pandas_table.columns)}"] + if len(spark_table) != len(pandas_table): + problems.append(f"row count {len(spark_table):,} vs {len(pandas_table):,}") + + merged = spark_table.merge( + pandas_table, on=key, how="outer", suffixes=("_spark", "_pandas"), indicator=True + ) + unmatched = merged["_merge"] != "both" + if unmatched.any(): + problems.append(f"{int(unmatched.sum()):,} key(s) present on one side only") + both = merged.loc[~unmatched] + + for column in spark_table.columns: + if column in key: + continue + a, b = both[f"{column}_spark"], both[f"{column}_pandas"] + if pd.api.types.is_numeric_dtype(a) and pd.api.types.is_numeric_dtype(b) and a.dtype != bool: + x, y = a.astype(float).to_numpy(), b.astype(float).to_numpy() + gap = np.abs(np.where(np.isnan(x) & np.isnan(y), 0.0, x - y)) + differing = int((gap > 1e-9).sum() + (np.isnan(x) != np.isnan(y)).sum()) + if differing: + problems.append(f"{column}: {differing:,} row(s) differ, largest gap {np.nanmax(gap):.3g}") + else: + differing = int((a.astype(str).to_numpy() != b.astype(str).to_numpy()).sum()) + if differing: + problems.append(f"{column}: {differing:,} row(s) differ") + return problems + + +def spark_round_disagreements(raw_csv: str) -> tuple[int, int]: + """Sales lines where Spark's own round() would have given different revenue. + + Counted in Spark, on the job's own reading of the raw zone: round() against + the half-to-even rounding the job uses to match pandas. + """ + import importlib.util + + from pyspark.sql import SparkSession + from pyspark.sql import functions as F + + from rootsignal.pipeline.stages import JOB_PATH + + spec = importlib.util.spec_from_file_location("conform_online_retail", JOB_PATH) + job = importlib.util.module_from_spec(spec) + spec.loader.exec_module(job) + + spark = SparkSession.builder.appName("round-check").getOrCreate() + sales = job.conform_lines(job.read_raw(spark, raw_csv)).where(~F.col("is_return")) + revenue = F.col("units") * F.col("unit_price") + total = sales.count() + differing = sales.where(F.round(revenue, 2) != job.round_half_even(revenue, 2)).count() + spark.stop() + return differing, total + + +def main() -> None: + use_utf8_output() + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--lake", default="data/lake") + args = parser.parse_args() + + started = time.time() + spark_dataset, _ = read_curated(Lake(args.lake)) + raw = read_raw() + pandas_dataset = adapt(raw=raw) + print(f"{len(raw):,} published lines; both implementations read in {time.time() - started:.0f}s\n") + + failures = 0 + for name, key in KEYS.items(): + problems = compare(spark_dataset.tables[name], pandas_dataset.tables[name], key) + rows = len(spark_dataset.tables[name]) + print(f"{name:14s} {rows:>10,} rows {'identical' if not problems else 'DIFFERS'}") + for problem in problems: + print(f" {problem}") + failures += bool(problems) + + disagree, lines = spark_round_disagreements(Lake(args.lake).uri(RAW, RAW_CSV)) + print( + f"\nSpark's round() would have changed the revenue on {disagree:,} of {lines:,} sales lines " + f"({disagree / lines:.2%})." + ) + raise SystemExit(1 if failures else 0) + + +if __name__ == "__main__": + main() diff --git a/scripts/run_pipeline.py b/scripts/run_pipeline.py new file mode 100644 index 0000000..6e3be8f --- /dev/null +++ b/scripts/run_pipeline.py @@ -0,0 +1,74 @@ +"""Run the Online Retail II pipeline: land, conform in Spark, load, publish. + +Locally, the lake is a folder and Spark runs on this machine: + + docker compose up -d warehouse + docker compose run --rm pipeline + +On AWS the lake is an S3 bucket and the conform step runs as a Glue job; see +scripts/run_pipeline_aws.py. +""" + +from __future__ import annotations + +import argparse +import time + +from rootsignal.console import use_utf8_output +from rootsignal.pipeline import Lake, conform_locally, land, load, publish, read_curated + +STAGES = ("land", "conform", "load", "publish") + + +def main() -> None: + use_utf8_output() + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--lake", default="data/lake", help="Local folder for the raw and curated zones.") + parser.add_argument("--warehouse-url", default=None, help="Defaults to ROOTSIGNAL_WAREHOUSE_URL.") + parser.add_argument("--schema", default="rootsignal") + parser.add_argument("--stages", default=",".join(STAGES), help="Comma-separated subset of land,conform,load,publish.") + args = parser.parse_args() + + stages = [stage.strip() for stage in args.stages.split(",") if stage.strip()] + unknown = sorted(set(stages) - set(STAGES)) + if unknown: + parser.error(f"Unknown stage(s): {unknown}") + + lake = Lake(args.lake) + started = time.time() + + if "land" in stages: + landed = land(lake) + print(f"land {landed['raw_rows']:,} raw lines to {landed['raw_csv']}") + + if "conform" in stages: + summary = conform_locally(lake) + print( + f"conform {summary['source_rows']:,} lines in, {summary['returned_lines']:,} returns held apart, " + f"{summary['consolidated_lines']:,} repeated lines combined" + ) + for name, rows in summary["tables"].items(): + print(f" {name:14s} {rows:>10,}") + + if "load" in stages or "publish" in stages: + dataset, returns = read_curated(lake) + report = load(dataset, url=args.warehouse_url, schema=args.schema, lake=lake) + print( + f"load {report.errors_before_cleaning} validation error(s) before cleaning, " + f"{report.quarantined:,} row(s) quarantined, 0 after" + ) + for name, rows in report.loaded.items(): + if rows: + print(f" {name:14s} {rows:>10,}") + + if "publish" in stages: + published = publish(report.tables, returns, url=args.warehouse_url, schema=args.schema) + print("publish report tables") + for name, rows in published.items(): + print(f" {name:28s} {rows:>6,}") + + print(f"\nDone in {time.time() - started:.0f}s.") + + +if __name__ == "__main__": + main() diff --git a/src/rootsignal/pipeline/__init__.py b/src/rootsignal/pipeline/__init__.py new file mode 100644 index 0000000..92b9448 --- /dev/null +++ b/src/rootsignal/pipeline/__init__.py @@ -0,0 +1,35 @@ +"""From the published source to the warehouse: land, conform in Spark, load, publish.""" + +from .lake import CLEAN, CURATED, JOBS, RAW, Lake +from .stages import ( + LoadReport, + clean, + conform_locally, + land, + load, + publish, + publish_job, + read_curated, + read_summary, + write_clean_zone, + write_raw_csv, +) + +__all__ = [ + "CLEAN", + "CURATED", + "JOBS", + "RAW", + "Lake", + "LoadReport", + "clean", + "conform_locally", + "land", + "load", + "publish", + "publish_job", + "read_curated", + "read_summary", + "write_clean_zone", + "write_raw_csv", +] diff --git a/src/rootsignal/pipeline/lake.py b/src/rootsignal/pipeline/lake.py new file mode 100644 index 0000000..fd20bcc --- /dev/null +++ b/src/rootsignal/pipeline/lake.py @@ -0,0 +1,103 @@ +"""Where the pipeline keeps its files: a local folder, or an S3 bucket. + +Both are laid out the same way, one prefix per zone: + + raw/online_retail/ the source as published, plus a CSV Spark can read + curated/online_retail/ one Parquet dataset per conformed table + clean/online_retail/ the cleaned model, typed as schema.sql, for Redshift + jobs/ the Spark script, for Glue or EMR to fetch + +The rest of the pipeline only asks for a zone's URI, puts a file into one, or +fetches one to local disk, so a run on a laptop and a run against S3 differ in +the root they were given and nothing else. +""" + +from __future__ import annotations + +import shutil +from dataclasses import dataclass +from pathlib import Path + +RAW = "raw/online_retail" +CURATED = "curated/online_retail" +CLEAN = "clean/online_retail" +JOBS = "jobs" + + +@dataclass(frozen=True) +class Lake: + root: str + + @property + def is_s3(self) -> bool: + return self.root.startswith("s3://") + + @property + def bucket(self) -> str: + return self.root.removeprefix("s3://").split("/", 1)[0] + + @property + def prefix(self) -> str: + parts = self.root.removeprefix("s3://").split("/", 1) + return parts[1].strip("/") if len(parts) > 1 else "" + + def key(self, *parts: str) -> str: + return "/".join(part.strip("/") for part in (self.prefix, *parts) if part) + + def uri(self, *parts: str) -> str: + """The location Spark reads or writes: s3:// on AWS, a path locally.""" + if self.is_s3: + return f"s3://{self.bucket}/{self.key(*parts)}" + return str(Path(self.root, *parts)) + + def _client(self): + import boto3 + + return boto3.client("s3") + + def put(self, local: str | Path, *parts: str) -> str: + """Copy a local file into the lake and return its URI.""" + if self.is_s3: + self._client().upload_file(str(local), self.bucket, self.key(*parts)) + else: + target = Path(self.root, *parts) + target.parent.mkdir(parents=True, exist_ok=True) + if Path(local).resolve() != target.resolve(): + shutil.copyfile(local, target) + return self.uri(*parts) + + def _objects(self, *parts: str): + """Keys at exactly this path, or under it as a folder. + + A bare prefix would also match siblings: fact_sales would pick up + fact_sales_backup. + """ + base = self.key(*parts) + paginator = self._client().get_paginator("list_objects_v2") + for page in paginator.paginate(Bucket=self.bucket, Prefix=base): + for item in page.get("Contents", []): + if item["Key"] == base or item["Key"].startswith(base + "/"): + yield item["Key"] + + def exists(self, *parts: str) -> bool: + if not self.is_s3: + return Path(self.root, *parts).exists() + return next(self._objects(*parts), None) is not None + + def fetch(self, *parts: str, into: str | Path) -> Path: + """A local copy of a file or folder. Local lakes are read in place.""" + if not self.is_s3: + return Path(self.root, *parts) + client = self._client() + base = self.key(*parts) + target = Path(into, *parts) + found = False + for key in self._objects(*parts): + relative = key[len(base):].lstrip("/") + destination = target / relative if relative else target + destination.parent.mkdir(parents=True, exist_ok=True) + client.download_file(self.bucket, key, str(destination)) + found = True + if not found: + raise FileNotFoundError(f"Nothing in the lake at {self.uri(*parts)}") + return target diff --git a/src/rootsignal/pipeline/publish.py b/src/rootsignal/pipeline/publish.py new file mode 100644 index 0000000..963f4ad --- /dev/null +++ b/src/rootsignal/pipeline/publish.py @@ -0,0 +1,80 @@ +"""The analyses the warehouse serves alongside the model. + +The fact and dimension tables let a BI tool slice revenue, but the findings this +project exists for, which segments moved a metric and whether a rate moved +because segments changed or because the mix did, come from the Python layers. +These are computed here once, with the same calls and parameters as +scripts/run_external_dataset.py, and written to the warehouse as report tables, +so a dashboard reads the project's results rather than re-deriving them. +""" + +from __future__ import annotations + +import pandas as pd + +from ..adapters.online_retail import returns_by_period +from ..analysis import calculate_trend, summarise_by_period +from ..decomposition import component_coherence, decompose_additive, decompose_movement +from ..forecasting import ( + build_daily_series, + compare_against_baseline, + extract_metric, + rolling_origin_evaluate, + summarise_backtest, +) + + +def analysis_tables(tables: dict[str, pd.DataFrame], returns: pd.DataFrame) -> dict[str, pd.DataFrame]: + """Every report table, keyed by the name it is published under.""" + sales = tables["fact_sales"] + + monthly = summarise_by_period(tables, period="month") + trend = calculate_trend(monthly, "net_sales") + + by_country = summarise_by_period(tables, period="month", group_by=["region_code"]) + contribution = decompose_additive(by_country, "net_sales", ["region_code"]) + + # Countries with almost no volume swing between extremes; min_share folds + # them into one bucket rather than dropping volume, as the script does. + returns_monthly = returns_by_period(sales, returns, period="month", group_by=["region_code"]) + return_split = decompose_movement(returns_monthly, "return_rate", ["region_code"], min_share=0.01) + + daily = build_daily_series(sales, metrics=["net_sales", "units"]) + accuracy = compare_against_baseline( + summarise_backtest( + rolling_origin_evaluate(extract_metric(daily, "net_sales"), horizon=7, initial_train=56, step=14) + ) + ) + + return { + "rpt_monthly_trade": monthly, + "rpt_monthly_trend": trend, + "rpt_country_contribution": contribution, + "rpt_returns_monthly": returns_monthly, + "rpt_return_rate_split": return_split, + "rpt_return_rate_components": component_coherence(return_split), + "rpt_forecast_accuracy": accuracy, + } + + +def flatten(frame: pd.DataFrame) -> pd.DataFrame: + """A frame a SQL table can hold: plain columns, no index, no Python objects. + + Periods and timestamps become dates, and anything else that is not a + number, a boolean or a date is written as text. + """ + out = frame.reset_index(drop=True).copy() + out.columns = [str(column) for column in out.columns] + for column in out.columns: + series = out[column] + if isinstance(series.dtype, pd.PeriodDtype): + out[column] = series.dt.start_time.dt.date + elif pd.api.types.is_datetime64_any_dtype(series): + out[column] = series.dt.date + elif pd.api.types.is_bool_dtype(series) or pd.api.types.is_numeric_dtype(series): + continue + elif series.map(lambda value: value is None or hasattr(value, "isoformat")).all(): + continue + else: + out[column] = series.map(lambda value: None if value is None or value != value else str(value)) + return out diff --git a/src/rootsignal/pipeline/stages.py b/src/rootsignal/pipeline/stages.py new file mode 100644 index 0000000..3550992 --- /dev/null +++ b/src/rootsignal/pipeline/stages.py @@ -0,0 +1,238 @@ +"""The stages that take Online Retail II from its source to the warehouse. + + land the published workbook into the raw zone, with a CSV rendering of + it, because Spark has no Excel reader + conform jobs/conform_online_retail.py, in Spark: raw zone to curated zone + load curated Parquet through the existing cleaning and validation, into + PostgreSQL and the clean zone; nothing is loaded if validation + still finds an error + publish the analysis results, written to the warehouse as report tables + +Cleaning stays in pandas on purpose. It is the part of the project with the +audit trail and the quarantine, it runs in seconds on the conformed tables, and +rewriting it for Spark would create a second definition of every rule. +""" + +from __future__ import annotations + +import json +import subprocess +import sys +import tempfile +from dataclasses import dataclass, field +from pathlib import Path + +import pandas as pd + +from ..adapters.base import AdaptedDataset, DatasetCapabilities, empty_fact +from ..adapters.online_retail import ARCHIVE_NAME, CAPABILITIES, DEFAULT_CACHE, download, read_raw +from .lake import CLEAN, CURATED, JOBS, RAW, Lake + +JOB_PATH = Path(__file__).resolve().parents[3] / "jobs" / "conform_online_retail.py" +RAW_CSV = "invoices.csv" +CURATED_TABLES = ("fact_sales", "dim_date", "dim_sku", "dim_customer", "dim_region", "dim_kam") +EMPTY_FACTS = ("fact_orders", "fact_inventory", "fact_targets", "fact_kam_targets") +DATE_COLUMNS = {"fact_sales": ["date"], "dim_date": ["date"], "returns": ["date"]} + + +def write_raw_csv(raw: pd.DataFrame, path: str | Path) -> Path: + """The workbook's rows as CSV, values unchanged. + + Numbers are written at full precision and a missing value as an empty + field, which the Spark job reads back as null. Nothing is cleaned here. + """ + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + raw.to_csv(path, index=False) + return path + + +def land(lake: Lake, cache_dir: str | Path = DEFAULT_CACHE) -> dict: + """Put the source into the raw zone: the archive as published, and a CSV.""" + archive = download(cache_dir) + raw = read_raw(cache_dir) + lake.put(archive, RAW, ARCHIVE_NAME) + if lake.is_s3: + with tempfile.TemporaryDirectory() as scratch: + lake.put(write_raw_csv(raw, Path(scratch, RAW_CSV)), RAW, RAW_CSV) + else: + write_raw_csv(raw, Path(lake.uri(RAW, RAW_CSV))) + return {"raw_rows": len(raw), "raw_csv": lake.uri(RAW, RAW_CSV)} + + +def publish_job(lake: Lake) -> str: + """Put the Spark script where Glue or EMR can fetch it.""" + return lake.put(JOB_PATH, JOBS, JOB_PATH.name) + + +def conform_locally(lake: Lake) -> dict: + """Run the Spark job on this machine against a local lake.""" + if lake.is_s3: + raise ValueError("Run the conform stage on AWS for an S3 lake; see rootsignal.pipeline.aws.") + completed = subprocess.run( + [sys.executable, str(JOB_PATH), "--input", lake.uri(RAW, RAW_CSV), "--output", lake.uri(CURATED)], + capture_output=True, + text=True, + ) + if completed.returncode != 0: + raise RuntimeError(f"The Spark job failed:\n{completed.stderr[-4000:]}") + return read_summary(lake) + + +def read_summary(lake: Lake, into: str | Path | None = None) -> dict: + """The job's own record of what it read and wrote.""" + with tempfile.TemporaryDirectory() as scratch: + folder = lake.fetch(CURATED, "_summary", into=into or scratch) + text = "".join(path.read_text(encoding="utf-8") for path in sorted(folder.glob("part-*"))) + return json.loads(text) + + +def _read_table(folder: Path, name: str) -> pd.DataFrame: + frame = pd.read_parquet(folder / name) + for column in DATE_COLUMNS.get(name, []): + frame[column] = pd.to_datetime(frame[column]).dt.date + return frame + + +def read_curated( + lake: Lake, into: str | Path | None = None, zone: str = CURATED +) -> tuple[AdaptedDataset, pd.DataFrame]: + """The conformed tables as the pandas adapter would return them, and the returns. + + The facts this dataset cannot supply are added empty, as the adapter adds + them, so cleaning and validation see the same shape either way. `zone` reads + a Spark run written somewhere other than the curated zone. + """ + scratch = tempfile.TemporaryDirectory() if into is None else None + try: + folder = lake.fetch(zone, into=into or scratch.name) + tables = {name: _read_table(folder, name) for name in CURATED_TABLES} + returns = _read_table(folder, "returns") + summary = json.loads( + "".join(path.read_text(encoding="utf-8") for path in sorted((folder / "_summary").glob("part-*"))) + ) + finally: + if scratch is not None: + scratch.cleanup() + + for name in EMPTY_FACTS: + tables[name] = empty_fact(name) + + notes = CAPABILITIES.notes + ( + f"{summary['returned_lines']:,} of {summary['source_rows']:,} lines are returns and are " + "held separately from sales.", + f"{summary['consolidated_lines']:,} invoice lines repeat a product already on the same " + "invoice and are consolidated into one, adding units and weighting the price by quantity.", + "Conformed in Spark by jobs/conform_online_retail.py.", + ) + dataset = AdaptedDataset( + tables=tables, + capabilities=DatasetCapabilities( + name=CAPABILITIES.name, available_facts=CAPABILITIES.available_facts, notes=notes + ), + source_rows=summary["source_rows"], + ) + return dataset, returns + + +@dataclass +class LoadReport: + loaded: dict[str, int] + quarantined: int + errors_before_cleaning: int + audit: pd.DataFrame = field(repr=False) + tables: dict[str, pd.DataFrame] = field(repr=False, default_factory=dict) + + +def clean(dataset: AdaptedDataset): + """Clean and validate. Any error left after cleaning stops the pipeline.""" + from ..cleaning import clean_dataset + from ..validation import DatasetValidator + + before = DatasetValidator().validate(dataset.tables) + result = clean_dataset(dataset.tables) + after = DatasetValidator().validate(result.tables) + if after.errors(): + problems = "; ".join(f"{issue.table}.{issue.check}: {issue.message}" for issue in after.errors()) + raise ValueError(f"Not loading: validation still fails after cleaning. {problems}") + return result, len(before.errors()) + + +def write_clean_zone(lake: Lake, tables: dict[str, pd.DataFrame]) -> dict[str, str]: + """The cleaned model as Parquet, typed exactly as schema.sql declares it. + + This is what Redshift loads with COPY, so the types have to match the + tables: 32-bit integers for INTEGER, dates for DATE, 0/1 for flags. + """ + import pyarrow as pa + import pyarrow.parquet as pq + + from ..sql.database import LOAD_ORDER, SCHEMA_PATH + from ..sql.dialect import schema_columns + + arrow_types = {"TEXT": pa.string(), "INTEGER": pa.int32(), "REAL": pa.float64(), "DATE": pa.date32()} + declared = schema_columns(SCHEMA_PATH.read_text(encoding="utf-8")) + written = {} + with tempfile.TemporaryDirectory() as scratch: + for name in LOAD_ORDER: + frame = tables[name] + columns = declared[name] + arrays = [] + for column, kind in columns: + values = frame[column] + if kind == "INTEGER": + values = values.astype("int64") + arrays.append(pa.array(values.tolist(), type=arrow_types[kind])) + table = pa.Table.from_arrays(arrays, names=[column for column, _ in columns]) + local = Path(scratch, f"{name}.parquet") + pq.write_table(table, local) + written[name] = lake.put(local, CLEAN, name, "part-00000.parquet") + return written + + +def load( + dataset: AdaptedDataset, + url: str | None = None, + schema: str = "rootsignal", + lake: Lake | None = None, +) -> LoadReport: + """Clean, validate, and replace the warehouse schema with the result. + + Validation runs again after cleaning, and any remaining error stops the load + before the warehouse is touched. With a lake, the cleaned tables are also + written to its clean zone for Redshift. + """ + from ..sql.warehouse import build_warehouse + + result, errors_before = clean(dataset) + connection = build_warehouse(result.tables, url=url, schema=schema) + connection.close() + if lake is not None: + write_clean_zone(lake, result.tables) + return LoadReport( + loaded={name: len(frame) for name, frame in result.tables.items()}, + quarantined=sum(len(frame) for frame in result.quarantined.values()), + errors_before_cleaning=errors_before, + audit=result.audit, + tables=result.tables, + ) + + +def publish( + tables: dict[str, pd.DataFrame], + returns: pd.DataFrame, + url: str | None = None, + schema: str = "rootsignal", +) -> dict[str, int]: + """Write the analysis results into the warehouse as report tables.""" + from ..sql.warehouse import connect, write_report_table + from .publish import analysis_tables, flatten + + connection = connect(url, schema) + try: + return { + name: write_report_table(connection, name, flatten(frame)) + for name, frame in analysis_tables(tables, returns).items() + } + finally: + connection.close() diff --git a/tests/test_lake.py b/tests/test_lake.py new file mode 100644 index 0000000..021ad9c --- /dev/null +++ b/tests/test_lake.py @@ -0,0 +1,81 @@ +"""The lake behaves the same on a local folder and on S3. + +S3 is simulated with moto, so these run without an AWS account. +""" + +from __future__ import annotations + +import pytest + +from rootsignal.pipeline import CURATED, RAW, Lake + + +def test_a_local_lake_is_a_folder(tmp_path) -> None: + lake = Lake(str(tmp_path)) + source = tmp_path / "source.csv" + source.write_text("a,b\n1,2\n") + + uri = lake.put(source, RAW, "invoices.csv") + + assert uri == str(tmp_path / RAW / "invoices.csv") + assert lake.exists(RAW, "invoices.csv") + assert lake.fetch(RAW, "invoices.csv", into=tmp_path / "unused").read_text() == "a,b\n1,2\n" + + +def test_an_s3_lake_splits_bucket_and_prefix() -> None: + lake = Lake("s3://rootsignal-lake/project/") + assert lake.bucket == "rootsignal-lake" + assert lake.uri(CURATED, "fact_sales") == "s3://rootsignal-lake/project/curated/online_retail/fact_sales" + assert Lake("s3://rootsignal-lake").uri(RAW) == "s3://rootsignal-lake/raw/online_retail" + + +@pytest.fixture +def s3(monkeypatch): + moto = pytest.importorskip("moto") + import boto3 + + for name, value in { + "AWS_ACCESS_KEY_ID": "testing", + "AWS_SECRET_ACCESS_KEY": "testing", + "AWS_DEFAULT_REGION": "ap-south-1", + }.items(): + monkeypatch.setenv(name, value) + with moto.mock_aws(): + client = boto3.client("s3") + client.create_bucket( + Bucket="rootsignal-lake", CreateBucketConfiguration={"LocationConstraint": "ap-south-1"} + ) + yield client + + +def test_an_s3_lake_round_trips_a_prefix(s3, tmp_path) -> None: + lake = Lake("s3://rootsignal-lake/project") + for part in ("part-0000.parquet", "part-0001.parquet"): + source = tmp_path / part + source.write_bytes(part.encode()) + lake.put(source, CURATED, "fact_sales", part) + + assert lake.exists(CURATED, "fact_sales") + assert not lake.exists(CURATED, "dim_sku") + + fetched = lake.fetch(CURATED, "fact_sales", into=tmp_path / "copy") + assert sorted(path.name for path in fetched.iterdir()) == ["part-0000.parquet", "part-0001.parquet"] + assert (fetched / "part-0001.parquet").read_bytes() == b"part-0001.parquet" + + +def test_a_folder_does_not_pick_up_a_sibling_sharing_its_name(s3, tmp_path) -> None: + lake = Lake("s3://rootsignal-lake") + source = tmp_path / "part-0000.parquet" + source.write_bytes(b"x") + lake.put(source, CURATED, "fact_sales", "part-0000.parquet") + lake.put(source, CURATED, "fact_sales_backup", "part-0000.parquet") + + fetched = lake.fetch(CURATED, "fact_sales", into=tmp_path / "copy") + + assert [path.name for path in fetched.iterdir()] == ["part-0000.parquet"] + assert not (tmp_path / "copy" / CURATED / "fact_sales_backup").exists() + + +def test_fetching_nothing_from_s3_is_an_error_not_an_empty_folder(s3, tmp_path) -> None: + with pytest.raises(FileNotFoundError, match="curated/online_retail/returns"): + Lake("s3://rootsignal-lake").fetch(CURATED, "returns", into=tmp_path) diff --git a/tests/test_spark_conform.py b/tests/test_spark_conform.py new file mode 100644 index 0000000..6e57a0d --- /dev/null +++ b/tests/test_spark_conform.py @@ -0,0 +1,180 @@ +"""The Spark conform job produces the tables the pandas adapter produces. + +jobs/conform_online_retail.py is a second implementation of +rootsignal.adapters.online_retail.adapt, so it is held to the first one table by +table. The raw lines below plant every case the adapter handles: returns by +invoice prefix and by negative quantity, a missing customer, a product repeated +on one invoice, a zero price, descriptions that are missing, blank, quoted, +multi-line or padded, and a revenue that lands exactly on a half cent. + +Spark needs a Java runtime, and on Windows a Hadoop helper binary as well, so +these tests run where Spark can: in CI and in the project's container. +""" + +from __future__ import annotations + +import importlib.util +import os +import shutil + +import numpy as np +import pandas as pd +import pytest + +pyspark = pytest.importorskip("pyspark") + +if shutil.which("java") is None and not os.environ.get("JAVA_HOME"): + pytest.skip("Spark needs a Java runtime", allow_module_level=True) +if os.name == "nt" and not os.environ.get("HADOOP_HOME"): + pytest.skip("Spark cannot write files on Windows without HADOOP_HOME", allow_module_level=True) + +from rootsignal.adapters.online_retail import adapt, returns_frame # noqa: E402 +from rootsignal.pipeline import CURATED, RAW, Lake, read_curated, write_raw_csv # noqa: E402 +from rootsignal.pipeline.stages import JOB_PATH, RAW_CSV # noqa: E402 + +KEYS = { + "fact_sales": ["order_id", "sku_id", "sales_type"], + "dim_date": ["date"], + "dim_sku": ["sku_id"], + "dim_customer": ["customer_id"], + "dim_region": ["region_code"], + "dim_kam": ["kam_id"], +} + + +def raw_lines(repeat_a_product: bool = True) -> pd.DataFrame: + """Invoice lines shaped as pandas reads them from the workbook.""" + rows = [ + # Invoice, StockCode, Description, Quantity, InvoiceDate, Price, Customer ID, Country + (489434, 85048, "15CM CHRISTMAS GLASS BALL 20 LIGHTS", 12, "2009-12-01 07:45", 6.95, 13085.0, "United Kingdom"), + (489434, "79323P", "PINK CHERRY LIGHTS", 12, "2009-12-01 07:45", 6.75, 13085.0, "United Kingdom"), + (489434, "79323W", ' white "cherry" lights, boxed', 12, "2009-12-01 07:45", 6.75, 13085.0, "United Kingdom"), + (489435, 22350, "CAT BOWL\nwith lid", 12, "2009-12-01 07:46", 2.55, np.nan, "France"), + (489435, 22349, None, 24, "2009-12-01 07:46", 3.75, np.nan, "France"), + (489435, 22195, " ", 1, "2009-12-02 09:00", 0.0, np.nan, "France"), + (489436, 21755, "LOVE BUILDING BLOCK WORD", 2, "2009-12-02 10:03", 0.5025, 13078.0, "Germany"), + (489436, 85048, "15CM CHRISTMAS GLASS BALL 20 LIGHTS", 6, "2009-12-02 10:03", 7.95, 13078.0, "Germany"), + ("C489449", 22087, "PAPER BUNTING WHITE LACE", -12, "2009-12-03 10:33", 2.95, 16321.0, "Australia"), + (489450, 21755, "LOVE BUILDING BLOCK WORD", -2, "2009-12-03 11:00", 5.95, 13078.0, "Germany"), + (489451, "POST", "POSTAGE", 1, "2009-12-04 12:00", 18.0, 12682.0, "France"), + ] + if repeat_a_product: + # The same product twice on one invoice, at a different quantity and price. + rows.append((489434, 85048, "15CM CHRISTMAS GLASS BALL 20 LIGHTS", 5, "2009-12-01 07:45", 6.45, 13085.0, "United Kingdom")) + + frame = pd.DataFrame( + rows, + columns=["Invoice", "StockCode", "Description", "Quantity", "InvoiceDate", "Price", "Customer ID", "Country"], + ) + frame["InvoiceDate"] = pd.to_datetime(frame["InvoiceDate"]) + return frame + + +@pytest.fixture(scope="module") +def spark(): + from pyspark.sql import SparkSession + + session = ( + SparkSession.builder.master("local[1]") + .appName("rootsignal-tests") + .config("spark.sql.session.timeZone", "UTC") + .config("spark.ui.enabled", "false") + .getOrCreate() + ) + yield session + session.stop() + + +def load_job(): + spec = importlib.util.spec_from_file_location("conform_online_retail", JOB_PATH) + job = importlib.util.module_from_spec(spec) + spec.loader.exec_module(job) + return job + + +def run_job(spark, raw: pd.DataFrame, tmp_path): + """Land the lines as the pipeline does, run the job's entry point, read its output. + + main() picks up the test's session through getOrCreate, so this is the same + code path spark-submit and Glue run. + """ + lake = Lake(str(tmp_path / "lake")) + write_raw_csv(raw, lake.uri(RAW, RAW_CSV)) + load_job().main(["--input", lake.uri(RAW, RAW_CSV), "--output", lake.uri(CURATED)]) + return read_curated(lake) + + +def assert_tables_match(spark_table: pd.DataFrame, pandas_table: pd.DataFrame, key: list[str]) -> None: + assert sorted(spark_table.columns) == sorted(pandas_table.columns) + left = spark_table.sort_values(key).reset_index(drop=True) + right = pandas_table[list(spark_table.columns)].sort_values(key).reset_index(drop=True) + assert len(left) == len(right) + for column in left.columns: + a, b = left[column], right[column] + if pd.api.types.is_numeric_dtype(a) and pd.api.types.is_numeric_dtype(b) and a.dtype != bool: + np.testing.assert_allclose( + a.astype(float), b.astype(float), rtol=0, atol=1e-9, equal_nan=True, err_msg=column + ) + else: + assert a.astype(str).tolist() == b.astype(str).tolist(), column + + + +# Each Spark run takes tens of seconds, so each fixture is conformed once and +# shared by the tests that read it. + + +@pytest.fixture(scope="module") +def conformed(spark, tmp_path_factory): + raw = raw_lines() + dataset, returns = run_job(spark, raw, tmp_path_factory.mktemp("with_repeats")) + return raw, dataset, returns + + +@pytest.fixture(scope="module") +def conformed_without_repeats(spark, tmp_path_factory): + raw = raw_lines(repeat_a_product=False) + dataset, _ = run_job(spark, raw, tmp_path_factory.mktemp("without_repeats")) + return raw, dataset + + +@pytest.mark.parametrize("table", list(KEYS)) +def test_spark_matches_the_pandas_adapter(conformed, table) -> None: + raw, spark_dataset, _ = conformed + assert_tables_match(spark_dataset.tables[table], adapt(raw=raw).tables[table], KEYS[table]) + + +def test_without_repeated_lines_the_published_price_is_kept(conformed_without_repeats) -> None: + """The adapter only recomputes prices when it had lines to combine.""" + raw, spark_dataset = conformed_without_repeats + sales = spark_dataset.tables["fact_sales"] + assert sorted(sales["unit_price"].tolist()) == sorted( + raw.loc[(raw["Quantity"] > 0) & ~raw["Invoice"].astype(str).str.startswith("C"), "Price"].tolist() + ) + assert_tables_match(sales, adapt(raw=raw).tables["fact_sales"], KEYS["fact_sales"]) + + +def test_half_cent_revenue_rounds_the_way_pandas_rounds(spark, conformed) -> None: + """2 x 0.5025 is 1.005. pandas gives 1.0; Spark's own round() gives 1.01.""" + from pyspark.sql import functions as F + + assert spark.range(1).select(F.round(F.lit(2) * F.lit(0.5025), 2)).first()[0] == pytest.approx(1.01) + + _, spark_dataset, _ = conformed + sales = spark_dataset.tables["fact_sales"].set_index(["order_id", "sku_id"]) + assert sales.loc[("489436", "21755"), "net_sales"] == pytest.approx(1.0) + + +def test_returns_are_split_out_the_same_way(conformed) -> None: + raw, _, spark_returns = conformed + expected = returns_frame(raw=raw) + assert sorted(spark_returns["order_id"]) == sorted(expected["order_id"]) + assert spark_returns["returned_units"].sum() == expected["returned_units"].sum() + + +def test_summary_counts_what_the_job_did(conformed) -> None: + _, spark_dataset, _ = conformed + assert spark_dataset.source_rows == 12 + notes = " ".join(spark_dataset.capabilities.notes) + assert "2 of 12 lines are returns" in notes + assert "1 invoice lines repeat a product" in notes From eb08e857f2dc0a859e02e0e77244b70ce6ca9d16 Mon Sep 17 00:00:00 2001 From: vidit choudhary Date: Thu, 24 Sep 2026 17:11:19 +0530 Subject: [PATCH 3/8] Run the pipeline against S3 and Redshift Serverless scripts/run_pipeline_aws.py runs the same stages with the lake in S3 and Redshift Serverless as a second warehouse, one subcommand per step, so nothing that bills is created without being named. Run on 24 September 2026: the curated zone read from S3, the load wrote the cleaned model to the S3 clean zone as Parquet, and Redshift loaded 996,531 sales lines from it with COPY. On the generated sample, all seven analytical queries return exactly what SQLite returns. Redshift is created at 8 RPU with a usage limit that deactivates it after 4 RPU-hours in a day, and teardown removes it. The bucket and IAM roles are kept; they cost nothing. The Glue and EMR Serverless subcommands are written but have not run: this account is denied Glue, and its EMR Serverless vCPU quota was not yet in effect. --- scripts/run_pipeline_aws.py | 187 +++++++++++++ src/rootsignal/pipeline/aws.py | 461 +++++++++++++++++++++++++++++++++ 2 files changed, 648 insertions(+) create mode 100644 scripts/run_pipeline_aws.py create mode 100644 src/rootsignal/pipeline/aws.py diff --git a/scripts/run_pipeline_aws.py b/scripts/run_pipeline_aws.py new file mode 100644 index 0000000..4614e86 --- /dev/null +++ b/scripts/run_pipeline_aws.py @@ -0,0 +1,187 @@ +"""Run the pipeline on AWS: S3, Glue, EMR Serverless and Redshift Serverless. + +Each step is a subcommand, so a run can be stopped, inspected and resumed, and +nothing that bills is created without being asked for by name: + + python scripts/run_pipeline_aws.py setup bucket and IAM roles (free) + python scripts/run_pipeline_aws.py land raw zone to S3 (free tier) + python scripts/run_pipeline_aws.py glue conform in Glue (bills) + python scripts/run_pipeline_aws.py emr conform on EMR Serverless (bills) + python scripts/run_pipeline_aws.py load clean, into PostgreSQL and the S3 clean zone + python scripts/run_pipeline_aws.py redshift load Redshift and check it (bills) + python scripts/run_pipeline_aws.py status everything that can bill + python scripts/run_pipeline_aws.py teardown remove everything that can bill + +Credentials come from the AWS CLI configuration, as for any boto3 program. +""" + +from __future__ import annotations + +import argparse +import tempfile +import time + +import numpy as np +import pandas as pd + +from rootsignal.console import use_utf8_output +from rootsignal.pipeline import Lake, land, load, publish, read_curated +from rootsignal.pipeline import aws + +EMR_OUTPUT = "curated_emr/online_retail" + + +def lake_for(bucket: str | None) -> Lake: + return Lake(f"s3://{bucket or aws.default_bucket()}") + + +def roles(bucket: str) -> dict[str, str]: + return { + "glue": aws.ensure_role( + aws.GLUE_ROLE, + ["glue.amazonaws.com"], + bucket, + write=True, + managed=("arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole",), + ), + "emr": aws.ensure_role(aws.EMR_ROLE, ["emr-serverless.amazonaws.com"], bucket, write=True), + "redshift": aws.ensure_role( + aws.REDSHIFT_ROLE, + ["redshift.amazonaws.com", "redshift-serverless.amazonaws.com"], + bucket, + write=False, + ), + } + + +def show_run(run: aws.SparkRun) -> None: + print(f"{run.engine}: {run.state} in {run.seconds:.0f}s, {run.usage}") + print(f" output {run.output}") + if run.error: + print(f" error {run.error}") + + +def compare_frames(left: pd.DataFrame, right: pd.DataFrame, tolerance: dict[str, float]) -> list[str]: + """Columns that differ between two results of the same query.""" + if list(left.columns) != list(right.columns): + return [f"columns {list(left.columns)} vs {list(right.columns)}"] + if len(left) != len(right): + return [f"{len(left)} rows vs {len(right)}"] + problems = [] + left, right = left.reset_index(drop=True), right.reset_index(drop=True) + for column in left.columns: + a, b = left[column], right[column] + numeric_a = pd.to_numeric(a, errors="coerce") + numeric_b = pd.to_numeric(b, errors="coerce") + if numeric_a.notna().sum() == a.notna().sum() and numeric_b.notna().sum() == b.notna().sum(): + x, y = numeric_a.astype(float).to_numpy(), numeric_b.astype(float).to_numpy() + gap = np.abs(np.where(np.isnan(x) & np.isnan(y), 0.0, x - y)) + bad = int((gap > tolerance.get(column, 1e-9)).sum() + (np.isnan(x) != np.isnan(y)).sum()) + else: + bad = int((a.astype(str).to_numpy() != b.astype(str).to_numpy()).sum()) + if bad: + problems.append(f"{column}: {bad} row(s)") + return problems + + +def check_redshift_against_sqlite(schema: str) -> int: + """Every query on the generated sample, Redshift against SQLite.""" + from rootsignal.dataset import load_tables_for_analysis + from rootsignal.sql import available_queries, build_database, run_query_file + + sqlite = build_database(load_tables_for_analysis("data/raw/generated")) + failures = 0 + for name in available_queries(): + problems = compare_frames(aws.query_redshift(schema, name), run_query_file(sqlite, name), {}) + print(f" {name:24s} {'identical' if not problems else 'DIFFERS: ' + '; '.join(problems)}") + failures += bool(problems) + return failures + + +def main() -> None: + use_utf8_output() + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("step", choices=["setup", "land", "glue", "emr", "load", "redshift", "status", "teardown"]) + parser.add_argument("--bucket", default=None, help="Defaults to rootsignal-lake--.") + parser.add_argument("--warehouse-url", default=None, help="PostgreSQL; defaults to ROOTSIGNAL_WAREHOUSE_URL.") + args = parser.parse_args() + started = time.time() + + if args.step == "status": + found = aws.billable_resources() + print("\n".join(found) if found else "Nothing that can bill.") + return + if args.step == "teardown": + removed = aws.teardown() + print("\n".join(f"removed {item}" for item in removed) if removed else "Nothing to remove.") + left = aws.billable_resources() + print("Still present: " + ", ".join(left) if left else "Nothing that can bill is left.") + return + + bucket = args.bucket or aws.default_bucket() + lake = lake_for(bucket) + + if args.step == "setup": + aws.ensure_bucket(bucket) + for name, arn in roles(bucket).items(): + print(f"role {name:8s} {arn}") + print(f"lake {lake.root}") + + elif args.step == "land": + landed = land(lake) + print(f"{landed['raw_rows']:,} raw lines to {landed['raw_csv']}") + + elif args.step == "glue": + show_run(aws.run_glue(lake, roles(bucket)["glue"])) + + elif args.step == "emr": + show_run(aws.run_emr(lake, roles(bucket)["emr"], lake.uri(EMR_OUTPUT))) + # The same script on a second engine should write the same tables. + with tempfile.TemporaryDirectory() as scratch: + glue_dataset, _ = read_curated(lake, into=f"{scratch}/glue") + emr_dataset, _ = read_curated(lake, into=f"{scratch}/emr", zone=EMR_OUTPUT) + for name, frame in glue_dataset.tables.items(): + if frame.empty: + continue + key = list(frame.columns[:1]) if name != "fact_sales" else ["order_id", "sku_id", "sales_type"] + problems = compare_frames( + frame.sort_values(key).reset_index(drop=True), + emr_dataset.tables[name].sort_values(key).reset_index(drop=True), + {"net_sales": 1e-6, "unit_price": 1e-6}, + ) + print(f" EMR vs Glue {name:14s} {'identical' if not problems else 'DIFFERS: ' + '; '.join(problems)}") + + elif args.step == "load": + dataset, returns = read_curated(lake) + report = load(dataset, url=args.warehouse_url, lake=lake) + published = publish(report.tables, returns, url=args.warehouse_url) + print( + f"{report.errors_before_cleaning} validation errors before cleaning, " + f"{report.quarantined:,} rows quarantined, 0 after" + ) + print(f"PostgreSQL: {report.loaded['fact_sales']:,} sales lines, {len(published)} report tables") + print(f"clean zone: {lake.uri('clean/online_retail')}") + + elif args.step == "redshift": + role = roles(bucket)["redshift"] + aws.ensure_redshift(role) + counts = aws.load_redshift(lake, "online_retail") + print("Redshift online_retail: " + ", ".join(f"{k} {v:,}" for k, v in counts.items() if v)) + + # The generated sample fills every fact, so every query has rows to compare. + from rootsignal.dataset import load_tables_for_analysis + from rootsignal.pipeline import write_clean_zone + + sample = load_tables_for_analysis("data/raw/generated") + sample_lake = Lake(f"s3://{bucket}/sample") + write_clean_zone(sample_lake, sample) + aws.load_redshift(sample_lake, "sample") + print("Redshift against SQLite, every analytical query on the generated sample:") + failures = check_redshift_against_sqlite("sample") + print("all identical" if not failures else f"{failures} quer(ies) differ") + + print(f"\n{time.time() - started:.0f}s") + + +if __name__ == "__main__": + main() diff --git a/src/rootsignal/pipeline/aws.py b/src/rootsignal/pipeline/aws.py new file mode 100644 index 0000000..1ca41f5 --- /dev/null +++ b/src/rootsignal/pipeline/aws.py @@ -0,0 +1,461 @@ +"""The same pipeline on AWS: S3 for the lake, Glue and EMR Serverless for Spark, +Redshift Serverless as the warehouse. + +Nothing here changes what the pipeline computes. The Spark script is the one +tests/test_spark_conform.py holds to the pandas adapter, uploaded unchanged; +the SQL is the repository's, translated by rootsignal.sql.dialect.to_redshift. + +Everything that bills is created with a ceiling and removed by teardown(): + + Glue 2 workers, a 20-minute timeout + EMR Serverless capped at 8 vCPU, stops itself after 2 idle minutes + Redshift 8 RPU, with a usage limit that switches the workgroup off + after 4 RPU-hours in a day + +The bucket and the IAM roles cost nothing to keep and are left in place. +""" + +from __future__ import annotations + +import json +import time +from dataclasses import dataclass + +import pandas as pd + +from ..sql.database import ANALYTICS_DIR, LOAD_ORDER, MARTS_DIR, SCHEMA_PATH, STAGING_DIR, _sql_files +from ..sql.dialect import _strip_comments, to_redshift +from .lake import CLEAN, CURATED, RAW, Lake +from .stages import RAW_CSV, publish_job + +PREFIX = "rootsignal" +GLUE_JOB = f"{PREFIX}-conform" +EMR_APP = f"{PREFIX}-conform" +NAMESPACE = f"{PREFIX}-ns" +WORKGROUP = f"{PREFIX}-wg" +DATABASE = "dev" + +GLUE_ROLE = f"{PREFIX}-glue" +EMR_ROLE = f"{PREFIX}-emr-serverless" +REDSHIFT_ROLE = f"{PREFIX}-redshift" + + +def _client(service: str): + import boto3 + + return boto3.client(service) + + +def account_id() -> str: + return _client("sts").get_caller_identity()["Account"] + + +def region() -> str: + import boto3 + + return boto3.session.Session().region_name + + +def default_bucket() -> str: + return f"{PREFIX}-lake-{account_id()}-{region()}" + + +# -------------------------------------------------------------------------- +# Storage and access +# -------------------------------------------------------------------------- + + +def ensure_bucket(name: str) -> str: + """A private bucket for the lake. Public access is blocked outright.""" + s3 = _client("s3") + try: + s3.head_bucket(Bucket=name) + except s3.exceptions.ClientError: + s3.create_bucket(Bucket=name, CreateBucketConfiguration={"LocationConstraint": region()}) + s3.put_public_access_block( + Bucket=name, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": True, + "IgnorePublicAcls": True, + "BlockPublicPolicy": True, + "RestrictPublicBuckets": True, + }, + ) + return name + + +def _lake_policy(bucket: str, write: bool) -> dict: + actions = ["s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation"] + if write: + actions += ["s3:PutObject", "s3:DeleteObject"] + return { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": actions, + "Resource": [f"arn:aws:s3:::{bucket}", f"arn:aws:s3:::{bucket}/*"], + } + ], + } + + +def ensure_role( + name: str, services: list[str], bucket: str, write: bool, managed: tuple[str, ...] = () +) -> str: + """A role the services can assume, allowed into this bucket and nowhere else.""" + iam = _client("iam") + trust = { + "Version": "2012-10-17", + "Statement": [{"Effect": "Allow", "Principal": {"Service": services}, "Action": "sts:AssumeRole"}], + } + try: + arn = iam.get_role(RoleName=name)["Role"]["Arn"] + created = False + except iam.exceptions.NoSuchEntityException: + arn = iam.create_role(RoleName=name, AssumeRolePolicyDocument=json.dumps(trust))["Role"]["Arn"] + created = True + iam.put_role_policy( + RoleName=name, PolicyName=f"{PREFIX}-lake", PolicyDocument=json.dumps(_lake_policy(bucket, write)) + ) + for policy in managed: + iam.attach_role_policy(RoleName=name, PolicyArn=policy) + if created: + # A new role takes a few seconds to become assumable everywhere. + time.sleep(15) + return arn + + +# -------------------------------------------------------------------------- +# Spark: Glue and EMR Serverless +# -------------------------------------------------------------------------- + + +@dataclass +class SparkRun: + engine: str + state: str + seconds: float + usage: str + output: str + error: str = "" + + +def run_glue(lake: Lake, role_arn: str, output: str | None = None, workers: int = 2) -> SparkRun: + """Run the conform script as a Glue Spark job and wait for it.""" + glue = _client("glue") + output = output or lake.uri(CURATED) + job = { + "Role": role_arn, + "Command": {"Name": "glueetl", "ScriptLocation": publish_job(lake), "PythonVersion": "3"}, + "GlueVersion": "5.0", + "WorkerType": "G.1X", + "NumberOfWorkers": workers, + "Timeout": 20, + "MaxRetries": 0, + "DefaultArguments": {"--input": lake.uri(RAW, RAW_CSV), "--output": output}, + } + try: + glue.get_job(JobName=GLUE_JOB) + glue.update_job(JobName=GLUE_JOB, JobUpdate=job) + except glue.exceptions.EntityNotFoundException: + glue.create_job(Name=GLUE_JOB, **job) + + run_id = glue.start_job_run(JobName=GLUE_JOB)["JobRunId"] + while True: + run = glue.get_job_run(JobName=GLUE_JOB, RunId=run_id)["JobRun"] + if run["JobRunState"] in {"SUCCEEDED", "FAILED", "ERROR", "TIMEOUT", "STOPPED"}: + break + time.sleep(15) + seconds = float(run.get("ExecutionTime", 0)) + # Glue bills per second with a one-minute minimum, per worker (1 DPU each on G.1X). + dpu_hours = max(seconds, 60) / 3600 * workers + return SparkRun( + engine="Glue 5.0", + state=run["JobRunState"], + seconds=seconds, + usage=f"{dpu_hours:.3f} DPU-hours", + output=output, + error=run.get("ErrorMessage", ""), + ) + + +def ensure_emr_application() -> str: + """An EMR Serverless Spark application with a hard capacity ceiling.""" + emr = _client("emr-serverless") + for app in emr.list_applications()["applications"]: + if app["name"] == EMR_APP and app["state"] not in {"TERMINATED"}: + return app["id"] + return emr.create_application( + name=EMR_APP, + releaseLabel="emr-7.5.0", + type="SPARK", + maximumCapacity={"cpu": "8 vCPU", "memory": "32 GB", "disk": "100 GB"}, + autoStartConfiguration={"enabled": True}, + autoStopConfiguration={"enabled": True, "idleTimeoutMinutes": 2}, + )["applicationId"] + + +def run_emr(lake: Lake, role_arn: str, output: str) -> SparkRun: + """Run the same conform script on EMR Serverless and wait for it.""" + emr = _client("emr-serverless") + app_id = ensure_emr_application() + submit = " ".join( + [ + "--conf spark.dynamicAllocation.enabled=false", + "--conf spark.executor.instances=2", + "--conf spark.executor.cores=1", + "--conf spark.executor.memory=2g", + "--conf spark.driver.cores=1", + "--conf spark.driver.memory=2g", + ] + ) + job_id = emr.start_job_run( + applicationId=app_id, + executionRoleArn=role_arn, + name="conform-online-retail", + jobDriver={ + "sparkSubmit": { + "entryPoint": publish_job(lake), + "entryPointArguments": ["--input", lake.uri(RAW, RAW_CSV), "--output", output], + "sparkSubmitParameters": submit, + } + }, + configurationOverrides={ + "monitoringConfiguration": {"s3MonitoringConfiguration": {"logUri": lake.uri("logs", "emr")}} + }, + executionTimeoutMinutes=20, + )["jobRunId"] + while True: + run = emr.get_job_run(applicationId=app_id, jobRunId=job_id)["jobRun"] + if run["state"] in {"SUCCESS", "FAILED", "CANCELLED"}: + break + time.sleep(15) + used = run.get("totalResourceUtilization", {}) + return SparkRun( + engine=f"EMR Serverless {run.get('releaseLabel', 'emr-7.5.0')}", + state=run["state"], + seconds=float(run.get("totalExecutionDurationSeconds", 0)), + usage=f"{used.get('vCPUHour', 0):.3f} vCPU-hours, {used.get('memoryGBHour', 0):.3f} GB-hours", + output=output, + error=run.get("stateDetails", ""), + ) + + +# -------------------------------------------------------------------------- +# Redshift Serverless +# -------------------------------------------------------------------------- + + +def _default_network() -> tuple[list[str], list[str]]: + ec2 = _client("ec2") + vpc = ec2.describe_vpcs(Filters=[{"Name": "isDefault", "Values": ["true"]}])["Vpcs"][0]["VpcId"] + subnets = [ + subnet["SubnetId"] + for subnet in ec2.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [vpc]}])["Subnets"] + ] + group = ec2.describe_security_groups( + Filters=[{"Name": "vpc-id", "Values": [vpc]}, {"Name": "group-name", "Values": ["default"]}] + )["SecurityGroups"][0]["GroupId"] + return subnets, [group] + + +def ensure_redshift(role_arn: str, base_capacity: int = 8, daily_rpu_hours: int = 4) -> str: + """A Redshift Serverless workgroup at the smallest size, with a spending cap. + + The usage limit deactivates the workgroup once it has used daily_rpu_hours + of compute in a day, so a forgotten workgroup cannot run up a bill. + """ + rs = _client("redshift-serverless") + try: + rs.get_namespace(namespaceName=NAMESPACE) + except rs.exceptions.ResourceNotFoundException: + rs.create_namespace( + namespaceName=NAMESPACE, dbName=DATABASE, iamRoles=[role_arn], defaultIamRoleArn=role_arn + ) + try: + workgroup = rs.get_workgroup(workgroupName=WORKGROUP)["workgroup"] + except rs.exceptions.ResourceNotFoundException: + subnets, groups = _default_network() + workgroup = rs.create_workgroup( + workgroupName=WORKGROUP, + namespaceName=NAMESPACE, + baseCapacity=base_capacity, + maxCapacity=base_capacity, + publiclyAccessible=False, + subnetIds=subnets, + securityGroupIds=groups, + )["workgroup"] + while workgroup["status"] != "AVAILABLE": + time.sleep(15) + workgroup = rs.get_workgroup(workgroupName=WORKGROUP)["workgroup"] + + limits = rs.list_usage_limits(resourceArn=workgroup["workgroupArn"]).get("usageLimits", []) + if not limits: + rs.create_usage_limit( + resourceArn=workgroup["workgroupArn"], + usageType="serverless-compute", + amount=daily_rpu_hours, + period="daily", + breachAction="deactivate", + ) + return workgroup["workgroupArn"] + + +def execute(statements: list[str], timeout: int = 900) -> list[str]: + """Run statements in one transaction through the Redshift Data API.""" + data = _client("redshift-data") + statements = [sql for sql in statements if sql.strip()] + if len(statements) == 1: + response = data.execute_statement(WorkgroupName=WORKGROUP, Database=DATABASE, Sql=statements[0]) + else: + response = data.batch_execute_statement(WorkgroupName=WORKGROUP, Database=DATABASE, Sqls=statements) + started = time.time() + while True: + described = data.describe_statement(Id=response["Id"]) + if described["Status"] in {"FINISHED", "FAILED", "ABORTED"}: + break + if time.time() - started > timeout: + data.cancel_statement(Id=response["Id"]) + raise TimeoutError(f"Redshift statement did not finish in {timeout}s") + time.sleep(2) + if described["Status"] != "FINISHED": + raise RuntimeError(f"Redshift: {described.get('Error', described['Status'])}") + if len(statements) == 1: + return [response["Id"]] + return [part["Id"] for part in described["SubStatements"]] + + +def fetch(sql: str, schema: str | None = None) -> pd.DataFrame: + """A SELECT through the Data API, as a frame. + + Each Data API call is its own session, so a search_path set in one call is + gone by the next. With a schema, the SET and the SELECT go in one batch and + the SELECT's result is read. + """ + data = _client("redshift-data") + statements = [f"SET search_path TO {schema}", sql] if schema else [sql] + statement_id = execute(statements)[-1] + columns, rows, token = None, [], None + while True: + kwargs = {"Id": statement_id, **({"NextToken": token} if token else {})} + page = data.get_statement_result(**kwargs) + columns = columns or [column["name"] for column in page["ColumnMetadata"]] + for record in page["Records"]: + rows.append([None if field.get("isNull") else next(iter(field.values())) for field in record]) + token = page.get("NextToken") + if not token: + break + return pd.DataFrame(rows, columns=columns) + + +def statements_in(sql: str) -> list[str]: + """The statements in a SQL file, comments removed first. + + Splitting on semicolons before removing comments would cut a statement at a + semicolon inside a comment, and schema.sql's second line has one. + """ + return [part.strip() for part in _strip_comments(sql).split(";") if part.strip()] + + +def load_redshift(lake: Lake, schema: str) -> dict[str, int]: + """Create the schema, COPY the clean zone into it, and register the views.""" + statements = [ + f"DROP SCHEMA IF EXISTS {schema} CASCADE", + f"CREATE SCHEMA {schema}", + f"SET search_path TO {schema}", + *statements_in(to_redshift(SCHEMA_PATH.read_text(encoding="utf-8"))), + ] + for name in LOAD_ORDER: + statements.append(f"COPY {name} FROM '{lake.uri(CLEAN, name)}/' IAM_ROLE default FORMAT AS PARQUET") + execute(statements) + + views = [f"SET search_path TO {schema}"] + for directory in (STAGING_DIR, MARTS_DIR): + for path in _sql_files(directory): + views += statements_in(to_redshift(path.read_text(encoding="utf-8"))) + execute(views) + + counts = fetch(" UNION ALL ".join(f"SELECT '{name}' AS name, COUNT(*) AS n FROM {name}" for name in LOAD_ORDER), schema) + return dict(zip(counts["name"], counts["n"].astype(int), strict=True)) + + +def query_redshift(schema: str, name: str) -> pd.DataFrame: + """One of the analytical queries, run inside `schema`.""" + (sql,) = statements_in(to_redshift((ANALYTICS_DIR / f"{name}.sql").read_text(encoding="utf-8"))) + return fetch(sql, schema) + + +# -------------------------------------------------------------------------- +# What is running, and removing it +# -------------------------------------------------------------------------- + + +def billable_resources() -> list[str]: + """Everything this module creates that can bill while it exists.""" + found = [] + glue = _client("glue") + try: + glue.get_job(JobName=GLUE_JOB) + found.append(f"Glue job {GLUE_JOB} (bills only while running)") + except glue.exceptions.EntityNotFoundException: + pass + for app in _client("emr-serverless").list_applications()["applications"]: + if app["name"] == EMR_APP and app["state"] != "TERMINATED": + found.append(f"EMR Serverless application {app['id']} ({app['state']})") + rs = _client("redshift-serverless") + for workgroup in rs.list_workgroups()["workgroups"]: + if workgroup["workgroupName"] == WORKGROUP: + found.append(f"Redshift Serverless workgroup {WORKGROUP} ({workgroup['status']})") + for namespace in rs.list_namespaces()["namespaces"]: + if namespace["namespaceName"] == NAMESPACE: + found.append(f"Redshift Serverless namespace {NAMESPACE} (storage)") + return found + + +def teardown() -> list[str]: + """Remove every billable resource. The bucket and roles are kept.""" + removed = [] + glue = _client("glue") + try: + glue.delete_job(JobName=GLUE_JOB) + removed.append(f"Glue job {GLUE_JOB}") + except glue.exceptions.EntityNotFoundException: + pass + + emr = _client("emr-serverless") + for app in emr.list_applications()["applications"]: + if app["name"] != EMR_APP or app["state"] == "TERMINATED": + continue + if app["state"] not in {"STOPPED", "CREATED"}: + emr.stop_application(applicationId=app["id"]) + while emr.get_application(applicationId=app["id"])["application"]["state"] not in {"STOPPED"}: + time.sleep(10) + emr.delete_application(applicationId=app["id"]) + removed.append(f"EMR Serverless application {app['id']}") + + rs = _client("redshift-serverless") + try: + rs.get_workgroup(workgroupName=WORKGROUP) + rs.delete_workgroup(workgroupName=WORKGROUP) + while True: + try: + rs.get_workgroup(workgroupName=WORKGROUP) + time.sleep(15) + except rs.exceptions.ResourceNotFoundException: + break + removed.append(f"Redshift Serverless workgroup {WORKGROUP}") + except rs.exceptions.ResourceNotFoundException: + pass + try: + rs.delete_namespace(namespaceName=NAMESPACE) + while True: + try: + rs.get_namespace(namespaceName=NAMESPACE) + time.sleep(15) + except rs.exceptions.ResourceNotFoundException: + break + removed.append(f"Redshift Serverless namespace {NAMESPACE}") + except rs.exceptions.ResourceNotFoundException: + pass + return removed From 1cb4dc1ef452ab5f3e2f2a5fb61b73d0b3c3b2c4 Mon Sep 17 00:00:00 2001 From: vidit choudhary Date: Thu, 24 Sep 2026 17:36:15 +0530 Subject: [PATCH 4/8] Describe the pipeline and warehouse in the README Adds the pipeline's measured results, the path from S3 through Spark to PostgreSQL and Redshift, and states that the Glue and EMR subcommands have not run yet. Power BI is no longer out of scope; docs/power_bi.md covers the report. Test count updated to 363. --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++------- docs/docker.md | 2 +- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dfb4281..e823154 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,9 @@ Reproduced by the committed test suite and evaluation scripts. | **Data quality** | **8 errors** detected across 10 raw tables; cleaning imputes 3 fields, drops 2 exact duplicates, quarantines 1 impossible line; **zero errors** after | | **Plan vs actual** | Attainment spans **87.4% to 125.6%** across 64 region/category/channel segments, evenly split 32 above plan and 32 below. KAM quota attainment runs **89.8% to 106.0%** across four managers, three ahead and one behind | | **Reproducibility** | Identical results on **numpy 1.26 under Windows and numpy 2.5 under Linux** — same signal, same impact to the cent, same forecast improvement. The container resolves the top of the declared dependency range rather than a lockfile, which is how the numpy 2.x break was found | -| **Tests** | **316 automated tests**, passing on both dependency sets. One asserts that no output ever claims causation | +| **Data pipeline** | The same 1,067,371 lines landed in S3, conformed by a **PySpark** job, cleaned, and loaded into **PostgreSQL** and **Redshift Serverless** (996,531 sales lines in each). Spark's output is **identical to the pandas adapter's in all six tables**, compared column by column on the full dataset | +| **Warehouse parity** | Every staging view and all seven analytical queries return the **same rows on PostgreSQL and Redshift as on SQLite**. The one tolerance is `aov`, which may differ by a cent on an exact half cent because the engines round ties differently | +| **Tests** | **363 automated tests**, passing on both dependency sets. One asserts that no output ever claims causation | | **Mutation testing** | `scripts/mutation_test.py` applies 37 operator and constant mutations to the KPI layer; the suite kills **34 (91.9%)**. The 3 survivors are equivalent mutants (`> 0` vs `>= 0` on a count that is never zero, or on a ratio where 0/0 is already NaN) | **On that false-alarm figure.** The 12 counts every signal that matched nothing @@ -119,6 +121,13 @@ Or don't, and check the claims instead: docker build -t rootsignal . && docker run --rm rootsignal pytest ``` +Or run the whole pipeline on the real dataset, from download to warehouse: + +```bash +docker compose up -d warehouse +docker compose run --rm pipeline # land, conform in Spark, load, publish +``` + Then, in rough order of what is worth seeing first: ```bash @@ -182,9 +191,35 @@ Evidence package — evidence, pattern, impact, confidence criteria, alternati Recommended investigation ``` -**Stack:** Python, pandas, NumPy, SQL (SQLite/PostgreSQL), Streamlit, Plotly, -pytest, GitHub Actions, and an optional OpenAI-compatible LLM that the system -works fully without. Power BI is deliberately out of scope. +**Stack:** Python, pandas, NumPy, PySpark, SQL (SQLite, PostgreSQL, Amazon +Redshift), AWS S3, Streamlit, Plotly, Power BI, pytest, Docker, GitHub Actions, +and an optional OpenAI-compatible LLM that the system works fully without. + +### Data pipeline + +```text +UCI workbook -> S3 raw zone -> Spark conform job -> S3 curated zone (Parquet) + -> cleaning + validation (nothing loads while an error remains) + -> PostgreSQL warehouse + S3 clean zone -> Redshift Serverless (COPY) + -> report tables (rate/mix split, drivers, forecast accuracy) -> Power BI +``` + +`jobs/conform_online_retail.py` is a PySpark version of the Online Retail +adapter with no imports from the package, so the same file runs locally, in the +container and on a Spark cluster. `scripts/compare_spark_to_pandas.py` checks it +against the pandas adapter on the full dataset. Cleaning stays in pandas: it +owns the audit trail and the quarantine, and a second implementation would mean +a second definition of every rule. + +Locally the lake is a folder and the warehouse a PostgreSQL container +(`docker compose run --rm pipeline`). `scripts/run_pipeline_aws.py` runs the same +stages with the lake in S3 and a Redshift Serverless warehouse, capped at 8 RPU +with a usage limit that switches it off, and a teardown step that removes +everything that bills. Glue and EMR Serverless subcommands exist for the Spark +step but have not been run: the account used is not yet permitted Glue jobs, +and its EMR Serverless quota had not taken effect. The Spark job itself has only +run locally so far. [power_bi.md](docs/power_bi.md) describes the report on the +warehouse. ## What's built @@ -204,6 +239,8 @@ works fully without. Power BI is deliberately out of scope. | RootSignal engine | Evidence, pattern, impact, confidence and recommended investigation, ranked | [signals.md](docs/signals.md) | | Scenario evaluation | Measures whether the engine tells three planted situations apart and stays silent on two controls | [signals.md](docs/signals.md) | | SQL layer | Staging views, commercial mart and seven business queries, all executed by tests | [sql.md](docs/sql.md) | +| Warehouse | The same SQL on PostgreSQL and Redshift, translated from one source and held row for row to SQLite | [power_bi.md](docs/power_bi.md) | +| Pipeline | Raw, curated and clean zones on disk or in S3, a PySpark conform job, and report tables for BI | [power_bi.md](docs/power_bi.md) | | Excel reporting | Six operational workbooks, each opening with what its figures mean | [reporting.md](docs/reporting.md) | | Dashboard | Five Streamlit pages over a Streamlit-free, tested data layer | [dashboard.md](docs/dashboard.md) | | Presentation | One map from schema keys to language a business reader already has | [dashboard.md](docs/dashboard.md#the-words-on-the-screen) | @@ -251,8 +288,10 @@ against six named criteria rather than a tuned score. It is validated against five planted scenarios — classifying three of three correctly at a medium confidence floor, with zero false alarms at a high one — and runs unchanged on a million real invoice lines from a public dataset, where it beats a naive -forecast baseline by 38.8% across 49 backtest folds. Python, pandas, SQL, -Streamlit; 308 tests, including one asserting that no output ever claims -causation. +forecast baseline by 38.8% across 49 backtest folds. That dataset moves through +a pipeline from S3 through a PySpark job into PostgreSQL and Redshift, with +Spark's output checked against the pandas implementation on every row. Python, +pandas, PySpark, SQL, AWS, Streamlit; 363 tests, including one asserting that no +output ever claims causation. diff --git a/docs/docker.md b/docs/docker.md index 28bd74f..4dd55d3 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -19,7 +19,7 @@ docker run --rm rootsignal pytest ```bash docker run --rm -p 8501:8501 rootsignal # the dashboard -docker run --rm rootsignal pytest # all 308 tests +docker run --rm rootsignal pytest # the full suite, Spark tests included docker run --rm rootsignal python scripts/detect_signals.py ``` From a08949072c7336cbd57e2ccfaecba47685c06cd1 Mon Sep 17 00:00:00 2001 From: vidit choudhary Date: Thu, 24 Sep 2026 18:07:03 +0530 Subject: [PATCH 5/8] Test the AWS module and the load stage; report only real removals tests/test_aws.py covers every AWS call the pipeline makes, with moto for S3 and recording fakes for the rest: the roles reach only their bucket, Glue and EMR get the lake paths and time limits, Redshift is created at one size with a usage limit that deactivates it, statements run alone or as one batch, failures and timeouts surface, and teardown stops before it deletes. Writing them found that teardown reported removing a Glue job that never existed, because DeleteJob succeeds either way. It now checks first. tests/test_pipeline_stages.py pins the clean zone to schema.sql's types, which Redshift's COPY requires, checks report tables flatten to plain columns, and checks nothing loads while validation fails. The image no longer copies data/lake in. The pipeline service mounts it, and building it in added the raw CSV to every image. docs/power_bi.md is now a step-by-step build of the report. --- .dockerignore | 4 + docs/power_bi.md | 258 +++++++++++++------- src/rootsignal/pipeline/aws.py | 3 + tests/test_aws.py | 431 +++++++++++++++++++++++++++++++++ tests/test_pipeline_stages.py | 151 ++++++++++++ 5 files changed, 759 insertions(+), 88 deletions(-) create mode 100644 tests/test_aws.py create mode 100644 tests/test_pipeline_stages.py diff --git a/.dockerignore b/.dockerignore index c5ca45a..2bba668 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,6 +11,10 @@ data/raw/generated # The external dataset cache is 44 MB and downloads on demand. data/raw/external +# The pipeline's lake: a 96 MB raw CSV and Spark's output. The pipeline +# service mounts ./data instead, and baking it in copied it into every image. +data/lake + # Build outputs, not inputs. reports **/*.xlsx diff --git a/docs/power_bi.md b/docs/power_bi.md index d48831e..d129090 100644 --- a/docs/power_bi.md +++ b/docs/power_bi.md @@ -1,108 +1,190 @@ # Power BI on the RootSignal warehouse The pipeline loads the cleaned model and the analysis results into PostgreSQL. -This report reads them from there. It computes nothing that the project has -not already computed and tested: revenue comes from the fact table, and the -country contributions, the rate and mix split and the forecast accuracy come -from report tables that `scripts/run_pipeline.py` writes. - -## Connect - -1. Start the warehouse and fill it: - ```bash - docker compose up -d warehouse - docker compose run --rm pipeline - ``` -2. In Power BI Desktop: **Get data** → **PostgreSQL database**. - - Server: `localhost:5433` - - Database: `rootsignal` - - Data connectivity mode: **Import** -3. Credentials: **Database**, user `rootsignal`, password `rootsignal`. These - are the local container's, set in `docker-compose.yml`. -4. The local container has no TLS certificate. Power BI asks whether to connect - without encryption; for this local warehouse, **OK**. -5. Tick these, all in the `rootsignal` schema: - - | Table | What it is | - | --- | --- | - | `fact_sales` | one row per invoice line, after cleaning | - | `dim_date`, `dim_sku`, `dim_customer`, `dim_region` | the dimensions | - | `rpt_monthly_trade` | revenue, units, invoices and AOV per month | - | `rpt_country_contribution` | how much each country moved the latest month's revenue | - | `rpt_return_rate_components` | the return rate's movement split into rate, mix and interaction | - | `rpt_return_rate_split` | the same split, country by country | - | `rpt_returns_monthly` | units sold and returned per country and month | - | `rpt_forecast_accuracy` | backtested forecast error for each model against the naive baseline | +This report reads them from there and computes nothing the project has not +already computed and tested: revenue comes from the fact table, and the country +contributions, the rate and mix split, month-on-month change and forecast +accuracy come from report tables the pipeline writes. + +About 30 minutes in Power BI Desktop (Windows, free). + +## 1. Fill the warehouse + +```bash +docker compose up -d warehouse +docker compose run --rm pipeline +``` + +The second command takes about eight minutes and ends with a list of loaded +tables. If the warehouse was filled before, only the first command is needed. -## Model +## 2. Connect -In **Model view**, relate the fact to its dimensions, many to one, single -direction: +1. Open Power BI Desktop and close the start screen. +2. **Home** → **Get data** → **More…** → **Database** → **PostgreSQL database** + → **Connect**. +3. Server `localhost:5433`, database `rootsignal`, mode **Import** → **OK**. +4. In the credentials window choose **Database** on the left. User name + `rootsignal`, password `rootsignal` → **Connect**. These belong to the local + container, set in `docker-compose.yml`. +5. Power BI says it could not connect with encryption and asks to connect + without it. The local container has no certificate: **OK**. +6. In the Navigator, expand `rootsignal` and tick: -| From | To | -| --- | --- | -| `fact_sales[date]` | `dim_date[date]` | -| `fact_sales[sku_id]` | `dim_sku[sku_id]` | -| `fact_sales[customer_id]` | `dim_customer[customer_id]` | -| `fact_sales[region_code]` | `dim_region[region_code]` | + | Table | What it holds | + | --- | --- | + | `rootsignal.fact_sales` | one row per invoice line, after cleaning (996,531) | + | `rootsignal.dim_date` | one row per trading day | + | `rootsignal.dim_region` | one row per country | + | `rootsignal.dim_sku` | one row per product | + | `rootsignal.dim_customer` | one row per customer | + | `rootsignal.rpt_monthly_trade` | revenue, units, invoices and AOV per month | + | `rootsignal.rpt_monthly_trend` | each month against the one before | + | `rootsignal.rpt_country_contribution` | how much each country moved the latest month's revenue | + | `rootsignal.rpt_return_rate_components` | the return rate's movement split into rate, mix and interaction | + | `rootsignal.rpt_return_rate_split` | that split, country by country | + | `rootsignal.rpt_forecast_accuracy` | backtested forecast error per model against the naive baseline | + +7. **Load**, not Transform. Loading a million rows takes a minute. + +## 3. Model + +1. Open **Model view** (third icon on the left). +2. Power BI may have drawn some relationships already. Open **Home** → + **Manage relationships** and make sure exactly these four exist, each + **Many to one (\*:1)** with cross-filter direction **Single**. Create any that + are missing with **New…**, and delete any others: + + | From (many) | To (one) | + | --- | --- | + | `fact_sales` `date` | `dim_date` `date` | + | `fact_sales` `sku_id` | `dim_sku` `sku_id` | + | `fact_sales` `customer_id` | `dim_customer` `customer_id` | + | `fact_sales` `region_code` | `dim_region` `region_code` | -Mark `dim_date` as the date table (**Table tools** → **Mark as date table**, -column `date`). The `rpt_` tables stand alone and need no relationships. + The `rpt_` tables stay unconnected. They are finished results, not facts to + slice. +3. Hide the key columns of `fact_sales` from report view so they are not + dragged onto visuals by mistake: right-click `order_id`, `sku_id`, + `customer_id` → **Hide in report view**. -## Measures +## 4. Measures -Add these to `fact_sales`: +Select `fact_sales` in the **Data** pane on the right, then for each measure: +**Home** → **New measure**, paste it, press Enter. ```DAX Net Sales = SUM ( fact_sales[net_sales] ) +``` +```DAX Units = SUM ( fact_sales[units] ) +``` +```DAX Invoices = DISTINCTCOUNT ( fact_sales[order_id] ) +``` +```DAX AOV = DIVIDE ( [Net Sales], [Invoices] ) - -Net Sales Previous Month = - CALCULATE ( [Net Sales], DATEADD ( dim_date[date], -1, MONTH ) ) - -Net Sales MoM % = - DIVIDE ( [Net Sales] - [Net Sales Previous Month], [Net Sales Previous Month] ) ``` -`Invoices` is a distinct count on purpose. An invoice lists several products, -so summing invoice counts across products counts one invoice several times; -the SQL mart makes the same point. - -## Pages - -**Overview.** Cards for Net Sales, Invoices, AOV and Net Sales MoM %. A line -chart of Net Sales by `dim_date[date]` at month level. A slicer on -`dim_region[region_code]`. - -**What moved revenue.** A bar chart of `rpt_country_contribution`, axis -`segment`, value `contribution`, sorted by value. The United Kingdom accounts -for most of the movement, and the smaller bars show which other markets pulled -the other way. - -**Why the return rate moved.** A bar chart of `rpt_return_rate_components`, -axis `component`, value `net`. Most of the movement is `rate_effect`: countries -genuinely returning a different share, rather than demand shifting toward -countries that always return more (`mix_effect`). Beside it, a table of -`rpt_return_rate_split` with `segment`, `rate_before`, `rate_after`, -`rate_effect` and `mix_effect`. - -**Forecast accuracy.** A bar chart of `rpt_forecast_accuracy`, axis `model`, -value `wape`, with `wape_improvement_vs_baseline` as a tooltip. Lower WAPE is -better; `naive` is the baseline every model is measured against. - -## Things to keep in mind - -- `rpt_monthly_trade` and `rpt_returns_monthly` include an `is_complete` flag. - The dataset stops on 9 December 2011, so the last month is partial: filter on - `is_complete = TRUE` before comparing months, or December 2011 reads as a - collapse. -- Order, fulfilment, inventory and target columns are empty for this dataset. - It records sales only, and the project reports those analyses as unavailable - rather than approximating them. -- A return rate is not a fill rate. Returns are customers sending goods back, - not a warehouse failing to ship. +`Invoices` is a distinct count on purpose: one invoice lists several products, +so adding up per-product counts would count it several times. + +Format them: select `Net Sales`, then **Measure tools** → **Format**: Currency, +£ English (United Kingdom), 0 decimals. Do the same for `AOV` with 2 decimals. +`Units` and `Invoices`: Whole number, thousands separator on. + +Month-on-month change is not a measure here. `dim_date` holds only days the +retailer traded, and Power BI's time-intelligence functions assume an unbroken +calendar, so `DATEADD` would quietly miscount months with different trading +days. The project already computes the change in `rpt_monthly_trend`, and page 1 +uses that. + +## 5. Page 1: Overview + +Rename the page (double-click the tab) to **Overview**. + +1. **Four cards.** From **Visualizations**, add a **Card** and drop `Net Sales` + into **Fields**. Repeat for `Invoices`, `AOV` and `Units`. Line them up + across the top. +2. **Revenue by month.** Add a **Line chart**. **X-axis**: + `rpt_monthly_trade` `period_start`; **Y-axis**: `rpt_monthly_trade` + `net_sales`. On the X-axis field choose `period_start` itself, not + **Date Hierarchy** (click the arrow next to it). + In the **Filters** pane, under **Filters on this visual**, set `is_complete` + to **True** only. The data stops on 9 December 2011, and a nine-day December + would otherwise read as a collapse. +3. **Month-on-month table.** Add a **Table** with `rpt_monthly_trend` + `period_start`, `value`, `previous_value` and `pct_change`. Filter it to + `metric` = `net_sales`. Format `pct_change` as a percentage. +4. **Country slicer.** Add a **Slicer** with `dim_region` `region_code`. It + filters the four cards; the chart and the table show the whole business. + +## 6. Page 2: What moved revenue + +New page (**+** at the bottom), named **Drivers**. + +1. Add a **Clustered bar chart**. **Y-axis**: `rpt_country_contribution` + `segment`; **X-axis**: `rpt_country_contribution` `contribution`. +2. Sort by contribution: **…** on the visual → **Sort axis** → `contribution`. +3. Add a **Card** with `rpt_country_contribution` `period_start` + (**Earliest**) and a second with `comparison_period` (**Earliest**), so the + page says which months are compared. +4. Title the chart *Change in net sales by country*. + +Most of the movement comes from the United Kingdom, which carries most of the +revenue. The short bars show the markets that moved the other way. + +## 7. Page 3: Why the return rate moved + +New page, named **Returns**. + +1. Add a **Clustered column chart**. **X-axis**: + `rpt_return_rate_components` `component`; **Y-axis**: `net` + (**Don't summarize**, or Sum; there is one row per component). +2. Add a **Table** with `rpt_return_rate_split` `segment`, `rate_before`, + `rate_after`, `rate_effect`, `mix_effect`. Format the rates as percentages. +3. Title the chart *Return rate movement: rate against mix*. + +`rate_effect` is countries genuinely returning a different share of what they +buy. `mix_effect` is demand moving toward countries that always returned more. +Here the rate effect carries almost all of the movement. + +A return rate is not a fill rate: returns are customers sending goods back, not +a warehouse failing to ship. + +## 8. Page 4: Forecast accuracy + +New page, named **Forecast**. + +1. Add a **Clustered bar chart**. **Y-axis**: `rpt_forecast_accuracy` `model`; + **X-axis**: `wape` (Sum). Sort ascending by `wape`: lower is better. +2. Add a **Table** with `model`, `wape`, `wape_improvement_vs_baseline` and + `folds`. Format `wape` and `wape_improvement_vs_baseline` as percentages. +3. Title the chart *Forecast error by model (lower is better)*. + +`seasonal_mean_7` should show about 38.8% lower error than `naive`, the same +figure the README reports. + +## 9. Save and share + +1. **File** → **Save as** → `RootSignal.pbix`, anywhere outside the repository. + With a million rows imported it is tens of megabytes, which is why the + repository keeps screenshots rather than the file. +2. For each page, **File** → **Export** → **Export to PDF**, or take a + screenshot of the page at full width. Save them as + `docs/screenshots/powerbi_overview.png`, `powerbi_drivers.png`, + `powerbi_returns.png` and `powerbi_forecast.png`. + +## If something does not match + +- **Invoices looks too high:** it is `Count` instead of `Count (Distinct)`, or + the measure was built on a different column than `order_id`. +- **The line drops sharply at the end:** the `is_complete` filter is missing. +- **A card does not change with the slicer:** it uses an `rpt_` column instead + of a measure. The `rpt_` tables are whole-business results and do not respond + to the slicer. +- **The navigator shows no tables:** the pipeline has not run, or the warehouse + container is stopped (`docker compose up -d warehouse`). diff --git a/src/rootsignal/pipeline/aws.py b/src/rootsignal/pipeline/aws.py index 1ca41f5..7cdd9ef 100644 --- a/src/rootsignal/pipeline/aws.py +++ b/src/rootsignal/pipeline/aws.py @@ -417,7 +417,10 @@ def teardown() -> list[str]: """Remove every billable resource. The bucket and roles are kept.""" removed = [] glue = _client("glue") + # DeleteJob succeeds whether or not the job exists, so asking first is the + # only way to report a removal that happened rather than one that did not. try: + glue.get_job(JobName=GLUE_JOB) glue.delete_job(JobName=GLUE_JOB) removed.append(f"Glue job {GLUE_JOB}") except glue.exceptions.EntityNotFoundException: diff --git a/tests/test_aws.py b/tests/test_aws.py new file mode 100644 index 0000000..f96642c --- /dev/null +++ b/tests/test_aws.py @@ -0,0 +1,431 @@ +"""The AWS side of the pipeline, without an AWS account. + +S3 runs on moto. Glue, EMR Serverless, IAM, EC2 and the two Redshift APIs are +small recording fakes: each test states which calls must happen, which must +not, and what the module does when AWS reports a failure. The fakes answer the +way the services were observed to answer on a real run, including the one +that surprised: Glue's DeleteJob succeeds for a job that does not exist. +""" + +from __future__ import annotations + +import json +from types import SimpleNamespace + +import pytest + +from rootsignal.pipeline import CLEAN, Lake, aws +from rootsignal.sql.database import LOAD_ORDER + + +class NotFound(Exception): + pass + + +class FakeClient: + """Records every call; answers from per-method handlers.""" + + def __init__(self, **handlers): + self.calls: list[tuple[str, dict]] = [] + self.handlers = handlers + self.exceptions = SimpleNamespace( + EntityNotFoundException=NotFound, + ResourceNotFoundException=NotFound, + NoSuchEntityException=NotFound, + ClientError=NotFound, + ) + + def __getattr__(self, name): + if name.startswith("__"): + raise AttributeError(name) + + def call(**kwargs): + self.calls.append((name, kwargs)) + handler = self.handlers.get(name) + if handler is None: + return {} + return handler(**kwargs) if callable(handler) else handler + + return call + + def called(self, name: str) -> list[dict]: + return [kwargs for method, kwargs in self.calls if method == name] + + +def missing(**_): + raise NotFound() + + +def sequence(*answers): + """Successive answers to repeated calls, the last one repeating.""" + remaining = list(answers) + + def answer(**_): + value = remaining.pop(0) if len(remaining) > 1 else remaining[0] + if isinstance(value, Exception): + raise value + return value + + return answer + + +@pytest.fixture +def clients(monkeypatch): + """Route aws._client to fakes a test registers, and make waiting instant.""" + registry: dict[str, FakeClient] = {} + monkeypatch.setattr(aws, "_client", lambda service: registry[service]) + monkeypatch.setattr(aws.time, "sleep", lambda _seconds: None) + monkeypatch.setattr(aws, "publish_job", lambda lake: lake.uri("jobs", "conform_online_retail.py")) + return registry + + +LAKE = Lake("s3://rootsignal-lake-test") + + +# -------------------------------------------------------------------------- +# Roles and the bucket +# -------------------------------------------------------------------------- + + +def test_a_role_can_only_reach_its_own_bucket(clients) -> None: + iam = clients["iam"] = FakeClient( + get_role=missing, create_role={"Role": {"Arn": "arn:aws:iam::1:role/r"}} + ) + + arn = aws.ensure_role("r", ["glue.amazonaws.com"], "lake-bucket", write=False) + + assert arn == "arn:aws:iam::1:role/r" + trust = json.loads(iam.called("create_role")[0]["AssumeRolePolicyDocument"]) + assert trust["Statement"][0]["Principal"]["Service"] == ["glue.amazonaws.com"] + policy = json.loads(iam.called("put_role_policy")[0]["PolicyDocument"])["Statement"][0] + assert policy["Resource"] == ["arn:aws:s3:::lake-bucket", "arn:aws:s3:::lake-bucket/*"] + assert "s3:PutObject" not in policy["Action"] and "s3:DeleteObject" not in policy["Action"] + + +def test_a_writing_role_may_put_and_delete_and_managed_policies_attach(clients) -> None: + iam = clients["iam"] = FakeClient(get_role={"Role": {"Arn": "arn:existing"}}) + + aws.ensure_role("r", ["glue.amazonaws.com"], "b", write=True, managed=("arn:managed",)) + + assert not iam.called("create_role"), "an existing role is reused, not recreated" + actions = json.loads(iam.called("put_role_policy")[0]["PolicyDocument"])["Statement"][0]["Action"] + assert {"s3:PutObject", "s3:DeleteObject"} <= set(actions) + assert iam.called("attach_role_policy") == [{"RoleName": "r", "PolicyArn": "arn:managed"}] + + +def test_the_bucket_is_created_private(monkeypatch) -> None: + moto = pytest.importorskip("moto") + import boto3 + + for name, value in { + "AWS_ACCESS_KEY_ID": "testing", + "AWS_SECRET_ACCESS_KEY": "testing", + "AWS_DEFAULT_REGION": "ap-south-1", + }.items(): + monkeypatch.setenv(name, value) + with moto.mock_aws(): + aws.ensure_bucket("rootsignal-private") + block = boto3.client("s3").get_public_access_block(Bucket="rootsignal-private") + assert all(block["PublicAccessBlockConfiguration"].values()) + aws.ensure_bucket("rootsignal-private") # a second run changes nothing + + +# -------------------------------------------------------------------------- +# Glue and EMR Serverless +# -------------------------------------------------------------------------- + + +def test_glue_creates_the_job_with_the_lake_paths_and_waits_for_it(clients) -> None: + glue = clients["glue"] = FakeClient( + get_job=missing, + start_job_run={"JobRunId": "jr_1"}, + get_job_run=sequence( + {"JobRun": {"JobRunState": "RUNNING"}}, + {"JobRun": {"JobRunState": "SUCCEEDED", "ExecutionTime": 150}}, + ), + ) + + run = aws.run_glue(LAKE, "arn:role", workers=2) + + job = glue.called("create_job")[0] + assert job["Name"] == aws.GLUE_JOB and job["Role"] == "arn:role" + assert job["DefaultArguments"] == { + "--input": "s3://rootsignal-lake-test/raw/online_retail/invoices.csv", + "--output": "s3://rootsignal-lake-test/curated/online_retail", + } + assert job["NumberOfWorkers"] == 2 and job["Timeout"] == 20 and job["MaxRetries"] == 0 + assert len(glue.called("get_job_run")) == 2 + assert run.state == "SUCCEEDED" + assert run.usage == f"{150 / 3600 * 2:.3f} DPU-hours" + + +def test_glue_updates_an_existing_job_and_bills_at_least_a_minute(clients) -> None: + glue = clients["glue"] = FakeClient( + get_job={"Job": {}}, + start_job_run={"JobRunId": "jr_2"}, + get_job_run={"JobRun": {"JobRunState": "SUCCEEDED", "ExecutionTime": 20}}, + ) + + run = aws.run_glue(LAKE, "arn:role", workers=2) + + assert not glue.called("create_job") and len(glue.called("update_job")) == 1 + assert run.usage == f"{60 / 3600 * 2:.3f} DPU-hours" + + +def test_a_failed_glue_run_is_reported_with_its_error(clients) -> None: + clients["glue"] = FakeClient( + get_job={"Job": {}}, + start_job_run={"JobRunId": "jr_3"}, + get_job_run={"JobRun": {"JobRunState": "FAILED", "ExecutionTime": 70, "ErrorMessage": "boom"}}, + ) + + run = aws.run_glue(LAKE, "arn:role") + + assert (run.state, run.error) == ("FAILED", "boom") + + +def test_emr_reuses_a_live_application_and_ignores_a_terminated_one(clients) -> None: + emr = clients["emr-serverless"] = FakeClient( + list_applications={ + "applications": [ + {"name": aws.EMR_APP, "state": "TERMINATED", "id": "old"}, + {"name": aws.EMR_APP, "state": "STOPPED", "id": "live"}, + ] + } + ) + assert aws.ensure_emr_application() == "live" + assert not emr.called("create_application") + + +def test_a_new_emr_application_is_capped_and_stops_itself(clients) -> None: + emr = clients["emr-serverless"] = FakeClient( + list_applications={"applications": []}, create_application={"applicationId": "new"} + ) + assert aws.ensure_emr_application() == "new" + created = emr.called("create_application")[0] + assert created["maximumCapacity"]["cpu"] == "8 vCPU" + assert created["autoStopConfiguration"] == {"enabled": True, "idleTimeoutMinutes": 2} + + +def test_emr_runs_the_same_script_with_the_same_arguments(clients) -> None: + emr = clients["emr-serverless"] = FakeClient( + list_applications={"applications": [{"name": aws.EMR_APP, "state": "STARTED", "id": "app"}]}, + start_job_run={"jobRunId": "run"}, + get_job_run=sequence( + {"jobRun": {"state": "RUNNING"}}, + {"jobRun": {"state": "SUCCESS", "totalExecutionDurationSeconds": 95, + "totalResourceUtilization": {"vCPUHour": 0.1, "memoryGBHour": 0.4}}}, + ), + ) + + run = aws.run_emr(LAKE, "arn:emr", "s3://rootsignal-lake-test/curated_emr/online_retail") + + driver = emr.called("start_job_run")[0]["jobDriver"]["sparkSubmit"] + assert driver["entryPoint"] == "s3://rootsignal-lake-test/jobs/conform_online_retail.py" + assert driver["entryPointArguments"] == [ + "--input", "s3://rootsignal-lake-test/raw/online_retail/invoices.csv", + "--output", "s3://rootsignal-lake-test/curated_emr/online_retail", + ] + assert emr.called("start_job_run")[0]["executionTimeoutMinutes"] == 20 + assert (run.state, run.seconds) == ("SUCCESS", 95.0) + assert run.usage == "0.100 vCPU-hours, 0.400 GB-hours" + + +# -------------------------------------------------------------------------- +# Redshift Serverless +# -------------------------------------------------------------------------- + + +def network_clients(registry) -> None: + registry["ec2"] = FakeClient( + describe_vpcs={"Vpcs": [{"VpcId": "vpc-1"}]}, + describe_subnets={"Subnets": [{"SubnetId": "a"}, {"SubnetId": "b"}, {"SubnetId": "c"}]}, + describe_security_groups={"SecurityGroups": [{"GroupId": "sg-1"}]}, + ) + + +def test_redshift_is_created_at_one_size_with_a_spending_cap(clients) -> None: + network_clients(clients) + rs = clients["redshift-serverless"] = FakeClient( + get_namespace=missing, + get_workgroup=sequence( + NotFound(), + {"workgroup": {"status": "AVAILABLE", "workgroupArn": "arn:wg"}}, + ), + create_workgroup={"workgroup": {"status": "CREATING", "workgroupArn": "arn:wg"}}, + list_usage_limits={"usageLimits": []}, + ) + + assert aws.ensure_redshift("arn:rs", base_capacity=8, daily_rpu_hours=4) == "arn:wg" + + namespace = rs.called("create_namespace")[0] + assert namespace["defaultIamRoleArn"] == "arn:rs" + workgroup = rs.called("create_workgroup")[0] + assert workgroup["baseCapacity"] == workgroup["maxCapacity"] == 8 + assert workgroup["publiclyAccessible"] is False + assert workgroup["subnetIds"] == ["a", "b", "c"] and workgroup["securityGroupIds"] == ["sg-1"] + assert rs.called("create_usage_limit") == [ + {"resourceArn": "arn:wg", "usageType": "serverless-compute", "amount": 4, + "period": "daily", "breachAction": "deactivate"} + ] + + +def test_an_existing_spending_cap_is_not_added_twice(clients) -> None: + rs = clients["redshift-serverless"] = FakeClient( + get_namespace={"namespace": {}}, + get_workgroup={"workgroup": {"status": "AVAILABLE", "workgroupArn": "arn:wg"}}, + list_usage_limits={"usageLimits": [{"usageLimitId": "u"}]}, + ) + aws.ensure_redshift("arn:rs") + assert not rs.called("create_namespace") and not rs.called("create_workgroup") + assert not rs.called("create_usage_limit") + + +def test_one_statement_is_executed_alone_and_several_as_one_batch(clients) -> None: + data = clients["redshift-data"] = FakeClient( + execute_statement={"Id": "single"}, + batch_execute_statement={"Id": "batch"}, + describe_statement=lambda Id: { + "Status": "FINISHED", "SubStatements": [{"Id": f"{Id}:1"}, {"Id": f"{Id}:2"}] + }, + ) + assert aws.execute(["SELECT 1", " "]) == ["single"] + assert aws.execute(["SET x", "SELECT 1"]) == ["batch:1", "batch:2"] + assert data.called("batch_execute_statement")[0]["Sqls"] == ["SET x", "SELECT 1"] + + +def test_a_failed_statement_raises_with_redshifts_own_message(clients) -> None: + clients["redshift-data"] = FakeClient( + execute_statement={"Id": "s"}, + describe_statement={"Status": "FAILED", "Error": "column \"x\" does not exist"}, + ) + with pytest.raises(RuntimeError, match='column "x" does not exist'): + aws.execute(["SELECT x"]) + + +def test_a_statement_that_never_finishes_is_cancelled(clients, monkeypatch) -> None: + data = clients["redshift-data"] = FakeClient( + execute_statement={"Id": "slow"}, describe_statement={"Status": "STARTED"} + ) + clock = iter([0.0, 5.0, 11.0]) + monkeypatch.setattr(aws.time, "time", lambda: next(clock)) + with pytest.raises(TimeoutError): + aws.execute(["SELECT pg_sleep(100)"], timeout=10) + assert data.called("cancel_statement") == [{"Id": "slow"}] + + +def test_fetch_sets_the_schema_in_the_same_batch_and_reads_every_page(clients) -> None: + pages = { + None: { + "ColumnMetadata": [{"name": "region_code"}, {"name": "net_sales"}], + "Records": [[{"stringValue": "France"}, {"doubleValue": 10.5}]], + "NextToken": "page2", + }, + "page2": { + "ColumnMetadata": [{"name": "region_code"}, {"name": "net_sales"}], + "Records": [[{"stringValue": "Spain"}, {"isNull": True}]], + }, + } + data = clients["redshift-data"] = FakeClient( + batch_execute_statement={"Id": "b"}, + describe_statement={"Status": "FINISHED", "SubStatements": [{"Id": "b:1"}, {"Id": "b:2"}]}, + get_statement_result=lambda Id, NextToken=None: pages[NextToken], + ) + + frame = aws.fetch("SELECT * FROM mart", schema="online_retail") + + assert data.called("batch_execute_statement")[0]["Sqls"] == [ + "SET search_path TO online_retail", "SELECT * FROM mart" + ] + assert {call["Id"] for call in data.called("get_statement_result")} == {"b:2"} + assert frame["region_code"].tolist() == ["France", "Spain"] + assert frame["net_sales"].tolist()[0] == 10.5 and frame["net_sales"].isna().tolist()[1] + + +def test_the_redshift_load_copies_every_table_from_the_clean_zone(clients) -> None: + batches = [] + + def batch(Sqls, **_): + batches.append(Sqls) + return {"Id": f"b{len(batches)}"} + + counts_page = { + "ColumnMetadata": [{"name": "name"}, {"name": "n"}], + "Records": [[{"stringValue": name}, {"longValue": 7}] for name in LOAD_ORDER], + } + clients["redshift-data"] = FakeClient( + batch_execute_statement=batch, + describe_statement=lambda Id: {"Status": "FINISHED", "SubStatements": [{"Id": f"{Id}:1"}, {"Id": f"{Id}:2"}]}, + get_statement_result=counts_page, + ) + + counts = aws.load_redshift(LAKE, "online_retail") + + ddl = batches[0] + assert ddl[:3] == ["DROP SCHEMA IF EXISTS online_retail CASCADE", "CREATE SCHEMA online_retail", + "SET search_path TO online_retail"] + assert sum(statement.startswith("CREATE TABLE") for statement in ddl) == len(LOAD_ORDER) + assert not any("CHECK" in statement or "CREATE INDEX" in statement for statement in ddl) + copies = [statement for statement in ddl if statement.startswith("COPY")] + assert copies == [ + f"COPY {name} FROM '{LAKE.uri(CLEAN, name)}/' IAM_ROLE default FORMAT AS PARQUET" for name in LOAD_ORDER + ] + views = batches[1] + assert views[0] == "SET search_path TO online_retail" + assert any(statement.startswith("CREATE VIEW mart_commercial_daily") for statement in views) + assert counts == {name: 7 for name in LOAD_ORDER} + + +# -------------------------------------------------------------------------- +# Teardown +# -------------------------------------------------------------------------- + + +def test_teardown_reports_only_what_it_actually_removed(clients) -> None: + """DeleteJob succeeds for a job that never existed; that is not a removal.""" + glue = clients["glue"] = FakeClient(get_job=missing, delete_job={"JobName": aws.GLUE_JOB}) + clients["emr-serverless"] = FakeClient(list_applications={"applications": []}) + clients["redshift-serverless"] = FakeClient(get_workgroup=missing, delete_namespace=missing) + + assert aws.teardown() == [] + assert not glue.called("delete_job") + + +def test_teardown_stops_a_running_application_before_deleting_it(clients) -> None: + clients["glue"] = FakeClient(get_job={"Job": {}}) + emr = clients["emr-serverless"] = FakeClient( + list_applications={"applications": [{"name": aws.EMR_APP, "state": "STARTED", "id": "app"}]}, + get_application=sequence({"application": {"state": "STOPPING"}}, {"application": {"state": "STOPPED"}}), + ) + rs = clients["redshift-serverless"] = FakeClient( + get_workgroup=sequence({"workgroup": {}}, {"workgroup": {}}, NotFound()), + get_namespace=sequence({"namespace": {}}, NotFound()), + ) + + removed = aws.teardown() + + order = [method for method, _ in emr.calls] + assert order.index("stop_application") < order.index("delete_application") + assert rs.called("delete_workgroup") and rs.called("delete_namespace") + assert removed == [ + f"Glue job {aws.GLUE_JOB}", + "EMR Serverless application app", + f"Redshift Serverless workgroup {aws.WORKGROUP}", + f"Redshift Serverless namespace {aws.NAMESPACE}", + ] + + +def test_billable_resources_lists_what_exists_and_nothing_else(clients) -> None: + clients["glue"] = FakeClient(get_job=missing) + clients["emr-serverless"] = FakeClient( + list_applications={"applications": [ + {"name": aws.EMR_APP, "state": "TERMINATED", "id": "gone"}, + {"name": "someone-elses-app", "state": "STARTED", "id": "other"}, + ]} + ) + clients["redshift-serverless"] = FakeClient( + list_workgroups={"workgroups": [{"workgroupName": aws.WORKGROUP, "status": "AVAILABLE"}]}, + list_namespaces={"namespaces": [{"namespaceName": "other-ns"}]}, + ) + + assert aws.billable_resources() == [f"Redshift Serverless workgroup {aws.WORKGROUP} (AVAILABLE)"] diff --git a/tests/test_pipeline_stages.py b/tests/test_pipeline_stages.py new file mode 100644 index 0000000..7ec205c --- /dev/null +++ b/tests/test_pipeline_stages.py @@ -0,0 +1,151 @@ +"""The load and publish stages: what they write, and when they refuse to. + +The clean zone is what Redshift loads with COPY, which rejects a Parquet type +that does not match the table, so its types are pinned to schema.sql here. +The report tables must be plain enough for a SQL table to hold. +""" + +from __future__ import annotations + +import datetime as dt +import os +import uuid + +import pandas as pd +import pytest + +from rootsignal.adapters.base import AdaptedDataset, DatasetCapabilities +from rootsignal.pipeline import CLEAN, Lake, load, write_clean_zone +from rootsignal.pipeline.publish import flatten +from rootsignal.sql.database import LOAD_ORDER, SCHEMA_PATH +from rootsignal.sql.dialect import schema_columns +from rootsignal.sql.warehouse import URL_VARIABLE, connect, write_report_table +from rootsignal.validation.validator import ValidationIssue, ValidationReport + + +def test_the_clean_zone_is_typed_exactly_as_the_schema_declares(cleaned_dataset, tmp_path) -> None: + import pyarrow as pa + import pyarrow.parquet as pq + + expected_types = {"TEXT": pa.string(), "INTEGER": pa.int32(), "REAL": pa.float64(), "DATE": pa.date32()} + lake = Lake(str(tmp_path)) + write_clean_zone(lake, cleaned_dataset.tables) + + declared = schema_columns(SCHEMA_PATH.read_text(encoding="utf-8")) + for name in LOAD_ORDER: + schema = pq.read_schema(tmp_path / CLEAN / name / "part-00000.parquet") + assert schema.names == [column for column, _ in declared[name]], name + for column, kind in declared[name]: + assert schema.field(column).type == expected_types[kind], f"{name}.{column}" + + +def test_flags_are_written_as_zero_or_one(cleaned_dataset, tmp_path) -> None: + import pyarrow.parquet as pq + + write_clean_zone(Lake(str(tmp_path)), cleaned_dataset.tables) + flags = pq.read_table(tmp_path / CLEAN / "dim_sku" / "part-00000.parquet").column("active_flag") + assert set(flags.to_pylist()) <= {0, 1} + + +def test_row_counts_survive_the_clean_zone(cleaned_dataset, tmp_path) -> None: + import pyarrow.parquet as pq + + write_clean_zone(Lake(str(tmp_path)), cleaned_dataset.tables) + for name in LOAD_ORDER: + rows = pq.read_metadata(tmp_path / CLEAN / name / "part-00000.parquet").num_rows + assert rows == len(cleaned_dataset.tables[name]), name + + +def test_report_tables_are_flattened_to_plain_columns() -> None: + frame = pd.DataFrame( + { + "period_start": pd.PeriodIndex(["2011-10", "2011-11"], freq="M"), + "stamp": pd.to_datetime(["2011-10-03 09:00", "2011-11-04 10:00"]), + "value": [1.5, float("nan")], + "count": [3, 4], + "complete": [True, False], + "segments": [("United Kingdom", "France"), None], + }, + index=pd.Index(["a", "b"], name="kept_out"), + ) + + flat = flatten(frame) + + assert list(flat.columns) == ["period_start", "stamp", "value", "count", "complete", "segments"] + assert flat["period_start"].tolist() == [dt.date(2011, 10, 1), dt.date(2011, 11, 1)] + assert flat["stamp"].tolist() == [dt.date(2011, 10, 3), dt.date(2011, 11, 4)] + assert flat["segments"].tolist() == ["('United Kingdom', 'France')", None] + assert flat["count"].tolist() == [3, 4] and flat["complete"].tolist() == [True, False] + + +def test_nothing_is_loaded_while_validation_still_fails(monkeypatch, cleaned_dataset) -> None: + import rootsignal.sql.warehouse as warehouse + from rootsignal.validation import DatasetValidator + + failing = ValidationReport( + issues=(ValidationIssue("fact_sales", "keys", "ERROR", 2, "duplicate order lines"),) + ) + monkeypatch.setattr(DatasetValidator, "validate", lambda self, tables: failing) + built = [] + monkeypatch.setattr(warehouse, "build_warehouse", lambda *args, **kwargs: built.append(True)) + dataset = AdaptedDataset( + tables=cleaned_dataset.tables, + capabilities=DatasetCapabilities(name="test", available_facts=("fact_sales",)), + source_rows=0, + ) + + with pytest.raises(ValueError, match="fact_sales.keys: duplicate order lines"): + load(dataset, url="postgresql://unused") + assert not built + + +needs_warehouse = pytest.mark.skipif( + not os.environ.get(URL_VARIABLE), + reason=f"{URL_VARIABLE} is not set; start one with `docker compose up -d warehouse`", +) + + +@needs_warehouse +def test_a_report_table_round_trips_through_the_warehouse() -> None: + schema = f"test_{uuid.uuid4().hex[:10]}" + conn = connect(schema="public") + conn.execute(f'CREATE SCHEMA "{schema}"') + conn.execute(f'SET search_path TO "{schema}"') + conn.commit() + try: + frame = pd.DataFrame( + { + "segment": ["United Kingdom", None], + "contribution": [386371.2, float("nan")], + "folds": [49, 7], + "is_complete": [True, False], + "period_start": [dt.date(2011, 11, 1), dt.date(2011, 10, 1)], + } + ) + assert write_report_table(conn, "rpt_example", frame) == 2 + types = dict( + conn.execute( + "SELECT column_name, data_type FROM information_schema.columns " + "WHERE table_schema = %s AND table_name = 'rpt_example'", + (schema,), + ).fetchall() + ) + assert types == { + "segment": "text", + "contribution": "double precision", + "folds": "bigint", + "is_complete": "boolean", + "period_start": "date", + } + rows = conn.execute("SELECT * FROM rpt_example ORDER BY folds DESC").fetchall() + assert rows == [ + ("United Kingdom", 386371.2, 49, True, dt.date(2011, 11, 1)), + (None, None, 7, False, dt.date(2011, 10, 1)), + ] + assert write_report_table(conn, "rpt_example", frame.head(1)) == 1, "a rerun replaces the table" + assert conn.execute("SELECT COUNT(*) FROM rpt_example").fetchone()[0] == 1 + finally: + conn.rollback() + conn.execute(f'DROP SCHEMA "{schema}" CASCADE') + conn.commit() + conn.close() From ed468236b5557d6bd4b4b6fa5a0a0b640a3a1a3d Mon Sep 17 00:00:00 2001 From: vidit choudhary Date: Thu, 24 Sep 2026 20:32:29 +0530 Subject: [PATCH 6/8] Add the Power BI report on the warehouse Four pages read the PostgreSQL warehouse: sales KPIs with a country slicer and month-on-month change, each country's contribution to the latest month's movement, the return rate split into rate and mix effects, and forecast error against the naive baseline. Every figure comes from the fact table or a report table the pipeline writes; the report derives nothing new. docs/RootSignal.pbit is the report without data, and the screenshots are rendered from its PDF export. Building it found that 172 product codes differ only in case, which Power BI treats as duplicates; docs/power_bi.md records it until the adapter handles it. --- docs/RootSignal.pbit | Bin 0 -> 68911 bytes docs/power_bi.md | 263 ++++++++------------------ docs/screenshots/powerbi_drivers.png | Bin 0 -> 65506 bytes docs/screenshots/powerbi_forecast.png | Bin 0 -> 77938 bytes docs/screenshots/powerbi_overview.png | Bin 0 -> 226324 bytes docs/screenshots/powerbi_returns.png | Bin 0 -> 74697 bytes 6 files changed, 80 insertions(+), 183 deletions(-) create mode 100644 docs/RootSignal.pbit create mode 100644 docs/screenshots/powerbi_drivers.png create mode 100644 docs/screenshots/powerbi_forecast.png create mode 100644 docs/screenshots/powerbi_overview.png create mode 100644 docs/screenshots/powerbi_returns.png diff --git a/docs/RootSignal.pbit b/docs/RootSignal.pbit new file mode 100644 index 0000000000000000000000000000000000000000..e6f78b257f6c59e819a151902c7a68ffe161287d GIT binary patch literal 68911 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 Z$8GDWCxLWZRTEk1Z9&4o%7BCCegQUJYDfS8 literal 0 HcmV?d00001 diff --git a/docs/screenshots/powerbi_forecast.png b/docs/screenshots/powerbi_forecast.png new file mode 100644 index 0000000000000000000000000000000000000000..8f0a2f6e4a5e7cf3b60ebe1cc250e5b3b844f563 GIT binary patch literal 77938 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 BRHXm_ literal 0 HcmV?d00001 diff --git a/docs/screenshots/powerbi_overview.png b/docs/screenshots/powerbi_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..6109031f8ff4eb2fde9d1715a6ff26f071b34713 GIT binary patch literal 226324 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 E2kO-;6951J literal 0 HcmV?d00001 diff --git a/docs/screenshots/powerbi_returns.png b/docs/screenshots/powerbi_returns.png new file mode 100644 index 0000000000000000000000000000000000000000..c7f3e5b9a8e1779bd2c5c46c06ee5fd5ccc78afd GIT binary patch literal 74697 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 Date: Fri, 25 Sep 2026 00:45:15 +0530 Subject: [PATCH 7/8] Treat stock codes as case-insensitive; close the gaps mutation testing found 172 stock codes appear in two forms for one product, 15056BL and 15056bl, or 47503J with a trailing space. 163 pairs carry the same description and the rest the same product worded differently. Both the pandas adapter and the Spark job now trim and upper-case the code, so a product's sales no longer split across two rows, and Power BI no longer sees duplicate keys. scripts/mutation_test_pipeline.py applies 28 realistic bugs one at a time. Its first run caught 22; each miss had a cause, now covered: - The Spark fixture lacked a credit note with a positive quantity, a product sold at both a zero and a real price, a non-breaking space, a zero quantity, and case variants of a code. It has them now. - One Spark partition delivers rows in file order anyway, so taking the first value in any order went unnoticed. A test now feeds rows whose arrival order differs from their file order. - No sample data ties in weekly movers, so a dropped tie-breaker passed. A test now requires every query to sort on its full output grain. - Redshift averages an integer column to an integer; SQLite and PostgreSQL do not. A test now requires every AVG to average a cast or a REAL column. The operator-level mutants also showed untested paths: the job's command line entry point, a failed Spark run, reading the curated zone, nested S3 prefixes, exact CHECK removal, NULL numbers from PostgreSQL, the Redshift query path, the timeout boundary and the spending defaults. Each has a test. The backtest settings behind the README's 38.8% figure were written twice, in the report tables and in scripts/run_external_dataset.py. Both now read one constant. --- jobs/conform_online_retail.py | 5 +- scripts/mutation_test_pipeline.py | 164 +++++++++++++++++++++++ scripts/run_external_dataset.py | 3 +- src/rootsignal/adapters/online_retail.py | 8 +- src/rootsignal/pipeline/publish.py | 9 +- tests/test_adapters.py | 32 +++++ tests/test_aws.py | 74 ++++++++++ tests/test_lake.py | 7 + tests/test_pipeline_stages.py | 90 +++++++++++++ tests/test_spark_conform.py | 55 +++++++- tests/test_warehouse.py | 95 ++++++++++++- 11 files changed, 532 insertions(+), 10 deletions(-) create mode 100644 scripts/mutation_test_pipeline.py diff --git a/jobs/conform_online_retail.py b/jobs/conform_online_retail.py index d872840..64ba272 100644 --- a/jobs/conform_online_retail.py +++ b/jobs/conform_online_retail.py @@ -86,7 +86,10 @@ def read_raw(spark: SparkSession, path: str) -> DataFrame: return raw.select( F.col("_row"), F.col("Invoice").alias("order_id"), - F.col("StockCode").alias("sku_id"), + # The same product appears as 15056BL and 15056bl, and as 47503J with + # and without a trailing space. Trimmed as Python's str.strip() trims, + # any Unicode whitespace, then upper-cased. + F.upper(F.regexp_replace("StockCode", r"(?U)^\s+|\s+$", "")).alias("sku_id"), F.col("Description").alias("sku_name"), F.col("Quantity").cast("long").alias("units"), F.to_timestamp("InvoiceDate").alias("timestamp"), diff --git a/scripts/mutation_test_pipeline.py b/scripts/mutation_test_pipeline.py new file mode 100644 index 0000000..b4dc696 --- /dev/null +++ b/scripts/mutation_test_pipeline.py @@ -0,0 +1,164 @@ +"""Mutation testing for the pipeline with bugs it could realistically ship. + +scripts/mutation_test.py swaps operators and nudges constants, which suits the +KPI layer. Most of the pipeline's mistakes would not look like that: a +rounding rule swapped for Spark's own, a NULL ordering dropped, a CSV option +lost, a spending cap that only logs. Each mutant below is one such change, +applied alone; the suite that should notice is run, and the file restored. + +Run it where Spark and PostgreSQL are both available: + + docker compose up -d warehouse + docker compose run --rm pipeline python scripts/mutation_test_pipeline.py + +A surviving mutant is either a gap in the tests or a change no test here can +see. The second kind is listed with its reason, not hidden. +""" + +from __future__ import annotations + +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path + +from rootsignal.console import use_utf8_output + +ROOT = Path(__file__).resolve().parents[1] +JOB = "jobs/conform_online_retail.py" +SPARK_TESTS = ("tests/test_spark_conform.py",) +WAREHOUSE_TESTS = ("tests/test_warehouse.py",) + + +@dataclass(frozen=True) +class Mutant: + name: str + path: str + old: str + new: str + tests: tuple[str, ...] + + +MUTANTS = [ + # The Spark job + Mutant("Spark's own round() instead of pandas rounding", JOB, + "return F.rint(column * F.lit(scale)) / F.lit(scale)", "return F.round(column, places)", SPARK_TESTS), + Mutant("A C-prefixed invoice is no longer a return", JOB, + '(F.col("units") < 0) | F.upper(F.col("order_id")).startswith("C")', '(F.col("units") < 0)', SPARK_TESTS), + Mutant("A negative quantity is no longer a return", JOB, + '(F.col("units") < 0) | F.upper(F.col("order_id")).startswith("C")', + 'F.upper(F.col("order_id")).startswith("C")', SPARK_TESTS), + Mutant("Customer IDs keep the trailing .0", JOB, + 'F.regexp_replace("customer_id_raw", r"\\.0$", "")', 'F.col("customer_id_raw")', SPARK_TESTS), + Mutant("List price is the mean, not the median", JOB, + 'F.expr("percentile(unit_price, 0.5)")', 'F.avg("unit_price")', SPARK_TESTS), + Mutant("Zero prices count toward the list price", JOB, + 'sales.where(F.col("unit_price") > 0)', "sales", SPARK_TESTS), + Mutant("Prices are recomputed even with nothing to combine", JOB, + "if repeated == 0:", "if repeated < 0:", SPARK_TESTS), + Mutant("A blank description is not UNKNOWN", JOB, + 'F.when(first_word == "", "UNKNOWN").otherwise(first_word)', "first_word", SPARK_TESTS), + Mutant("CSV read line by line", JOB, '.option("multiLine", True)', '.option("multiLine", False)', SPARK_TESTS), + Mutant("CSV quotes escaped the Spark default way", JOB, """.option("escape", '"')""", "", SPARK_TESTS), + Mutant("Whitespace matched as ASCII only", JOB, 'r"(?U)^\\s*(\\S+)"', 'r"^\\s*(\\S+)"', SPARK_TESTS), + Mutant("First value taken in any order", JOB, + 'return F.min_by(F.col(column), F.when(F.col(column).isNotNull(), F.col("_row")))', + "return F.first(F.col(column), ignorenulls=True)", SPARK_TESTS), + # The SQL translation + Mutant("Week starts at the month", "src/rootsignal/sql/dialect.py", + "CAST(DATE_TRUNC('week', \\1) AS DATE)", "CAST(DATE_TRUNC('month', \\1) AS DATE)", WAREHOUSE_TESTS), + Mutant("REAL left as PostgreSQL's 4-byte float", "src/rootsignal/sql/dialect.py", + 'out = _REAL.sub("DOUBLE PRECISION", out)', "out = out", WAREHOUSE_TESTS), + Mutant("Untranslated SQLite passed through silently", "src/rootsignal/sql/dialect.py", + " if match:\n raise ValueError(", " if False:\n raise ValueError(", + WAREHOUSE_TESTS), + Mutant("CHECK constraints sent to Redshift", "src/rootsignal/sql/dialect.py", + " out = _strip_checks(out)\n", "", ("tests/test_aws.py",)), + Mutant("The warehouse truncates instead of rounding", "src/rootsignal/sql/dialect.py", + "AS $$ SELECT round(value::numeric, places)::double precision $$;", + "AS $$ SELECT trunc(value::numeric, places)::double precision $$;", + WAREHOUSE_TESTS), + # The queries + Mutant("Target variance sorted without a tie-breaker", "sql/analytics/target_variance.sql", + "ORDER BY variance_pct NULLS FIRST, p.region_code, p.category, p.channel;", + "ORDER BY variance_pct;", WAREHOUSE_TESTS), + Mutant("Weekly movers sorted without a tie-breaker", "sql/analytics/weekly_movers.sql", + "ORDER BY c.week_start, c.contribution, c.region_code, c.category;", + "ORDER BY c.week_start, c.contribution;", WAREHOUSE_TESTS), + Mutant("Stockout rate averaged over integers", "sql/analytics/supply_watchlist.sql", + "ROUND(AVG(CAST(i.stockout_flag AS REAL)), 4)", "ROUND(AVG(i.stockout_flag), 4)", WAREHOUSE_TESTS), + # Loading + Mutant("The old warehouse is dropped outside the load's transaction", "src/rootsignal/sql/warehouse.py", + " cursor.execute(f'CREATE SCHEMA \"{schema}\"')", + " conn.commit()\n cursor.execute(f'CREATE SCHEMA \"{schema}\"')", WAREHOUSE_TESTS), + Mutant("Clean zone integers written as 64-bit", "src/rootsignal/pipeline/stages.py", + '"INTEGER": pa.int32()', '"INTEGER": pa.int64()', ("tests/test_pipeline_stages.py",)), + Mutant("Report tables keep pandas Periods", "src/rootsignal/pipeline/publish.py", + " out[column] = series.dt.start_time.dt.date\n", " pass\n", + ("tests/test_pipeline_stages.py",)), + # The lake and AWS + Mutant("A folder picks up its siblings", "src/rootsignal/pipeline/lake.py", + 'if item["Key"] == base or item["Key"].startswith(base + "/"):', "if True:", ("tests/test_lake.py",)), + Mutant("Teardown reports a Glue job it did not remove", "src/rootsignal/pipeline/aws.py", + " glue.get_job(JobName=GLUE_JOB)\n glue.delete_job(JobName=GLUE_JOB)", + " glue.delete_job(JobName=GLUE_JOB)", ("tests/test_aws.py",)), + Mutant("Redshift's spending cap only logs", "src/rootsignal/pipeline/aws.py", + 'breachAction="deactivate"', 'breachAction="log"', ("tests/test_aws.py",)), + Mutant("Redshift may scale past its base size", "src/rootsignal/pipeline/aws.py", + "maxCapacity=base_capacity,", "maxCapacity=base_capacity * 4,", ("tests/test_aws.py",)), + Mutant("SET and SELECT sent as separate calls", "src/rootsignal/pipeline/aws.py", + 'statements = [f"SET search_path TO {schema}", sql] if schema else [sql]', + "statements = [sql]", ("tests/test_aws.py",)), +] + +# Mutants no test in this repository can catch, and why. They are still run, so +# the day a test starts catching one, this list is visibly out of date. +EXPECTED_SURVIVORS: dict[str, str] = {} + + +def run(mutant: Mutant) -> bool: + """Apply one mutant, run its tests, restore the file. True if killed.""" + path = ROOT / mutant.path + original = path.read_text(encoding="utf-8") + count = original.count(mutant.old) + if count != 1: + raise SystemExit(f"{mutant.name}: expected the original text once in {mutant.path}, found {count}") + try: + path.write_text(original.replace(mutant.old, mutant.new), encoding="utf-8") + result = subprocess.run( + [sys.executable, "-m", "pytest", "-x", "-q", "-p", "no:cacheprovider", "-o", "addopts=", *mutant.tests], + cwd=ROOT, + capture_output=True, + text=True, + ) + finally: + path.write_text(original, encoding="utf-8") + # Only a failing test is a kill. pytest also exits non-zero when a file is + # missing or nothing was collected, and counting that would inflate the score. + if result.returncode not in (0, 1): + raise SystemExit(f"{mutant.name}: pytest exited {result.returncode}\n{result.stdout[-2000:]}") + if result.returncode == 0 and " passed" not in result.stdout: + raise SystemExit(f"{mutant.name}: its tests all skipped, so the result would mean nothing") + return result.returncode == 1 + + +def main() -> int: + use_utf8_output() + killed, survived = [], [] + for mutant in MUTANTS: + (killed if run(mutant) else survived).append(mutant) + print(f"{'killed ' if mutant in killed else 'SURVIVED'} {mutant.name}", flush=True) + + print(f"\n{len(killed)}/{len(MUTANTS)} killed") + unexplained = [mutant for mutant in survived if mutant.name not in EXPECTED_SURVIVORS] + for mutant in survived: + reason = EXPECTED_SURVIVORS.get(mutant.name, "no test catches this") + print(f" survived: {mutant.name}. {reason}") + stale = [name for name in EXPECTED_SURVIVORS if name in {mutant.name for mutant in killed}] + for name in stale: + print(f" now killed, remove from EXPECTED_SURVIVORS: {name}") + return 1 if unexplained or stale else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/run_external_dataset.py b/scripts/run_external_dataset.py index db26ab9..55bc6eb 100644 --- a/scripts/run_external_dataset.py +++ b/scripts/run_external_dataset.py @@ -33,6 +33,7 @@ rolling_origin_evaluate, summarise_backtest, ) +from rootsignal.pipeline.publish import BACKTEST from rootsignal.validation import DatasetValidator @@ -148,7 +149,7 @@ def main() -> None: series = extract_metric(daily, "net_sales") print(f"{len(series)} days of history.") accuracy = compare_against_baseline( - summarise_backtest(rolling_origin_evaluate(series, horizon=7, initial_train=56, step=14)) + summarise_backtest(rolling_origin_evaluate(series, **BACKTEST)) ) print(accuracy[["model", "folds", "n_observations", "mae", "wape", "wape_improvement_vs_baseline"]].to_string(index=False)) diff --git a/src/rootsignal/adapters/online_retail.py b/src/rootsignal/adapters/online_retail.py index 7b654f0..8a4eeca 100644 --- a/src/rootsignal/adapters/online_retail.py +++ b/src/rootsignal/adapters/online_retail.py @@ -62,6 +62,9 @@ "Roughly a fifth of lines have no customer identifier. Those rows are " "kept with an explicit UNKNOWN customer rather than dropped, because " "discarding a fifth of revenue would misstate the business.", + "Stock codes are upper-cased and trimmed. 172 codes appear in two forms, " + "such as 15056BL and 15056bl, or 47503J with a trailing space, for the " + "same product; kept apart, one product's sales would split across two.", ), ) @@ -137,7 +140,10 @@ def adapt( frame["date"] = pd.to_datetime(frame["timestamp"], errors="coerce").dt.date frame["order_id"] = frame["order_id"].astype(str) - frame["sku_id"] = frame["sku_id"].astype(str) + # The same product appears as 15056BL and 15056bl, and as 47503J with and + # without a trailing space. The code identifies the product; its case and + # padding do not. + frame["sku_id"] = frame["sku_id"].astype(str).str.strip().str.upper() frame["category"] = _derive_category(frame["sku_name"]) # A fifth of lines have no customer. Dropping them would remove a fifth of diff --git a/src/rootsignal/pipeline/publish.py b/src/rootsignal/pipeline/publish.py index 963f4ad..7aa5326 100644 --- a/src/rootsignal/pipeline/publish.py +++ b/src/rootsignal/pipeline/publish.py @@ -24,6 +24,13 @@ ) +# The backtest behind the README's forecast figure: a week ahead, eight weeks of +# history before the first fold, a new fold every fortnight. On Online Retail II +# that is 49 folds. scripts/run_external_dataset.py uses the same settings, so +# the report table and the published number cannot drift apart. +BACKTEST = {"horizon": 7, "initial_train": 56, "step": 14} + + def analysis_tables(tables: dict[str, pd.DataFrame], returns: pd.DataFrame) -> dict[str, pd.DataFrame]: """Every report table, keyed by the name it is published under.""" sales = tables["fact_sales"] @@ -42,7 +49,7 @@ def analysis_tables(tables: dict[str, pd.DataFrame], returns: pd.DataFrame) -> d daily = build_daily_series(sales, metrics=["net_sales", "units"]) accuracy = compare_against_baseline( summarise_backtest( - rolling_origin_evaluate(extract_metric(daily, "net_sales"), horizon=7, initial_train=56, step=14) + rolling_origin_evaluate(extract_metric(daily, "net_sales"), **BACKTEST) ) ) diff --git a/tests/test_adapters.py b/tests/test_adapters.py index cd7c1d0..7bfb584 100644 --- a/tests/test_adapters.py +++ b/tests/test_adapters.py @@ -134,6 +134,38 @@ def test_a_product_repeated_on_one_invoice_is_consolidated() -> None: assert not sales.duplicated(subset=["order_id", "sku_id", "sales_type"]).any() +def test_a_stock_code_is_one_product_whatever_its_case_or_padding() -> None: + """The published file writes some codes in both cases, and one with a trailing space. + + 172 codes appear twice this way. 163 pairs carry the same description and + the rest the same product worded differently, so the code is the product + and its case is noise. Kept apart, one product's sales split across two + rows, and any tool comparing text without case (Power BI does) sees + duplicate keys. + """ + raw = raw_lines() + extra = pd.DataFrame( + { + "Invoice": ["536370", "536370", "536371"], + "StockCode": ["15056bl", "15056BL", "47503J "], + "Description": ["EDWARDIAN PARASOL BLACK", "EDWARDIAN PARASOL BLACK", "SET/3 FLORAL GARDEN TOOLS IN BAG"], + "Quantity": [2, 3, 1], + "InvoiceDate": pd.to_datetime(["2009-12-04 11:00", "2009-12-04 11:00", "2009-12-04 12:00"]), + "Price": [5.95, 5.95, 3.75], + "Customer ID": [17850.0, 17850.0, 17850.0], + "Country": ["United Kingdom"] * 3, + } + ) + dataset = adapt(raw=pd.concat([raw, extra], ignore_index=True)) + sales, skus = dataset.tables["fact_sales"], dataset.tables["dim_sku"] + + parasol = sales[sales["sku_id"] == "15056BL"] + assert len(parasol) == 1 and parasol["units"].iloc[0] == 5 + assert "15056bl" not in set(sales["sku_id"]) | set(skus["sku_id"]) + assert "47503J" in set(skus["sku_id"]) and "47503J " not in set(skus["sku_id"]) + assert skus["sku_id"].str.upper().is_unique + + def test_missing_customers_are_named_rather_than_dropped() -> None: """A fifth of real lines have no customer. Dropping them would lose a fifth of revenue.""" dataset = adapt(raw=raw_lines()) diff --git a/tests/test_aws.py b/tests/test_aws.py index f96642c..efe5f1c 100644 --- a/tests/test_aws.py +++ b/tests/test_aws.py @@ -429,3 +429,77 @@ def test_billable_resources_lists_what_exists_and_nothing_else(clients) -> None: ) assert aws.billable_resources() == [f"Redshift Serverless workgroup {aws.WORKGROUP} (AVAILABLE)"] + + +# -------------------------------------------------------------------------- +# The ceilings, and the edges of the arithmetic +# -------------------------------------------------------------------------- + + +def test_the_spending_ceilings_are_the_ones_the_module_states() -> None: + """Glue at two workers, Redshift at 8 RPU capped at 4 RPU-hours a day.""" + import inspect + + assert inspect.signature(aws.run_glue).parameters["workers"].default == 2 + redshift = inspect.signature(aws.ensure_redshift).parameters + assert redshift["base_capacity"].default == 8 + assert redshift["daily_rpu_hours"].default == 4 + assert inspect.signature(aws.execute).parameters["timeout"].default == 900 + + +def test_an_hour_on_two_glue_workers_is_two_dpu_hours(clients) -> None: + clients["glue"] = FakeClient( + get_job={"Job": {}}, + start_job_run={"JobRunId": "jr"}, + get_job_run={"JobRun": {"JobRunState": "SUCCEEDED", "ExecutionTime": 3600}}, + ) + assert aws.run_glue(LAKE, "arn:role", workers=2).usage == "2.000 DPU-hours" + + +def test_a_run_that_reports_no_time_is_billed_the_minimum(clients) -> None: + clients["glue"] = FakeClient( + get_job={"Job": {}}, start_job_run={"JobRunId": "jr"}, get_job_run={"JobRun": {"JobRunState": "FAILED"}} + ) + run = aws.run_glue(LAKE, "arn:role", workers=2) + assert run.seconds == 0.0 + assert run.usage == f"{60 / 3600 * 2:.3f} DPU-hours" + + +def test_emr_usage_missing_from_the_response_is_reported_as_zero(clients) -> None: + clients["emr-serverless"] = FakeClient( + list_applications={"applications": [{"name": aws.EMR_APP, "state": "STARTED", "id": "app"}]}, + start_job_run={"jobRunId": "run"}, + get_job_run={"jobRun": {"state": "FAILED", "stateDetails": "quota"}}, + ) + run = aws.run_emr(LAKE, "arn:emr", "s3://x/out") + assert (run.seconds, run.usage, run.error) == (0.0, "0.000 vCPU-hours, 0.000 GB-hours", "quota") + + +def test_a_statement_finishing_exactly_at_the_timeout_is_not_cancelled(clients, monkeypatch) -> None: + """The limit is exceeded only after the timeout, not on it. + + The clock starts at 100 rather than 0, so the elapsed time is measured, not + the clock's reading. + """ + data = clients["redshift-data"] = FakeClient( + execute_statement={"Id": "s"}, + describe_statement=sequence({"Status": "STARTED"}, {"Status": "FINISHED"}), + ) + clock = iter([100.0, 110.0]) + monkeypatch.setattr(aws.time, "time", lambda: next(clock)) + assert aws.execute(["SELECT 1"], timeout=10) == ["s"] + assert not data.called("cancel_statement") + + +def test_an_analytical_query_runs_translated_inside_its_schema(clients) -> None: + data = clients["redshift-data"] = FakeClient( + batch_execute_statement={"Id": "b"}, + describe_statement={"Status": "FINISHED", "SubStatements": [{"Id": "b:1"}, {"Id": "b:2"}]}, + get_statement_result={"ColumnMetadata": [{"name": "week_start"}], "Records": [[{"stringValue": "2011-11-28"}]]}, + ) + frame = aws.query_redshift("sample", "weekly_movers") + + set_path, sql = data.called("batch_execute_statement")[0]["Sqls"] + assert set_path == "SET search_path TO sample" + assert "DATE_TRUNC('week'" in sql and "weekday 0" not in sql + assert frame["week_start"].tolist() == ["2011-11-28"] diff --git a/tests/test_lake.py b/tests/test_lake.py index 021ad9c..125c36c 100644 --- a/tests/test_lake.py +++ b/tests/test_lake.py @@ -29,6 +29,13 @@ def test_an_s3_lake_splits_bucket_and_prefix() -> None: assert Lake("s3://rootsignal-lake").uri(RAW) == "s3://rootsignal-lake/raw/online_retail" +def test_an_s3_prefix_can_be_several_folders_deep() -> None: + lake = Lake("s3://rootsignal-lake/teams/analytics/rootsignal") + assert lake.bucket == "rootsignal-lake" + assert lake.prefix == "teams/analytics/rootsignal" + assert lake.key(RAW, "invoices.csv") == "teams/analytics/rootsignal/raw/online_retail/invoices.csv" + + @pytest.fixture def s3(monkeypatch): moto = pytest.importorskip("moto") diff --git a/tests/test_pipeline_stages.py b/tests/test_pipeline_stages.py index 7ec205c..61c6350 100644 --- a/tests/test_pipeline_stages.py +++ b/tests/test_pipeline_stages.py @@ -78,6 +78,96 @@ def test_report_tables_are_flattened_to_plain_columns() -> None: assert flat["count"].tolist() == [3, 4] and flat["complete"].tolist() == [True, False] +def test_the_spark_job_is_where_the_pipeline_looks_for_it() -> None: + from rootsignal.pipeline.stages import JOB_PATH + + assert JOB_PATH.is_file() and JOB_PATH.name == "conform_online_retail.py" + + +def test_a_failed_spark_run_stops_the_pipeline_with_its_error(monkeypatch, tmp_path) -> None: + """The job's own last words are what someone debugging it needs.""" + import subprocess + + from rootsignal.pipeline import conform_locally, stages + + failed = subprocess.CompletedProcess(args=[], returncode=1, stdout="", stderr="x" * 6000 + "Py4JJavaError: disk full") + monkeypatch.setattr(stages.subprocess, "run", lambda *args, **kwargs: failed) + + with pytest.raises(RuntimeError, match="disk full") as raised: + conform_locally(Lake(str(tmp_path))) + assert len(str(raised.value)) < 4100, "the whole log is not pasted into the error" + + +def test_a_run_that_exits_cleanly_is_not_reported_as_failed(monkeypatch, tmp_path) -> None: + import subprocess + + from rootsignal.pipeline import conform_locally, stages + + monkeypatch.setattr( + stages.subprocess, "run", + lambda *args, **kwargs: subprocess.CompletedProcess(args=[], returncode=0, stdout="", stderr="WARN only"), + ) + monkeypatch.setattr(stages, "read_summary", lambda lake: {"source_rows": 3}) + assert conform_locally(Lake(str(tmp_path))) == {"source_rows": 3} + + +def write_curated_zone(root, cleaned_tables) -> None: + """A curated zone laid out as the Spark job writes it, without running Spark.""" + import json + + from rootsignal.pipeline import CURATED + from rootsignal.pipeline.stages import CURATED_TABLES + + for name in CURATED_TABLES: + folder = root / CURATED / name + folder.mkdir(parents=True) + cleaned_tables[name].to_parquet(folder / "part-00000.parquet", index=False) + returns = pd.DataFrame( + {"order_id": ["C1"], "sku_id": ["A"], "region_code": ["France"], "customer_id": ["UNKNOWN"], + "date": [dt.date(2024, 1, 3)], "units": [-2], "returned_units": [2]} + ) + (root / CURATED / "returns").mkdir(parents=True) + returns.to_parquet(root / CURATED / "returns" / "part-00000.parquet", index=False) + (root / CURATED / "_summary").mkdir(parents=True) + summary = {"source_rows": 1234, "returned_lines": 56, "consolidated_lines": 7, "tables": {}} + (root / CURATED / "_summary" / "part-00000.txt").write_text(json.dumps(summary), encoding="utf-8") + + +def test_the_curated_zone_reads_back_as_the_adapter_would_return_it(cleaned_dataset, tmp_path) -> None: + from rootsignal.pipeline import read_curated + from rootsignal.pipeline.stages import EMPTY_FACTS + + write_curated_zone(tmp_path, cleaned_dataset.tables) + dataset, returns = read_curated(Lake(str(tmp_path))) + + assert dataset.source_rows == 1234 + sales = dataset.tables["fact_sales"] + assert len(sales) == len(cleaned_dataset.tables["fact_sales"]) + assert isinstance(sales["date"].iloc[0], dt.date) and not isinstance(sales["date"].iloc[0], dt.datetime) + assert isinstance(returns["date"].iloc[0], dt.date) + for name in EMPTY_FACTS: + assert name in dataset.tables and dataset.tables[name].empty + notes = " ".join(dataset.capabilities.notes) + assert "56 of 1,234 lines are returns" in notes + assert "7 invoice lines repeat a product" in notes + assert "Conformed in Spark" in notes + + +def test_the_report_backtest_is_the_one_behind_the_published_figure() -> None: + """The README's 38.8% over 49 folds comes from these settings. + + scripts/run_external_dataset.py produces that figure and the report table + must match it, so both read one constant rather than two copies. + """ + from pathlib import Path + + from rootsignal.pipeline.publish import BACKTEST + + assert BACKTEST == {"horizon": 7, "initial_train": 56, "step": 14} + script = (Path(__file__).resolve().parents[1] / "scripts" / "run_external_dataset.py").read_text(encoding="utf-8") + assert "rolling_origin_evaluate(series, **BACKTEST)" in script + + def test_nothing_is_loaded_while_validation_still_fails(monkeypatch, cleaned_dataset) -> None: import rootsignal.sql.warehouse as warehouse from rootsignal.validation import DatasetValidator diff --git a/tests/test_spark_conform.py b/tests/test_spark_conform.py index 6e57a0d..d97467f 100644 --- a/tests/test_spark_conform.py +++ b/tests/test_spark_conform.py @@ -57,10 +57,24 @@ def raw_lines(repeat_a_product: bool = True) -> pd.DataFrame: ("C489449", 22087, "PAPER BUNTING WHITE LACE", -12, "2009-12-03 10:33", 2.95, 16321.0, "Australia"), (489450, 21755, "LOVE BUILDING BLOCK WORD", -2, "2009-12-03 11:00", 5.95, 13078.0, "Germany"), (489451, "POST", "POSTAGE", 1, "2009-12-04 12:00", 18.0, 12682.0, "France"), + # A credit note with a positive quantity: a return by its prefix alone. + ("C489460", 22423, "REGENCY CAKESTAND 3 TIER", 1, "2009-12-05 09:00", 12.75, 12682.0, "France"), + # 22195 already sold at a zero price above; its list price must ignore it. + (489461, 22195, "HEART MEASURING SPOONS", 3, "2009-12-05 10:00", 1.65, 12682.0, "France"), + # A trailing space on a code that is the same product without it. + (489463, "47503J ", "SET/3 FLORAL GARDEN TOOLS IN BAG", 1, "2009-12-05 12:00", 3.75, 13085.0, "United Kingdom"), + # A non-breaking space before the first word, which Python's split() + # treats as whitespace and an ASCII-only regex would not. + (489464, 20725, "\xa0LUNCH BAG RED RETROSPOT", 2, "2009-12-05 13:00", 1.65, 13085.0, "United Kingdom"), + # A zero quantity is a sale of nothing, not a return. + (489465, 21212, "PACK OF 72 RETROSPOT CAKE CASES", 0, "2009-12-05 14:00", 0.55, 13085.0, "United Kingdom"), ] if repeat_a_product: # The same product twice on one invoice, at a different quantity and price. rows.append((489434, 85048, "15CM CHRISTMAS GLASS BALL 20 LIGHTS", 5, "2009-12-01 07:45", 6.45, 13085.0, "United Kingdom")) + # The same product on one invoice under two cases of its code. + rows.append((489462, "15056bl", "EDWARDIAN PARASOL BLACK", 2, "2009-12-05 11:00", 5.95, 13085.0, "United Kingdom")) + rows.append((489462, "15056BL", "EDWARDIAN PARASOL BLACK", 3, "2009-12-05 11:00", 5.95, 13085.0, "United Kingdom")) frame = pd.DataFrame( rows, @@ -149,9 +163,10 @@ def test_without_repeated_lines_the_published_price_is_kept(conformed_without_re raw, spark_dataset = conformed_without_repeats sales = spark_dataset.tables["fact_sales"] assert sorted(sales["unit_price"].tolist()) == sorted( - raw.loc[(raw["Quantity"] > 0) & ~raw["Invoice"].astype(str).str.startswith("C"), "Price"].tolist() + raw.loc[(raw["Quantity"] >= 0) & ~raw["Invoice"].astype(str).str.startswith("C"), "Price"].tolist() ) assert_tables_match(sales, adapt(raw=raw).tables["fact_sales"], KEYS["fact_sales"]) + assert "0 invoice lines repeat a product" in " ".join(spark_dataset.capabilities.notes) def test_half_cent_revenue_rounds_the_way_pandas_rounds(spark, conformed) -> None: @@ -174,7 +189,39 @@ def test_returns_are_split_out_the_same_way(conformed) -> None: def test_summary_counts_what_the_job_did(conformed) -> None: _, spark_dataset, _ = conformed - assert spark_dataset.source_rows == 12 + assert spark_dataset.source_rows == 19 notes = " ".join(spark_dataset.capabilities.notes) - assert "2 of 12 lines are returns" in notes - assert "1 invoice lines repeat a product" in notes + assert "3 of 19 lines are returns" in notes + assert "2 invoice lines repeat a product" in notes + + +def test_a_zero_quantity_is_a_sale_of_nothing_not_a_return(conformed) -> None: + _, spark_dataset, spark_returns = conformed + sales = spark_dataset.tables["fact_sales"].set_index(["order_id", "sku_id"]) + assert sales.loc[("489465", "21212"), "units"] == 0 + assert "489465" not in set(spark_returns["order_id"]) + + +def test_the_first_value_follows_the_file_not_the_arrival_order(spark) -> None: + """pandas' groupby 'first' is the first non-null value in file order. + + On one partition, rows arrive in file order anyway, so the fixture above + cannot tell file order from arrival order. Here the rows arrive in a + different order from their position in the file, as they can on a cluster. + """ + job = load_job() + rows = [("k", "arrived first, later in the file", 5), ("k", None, 0), ("k", "earliest non-null", 1)] + frame = spark.createDataFrame(rows, "key string, value string, _row long").coalesce(1) + picked = frame.groupBy("key").agg(job.first_in_file_order("value").alias("value")).first()["value"] + assert picked == "earliest non-null" + + +def test_the_job_runs_from_the_command_line(tmp_path) -> None: + """The entry point spark-submit, Glue and EMR use, run as a separate process.""" + from rootsignal.pipeline import conform_locally, read_summary + + lake = Lake(str(tmp_path / "lake")) + write_raw_csv(raw_lines(), lake.uri(RAW, RAW_CSV)) + summary = conform_locally(lake) + assert summary == read_summary(lake) + assert summary["source_rows"] == 19 and summary["returned_lines"] == 3 diff --git a/tests/test_warehouse.py b/tests/test_warehouse.py index c1e7764..ef413a4 100644 --- a/tests/test_warehouse.py +++ b/tests/test_warehouse.py @@ -8,6 +8,7 @@ from __future__ import annotations import os +import re import uuid import numpy as np @@ -16,8 +17,8 @@ from rootsignal.modeling import build_commercial_mart from rootsignal.sql import available_queries, build_database, query, run_query_file -from rootsignal.sql.database import ANALYTICS_DIR, MARTS_DIR, SCHEMA_PATH, STAGING_DIR -from rootsignal.sql.dialect import to_postgres +from rootsignal.sql.database import ANALYTICS_DIR, LOAD_ORDER, MARTS_DIR, SCHEMA_PATH, STAGING_DIR +from rootsignal.sql.dialect import schema_columns, to_postgres, to_redshift from rootsignal.sql.warehouse import ( URL_VARIABLE, build_warehouse, @@ -68,6 +69,86 @@ def test_every_sql_file_in_the_repository_translates(path) -> None: to_postgres(path.read_text(encoding="utf-8")) +def test_redshift_translation_removes_checks_and_nothing_else() -> None: + sql = ( + "CREATE TABLE t (\n" + " a TEXT NOT NULL CHECK (a IN ('x', 'y')),\n" + " b REAL NOT NULL CHECK (b >= 0 AND (b <= 1)),\n" + " c INTEGER\n" + ");" + ) + assert to_redshift(sql) == ( + "CREATE TABLE t (\n" + " a VARCHAR(1024) NOT NULL,\n" + " b DOUBLE PRECISION NOT NULL,\n" + " c INTEGER\n" + ");" + ) + + +def test_schema_columns_reads_names_and_types_in_order() -> None: + declared = schema_columns(SCHEMA_PATH.read_text(encoding="utf-8")) + assert set(declared) == set(LOAD_ORDER) + assert declared["dim_date"] == [ + ("date", "DATE"), ("week", "INTEGER"), ("month", "INTEGER"), ("quarter", "TEXT"), ("year", "INTEGER") + ] + + +def _sql_code(path) -> str: + return re.sub(r"--[^\n]*", "", path.read_text(encoding="utf-8")) + + +ALL_SQL = [*sorted(STAGING_DIR.glob("*.sql")), *sorted(MARTS_DIR.glob("*.sql")), *sorted(ANALYTICS_DIR.glob("*.sql"))] + + +@pytest.mark.parametrize("path", ALL_SQL, ids=lambda path: path.stem) +def test_every_average_is_of_a_real_number(path) -> None: + """Redshift's AVG of an integer column is an integer. + + SQLite and PostgreSQL return a fraction, so the warehouse tests cannot see + the difference; a stockout rate over 0/1 flags would read 0 on Redshift + alone. Every AVG therefore averages a cast, or a column declared REAL. + """ + real_columns = { + column for columns in schema_columns(SCHEMA_PATH.read_text(encoding="utf-8")).values() + for column, kind in columns if kind == "REAL" + } + code = _sql_code(path) + for match in re.finditer(r"\bAVG\(\s*", code, re.IGNORECASE): + rest = code[match.end():] + if rest.upper().startswith("CAST("): + continue + column = re.match(r"([\w.]+)\s*\)", rest).group(1).split(".")[-1] + assert column in real_columns, f"{path.stem}: AVG({column}) averages an integer column" + + +# The columns that identify one output row of each query. Sorting on anything +# less leaves tied rows in whatever order the engine produces, and SQLite and +# PostgreSQL produce different ones. +QUERY_GRAIN = { + "daily_sales_tracker": ["date"], + "fill_rate_by_segment": ["week_start", "region_code", "category"], + "kam_scorecard": ["kam_id"], + "primary_secondary_mix": ["month", "region_code"], + "supply_watchlist": ["week_start", "region_code", "category"], + "target_variance": ["region_code", "category", "channel"], + "weekly_movers": ["week_start", "region_code", "category"], +} + + +def test_every_query_has_a_declared_grain() -> None: + assert set(QUERY_GRAIN) == set(available_queries()) + + +@pytest.mark.parametrize("name", sorted(QUERY_GRAIN)) +def test_every_query_sorts_on_its_full_grain(name) -> None: + code = _sql_code(ANALYTICS_DIR / f"{name}.sql") + order_by = code[code.upper().rindex("ORDER BY"):] + sorted_on = {term.strip().split()[0].split(".")[-1] for term in order_by[len("ORDER BY"):].rstrip("; \n").split(",")} + missing = [column for column in QUERY_GRAIN[name] if column not in sorted_on] + assert not missing, f"{name} does not sort on {missing}" + + # -------------------------------------------------------------------------- # The warehouse against the SQLite database # -------------------------------------------------------------------------- @@ -151,6 +232,16 @@ def test_every_analytical_query_matches_sqlite_row_for_row(engines, name) -> Non _assert_same(run_query_file(sqlite, name), run_warehouse_query_file(warehouse, name)) +@needs_warehouse +def test_a_column_of_nulls_comes_back_as_numbers_not_objects(engines) -> None: + """psycopg returns None for NULL, which pandas would hold as an object column.""" + _, _, warehouse = engines + frame = query_warehouse( + warehouse, "SELECT CAST(NULL AS DOUBLE PRECISION) AS x UNION ALL SELECT CAST(NULL AS DOUBLE PRECISION)" + ) + assert frame["x"].dtype == float and frame["x"].isna().all() + + @needs_warehouse def test_a_load_that_breaks_a_foreign_key_leaves_the_warehouse_untouched(engines) -> None: tables, _, warehouse = engines From 54c526b04dc4db5b36ec901de7780551772b742b Mon Sep 17 00:00:00 2001 From: vidit choudhary Date: Fri, 25 Sep 2026 02:34:44 +0530 Subject: [PATCH 8/8] Record the pipeline mutation results and the case-insensitive stock codes The pipeline suite now catches all 28 realistic bugs in scripts/mutation_test_pipeline.py, up from 22, and 93 of 108 operator mutants; the 15 left are wait times, output file counts and formatting. With stock codes trimmed and upper-cased, 172 duplicate products merge (4,985 to 4,813) and PostgreSQL holds 996,528 sales lines. Redshift's 996,531 was loaded before the change and is reported as such. Every figure in the Power BI report is unchanged, so its screenshots stand. --- README.md | 8 ++++---- docs/power_bi.md | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e823154..225b133 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,10 @@ Reproduced by the committed test suite and evaluation scripts. | **Data quality** | **8 errors** detected across 10 raw tables; cleaning imputes 3 fields, drops 2 exact duplicates, quarantines 1 impossible line; **zero errors** after | | **Plan vs actual** | Attainment spans **87.4% to 125.6%** across 64 region/category/channel segments, evenly split 32 above plan and 32 below. KAM quota attainment runs **89.8% to 106.0%** across four managers, three ahead and one behind | | **Reproducibility** | Identical results on **numpy 1.26 under Windows and numpy 2.5 under Linux** — same signal, same impact to the cent, same forecast improvement. The container resolves the top of the declared dependency range rather than a lockfile, which is how the numpy 2.x break was found | -| **Data pipeline** | The same 1,067,371 lines landed in S3, conformed by a **PySpark** job, cleaned, and loaded into **PostgreSQL** and **Redshift Serverless** (996,531 sales lines in each). Spark's output is **identical to the pandas adapter's in all six tables**, compared column by column on the full dataset | +| **Data pipeline** | The same 1,067,371 lines landed in S3, conformed by a **PySpark** job, cleaned, and loaded into **PostgreSQL** (996,528 sales lines) and **Redshift Serverless** (996,531, loaded before stock codes were made case-insensitive). Spark's output is **identical to the pandas adapter's in all six tables**, compared column by column on the full dataset | | **Warehouse parity** | Every staging view and all seven analytical queries return the **same rows on PostgreSQL and Redshift as on SQLite**. The one tolerance is `aov`, which may differ by a cent on an exact half cent because the engines round ties differently | -| **Tests** | **363 automated tests**, passing on both dependency sets. One asserts that no output ever claims causation | -| **Mutation testing** | `scripts/mutation_test.py` applies 37 operator and constant mutations to the KPI layer; the suite kills **34 (91.9%)**. The 3 survivors are equivalent mutants (`> 0` vs `>= 0` on a count that is never zero, or on a ratio where 0/0 is already NaN) | +| **Tests** | **427 automated tests**, passing on both dependency sets. One asserts that no output ever claims causation | +| **Mutation testing** | `scripts/mutation_test.py` applies 37 operator and constant mutations to the KPI layer; the suite kills **34 (91.9%)**. The 3 survivors are equivalent mutants (`> 0` vs `>= 0` on a count that is never zero, or on a ratio where 0/0 is already NaN). On the pipeline, `scripts/mutation_test_pipeline.py` applies **28 realistic bugs** (Spark's rounding, a lost CSV option, a dropped tie-breaker, a spending cap that only logs) and the suite catches **all 28**, up from 22 before the gaps it found were tested. Operator mutants on the pipeline code: **93 of 108** caught; the 15 survivors are wait times, output file counts and print formatting | **On that false-alarm figure.** The 12 counts every signal that matched nothing planted, wherever it was raised: 9 on the two control windows and 3 alongside @@ -291,7 +291,7 @@ a million real invoice lines from a public dataset, where it beats a naive forecast baseline by 38.8% across 49 backtest folds. That dataset moves through a pipeline from S3 through a PySpark job into PostgreSQL and Redshift, with Spark's output checked against the pandas implementation on every row. Python, -pandas, PySpark, SQL, AWS, Streamlit; 363 tests, including one asserting that no +pandas, PySpark, SQL, AWS, Streamlit; 427 tests, including one asserting that no output ever claims causation. diff --git a/docs/power_bi.md b/docs/power_bi.md index 41f161a..1c9b320 100644 --- a/docs/power_bi.md +++ b/docs/power_bi.md @@ -59,11 +59,11 @@ The template is the reliable route. To rebuild the report from an empty file: - **Relationships.** `fact_sales` to `dim_date` on `date`, to `dim_customer` on `customer_id` and to `dim_region` on `region_code`, each many to one, single direction. Leave the `rpt_` tables unconnected; they are finished results. -- **Not `dim_sku`, for now.** 172 product codes in the published data differ - only in case (`15056BL` and `15056bl`, both "EDWARDIAN PARASOL BLACK"). - PostgreSQL keeps them apart, but Power BI compares text without case, finds - duplicates, and offers only a many-to-many relationship. The fix belongs in - the adapter, not the report. +- **Products.** `fact_sales` to `dim_sku` on `sku_id` works too. Building the + report found 172 stock codes written in two cases or with a trailing space + (`15056BL` and `15056bl`); Power BI compares text without case and saw + duplicates. The pipeline now trims and upper-cases codes, so the relationship + is many to one like the others. - **Measures.** Power BI names ignore case, so a measure cannot be called `Units` next to a column called `units`: ```DAX