soloresearch 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. soloresearch-0.1.1/.github/workflows/ci.yml +38 -0
  2. soloresearch-0.1.1/.github/workflows/publish.yml +32 -0
  3. soloresearch-0.1.1/.gitignore +21 -0
  4. soloresearch-0.1.1/LICENSE +1 -0
  5. soloresearch-0.1.1/PKG-INFO +161 -0
  6. soloresearch-0.1.1/README.md +141 -0
  7. soloresearch-0.1.1/pyproject.toml +27 -0
  8. soloresearch-0.1.1/soloresearch/__init__.py +0 -0
  9. soloresearch-0.1.1/soloresearch/autoresearch/CLAUDE.md +85 -0
  10. soloresearch-0.1.1/soloresearch/autoresearch/GEMINI.md +85 -0
  11. soloresearch-0.1.1/soloresearch/autoresearch/__init__.py +0 -0
  12. soloresearch-0.1.1/soloresearch/autoresearch/eval.py +91 -0
  13. soloresearch-0.1.1/soloresearch/autoresearch/logger.py +43 -0
  14. soloresearch-0.1.1/soloresearch/autoresearch/run.py +82 -0
  15. soloresearch-0.1.1/soloresearch/autoresearch/solution.py +33 -0
  16. soloresearch-0.1.1/soloresearch/cli.py +93 -0
  17. soloresearch-0.1.1/soloresearch/dq/__init__.py +35 -0
  18. soloresearch-0.1.1/soloresearch/dq/characterize.py +152 -0
  19. soloresearch-0.1.1/soloresearch/dq/definitions.py +182 -0
  20. soloresearch-0.1.1/soloresearch/dq/gate.py +142 -0
  21. soloresearch-0.1.1/soloresearch/dq/load.py +226 -0
  22. soloresearch-0.1.1/soloresearch/dq/regime.py +326 -0
  23. soloresearch-0.1.1/soloresearch/dq/report_excel.py +303 -0
  24. soloresearch-0.1.1/soloresearch/dq/report_html.py +133 -0
  25. soloresearch-0.1.1/soloresearch/dq/report_json.py +82 -0
  26. soloresearch-0.1.1/soloresearch/dq/run.py +295 -0
  27. soloresearch-0.1.1/soloresearch/ingest.py +121 -0
  28. soloresearch-0.1.1/soloresearch/resolver.py +96 -0
  29. soloresearch-0.1.1/soloresearch/run_forecast.py +206 -0
  30. soloresearch-0.1.1/tests/test_ci.py +55 -0
@@ -0,0 +1,38 @@
1
+ name: CI
2
+
3
+ # run on every push and pull request to main
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+ branches: [main]
9
+
10
+ jobs:
11
+ build-and-test:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ python-version: ["3.10", "3.11", "3.12"]
16
+
17
+ steps:
18
+ - name: Check out the code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Build the wheel
27
+ run: |
28
+ python -m pip install --upgrade pip build
29
+ python -m build --wheel
30
+
31
+ - name: Install the built wheel and pytest
32
+ run: |
33
+ python -m pip install dist/*.whl
34
+ python -m pip install pytest
35
+
36
+ - name: Run the tests
37
+ run: |
38
+ python -m pytest tests/ -q
@@ -0,0 +1,32 @@
1
+ name: Publish to PyPI
2
+
3
+ # runs when you push a version tag like v0.1.0
4
+ on:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+
13
+ # trusted publishing needs this permission to get an OIDC token
14
+ permissions:
15
+ id-token: write
16
+
17
+ steps:
18
+ - name: Check out the code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.12"
25
+
26
+ - name: Build the wheel and sdist
27
+ run: |
28
+ python -m pip install --upgrade pip build
29
+ python -m build
30
+
31
+ - name: Publish to PyPI
32
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,21 @@
1
+ # build artifacts
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ __pycache__/
6
+ *.pyc
7
+
8
+ # environments
9
+ .venv/
10
+ venv/
11
+ .env
12
+
13
+ # test data and run outputs (never ship these)
14
+ data/
15
+ outputs/
16
+ runs/
17
+ soloresearch/autoresearch/data/
18
+ soloresearch/autoresearch/runs/
19
+
20
+ # OS junk
21
+ .DS_Store
@@ -0,0 +1 @@
1
+ MIT License
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.5
2
+ Name: soloresearch
3
+ Version: 0.1.1
4
+ Summary: Autonomous single-track forecasting research: data-quality gating plus an agent-driven model search.
5
+ Project-URL: Homepage, https://github.com/shantiswarup2/soloresearch
6
+ Project-URL: Repository, https://github.com/shantiswarup2/soloresearch
7
+ Author-email: Shanti Swarup Nayak <shantiswarup.nayak@sigmoidanalytics.com>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.9
11
+ Requires-Dist: numpy>=1.23
12
+ Requires-Dist: openpyxl>=3.1
13
+ Requires-Dist: pandas>=1.5
14
+ Requires-Dist: pyarrow>=10.0
15
+ Requires-Dist: pyyaml>=6.0
16
+ Requires-Dist: ruptures>=1.1.7
17
+ Requires-Dist: scipy>=1.9
18
+ Requires-Dist: statsmodels>=0.14
19
+ Description-Content-Type: text/markdown
20
+
21
+ # soloresearch
22
+
23
+ Autonomous single-track forecasting research. Point it at a table of historical
24
+ values and it does two things: checks whether the data can be forecast at all,
25
+ then lets a coding agent iteratively discover a forecasting model that drives
26
+ validation error down, with every attempt scored against a leakage-safe judge
27
+ and logged.
28
+
29
+ It is the packaged version of a research loop that, on the M5 competition data,
30
+ took a seasonal-naive baseline from WRMSSE 0.807 to 0.542 with no human in the
31
+ loop. The package ships the *method* (the data-quality gate, the scoring judge,
32
+ and the agent-driven search); the numbers you get depend on your data.
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ ```
39
+ pip install soloresearch
40
+ ```
41
+
42
+ Python 3.9+. All dependencies (pandas, numpy, scipy, statsmodels, ruptures,
43
+ openpyxl, pyarrow, pyyaml) install automatically.
44
+
45
+ For stage 2 you also need a coding-agent CLI, either the Gemini CLI or Claude
46
+ Code. The package works with whichever you have.
47
+
48
+ ---
49
+
50
+ ## Quickstart
51
+
52
+ ```
53
+ soloresearch init myproject # 1. scaffold a project
54
+ # 2. put your data file in myproject/data/ and fill in myproject/forecast.yaml
55
+ soloresearch run myproject # 3. data-quality checks + stage the panel
56
+ cd myproject/autoresearch
57
+ gemini # 4. or `claude`
58
+ ```
59
+
60
+ Then give the agent this prompt:
61
+
62
+ > Read GEMINI.md and begin the autonomous forecasting research loop.
63
+ > Iterate on solution.py until RMSSE plateaus.
64
+
65
+ That is the whole flow. Stage 1 (`run`) is automatic. Stage 2 is an agent
66
+ working in the project, exactly as the original research loop did.
67
+
68
+ ---
69
+
70
+ ## How it works
71
+
72
+ ### Stage 1 - data quality (automatic)
73
+
74
+ `soloresearch run` reads your `forecast.yaml`, loads the data, and runs a
75
+ two-part assessment on every series:
76
+
77
+ - **Structural** - parses timestamps, sorts, de-duplicates, fills calendar gaps,
78
+ and infers the target profile from the actual values. Verdict: Clean /
79
+ Acceptable / Deficient.
80
+ - **Forecastability** - gates on the three things no model can fix: too little
81
+ history, a constant/flat series, or pure white noise. Verdict: Forecastable /
82
+ Not Forecastable.
83
+
84
+ It also characterises each series (trend, seasonality, demand class) and detects
85
+ four kinds of regime change (level, trend, variance, seasonality). The headline
86
+ is **Forecast-Ready %**: the share of series that are both non-Deficient and
87
+ Forecastable.
88
+
89
+ Reports are written to `myproject/outputs/` as Excel (detailed), HTML (summary),
90
+ and JSON (machine-readable). A cleaned panel is staged for stage 2.
91
+
92
+ If too little of the data is forecastable, it stops here and tells you why.
93
+
94
+ ### Stage 2 - agent-driven model search
95
+
96
+ The clean panel becomes a research problem. A coding agent, following the
97
+ `GEMINI.md` / `CLAUDE.md` instructions in the project, repeatedly:
98
+
99
+ 1. reads the current best score,
100
+ 2. edits `solution.py` (one hypothesis at a time),
101
+ 3. runs `python run.py` to score it against a leakage-safe RMSSE judge that
102
+ holds out the last `horizon` periods of each series,
103
+ 4. keeps the change if the score improved, reverts it otherwise,
104
+ 5. repeats until the score plateaus.
105
+
106
+ Every attempt is snapshotted and logged to `autoresearch/runs/`, so the whole
107
+ search is auditable. The judge only ever shows `predict()` the training history;
108
+ the holdout is hidden, and anti-leakage rules are enforced in the instructions.
109
+
110
+ ---
111
+
112
+ ## forecast.yaml
113
+
114
+ ```yaml
115
+ file: sales.parquet # data file inside data/ (.parquet or .csv)
116
+ time: date # timestamp column
117
+ target: target # column to forecast
118
+ entity: series_id # column identifying each series (omit if single series)
119
+ partitions: [] # extra grouping columns (optional)
120
+ horizon: 6 # periods ahead to forecast and hold out for validation
121
+ timestep: M # Y / Q / M / W / D / H
122
+ metric: smape # reporting metric
123
+ ```
124
+
125
+ Only `file`, `time`, and `target` are strictly required; the rest are inferred
126
+ when omitted, but declaring them is more reliable.
127
+
128
+ ---
129
+
130
+ ## What a project looks like
131
+
132
+ ```
133
+ myproject/
134
+ data/ your input file
135
+ forecast.yaml your config
136
+ outputs/ DQ reports (Excel / HTML / JSON) + staged panel
137
+ autoresearch/ the research harness the agent drives
138
+ solution.py the model file the agent evolves
139
+ run.py scores the current solution
140
+ GEMINI.md agent instructions
141
+ runs/ every attempt, snapshotted and scored
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Scope and honest notes
147
+
148
+ - This ships a **method**, not a guaranteed number. The M5 result (0.807 to
149
+ 0.542) is a documented demonstration of the loop; your dataset, metric, and
150
+ agent will produce their own result.
151
+ - Stage 2 is **agent-driven** by design: an external coding agent operates the
152
+ harness by following the prompt, which is what makes the search auditable and
153
+ reproducible as a process. It is not a black-box API call.
154
+ - The search is **single-track** (one solution improved along one path). A
155
+ population-based version with an explicit trust layer is a separate project.
156
+
157
+ ---
158
+
159
+ ## License
160
+
161
+ MIT
@@ -0,0 +1,141 @@
1
+ # soloresearch
2
+
3
+ Autonomous single-track forecasting research. Point it at a table of historical
4
+ values and it does two things: checks whether the data can be forecast at all,
5
+ then lets a coding agent iteratively discover a forecasting model that drives
6
+ validation error down, with every attempt scored against a leakage-safe judge
7
+ and logged.
8
+
9
+ It is the packaged version of a research loop that, on the M5 competition data,
10
+ took a seasonal-naive baseline from WRMSSE 0.807 to 0.542 with no human in the
11
+ loop. The package ships the *method* (the data-quality gate, the scoring judge,
12
+ and the agent-driven search); the numbers you get depend on your data.
13
+
14
+ ---
15
+
16
+ ## Install
17
+
18
+ ```
19
+ pip install soloresearch
20
+ ```
21
+
22
+ Python 3.9+. All dependencies (pandas, numpy, scipy, statsmodels, ruptures,
23
+ openpyxl, pyarrow, pyyaml) install automatically.
24
+
25
+ For stage 2 you also need a coding-agent CLI, either the Gemini CLI or Claude
26
+ Code. The package works with whichever you have.
27
+
28
+ ---
29
+
30
+ ## Quickstart
31
+
32
+ ```
33
+ soloresearch init myproject # 1. scaffold a project
34
+ # 2. put your data file in myproject/data/ and fill in myproject/forecast.yaml
35
+ soloresearch run myproject # 3. data-quality checks + stage the panel
36
+ cd myproject/autoresearch
37
+ gemini # 4. or `claude`
38
+ ```
39
+
40
+ Then give the agent this prompt:
41
+
42
+ > Read GEMINI.md and begin the autonomous forecasting research loop.
43
+ > Iterate on solution.py until RMSSE plateaus.
44
+
45
+ That is the whole flow. Stage 1 (`run`) is automatic. Stage 2 is an agent
46
+ working in the project, exactly as the original research loop did.
47
+
48
+ ---
49
+
50
+ ## How it works
51
+
52
+ ### Stage 1 - data quality (automatic)
53
+
54
+ `soloresearch run` reads your `forecast.yaml`, loads the data, and runs a
55
+ two-part assessment on every series:
56
+
57
+ - **Structural** - parses timestamps, sorts, de-duplicates, fills calendar gaps,
58
+ and infers the target profile from the actual values. Verdict: Clean /
59
+ Acceptable / Deficient.
60
+ - **Forecastability** - gates on the three things no model can fix: too little
61
+ history, a constant/flat series, or pure white noise. Verdict: Forecastable /
62
+ Not Forecastable.
63
+
64
+ It also characterises each series (trend, seasonality, demand class) and detects
65
+ four kinds of regime change (level, trend, variance, seasonality). The headline
66
+ is **Forecast-Ready %**: the share of series that are both non-Deficient and
67
+ Forecastable.
68
+
69
+ Reports are written to `myproject/outputs/` as Excel (detailed), HTML (summary),
70
+ and JSON (machine-readable). A cleaned panel is staged for stage 2.
71
+
72
+ If too little of the data is forecastable, it stops here and tells you why.
73
+
74
+ ### Stage 2 - agent-driven model search
75
+
76
+ The clean panel becomes a research problem. A coding agent, following the
77
+ `GEMINI.md` / `CLAUDE.md` instructions in the project, repeatedly:
78
+
79
+ 1. reads the current best score,
80
+ 2. edits `solution.py` (one hypothesis at a time),
81
+ 3. runs `python run.py` to score it against a leakage-safe RMSSE judge that
82
+ holds out the last `horizon` periods of each series,
83
+ 4. keeps the change if the score improved, reverts it otherwise,
84
+ 5. repeats until the score plateaus.
85
+
86
+ Every attempt is snapshotted and logged to `autoresearch/runs/`, so the whole
87
+ search is auditable. The judge only ever shows `predict()` the training history;
88
+ the holdout is hidden, and anti-leakage rules are enforced in the instructions.
89
+
90
+ ---
91
+
92
+ ## forecast.yaml
93
+
94
+ ```yaml
95
+ file: sales.parquet # data file inside data/ (.parquet or .csv)
96
+ time: date # timestamp column
97
+ target: target # column to forecast
98
+ entity: series_id # column identifying each series (omit if single series)
99
+ partitions: [] # extra grouping columns (optional)
100
+ horizon: 6 # periods ahead to forecast and hold out for validation
101
+ timestep: M # Y / Q / M / W / D / H
102
+ metric: smape # reporting metric
103
+ ```
104
+
105
+ Only `file`, `time`, and `target` are strictly required; the rest are inferred
106
+ when omitted, but declaring them is more reliable.
107
+
108
+ ---
109
+
110
+ ## What a project looks like
111
+
112
+ ```
113
+ myproject/
114
+ data/ your input file
115
+ forecast.yaml your config
116
+ outputs/ DQ reports (Excel / HTML / JSON) + staged panel
117
+ autoresearch/ the research harness the agent drives
118
+ solution.py the model file the agent evolves
119
+ run.py scores the current solution
120
+ GEMINI.md agent instructions
121
+ runs/ every attempt, snapshotted and scored
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Scope and honest notes
127
+
128
+ - This ships a **method**, not a guaranteed number. The M5 result (0.807 to
129
+ 0.542) is a documented demonstration of the loop; your dataset, metric, and
130
+ agent will produce their own result.
131
+ - Stage 2 is **agent-driven** by design: an external coding agent operates the
132
+ harness by following the prompt, which is what makes the search auditable and
133
+ reproducible as a process. It is not a black-box API call.
134
+ - The search is **single-track** (one solution improved along one path). A
135
+ population-based version with an explicit trust layer is a separate project.
136
+
137
+ ---
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "soloresearch"
7
+ version = "0.1.1"
8
+ description = "Autonomous single-track forecasting research: data-quality gating plus an agent-driven model search."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Shanti Swarup Nayak", email = "shantiswarup.nayak@sigmoidanalytics.com" }]
13
+ dependencies = [
14
+ "numpy>=1.23", "pandas>=1.5", "pyarrow>=10.0", "scipy>=1.9",
15
+ "statsmodels>=0.14", "ruptures>=1.1.7", "openpyxl>=3.1", "pyyaml>=6.0",
16
+ ]
17
+
18
+ [project.scripts]
19
+ soloresearch = "soloresearch.cli:main"
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/shantiswarup2/soloresearch"
23
+ Repository = "https://github.com/shantiswarup2/soloresearch"
24
+
25
+ [tool.hatch.build.targets.wheel]
26
+ packages = ["soloresearch"]
27
+ artifacts = ["*.md"]
File without changes
@@ -0,0 +1,85 @@
1
+ # CLAUDE.md - autonomous forecasting research
2
+
3
+ You are running an autonomous forecasting research loop. Drive the
4
+ validation **RMSSE** as low as possible by iterating on ONE file:
5
+ `solution.py`. The dataset is whatever clean panel the data-quality stage
6
+ produced - it is not tied to any one domain.
7
+
8
+ ## This is the ML phase (hard constraint)
9
+ Every solution MUST be built around a trained machine-learning model that
10
+ learns from features. Hand-built statistical rules - moving averages,
11
+ seasonal-naive, multiplying by fixed factors, manual weekday adjustments -
12
+ are NOT valid solutions on their own; they may appear ONLY as a fallback for
13
+ series too short or too sparse to train. The seasonal-naive starting file is
14
+ a placeholder to beat, not a thing to tune: your FIRST iteration must replace
15
+ it with a trained model.
16
+
17
+ WHICH model is yours to discover by measuring - try a model family, see if it
18
+ lowers RMSSE, keep it if it does. Whatever you choose, use only libraries that
19
+ are already installed in the run environment; if an import fails, that library
20
+ is unavailable - switch to one that is present rather than trying to install
21
+ it. Improvements must come from the model (its features, target transform,
22
+ loss, hyperparameters, or per-group training), each justified by a measured
23
+ drop in RMSSE.
24
+
25
+ ## What the data is
26
+ `run.py` loads a clean long panel (`series_id, t, target`), holds out the
27
+ LAST `horizon` periods of each series as validation, and scores your
28
+ forecast of that holdout with RMSSE (scale-free per-series error, averaged).
29
+ `predict()` only ever sees TRAIN history; the holdout is hidden.
30
+
31
+ ## The contract (do NOT change)
32
+ ```
33
+ predict(train_panel, series_ids, horizon) -> np.ndarray # (n_series, horizon)
34
+ ```
35
+ Rows in `series_ids` order. You edit ONLY `solution.py`. `eval.py`,
36
+ `run.py`, `logger.py` are fixed - never edit them.
37
+
38
+ ## The loop (repeat on your own; do not stop to ask between runs)
39
+ 1. Read `runs/log.jsonl`. Find the best `overall` RMSSE so far.
40
+ 2. Read the current `solution.py`.
41
+ 3. Form ONE hypothesis. Make ONE change that tests it. Small, reviewable diff.
42
+ 4. Run: `python run.py <short_name> "<what you changed and why>"`
43
+ 5. Read the printed RMSSE.
44
+ - Lower than best -> keep it.
45
+ - Not lower -> revert `solution.py` to the best snapshot in
46
+ `runs/solutions/` and try a different idea.
47
+ 6. Go back to step 1.
48
+
49
+ ## Stop only when
50
+ - 20 runs in a row fail to beat the best, OR
51
+ - you judge the score has plateaued near the achievable floor.
52
+ Then write a short summary: what worked, what didn't, and why.
53
+
54
+ ## Discover, do not assume
55
+ - Start from the baseline (seasonal-naive). Do not jump to a known recipe.
56
+ - Try the cheapest idea that could move the metric before any complex one.
57
+ - A change is "better" ONLY if it lowers validation RMSSE. Nothing else counts.
58
+ - Every choice (features, model, loss, hyperparameters) must be justified by a
59
+ result YOU measured and logged in this loop. No recipes recalled from
60
+ memory; no web search; no external solutions or papers.
61
+ - Keep a running `runs/feature_journal.md`: one line per idea, marked KEPT /
62
+ DROPPED / UNTESTED-IDEA. Check it before proposing a feature so you do not
63
+ repeat a dead end.
64
+
65
+ ## Hard rules (breaking these invalidates results)
66
+ - Use ONLY `train_panel` passed to `predict()`. Never read the holdout, the
67
+ original files, or any other source.
68
+ - Lag/rolling features must be shift-then-roll: no row may see its own day or
69
+ any future day.
70
+ - If you forecast recursively, your own predictions feed the next step's
71
+ lags - never real future values.
72
+ - Output shape exactly (len(series_ids), horizon), rows in series_ids order.
73
+ - Forecasts are non-negative unless the data is genuinely signed.
74
+
75
+ ## Memory (large panels can crash the machine)
76
+ - Read only the columns you need; downcast to float32 / category.
77
+ - Work per group (per series, or per partition) when models get heavy:
78
+ train + predict one group, write its rows, then `del` large locals and
79
+ `gc.collect()` before the next.
80
+ - If a run dies on memory, the next experiment must shrink footprint, not grow it.
81
+
82
+ ## Environment
83
+ Run Python in the env where `python run.py baseline` worked. The wrapper
84
+ stages the dataset at `data/clean_panel.parquet` (the default `run.py` reads);
85
+ override with `--panel PATH --horizon N` if needed.
@@ -0,0 +1,85 @@
1
+ # GEMINI.md - autonomous forecasting research
2
+
3
+ You are running an autonomous forecasting research loop. Drive the
4
+ validation **RMSSE** as low as possible by iterating on ONE file:
5
+ `solution.py`. The dataset is whatever clean panel the data-quality stage
6
+ produced - it is not tied to any one domain.
7
+
8
+ ## This is the ML phase (hard constraint)
9
+ Every solution MUST be built around a trained machine-learning model that
10
+ learns from features. Hand-built statistical rules - moving averages,
11
+ seasonal-naive, multiplying by fixed factors, manual weekday adjustments -
12
+ are NOT valid solutions on their own; they may appear ONLY as a fallback for
13
+ series too short or too sparse to train. The seasonal-naive starting file is
14
+ a placeholder to beat, not a thing to tune: your FIRST iteration must replace
15
+ it with a trained model.
16
+
17
+ WHICH model is yours to discover by measuring - try a model family, see if it
18
+ lowers RMSSE, keep it if it does. Whatever you choose, use only libraries that
19
+ are already installed in the run environment; if an import fails, that library
20
+ is unavailable - switch to one that is present rather than trying to install
21
+ it. Improvements must come from the model (its features, target transform,
22
+ loss, hyperparameters, or per-group training), each justified by a measured
23
+ drop in RMSSE.
24
+
25
+ ## What the data is
26
+ `run.py` loads a clean long panel (`series_id, t, target`), holds out the
27
+ LAST `horizon` periods of each series as validation, and scores your
28
+ forecast of that holdout with RMSSE (scale-free per-series error, averaged).
29
+ `predict()` only ever sees TRAIN history; the holdout is hidden.
30
+
31
+ ## The contract (do NOT change)
32
+ ```
33
+ predict(train_panel, series_ids, horizon) -> np.ndarray # (n_series, horizon)
34
+ ```
35
+ Rows in `series_ids` order. You edit ONLY `solution.py`. `eval.py`,
36
+ `run.py`, `logger.py` are fixed - never edit them.
37
+
38
+ ## The loop (repeat on your own; do not stop to ask between runs)
39
+ 1. Read `runs/log.jsonl`. Find the best `overall` RMSSE so far.
40
+ 2. Read the current `solution.py`.
41
+ 3. Form ONE hypothesis. Make ONE change that tests it. Small, reviewable diff.
42
+ 4. Run: `python run.py <short_name> "<what you changed and why>"`
43
+ 5. Read the printed RMSSE.
44
+ - Lower than best -> keep it.
45
+ - Not lower -> revert `solution.py` to the best snapshot in
46
+ `runs/solutions/` and try a different idea.
47
+ 6. Go back to step 1.
48
+
49
+ ## Stop only when
50
+ - 20 runs in a row fail to beat the best, OR
51
+ - you judge the score has plateaued near the achievable floor.
52
+ Then write a short summary: what worked, what didn't, and why.
53
+
54
+ ## Discover, do not assume
55
+ - Start from the baseline (seasonal-naive). Do not jump to a known recipe.
56
+ - Try the cheapest idea that could move the metric before any complex one.
57
+ - A change is "better" ONLY if it lowers validation RMSSE. Nothing else counts.
58
+ - Every choice (features, model, loss, hyperparameters) must be justified by a
59
+ result YOU measured and logged in this loop. No recipes recalled from
60
+ memory; no web search; no external solutions or papers.
61
+ - Keep a running `runs/feature_journal.md`: one line per idea, marked KEPT /
62
+ DROPPED / UNTESTED-IDEA. Check it before proposing a feature so you do not
63
+ repeat a dead end.
64
+
65
+ ## Hard rules (breaking these invalidates results)
66
+ - Use ONLY `train_panel` passed to `predict()`. Never read the holdout, the
67
+ original files, or any other source.
68
+ - Lag/rolling features must be shift-then-roll: no row may see its own day or
69
+ any future day.
70
+ - If you forecast recursively, your own predictions feed the next step's
71
+ lags - never real future values.
72
+ - Output shape exactly (len(series_ids), horizon), rows in series_ids order.
73
+ - Forecasts are non-negative unless the data is genuinely signed.
74
+
75
+ ## Memory (large panels can crash the machine)
76
+ - Read only the columns you need; downcast to float32 / category.
77
+ - Work per group (per series, or per partition) when models get heavy:
78
+ train + predict one group, write its rows, then `del` large locals and
79
+ `gc.collect()` before the next.
80
+ - If a run dies on memory, the next experiment must shrink footprint, not grow it.
81
+
82
+ ## Environment
83
+ Run Python in the env where `python run.py baseline` worked. The wrapper
84
+ stages the dataset at `data/clean_panel.parquet` (the default `run.py` reads);
85
+ override with `--panel PATH --horizon N` if needed.