Skip to content

Commit c29febf

Browse files
igerberclaude
andcommitted
Address PR #355 R11 P3: emit null instead of NaN in coverage artifact
The SDID coverage MC artifact carried bare ``NaN`` tokens for the ``stratified_survey`` × ``placebo`` / ``jackknife`` cells (unsupported by design — strata/PSU/FPC raises at fit-time). Python's ``json`` module tolerates those tokens on read, but strict JSON parsers reject them, making the committed artifact non-strict. ``_summarize`` now returns ``None`` (serializes as ``null``) instead of ``float('nan')`` on the all-failed branch, and ``json.dump`` is called with ``allow_nan=False`` so any stray non-finite value fails loudly instead of serializing as a bare ``NaN`` / ``Infinity`` token. The committed artifact has been patched in place (bare ``NaN`` → ``null``) and strict-loader-verified; no regen needed since the numeric content on the previously-NaN cells was definitionally absent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1a20c16 commit c29febf

2 files changed

Lines changed: 32 additions & 20 deletions

File tree

benchmarks/data/sdid_coverage.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,13 @@
136136
"placebo": {
137137
"n_successful_fits": 0,
138138
"rejection_rate": {
139-
"0.01": NaN,
140-
"0.05": NaN,
141-
"0.10": NaN
139+
"0.01": null,
140+
"0.05": null,
141+
"0.10": null
142142
},
143-
"mean_se": NaN,
144-
"true_sd_tau_hat": NaN,
145-
"se_over_truesd": NaN
143+
"mean_se": null,
144+
"true_sd_tau_hat": null,
145+
"se_over_truesd": null
146146
},
147147
"bootstrap": {
148148
"n_successful_fits": 500,
@@ -158,13 +158,13 @@
158158
"jackknife": {
159159
"n_successful_fits": 0,
160160
"rejection_rate": {
161-
"0.01": NaN,
162-
"0.05": NaN,
163-
"0.10": NaN
161+
"0.01": null,
162+
"0.05": null,
163+
"0.10": null
164164
},
165-
"mean_se": NaN,
166-
"true_sd_tau_hat": NaN,
167-
"se_over_truesd": NaN
165+
"mean_se": null,
166+
"true_sd_tau_hat": null,
167+
"se_over_truesd": null
168168
},
169169
"_elapsed_sec": 33.17
170170
}

benchmarks/python/coverage_sdid.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -301,24 +301,31 @@ def _summarize(
301301
ses: np.ndarray,
302302
p_values: np.ndarray,
303303
) -> Dict[str, Any]:
304-
"""Reduce per-seed (att, se, p_value) arrays to summary stats."""
304+
"""Reduce per-seed (att, se, p_value) arrays to summary stats.
305+
306+
Unsupported / all-failed cells serialize as ``null`` rather than
307+
``float('nan')`` so the output is strict JSON (PR #355 R11 P3).
308+
"""
305309
finite = np.isfinite(atts) & np.isfinite(ses) & np.isfinite(p_values)
306310
n_successful = int(finite.sum())
307311
if n_successful == 0:
308312
return {
309313
"n_successful_fits": 0,
310-
"rejection_rate": {f"{a:.2f}": float("nan") for a in ALPHAS},
311-
"mean_se": float("nan"),
312-
"true_sd_tau_hat": float("nan"),
313-
"se_over_truesd": float("nan"),
314+
"rejection_rate": {f"{a:.2f}": None for a in ALPHAS},
315+
"mean_se": None,
316+
"true_sd_tau_hat": None,
317+
"se_over_truesd": None,
314318
}
315319
atts_f = atts[finite]
316320
ses_f = ses[finite]
317321
ps_f = p_values[finite]
318322
rejection = {f"{a:.2f}": float(np.mean(ps_f < a)) for a in ALPHAS}
319323
mean_se = float(ses_f.mean())
320-
true_sd = float(atts_f.std(ddof=1)) if n_successful > 1 else float("nan")
321-
ratio = float(mean_se / true_sd) if np.isfinite(true_sd) and true_sd > 0 else float("nan")
324+
true_sd = float(atts_f.std(ddof=1)) if n_successful > 1 else None
325+
if true_sd is not None and np.isfinite(true_sd) and true_sd > 0:
326+
ratio: Optional[float] = float(mean_se / true_sd)
327+
else:
328+
ratio = None
322329
return {
323330
"n_successful_fits": n_successful,
324331
"rejection_rate": rejection,
@@ -473,7 +480,12 @@ def main() -> None:
473480
out_path = Path(args.output)
474481
out_path.parent.mkdir(parents=True, exist_ok=True)
475482
with open(out_path, "w") as f:
476-
json.dump(output, f, indent=2)
483+
# ``allow_nan=False`` so any stray float('nan') / inf fails loudly
484+
# instead of serializing as bare ``NaN`` / ``Infinity`` tokens
485+
# (non-strict JSON that strict parsers reject; PR #355 R11 P3).
486+
# ``_summarize`` returns ``None`` for unsupported / all-failed
487+
# cells precisely so this gate doesn't trip.
488+
json.dump(output, f, indent=2, allow_nan=False)
477489
print(f"\nWrote {out_path} ({out_path.stat().st_size / 1024:.1f} KB)", flush=True)
478490

479491

0 commit comments

Comments
 (0)