# Three exposure-gradient event studies on the CrUX origin-month panel.
# SCOPE: this is the event-study layer only. The balanced-panel runs, the technology strata and the
# additive/Shapley allocation described in the article are specified there but NOT implemented here.
#
# Inputs: the two CSVs exported by step 04 of crux-origin-panel-extract.sql. Run that first — the SQL
# creates BigQuery tables, and without the extract step these files do not exist.
#
# Outcome note: pass_cwv_inp is NULL where LCP or CLS is unmeasurable, so those origin-months drop out
# of the regressions instead of counting as failures. feols() does that by default; do not coalesce it
# to 0, or every INP-coverage change becomes a fake pass/fail change at the events being estimated.

library(data.table)
library(fixest)

p <- fread('origin_month_panel_202401_202605.csv')
e <- fread('event_exposure_features.csv')
dt <- merge(p, e, by = 'origin', all.x = TRUE)
dt[, ym_index := as.integer(substr(yyyymm,1,4))*12 + as.integer(substr(yyyymm,5,6))]

# Android data-quality/fix differential effect.
dt[, rel_android_fix := ym_index - (2025*12 + 7)]
m_android <- feols(
  pass_cwv_inp ~ i(rel_android_fix, pre_android_issue_phone_density, ref = -1) |
    origin + yyyymm,
  cluster = ~ origin,
  data = dt[rel_android_fix >= -6 & rel_android_fix <= 6]
)
print(summary(m_android))

# bfcache exposure model.
# navigation_type_back_forward and navigation_type_back_forward_cache are MUTUALLY EXCLUSIVE shares of
# all page loads (they sum to ~1 with the other navigation types), so back_forward IS the miss share.
# Subtracting the hit share from it — as an earlier revision did — is not total-minus-subset; it is
# misses minus hits, and pmax(0, .) then flattens to zero every origin whose bfcache hit rate exceeds
# 50%. On Chrome's own documented example (0.0403 / 0.0677) that assigns ZERO opportunity to an origin
# with a 37% bfcache miss rate. The primary exposure is the raw miss share: the fraction of ALL loads
# that could have been served from bfcache and were not.
dt[, bfcache_opportunity := pre_bfcache_miss_share]
dt[, rel_bfcache := ym_index - (2025*12 + 3)]
m_bfcache <- feols(
  pass_cwv_inp ~ i(rel_bfcache, bfcache_opportunity, ref = -1) |
    origin + yyyymm,
  cluster = ~ origin,
  data = dt[rel_bfcache >= -6 & rel_bfcache <= 6]
)
print(summary(m_bfcache))

# Sensitivity: the same model on the normalised miss RATE among history navigations
# (back_forward / (back_forward + back_forward_cache)), which ranks origins by how badly bfcache is
# working rather than by how much history traffic they get. Both should point the same way.
m_bfcache_rate <- feols(
  pass_cwv_inp ~ i(rel_bfcache, pre_bfcache_miss_rate, ref = -1) |
    origin + yyyymm,
  cluster = ~ origin,
  data = dt[rel_bfcache >= -6 & rel_bfcache <= 6]
)
print(summary(m_bfcache_rate))

# INP scroll exclusion differential effect.
dt[, scroll_exposure := pre_scroll_phone_density * pre_scroll_inp_risk]
dt[, rel_inp_scroll := ym_index - (2024*12 + 8)]
m_scroll <- feols(
  pass_cwv_inp ~ i(rel_inp_scroll, scroll_exposure, ref = -1) |
    origin + yyyymm,
  cluster = ~ origin,
  data = dt[rel_inp_scroll >= -6 & rel_inp_scroll <= 6]
)
print(summary(m_scroll))
