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.
- soloresearch-0.1.1/.github/workflows/ci.yml +38 -0
- soloresearch-0.1.1/.github/workflows/publish.yml +32 -0
- soloresearch-0.1.1/.gitignore +21 -0
- soloresearch-0.1.1/LICENSE +1 -0
- soloresearch-0.1.1/PKG-INFO +161 -0
- soloresearch-0.1.1/README.md +141 -0
- soloresearch-0.1.1/pyproject.toml +27 -0
- soloresearch-0.1.1/soloresearch/__init__.py +0 -0
- soloresearch-0.1.1/soloresearch/autoresearch/CLAUDE.md +85 -0
- soloresearch-0.1.1/soloresearch/autoresearch/GEMINI.md +85 -0
- soloresearch-0.1.1/soloresearch/autoresearch/__init__.py +0 -0
- soloresearch-0.1.1/soloresearch/autoresearch/eval.py +91 -0
- soloresearch-0.1.1/soloresearch/autoresearch/logger.py +43 -0
- soloresearch-0.1.1/soloresearch/autoresearch/run.py +82 -0
- soloresearch-0.1.1/soloresearch/autoresearch/solution.py +33 -0
- soloresearch-0.1.1/soloresearch/cli.py +93 -0
- soloresearch-0.1.1/soloresearch/dq/__init__.py +35 -0
- soloresearch-0.1.1/soloresearch/dq/characterize.py +152 -0
- soloresearch-0.1.1/soloresearch/dq/definitions.py +182 -0
- soloresearch-0.1.1/soloresearch/dq/gate.py +142 -0
- soloresearch-0.1.1/soloresearch/dq/load.py +226 -0
- soloresearch-0.1.1/soloresearch/dq/regime.py +326 -0
- soloresearch-0.1.1/soloresearch/dq/report_excel.py +303 -0
- soloresearch-0.1.1/soloresearch/dq/report_html.py +133 -0
- soloresearch-0.1.1/soloresearch/dq/report_json.py +82 -0
- soloresearch-0.1.1/soloresearch/dq/run.py +295 -0
- soloresearch-0.1.1/soloresearch/ingest.py +121 -0
- soloresearch-0.1.1/soloresearch/resolver.py +96 -0
- soloresearch-0.1.1/soloresearch/run_forecast.py +206 -0
- 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.
|
|
File without changes
|