Code / tools/new_experiment.py
tools/new_experiment.py
164 lines
#!/usr/bin/env python3
# =============================================================================
# Project : anomaly-atlas
# File : tools/new_experiment.py
# Purpose : Scaffold a header-compliant experiment directory (CLAUDE.md §3, §10)
# Author : Simon-Pierre Boucher
# Contact : contact@spboucher.ai
# Data src : hfmarketdata.io (sole data source)
# Created : 2026-08-12
# Modified : 2026-08-12
# Platform : macOS / Apple Silicon (arm64)
# License : All rights reserved (research code)
# =============================================================================
"""Scaffold a new experiment directory with the mandatory structure.
Creates: README.md, hypothesis.md (seven-field scientific block, incl. the
artifact null), benchmark.py, implementation/, results/, analysis.md — all
with conforming author headers.
Usage:
python3 tools/new_experiment.py experiments/micro/expX_name "One-line purpose"
python3 tools/new_experiment.py experiments/candidate_04 "Candidate: ..."
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
TODAY = date.today().isoformat()
PY_HEADER = """\
# =============================================================================
# Project : anomaly-atlas
# File : {rel}
# Purpose : {purpose}
# Author : Simon-Pierre Boucher
# Contact : contact@spboucher.ai
# Data src : hfmarketdata.io (sole data source)
# Created : {today}
# Modified : {today}
# Platform : macOS / Apple Silicon (arm64)
# License : All rights reserved (research code)
# =============================================================================
"""
MD_HEADER = """\
---
project: anomaly-atlas
document: {doc}
author: Simon-Pierre Boucher
contact: contact@spboucher.ai
data_source: hfmarketdata.io
created: {today}
status: draft
---
"""
HYPOTHESIS_BODY = """
# Hypothesis — {name}
```text
Hypothesis
<what we believe and why — pre-specified BEFORE looking at results>
Falsification criterion
<the concrete measurable outcome that would prove this wrong>
Artifact null(s)
<the fake-signal baseline(s) this must beat: bounce / staleness /
non-synchronous timestamps / permuted calendar / random walk>
Method
<exact procedure, universe, split (train/validation/holdout), seeds,
number of hypotheses tested, correction applied>
Result
<filled after the run: effect size, bootstrap CIs, corrected p-values,
OOS status, cost-adjusted effect, credits used>
Interpretation
<what the numbers mean, WITH confidence level (0-3); alternative
explanations considered — artifact first>
Next experiment
<the most informative follow-up given this result>
```
"""
BENCHMARK_BODY = '''
"""Benchmark entry point for {name}.
Must embed the hardware manifest in all result output
(see benchmarks/hardware_manifest.py) and write results to
results/{name}/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
through src/anomaly_atlas/data/hf_client.py.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[{depth}] / "benchmarks"))
from hardware_manifest import collect_manifest # noqa: E402
def main() -> None:
collect_manifest() # embedded in results once implemented
raise NotImplementedError("experiment not yet implemented")
if __name__ == "__main__":
main()
'''
def scaffold(exp_dir: Path, purpose: str) -> None:
if exp_dir.exists() and any(exp_dir.iterdir()):
sys.exit(f"error: {exp_dir} already exists and is not empty")
name = exp_dir.name
rel = exp_dir.relative_to(REPO_ROOT)
(exp_dir / "implementation").mkdir(parents=True, exist_ok=True)
(exp_dir / "results").mkdir(exist_ok=True)
def md(doc: str) -> str:
return MD_HEADER.format(doc=doc, today=TODAY)
(exp_dir / "README.md").write_text(
md(f"{name}/README")
+ f"\n# {name}\n\n{purpose}\n\nStatus: scaffolded {TODAY}, not yet run.\n"
)
(exp_dir / "hypothesis.md").write_text(
md(f"{name}/hypothesis") + HYPOTHESIS_BODY.format(name=name)
)
(exp_dir / "analysis.md").write_text(
md(f"{name}/analysis") + f"\n# Analysis — {name}\n\n*To be written after results exist. "
"Must include the seven-field block and the evidence standard of CLAUDE.md §10 "
"(never report an in-sample number as a finding).*\n"
)
depth = len(rel.parts) # parents[] index up to repo root
(exp_dir / "benchmark.py").write_text(
PY_HEADER.format(
rel=rel / "benchmark.py",
purpose=f"Benchmark runner: {purpose}"[:82],
today=TODAY,
)
+ BENCHMARK_BODY.format(name=name, depth=depth)
)
print(f"scaffolded {rel} ({purpose})")
def main() -> None:
if len(sys.argv) < 3:
sys.exit(__doc__)
exp_dir = (REPO_ROOT / sys.argv[1]).resolve()
if REPO_ROOT not in exp_dir.parents:
sys.exit("error: experiment directory must live inside the repository")
scaffold(exp_dir, sys.argv[2])
if __name__ == "__main__":
main()