# WP37 — Preregistration of hypotheses, samples, estimands and analysis plan Status: **FROZEN** on 2026-11-01 before any hypothesis-relevant estimation (hash recorded in `research/decisions.md`). Amendments after this date are logged in `research/decisions.md` with a reason; the paper reports both the preregistered and any amended analysis. ## 0. What was examined before freezing (disclosure) Phase 0–1 work looked at: table counts, per-feed coverage, the event-log data-generating process, intra-day price oscillation diagnostics (share of A→B→A reversions, number of distinct price values, weekly Jaccard of value sets), unit-price coverage, the distribution of current discount depth (`1 − price/regular_price`, median 0.17), UPC validity, matching precision/recall and the validation sample. No cross-banner price gap, promotion duration, index, response model or forecast has been computed. The store-mixture finding (Phase 0 report §3.3) motivated the weekly modal-price definition below; this is a data-quality decision, not a result-driven one. ## 1. Universe, period and units - Snapshot `foodka-snapshot-20261101` (sha256 `3cd7684d…`), event log 2026-08-12 → 2026-11-01, **12 complete circular weeks** (Thu 2026-08-13 → Wed 2026-11-04 truncated at 2026-11-01; the first partial week 2026-08-06 and the last partial week 2026-10-29 are dropped from weekly analyses unless stated). - Grocery universe = listings from feeds whose `store_format` is not pharmacy or alcohol monopoly, in categories other than {Santé et beauté, Maison et entretien, Animaux, Bébé, Alcool}, with ≥ 1 observed price and no unrealistic-price flag. - **Posted price of listing i in week w** = duration-weighted modal price `p_mode` over the week (primary); `p_min`, `p_max`, `p_twmean` as sensitivity. Unit price = posted price / parsed package quantity (per 100 g, 100 ml or unit). - **Matched product** = cross-banner match group built from **tiers A + B** (primary); A+B+C and A+B+C+D in sensitivity; groups with > 1 simultaneously active listing from the same banner-channel are flagged `ambiguous` and excluded from the primary sample. - Channel: storefront vs circular. Same-banner channel links are not cross-banner comparisons. - Clustering default: two-way by match group and by banner (banner clusters are few — 17 banners with storefront feeds — so wild-cluster bootstrap p-values are reported alongside). ## 2. Hypotheses ### H1 — Matched-item cross-banner dispersion is economically meaningful and persistent - Sample: product-weeks of matched groups (A+B) with ≥ 2 banners posting a price in the same week; storefront channel only (primary), circulars added in sensitivity. - Outcome: `log p_{irw}`; dispersion statistics per group-week: coefficient of variation, max–min log gap, interquartile log range. - Estimand (Model A): `log p_irw = α_iw + γ_r + ε_irw` (product-week FE, banner FE). Banner effects γ_r are relative posted price levels; dispersion = sd of residual + sd of γ. Persistence: autocorrelation of the banner-pair log gap `g_{i,rs,w}` across weeks (AR(1) coefficient), share of pairs whose ranking never flips. - Null: median within-group max–min log gap = 0 (trivially rejected); the **economic threshold** we preregister is a median gap ≥ 0.05 (5 %) and AR(1) ≥ 0.5 for "meaningful and persistent". - Controls: none beyond FE in the primary spec; sensitivity adds category × week FE. - Missingness: a banner-week without a posted price is missing, not zero; groups with intermittent coverage are kept (unbalanced) and a balanced common-product panel is reported as robustness. - Falsification: Metro vs Super C exact-UPC pairs (same company, two formats) should show *smaller* dispersion than cross-company pairs if company-level pricing coordination exists; placebo: random re-assignment of banners within group-week should yield AR(1) ≈ 0. - Minimum duration: 8 weeks for AR(1); available. ### H2 — Promotion-adjusted price volatility differs from non-promotion volatility - Sample: listing-weeks of storefront feeds (grocery universe) with ≥ 8 observed weeks. - Promotion state inferred (storefront): a week is "promoted" if `p_mode_w ≤ 0.90 × reference_w`, where reference_w is the modal price over the trailing 8 weeks (including w) — the Nakamura–Steinsson/Kehoe–Midrigan reference filter; validated against the current `on_sale` flag in the snapshot week (report agreement) and against circular items of the same banner-week when matched. - Outcome: sd of weekly log price changes (i) raw, (ii) excluding promotion weeks (reference-price series). - Estimand: ratio of raw to reference-price volatility by banner and category; share of all weekly log changes attributable to promotion entry/exit. - Null: ratio = 1. Clustering: by banner × category (bootstrap over listings). - Falsification: single-store online shops (Shopify/WooCommerce feeds) must not display the 3–5-level oscillation; if they do, the store-mixture interpretation fails and H2 is re-examined at the daily frequency. - Missingness: weeks with no observed price are dropped from the change series (no interpolation). ### H3 — Promotions are associated with subsequent reference-price adjustments - Sample: promotion episodes (≥ 1 promoted week followed by return to a non-promoted week) in storefront feeds; circular items matched to a storefront listing of the same banner (tier channel link) provide the second sample with observed `original_price`. - Outcome: change in the reference price after the episode vs before: `Δ log ref = log ref_{post} − log ref_{pre}`. - Estimand (Model B): transition matrix regular↔promoted; episode duration, recurrence interval, depth; event-study of log price from −4 to +4 weeks around episode end with listing FE and week FE; test of pretrends (weeks −4…−2 coefficients jointly = 0). - Null: E[Δ log ref] = 0 (promotions neither precede nor follow reference increases). Direction is left to the data. - Falsification/censoring: episodes censored at panel edges are excluded; overlapping episodes within the same listing are merged; placebo events at random weeks must give Δ ≈ 0. - Minimum duration: 26 weeks for a well-powered test; with 12 weeks the test is **exploratory** and labelled so. ### H4 — Competitor responses are strongest for close substitutes and nearby stores - Store geography is unavailable → the "nearby stores" part is **not testable** and is stated as such. The testable part: banner-level weekly response of `Δ log p_{irw}` to lagged rival changes `Δ log p_{isw−k}` (k = 0…3) within matched group, with product-week and banner FE (Model C), separately for same-company rivals (Metro/Super C; Maxi/Provigo/Club Entrepôt) vs cross-company rivals. - Estimand: distributed-lag coefficients; contemporaneous (k = 0) coefficients are **not** causal (common shocks, simultaneity); k ≥ 1 coefficients are descriptive lead–lag associations. No IV/quasi-experiment is preregistered because no exogenous shifter is available. - Null: all lagged coefficients = 0. - Clustering: two-way group × week. Falsification: placebo with rival changes shifted forward (leads) must be ≈ 0. ### H5 — Package-size and assortment changes create gaps between naive and quality-consistent indices - Sample: grocery universe, weekly, storefront + circular. - Indices (Model D): (a) naive average posted price by category; (b) Jevons matched-model index (geometric mean of weekly log changes over listings present in both weeks); (c) fixed-basket Laspeyres with equal item weights (no expenditure weights exist — explicitly **not** a CPI); (d) chained version; (e) unit-price versions of (b)–(d) that re-match listings whose size label changed. - Estimand: cumulative difference between (a) and (b) over 12 weeks; count and unit-price effect of within-listing size changes. - Null: gap = 0. Uncertainty by listing-level bootstrap (B = 999, seed 20261101). - Benchmark: StatCan CPI "Food purchased from stores", Quebec, Aug→Sep 2026 (2 months) — comparison only, no superiority claim. ### H6 — Unconstrained minimum-price basket savings exceed savings feasible under constraints - Basket: 30 generic items specified by category and quantity (preregistered in `config/basket.yaml` before estimation), mapped to matched groups (A+B) and, where no matched group exists, to the cheapest listing per banner within a tight name rule (documented). - Model E: integer program minimising basket cost subject to at most K banners (K = 1…5), each item bought at one banner, availability = posted price exists that week. Travel costs are **not** observable; a sensitivity grid of fixed per-store costs (0, 5, 10, 20 CAD) is reported as illustrative, labelled as assumption-based. - Estimand: savings frontier S(K) = cost(K=1 best single banner) − cost(K); share of unconstrained savings attained at K = 2. - Null: S(∞) − S(2) = 0 (two banners capture everything). ### Forecasting (Model F) — preregistered evaluation - Target: next-week posted unit price (log) and next-week promotion indicator per listing (storefront). - Rolling origin: train weeks 1…t, test week t+1, t = 6…11; models: seasonal naive (last week), banner-category mean reversion, ridge, LightGBM; metrics: MAE / RMSE on log price, Brier / AUC on promotion. No future information in features (strict lags only). ## 3. QGPAI components (Phase 5) — fixed before computation 1. Comparable Price Index: Jevons matched-model index over matched groups (A+B), weekly, base = week of 2026-08-13 = 100. 2. Promotion Intensity: share of listing-weeks promoted; mean depth; sd of depth — storefront (inferred) and circular (observed) reported separately. 3. Retail Price Dispersion: median within-group max–min log gap (H1 statistic). 4. Feasible Basket Cost: Model E cost at K = 1 and K = 2. 5. Access & Affordability: **not computed** (no geography/income data joined); placeholder documented. Aggregation: components are published separately; an equal-weight z-score composite is shown only as an illustration with alternative weights (0.5/0.5 on components 1 and 4) — not an empirical estimate. ## 4. Multiple testing Six preregistered families (H1–H6); within each, Holm correction over the primary tests. Exploratory analyses are labelled.