Land

Take the file as published and change nothing. Every later stage has to be able to point back at this and say what it did.

  • pandas
What happened

The retail export arrives as two spreadsheet tabs that overlap by nine days. Concatenate them, as the obvious first line of code does, and you inherit a $632,383 double count that survives naive de-duplication because the line values differ.

Data governance
  • shared/datasets.py

439 lines

Profile

Measure what is actually in the file before assuming anything about it — nulls, types, keys, duplicates, encoded missingness.

  • pandas
  • NumPy
What happened

One scorecard over 26,304,951 cells across five public datasets. The bank set reports zero null cells and hides 12,718 missing values behind a token instead.

Cross-dataset quality
  • projects/20-cross-dataset-quality/run.py

1,317 lines

Contract

Declare the rules the data must satisfy — grain, keys, types, ranges, sign conventions — before scoring it, so thresholds cannot be tuned to flatter the result.

  • pandas
  • NumPy
What happened

Thirty-one rules written up front. 19 of 31 fail, nine at critical severity, and 65.4% of lines violate at least one. Twelve pass, which is worth stating as loudly as the failures.

Data governance
  • projects/02-data-governance/run.py

1,148 lines

Model

Build the tables the analysis reads: one row per grain, keys that hold, rejected rows accounted for rather than dropped quietly.

  • pandas
  • NumPy
What happened

8 tables over 1,024,826 fact rows with 108 tests on every build. Every rejected row is accounted for, so the warehouse total ties back to the raw file rather than being asserted to.

Analytics engineering
  • shared/loader.py
  • projects/03-analytics-engineering/run.py

1,307 lines

Analyse

The modelling itself — forecasts, elasticities, classifiers, segments. The shortest stage, and the one everybody thinks is the whole job.

  • NumPy
  • pandas
  • scikit-learn
What happened

Weekly demand across 1,344 SKUs on a 13-week holdout: 0.6848 WMAPE against 0.7239 for a four-week moving average. A modest win, published as one.

Demand forecasting
  • projects/01-demand-forecasting/run.py

738 lines

Challenge

Test the result against the stupid version, and against what would happen if the effect were not there at all. Most methods do not survive this.

  • pandas
  • NumPy
  • statsmodels
What happened

Our own exception rule captured 34.0% of revenue variance. A naive top-N rule captured 51.2% from a third of the rows. The loss is published, because a method never tested against the obvious one is a method you cannot price.

Decision intelligence
  • projects/04-decision-intelligence/run.py
  • projects/06-pricing-elasticity/run.py

1,654 lines

Publish

Emit the result, the findings and what could not be established — then generate every page and figure from that one file.

  • Python
  • HTML
  • CSS
  • JavaScript
  • TypeScript
What happened

emit.py refuses to write a result that carries no findings or no limitations. Across the portfolio that is 51 findings and 50 limitations, and every number on this site is read from those files rather than typed into a page.

All projects
  • shared/emit.py
  • tools/build-project-pages.py

1,247 lines

Verify

Delete the results, run everything again, and diff. Then check the prose, because reproducing a number says nothing about the sentence around it.

  • Python
  • Git
  • Actions
What happened

verify.py re-runs all eight projects and diffs every headline value and chart series. It passed twenty out of twenty while four published claims were factually wrong — three Manual adjustment lines where the data has seven, among others — because all four lived inside f-strings. lint_narrative.py exists to catch that class, and found them.

All projects
  • verify.py
  • lint_narrative.py

433 lines

The whole chain

Every stage is in the repository, not in a diagram.

Each file named above is real, and the tool list beside it is read from that code when this page is built. Where a stage is pure standard library, the tools it plainly uses are named instead — this stage writes the site's HTML and CSS, and emit.py stamps the git revision into every result.