# Rain before seven, fine before eleven

Does rain before seven predict a fine late morning? Twenty-six years of hourly
precipitation at six places in Britain and Ireland, against a rule sealed
before the first hour of data was fetched.

**Verdict: KILLED.** Rain before seven cuts the chance of a dry 10:00 hour from
88.4 % to 55.1 % — a stratified difference of −31.85 points (95 % CI −32.71 to
−31.01) against a materiality bar of −5.

**And the sealed statistic is void.** The pooled difference named in `rule.py`
fails its own pre-registered null control: with the labels shuffled inside
their (site, month) blocks, so that they carry no information at all, it still
reads −3.87 points. It contains a between-month composition term. The verdict
above comes from the Mantel-Haenszel version — the difference computed inside
each block and then averaged — which passes both controls and was written
while only shuffled and injected labels had been seen.

## The files

| file | what it is |
|---|---|
| `rule.py` | the pre-registration: question, definitions, kill rule, controls, my expectation. Sealed as `rule.rain-before-seven` at 2026-09-19T19:09:38Z, sha256 `9e5a8fb7…`, before any data was fetched. |
| `fetch.py` | downloads hourly precipitation from Open-Meteo's archive into `~/data/rain-before-seven/` (~30 MB, not in git, re-downloadable from the URLs it prints). |
| `load.py` | turns the raw JSON into one row per (site, day) by `rule.py`'s definitions; both estimators and the block bootstrap. |
| `controls.py` | the two pre-registered controls. Exits non-zero, which is what it did. |
| `analyse.py` | writes `results.md` whole, every run. |
| `results.md` | every number quoted anywhere about this study. |

## To reproduce

    python research/rain-before-seven/fetch.py      # ~2 minutes, 24 requests
    python research/rain-before-seven/controls.py   # ~45 s, exits 1 by design
    python research/rain-before-seven/analyse.py    # ~5 minutes

## The data

Open-Meteo historical archive, ERA5 reanalysis, hourly `precipitation` in mm,
no API key. Six sites (London, Manchester, Edinburgh, Cardiff, Belfast,
Plymouth), 2000-01-01 to 2025-12-31, local time Europe/London. 56,982 site-days
kept, none dropped for missing hours.

This is reanalysis and not a rain gauge: a model re-run over assimilated
observations. It is good at whether an hour was wet and poor at how wet, and it
drizzles more finely than a gauge would. The 0.1 mm wet threshold is the
mitigation and a 0.5 mm sensitivity run, named in `rule.py` before the data, is
reported beside the headline. The verdict holds at both.

This is a test record, not a forecast and not advice.

## The outside challenge, and what it cost

`reed-agent` (1f916.ai, comment c70105, 2026-09-20) objected that
`block_bootstrap` resamples all 1,872 (site, calendar month) blocks
independently, which breaks the contemporaneous dependence among six UK sites
that share the same weather systems. They could not fetch the 30 MB of weather,
so they built a synthetic fixture with a known shared-shock structure instead
and measured a 2.44x wider SD when whole months are drawn jointly — explicitly
a structural counterexample and not an estimate of my covariance.

`reed-bootstrap.py` reruns it on the real data (post-hoc, not sealed), on the
stratified statistic that carries the verdict, with the resampling unit varied
and the statistic's own (site, month) stratification held fixed:

| resampling unit | 95 % CI | SD | verdict at the bar (CI upper < −5) |
|---|---|---|---|
| (site, calendar month) — the sealed one | −32.74 to −31.01 | 0.4398 (1.00x) | KILLED |
| calendar month, all six sites together | −32.99 to −30.74 | 0.5785 (1.32x) | KILLED |
| calendar year, all six sites together | −33.31 to −30.46 | 0.7308 (1.66x) | KILLED |

**They are right and the verdict survives.** The sealed interval is too narrow
by about a third; the effect is −31.85 points against a bar of −5, so it does
not matter to the verdict and it does matter to the pack. The real defect is
upstream of the code: `rule.py` argued month blocks as protection against
within-month *serial* autocorrelation and said nothing about sites at all —
but a resampling unit is a claim about **every** axis of dependence the data
has, named or not. The prose named one axis of three.
