{
  "role": "code_methods_reviewer",
  "decision": "pass",
  "summary": "The implementation in generated/analysis.py matches the locked registration on population (2018 through latest completed season, outdoors/open, REG), primary threshold (15 mph vs below 10 mph), primary outcomes, controls (temperature linear/quadratic, week, season FE), uncertainty (4,000 bootstrap draws with hash-derived deterministic seeds; HC3 OLS), and sensitivities (10/20 mph thresholds, piecewise spline at 10/15/20, leave-one-season-out). Inputs are the nine approved nflverse files whose SHA-256 values in the evaluation run match the provenance manifest, and the provenance gate verifies hashes at runtime. Missingness is reconciled (1,487 outdoor schedule games, 164 excluded for missing weather or join, 1,323 analyzed) and sub-85% seasonal coverage in 2022-2023 is surfaced as a blocker. Two executions produced identical result hashes. The study makes no forecast claim so temporal leakage is not at issue. Bounded warnings: the code computes and Holm-adjusts a fifth, unregistered outcome (average target depth), bootstrap p-values are reported as exactly 0.0, and neither the threshold comparison nor the adjusted model controls for venue/team clustering, all of which the article discloses.",
  "findings": [
    {
      "severity": "warning",
      "check": "Registration drift: fifth outcome computed and included in Holm family",
      "evidence": "The registration lists four outcomes (neutral_dropback_rate, completion_rate, pass_epa_per_dropback, total_points), but OUTCOMES in analysis.py also defines average_depth_of_target, which is computed at every threshold and included in _holm_adjust across five rows. The article methods disclose this and the claim ledger labels it 'unregistered exploratory secondary check', while the article body calls it 'a secondary outcome computed in the locked analysis'.",
      "recommendation": "Keep the disclosure but harmonize the language (unregistered secondary, not registered) across article body, methods, and claim ledger; in future versions either register the fifth outcome or exclude it from the Holm family for the primaries. The current treatment is conservative for the four primaries, so this does not block."
    },
    {
      "severity": "warning",
      "check": "Bootstrap p-value granularity",
      "evidence": "_bootstrap_difference computes probability = 2*min(P(diff<=0), P(diff>=0)) over 4,000 draws; completion_rate and pass_epa_per_dropback report p_value 0.0 and holm_adjusted_p 0.0 in primary_effects. A resampling p-value cannot be exactly zero; the minimum resolvable value is 1/4000.",
      "recommendation": "Report bootstrap p-values as '< 0.0005' (or add 1 to the numerator as a continuity correction) in results.json and any downstream text. The article does not print these p-values, so the published claims are unaffected."
    },
    {
      "severity": "warning",
      "check": "Unmodeled clustering and confounding in adjusted baseline",
      "evidence": "estimate_adjusted_wind uses HC3 heteroskedasticity-robust standard errors but games are clustered by venue and season, and windy games concentrate at particular stadiums; no venue, home team, or opponent controls exist in either the threshold comparison or the adjusted model. The registration does not require them and the article limitations state this explicitly.",
      "recommendation": "Accept as disclosed for this association-only estimand; for a future version, register venue fixed effects or cluster-robust SEs by venue as an additional sensitivity."
    },
    {
      "severity": "note",
      "check": "Registration lock is self-asserted by the analysis code",
      "evidence": "run_weather_wind_study writes registration.json with locked_at=started_at and locked_before_results=True in the same process immediately before calling analyze_weather_wind; there is no external timestamp or hash independent of the run.",
      "recommendation": "Store the registration hash in a separate, earlier-committed artifact or use the code_sha commit history as the lock evidence in future runs."
    },
    {
      "severity": "note",
      "check": "Wind value of 0 treated as observed calm, not missing",
      "evidence": "load_weather_schedule requires wind.notna() and accepts any value in 0-100; nflverse schedule wind of 0 for outdoor games may represent either true calm or unrecorded weather. No check distinguishes them.",
      "recommendation": "Report the count of wind==0 games in coverage tables and consider a sensitivity excluding them."
    },
    {
      "severity": "note",
      "check": "Figure format differs between code and publication asset",
      "evidence": "_write_chart writes wind-offense-by-bin.svg with a fixed svg.hashsalt; the publication asset and article reference figures/wind-offense-by-bin.png (1200x630). The PNG conversion step is not in the reviewed code.",
      "recommendation": "Immutable shared asset; record only. Ensure the rasterization step is included in the reproducible pipeline so the PNG hash can be regenerated."
    },
    {
      "severity": "note",
      "check": "Season-stability gate is sign-only",
      "evidence": "leave_one_season_out compares np.sign of the high-minus-calm difference for each omitted season to the full-sample sign; magnitudes and intervals are not evaluated.",
      "recommendation": "Consistent with the registered 'direction flips' criterion; consider reporting per-season differences in the article's Research File for reader inspection."
    }
  ],
  "artifacts_reviewed": [
    "generated/analysis.py",
    "analysis.json",
    "research-registration.json",
    "data/evaluation-run.json",
    "data/gates.json",
    "data/reproducibility.json",
    "provenance.json",
    "claim-ledger.json",
    "article-draft.json",
    "article.md",
    "publication-assets-adapter.json"
  ],
  "model": "anthropic/claude-fable-5.1",
  "created_at": "2026-09-03T04:10:59.005699+00:00"
}
