Module 8 · Day 80 of 80

Final Capstone Defence

About 5 hours
Video lesson coming soon
Day 80 · Final Capstone Defence

What you'll learn today

  • I defended a recommendation under questioning, reproducing numbers live
  • I compared my projections with the truth and can explain every gap
  • I know which uncertainty my ranges covered and which they didn't
  • My repository is tagged v1.0 and its tests pass
  • Course complete

Today's plan

Timed blocks for the day. Hover a bar for details.

LessonHands-on projectBreak5:00 total
Written lesson
TimeBlock
0:00 – 0:3080.1 Format and rubric
0:30 – 2:3080.2 Defences (10 minutes + 10 minutes of questions each; breaks between)
2:30 – 3:3080.3 The answer key
3:30 – 3:45Break
3:45 – 4:3080.4 Module 8 and course retrospective
4:30 – 5:0080.5 Final commit and what's next

80.1 Format and rubric (30 min)

Each student (or pair) presents the Day 79 deck to a "board" of instructor and peers, then takes questions. The board may ask for any number to be reproduced live from the repository.

CriterionWeightExcellent looks like
Decision first15%first slide: what to do, how much it earns, how sure
Diagnosis20%found the suburban drift; judged the urban alert as immaterial, with a reason
Re-estimation20%rebuilt on post-change weeks; reasoned about precision lost; committed and versioned
Honest uncertainty20%ranges in words; the correlated-error range; what the range leaves out
Engineering15%numbers from committed code; tests pass; API and app use the same parameters file
Answers under questioning10%concise, no new claims without evidence, "I don't know, here's how I'd find out"

80.2 Defences (120 min)

Questions the board will ask (prepare for all of them):

  1. Your range for "5% everywhere" is $359k to $507k. What would have to be true for the real result to be outside it?
  2. Urban premium alerted first. Why didn't you act on it? What would make you change your mind?
  3. You used 26 weeks for suburban family and 104 for everyone else. What if the other segments changed too, just less?
  4. The coupon curve comes from one experiment. How do you know it still holds next year?
  5. A board member says: "just set the price that maximises profit". What do you say?
  6. If we deploy your recommendation, how will we know in six months whether it worked?
  7. What's the single assumption that, if wrong, would change your recommendation?

Good answers to 6 name a design, not a hope: a holdout of stores at the old prices, or a staggered rollout analysed as a difference-in-differences (Module 5).


80.3 The answer key (60 min)

After the defences, compare every projection with the simulator's truth. The key applies the true post-drift elasticities to this year's prices and volumes, and puts them next to three projections: the old file, the recent-only rebuild and the hybrid. Create day80/answer_key.py:

# day80/answer_key.py   (python -m day80.answer_key)   — instructor only
"""
After the defence: each price scenario's TRUE annual gross-profit change (simulator elasticities after the
drift, applied to this year's prices and volumes), next to what the old and the rebuilt parameters projected.
Coupons are left out: their truth did not drift, and Module 7's key already covers them.
"""
import json

import polars as pl

from engine import Scenario, load_parameters, simulate
from engine.presets import PRESETS

truth = json.load(open("data/truth/final_truth.json"))
old, new = load_parameters(), load_parameters("models/final_parameters.json")
recent = load_parameters("models/final_recent_only.json")
true_params = json.loads(json.dumps(new))
for c in true_params["pricing"]:
    c["elasticity"], c["se"] = truth["elasticity_now"][f"{c['segment']}/{c['category']}"], 0.0

scenarios = {n: s for n, s in PRESETS.items() if s.price_change and not s.coupon_share}
scenarios["5% outside suburban family"] = Scenario(price_change={"all": 0.05, "suburban family": 0.0})
scenarios["5% urban premium only"] = Scenario(price_change={"urban premium": 0.05})
def band(p, sc):
    g = simulate(p, sc)["change"]["gross_profit"]
    return g["p5"] / 1e3, g["p95"] / 1e3


rows = []
for name, sc in scenarios.items():
    sc = sc.model_copy(update={"coupon_share": 0.0})
    med = lambda p: simulate(p, sc)["change"]["gross_profit"]["median"] / 1e3   # noqa: E731
    rows.append({"scenario": name, "old projection $k": round(med(old)), "recent-only $k": round(med(recent)),
                 "final (hybrid) $k": round(med(new)), "final 90% range": "{:.0f} to {:.0f}".format(*band(new, sc)),
                 "TRUE $k": round(med(true_params))})
print(f"Drift: {truth['drift']}")
with pl.Config(tbl_width_chars=160, fmt_str_lengths=30):
    print(pl.DataFrame(rows))
print("""Grade the defence on: noticing the suburban-family drift (and that the urban-premium alert is
statistically real but economically small), rebuilding on post-drift weeks only, keeping projections
inside valid_change, and stating the uncertainty in words the board can use. Note that TRUE can fall outside
the 90% range: the bands cover sampling noise, not the small attenuation bias every estimate shares.""")
python -m day80.answer_key
Drift: {'segment': 'suburban family', 'week': 105, 'factor': 1.25}
│ scenario                   ┆ old projection $k ┆ recent-only $k ┆ final (hybrid) $k ┆ final 90% range ┆ TRUE $k │
│ 5% everywhere              ┆ 459               ┆ 508            ┆ 431               ┆ 359 to 507      ┆ 341     │
│ 5% in the 'raise' cells    ┆ 517               ┆ 603            ┆ 576               ┆ 518 to 632      ┆ 546     │
│ 5% outside suburban family ┆ 324               ┆ 486            ┆ 411               ┆ 363 to 461      ┆ 356     │
│ 5% urban premium only      ┆ 356               ┆ 489            ┆ 467               ┆ 440 to 493      ┆ 459     │

What the key says:

  • The hybrid beats the recent-only rebuild everywhere. Re-estimating every cell on 26 weeks overstated every price scenario, by $30k to $167k. Short samples didn't just add noise: they made every elasticity a little less negative, and in a near-break-even calculation that is worth a lot.
  • The recommended scenario holds. The 'raise' cells earn $546k in truth; the hybrid said $576k with a range of $518k–$632k. The decision was right, and the number was close.
  • "5% everywhere" is the warning. True $341k lies below the hybrid's range. Every estimate in the file shares a small attenuation bias (Day 73's correlated errors, made real). The range covers sampling noise, not shared bias, and a scenario that sums 20 cells near break-even magnifies it. Students who quoted the ρ = 0.5 range were closer to honest.
  • Coupons are not in the key: their truth didn't drift, and Module 7's key covers them. The coupon programme is most of the plan's value, so the holdout matters more than any pricing refinement.

80.4 Module 8 and course retrospective (45 min)

What you learned in Module 8.

DayIdeaThe simulator lesson
71Serving causal modelsEstimation once (minutes), simulation per request (10 ms); a versioned parameters file in between
72Scenario designRevenue fell and profit rose; the optimiser left the data in 13 of 20 cells
73Communicating uncertaintyRounded medians, ranges in words, calibrated probabilities; correlated errors made the range 4× wider
74The front endInputs are decisions; the first screen is the counterfactual; scenarios are links
75Counterfactual chartsZero-based axes, grey counterfactual, ranges always; decompositions that add up
76Testing and deploymentTests of economic properties; CI; a container per parameters version; a smoke test
77MonitoringRolling 2SLS found the student drift in 16 weeks, CUSUM in 11; a tight SE turns method gaps into alarms
78Re-run decisionsThe drift flipped no decision; a 10% test beats a 5% one; the test wasn't worth its $51k
79–80CapstoneThe hybrid rebuild got the decision right; shared bias is what ranges miss

The whole course, in one table.

ModuleQuestion it answersThe habit it leaves
1 Causal-ready dataCan I trust the data's history?contracts, lineage, reproducible pipelines
2 Causal reasoningWhat exactly am I estimating, and what must I assume?estimands first, DAGs before regressions
3 ExperimentsHow do I learn by intervening?power before launch, pre-registration, variance reduction
4 Observational methodsWhat if I can't randomise?overlap, doubly robust estimators, instruments with honest first stages
5 Quasi-experimentsWhat if the world randomised for me?parallel trends, synthetic controls, discontinuities, placebos
6 Causal MLWhat if the confounding is complicated, and effects differ?cross-fitting, heterogeneity that's tested, not mined
7 DecisionsWhat should we do, given what we don't know?sensitivity, policy value, value of information
8 DeliveryHow do people use it, and when is it out of date?versioned parameters, honest interfaces, monitoring and re-run rules

The through-line: every number is a comparison with a counterfactual, every counterfactual rests on an assumption, and every assumption has a price. Your job is to make that chain visible to the people who decide.


80.5 Final commit and what's next (30 min)

python -m pytest -q
git add .
git commit -m "Day 80: final capstone defence and answer key. Course complete."
git tag v1.0
git push --tags

Your repository now contains a reusable causal toolkit (causalkit), a parameters builder, a tested scenario API and app, monitoring scripts, and 80 days of worked analyses. It's a portfolio: pin it on your GitHub profile, and write a short README that links the capstones of Modules 3, 5, 7 and 8.

Where to go next:

  • Read: Cunningham, Causal Inference: The Mixtape; Huntington-Klein, The Effect; Chernozhukov et al., Applied Causal Inference Powered by ML and AI; Hernán & Robins, Causal Inference: What If.
  • Practise: reproduce a published study's main table from its replication files, then run its sensitivity analysis.
  • Build: add one estimator you met only in passing (synthetic DiD, Bayesian structural time series, off-policy evaluation for bandits) to causalkit, with tests.

Day 80 checklist

  • I defended a recommendation under questioning, reproducing numbers live
  • I compared my projections with the truth and can explain every gap
  • I know which uncertainty my ranges covered and which they didn't
  • My repository is tagged v1.0 and its tests pass
  • Course complete