publishable 0.1.0__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.
- publishable-0.1.0/.gitignore +226 -0
- publishable-0.1.0/CITATION.cff +26 -0
- publishable-0.1.0/LICENSE +21 -0
- publishable-0.1.0/PKG-INFO +402 -0
- publishable-0.1.0/README.md +372 -0
- publishable-0.1.0/docs/design-principles.md +222 -0
- publishable-0.1.0/docs/experimental-designs.md +402 -0
- publishable-0.1.0/docs/reference.md +4391 -0
- publishable-0.1.0/pyproject.toml +130 -0
- publishable-0.1.0/src/publishable/__init__.py +39 -0
- publishable-0.1.0/src/publishable/apparatus.py +1003 -0
- publishable-0.1.0/src/publishable/artifacts.py +1408 -0
- publishable-0.1.0/src/publishable/base_experiment.py +76 -0
- publishable-0.1.0/src/publishable/base_step.py +53 -0
- publishable-0.1.0/src/publishable/cli.py +6261 -0
- publishable-0.1.0/src/publishable/coercion.py +264 -0
- publishable-0.1.0/src/publishable/config.py +95 -0
- publishable-0.1.0/src/publishable/contrasts.py +261 -0
- publishable-0.1.0/src/publishable/correction.py +556 -0
- publishable-0.1.0/src/publishable/demo.py +690 -0
- publishable-0.1.0/src/publishable/diagnostics.py +77 -0
- publishable-0.1.0/src/publishable/diff.py +614 -0
- publishable-0.1.0/src/publishable/docs.py +763 -0
- publishable-0.1.0/src/publishable/envelope.py +366 -0
- publishable-0.1.0/src/publishable/errors.py +21 -0
- publishable-0.1.0/src/publishable/estimate.py +33 -0
- publishable-0.1.0/src/publishable/freeze.py +608 -0
- publishable-0.1.0/src/publishable/generators/__init__.py +0 -0
- publishable-0.1.0/src/publishable/generators/experiment.py +176 -0
- publishable-0.1.0/src/publishable/generators/report.py +88 -0
- publishable-0.1.0/src/publishable/generators/step.py +59 -0
- publishable-0.1.0/src/publishable/generators/template.py +110 -0
- publishable-0.1.0/src/publishable/hashes.py +184 -0
- publishable-0.1.0/src/publishable/hypotheses.py +341 -0
- publishable-0.1.0/src/publishable/lineage.py +782 -0
- publishable-0.1.0/src/publishable/manifest.py +106 -0
- publishable-0.1.0/src/publishable/materialize.py +174 -0
- publishable-0.1.0/src/publishable/param.py +213 -0
- publishable-0.1.0/src/publishable/plugin_scaffold.py +227 -0
- publishable-0.1.0/src/publishable/plugins.py +316 -0
- publishable-0.1.0/src/publishable/provenance.py +211 -0
- publishable-0.1.0/src/publishable/py.typed +0 -0
- publishable-0.1.0/src/publishable/readme_templates/CITATION.cff.tmpl +8 -0
- publishable-0.1.0/src/publishable/readme_templates/LICENSE.mit.tmpl +7 -0
- publishable-0.1.0/src/publishable/readme_templates/README.md.tmpl +49 -0
- publishable-0.1.0/src/publishable/readme_templates/__init__.py +0 -0
- publishable-0.1.0/src/publishable/readme_templates/gitignore.tmpl +7 -0
- publishable-0.1.0/src/publishable/replication.py +478 -0
- publishable-0.1.0/src/publishable/report.py +1583 -0
- publishable-0.1.0/src/publishable/reproduce.py +1565 -0
- publishable-0.1.0/src/publishable/run_identity.py +399 -0
- publishable-0.1.0/src/publishable/run_record.py +324 -0
- publishable-0.1.0/src/publishable/runner.py +939 -0
- publishable-0.1.0/src/publishable/scaffold.py +85 -0
- publishable-0.1.0/src/publishable/scope.py +71 -0
- publishable-0.1.0/src/publishable/secrets.py +135 -0
- publishable-0.1.0/src/publishable/sourceimport.py +132 -0
- publishable-0.1.0/src/publishable/stats.py +3454 -0
- publishable-0.1.0/src/publishable/strata.py +40 -0
- publishable-0.1.0/src/publishable/study.py +454 -0
- publishable-0.1.0/src/publishable/sweep.py +1157 -0
- publishable-0.1.0/src/publishable/templates/__init__.py +0 -0
- publishable-0.1.0/src/publishable/templates/base.py +44 -0
- publishable-0.1.0/src/publishable/templates/builtin/__init__.py +0 -0
- publishable-0.1.0/src/publishable/templates/builtin/generic.py +24 -0
- publishable-0.1.0/src/publishable/templates/discovery.py +439 -0
- publishable-0.1.0/src/publishable/templates/registry.py +193 -0
- publishable-0.1.0/src/publishable/units.py +3484 -0
- publishable-0.1.0/src/publishable/uv_support.py +64 -0
- publishable-0.1.0/src/publishable/validate.py +6795 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
219
|
+
|
|
220
|
+
# macOS
|
|
221
|
+
.DS_Store
|
|
222
|
+
|
|
223
|
+
# `docs/superpowers/` and the SDD workspace under `.superpowers/sdd/` are TRACKED —
|
|
224
|
+
# they are the development record. See CLAUDE.md § The development record.
|
|
225
|
+
# Only the derived artifacts inside the workspace are ignored, by
|
|
226
|
+
# `.superpowers/sdd/.gitignore`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
type: software
|
|
4
|
+
title: publishable
|
|
5
|
+
abstract: >-
|
|
6
|
+
Run experiments so the record is publishable by default — one config in,
|
|
7
|
+
one file a reviewer can re-run exactly. Every parameter lives in a single
|
|
8
|
+
config file, and every run pins code, environment, and input data by hash.
|
|
9
|
+
authors:
|
|
10
|
+
- family-names: Lee
|
|
11
|
+
given-names: Kyungjoon
|
|
12
|
+
email: joon_lee@hms.harvard.edu
|
|
13
|
+
affiliation: Harvard Medical School
|
|
14
|
+
repository-code: "https://github.com/seouri/publishable"
|
|
15
|
+
url: "https://github.com/seouri/publishable"
|
|
16
|
+
license: MIT
|
|
17
|
+
version: 0.1.0
|
|
18
|
+
date-released: "2026-08-26"
|
|
19
|
+
keywords:
|
|
20
|
+
- reproducibility
|
|
21
|
+
- research-software
|
|
22
|
+
- experiments
|
|
23
|
+
- provenance
|
|
24
|
+
- preregistration
|
|
25
|
+
- open-science
|
|
26
|
+
- python
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joon Lee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: publishable
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Run experiments so the record is publishable by default
|
|
5
|
+
Project-URL: Homepage, https://github.com/seouri/publishable
|
|
6
|
+
Project-URL: Repository, https://github.com/seouri/publishable
|
|
7
|
+
Project-URL: Documentation, https://github.com/seouri/publishable/blob/main/docs/reference.md
|
|
8
|
+
Project-URL: Issues, https://github.com/seouri/publishable/issues
|
|
9
|
+
Author-email: Kyungjoon Lee <joon_lee@hms.harvard.edu>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: experiments,open-science,preregistration,provenance,reproducibility,research-software
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: numpy>=1.26
|
|
25
|
+
Requires-Dist: pyarrow>=15.0
|
|
26
|
+
Requires-Dist: python-dotenv>=1.2.3
|
|
27
|
+
Requires-Dist: pyyaml>=6.0
|
|
28
|
+
Requires-Dist: scipy>=1.11
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# publishable
|
|
32
|
+
|
|
33
|
+
**Every parameter in one file. Every run reproducible from one command.**
|
|
34
|
+
|
|
35
|
+
You have an experiment to run. Here's the whole arc with `publishable`:
|
|
36
|
+
|
|
37
|
+
1. **Design the run.** One config file holds every parameter, the conditions you're comparing, and the repeats you'll average over. `publishable` generates it fully populated — you edit rather than author.
|
|
38
|
+
2. **Run it.** `publishable run config.yaml`. No flags. Conditions and repeats expand on their own, and each one gets its own place in the output tree.
|
|
39
|
+
3. **Read the results.** Estimates, confidence intervals over your units, and effect sizes against your baseline, already computed and sitting next to the run that produced them.
|
|
40
|
+
4. **Publish it.** Hand a collaborator, a reviewer, or your future self one file. `publishable reproduce` rebuilds exactly what ran — same commit, same locked environment, the input checked against a recorded manifest — and stops at the two things only a person can supply: your copy of the data, and your credentials.
|
|
41
|
+
|
|
42
|
+
Nothing about what ran ends up in a shell history, so nothing has to be reconstructed later.
|
|
43
|
+
|
|
44
|
+
## Try it
|
|
45
|
+
|
|
46
|
+
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/). Nothing else to configure:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv tool install publishable # or: pipx install publishable
|
|
50
|
+
publishable demo
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`demo` builds a complete worked example — synthetic data, a three-step pipeline, a parameter sweep — and then walks you through running it, one command at a time. It opens with the part you can't easily make yourself:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Created publishable-demo/
|
|
57
|
+
240 synthetic units ~/publishable-demo-data/input/
|
|
58
|
+
template templates/correlation.py
|
|
59
|
+
experiment src/correlation_pilot/
|
|
60
|
+
config configs/correlation-pilot/config.yaml
|
|
61
|
+
|
|
62
|
+
Your data sits outside the repo, where real data belongs. Everything from
|
|
63
|
+
here is the CLI you'd use on an experiment of your own.
|
|
64
|
+
|
|
65
|
+
Next: a look at the config that describes this run
|
|
66
|
+
[Enter] to continue · q to stop here
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Six stops, the middle three with the same beat: the real command, its real output, then what it meant. After the config comes `validate`:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Next: publishable validate configs/correlation-pilot/config.yaml
|
|
73
|
+
[Enter] to run it · q to stop here
|
|
74
|
+
|
|
75
|
+
✓ config valid · configs/correlation-pilot/config.yaml
|
|
76
|
+
|
|
77
|
+
validate read your config and your data. It created nothing and reached
|
|
78
|
+
nothing off this machine — the 240 units it resolved came from the
|
|
79
|
+
index.csv outside the repo, and `input_dir` being outside is enforced.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Nothing you press changes what runs — every stop is proceed-or-quit, and the config written at the first stop is the one executed at the fifth. `q` prints the remaining commands and `publishable demo` picks up where you left off. Two stops later, past `dry-run`:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Next: publishable run configs/correlation-pilot/config.yaml
|
|
86
|
+
[Enter] to run it · q to stop here
|
|
87
|
+
|
|
88
|
+
warning W-ENV-UNLOCKED environment
|
|
89
|
+
no uv.lock found at ~/publishable-demo; the environment is not
|
|
90
|
+
pinned, and `reproduce` will not be able to restore it
|
|
91
|
+
1 problem (0 errors, 1 warning)
|
|
92
|
+
run.yaml → ~/publishable-demo-data/results/run_2026-08-07T09-14-03Z_2f5c8d0/run.yaml
|
|
93
|
+
|
|
94
|
+
W-ENV-UNLOCKED fired because this project has no uv.lock: its pyproject
|
|
95
|
+
depends on `publishable`, which cannot resolve until the package is
|
|
96
|
+
published, so there is nothing to pin yet. Nothing was suppressed.
|
|
97
|
+
|
|
98
|
+
run printed no table — its whole output is that warning and the path to the
|
|
99
|
+
record. Everything below is `demo` reading the record back:
|
|
100
|
+
|
|
101
|
+
condition r 95% CI vs baseline (paired, 95% CI)
|
|
102
|
+
00_baseline 0.697 [0.630, 0.757] —
|
|
103
|
+
01_method=spearman 0.666 [0.582, 0.739] -0.031 [-0.068, -0.002]
|
|
104
|
+
02_method=kendall 0.482 [0.413, 0.550] -0.215 [-0.240, -0.190]
|
|
105
|
+
|
|
106
|
+
intervals over 228 of 240 units (12 failed) · seed spread std 0.003 of recorded `pred`
|
|
107
|
+
|
|
108
|
+
`pred` and `truth` are recorded columns, so each publishes its own
|
|
109
|
+
metric and joins the correction family beside `r` — six members, and
|
|
110
|
+
two of them nobody reads. A template that derived twenty diagnostics
|
|
111
|
+
would correct every interval in the run for numbers nobody reads.
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Those are the numbers `demo` computes on your machine, from the 240 rows it generated — `run` itself prints no table, no banner and no progress bar, so everything under the record's path is `demo` saying what the record means. The sweep is `3 conditions × 5 repeats = 15` repeat-scoped executions and **19 in all**: the plan also runs `step01_load_cohort` once for the whole sweep and `step02_fit_model` once per condition, and 19 is the figure `dry-run` prints. Absolute paths are elided to `~` above, and the `run.yaml` path is illustrative either way — a run ID carries the timestamp and the code hash of *your* run.
|
|
115
|
+
|
|
116
|
+
That `run.yaml` is the point, and the last stop opens it and leaves you there. It carries the results *and* everything needed to regenerate them — so on any other machine:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
publishable reproduce <path-to-run.yaml>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
clones the exact commit, restores the locked environment, writes the config back out, and prints what's left to fill in. Neither data nor credentials travel, so the last step is yours — and `run` then verifies the input against the recorded manifest before spending anything.
|
|
123
|
+
|
|
124
|
+
> **v0.x — the design is settled, interfaces may still shift before 1.0.** Issues and design feedback are very welcome.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Is this for you?
|
|
129
|
+
|
|
130
|
+
**A good fit if** you run experiments with parameters you sweep, repeats you average over, and results that end up in a paper — especially with data that can't live in your git repo. What a run executes is your pipeline over that data; measurements taken outside the pipeline arrive as input and are [pinned by hash, not produced](https://github.com/seouri/publishable/blob/main/docs/design-principles.md#what-core-does-not-promise).
|
|
131
|
+
|
|
132
|
+
**Probably not** if you want a pipeline scheduler, a live dashboard, or something to retrofit onto existing scripts. `publishable` is [greenfield only](https://github.com/seouri/publishable/blob/main/docs/design-principles.md#greenfield-only).
|
|
133
|
+
|
|
134
|
+
**Designs it speaks natively:** within- and between-subjects (with recorded randomization), factorial over parameters or over crossed arms, ablation, dose-response, train-test holdout, repeated cross-validation, bootstrap, permutation, technical-vs-biological replication, clustered units, matched case-control. The statistics follow the design — bootstrap resamples get percentile intervals, technical replicates never enter `n`, and a multi-condition sweep won't report uncorrected comparisons without warning you. Modelling beyond summary statistics is yours: see [Experimental designs](https://github.com/seouri/publishable/blob/main/docs/experimental-designs.md) for what's supported, what needs an override, and the [errors core refuses to let you make](https://github.com/seouri/publishable/blob/main/docs/experimental-designs.md#mistakes-core-prevents).
|
|
135
|
+
|
|
136
|
+
| Tool | Optimizes for | `publishable` differs by |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| **MLflow / W&B** | Tracking and comparing runs as they happen | Producing a self-contained record built for *publication*, not a server you query |
|
|
139
|
+
| **Hydra** | Flexible config composition and CLI overrides | Refusing overrides entirely — the file is the only description of the run |
|
|
140
|
+
| **DVC** | Versioning data and pipelines in git | Keeping data structurally *out* of the repo, for governed and clinical work |
|
|
141
|
+
| **Snakemake / Nextflow** | Expressing and scheduling complex DAGs | A linear pipeline, with statistics and provenance as first-class concerns |
|
|
142
|
+
| **Sacred** | Lightweight run capture | Pinning code, environment, *and* input data, with pre-registration and effect sizes built in |
|
|
143
|
+
|
|
144
|
+
These overlap, and several compose fine with `publishable`. The distinguishing bet: the deliverable is a **paper**, so the tool should produce something a reviewer can check, not just something you can query.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## How it fits together
|
|
149
|
+
|
|
150
|
+
Five words carry the whole model:
|
|
151
|
+
|
|
152
|
+
| Word | Means |
|
|
153
|
+
|---|---|
|
|
154
|
+
| **Unit** | the thing you measure — a patient, sample, trial, respondent |
|
|
155
|
+
| **Step** | one stage of your pipeline, one file in `src/` |
|
|
156
|
+
| **Condition** | one parameter combination you're *comparing* |
|
|
157
|
+
| **Repeat** | one re-execution you're *averaging over* — a seed or a cross-validation fold |
|
|
158
|
+
| **Run** | one execution of the whole thing: every step, every condition, every repeat |
|
|
159
|
+
|
|
160
|
+
Statistics aggregate *within* a condition and compare *across* conditions. Getting that backwards is the most common way a reproducible pipeline still produces a wrong number, which is why the two are named separately rather than both being called "runs."
|
|
161
|
+
|
|
162
|
+
### Where things live
|
|
163
|
+
|
|
164
|
+
Your repo holds code and configs — never data:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
my-study/
|
|
168
|
+
├── src/cohort_pilot/ # your pipeline, one file per step → code_hash
|
|
169
|
+
│ ├── experiment.py # declares step order
|
|
170
|
+
│ └── steps/
|
|
171
|
+
│ ├── step01_load_cohort.py
|
|
172
|
+
│ ├── step02_fit_model.py
|
|
173
|
+
│ ├── step03_analyze.py
|
|
174
|
+
│ └── step04_compare_methods.py
|
|
175
|
+
├── templates/ # this project's own templates, if any → code_hash
|
|
176
|
+
├── configs/cohort-pilot/
|
|
177
|
+
│ └── config.yaml # every parameter → parameters_hash
|
|
178
|
+
├── pyproject.toml + uv.lock # the environment → locked
|
|
179
|
+
└── .env # credentials, never committed
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Your data lives outside it, and so does everything a run produces:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
~/results/cohort-pilot/
|
|
186
|
+
└── run_2026-08-07T09-14-03Z_8e21ab3/
|
|
187
|
+
├── run.yaml ← results + all three hashes. This is the deliverable.
|
|
188
|
+
├── conditions/
|
|
189
|
+
│ ├── 00_baseline/ ← one folder per condition, self-describing
|
|
190
|
+
│ │ ├── seed17/ ← one folder per repeat
|
|
191
|
+
│ │ │ └── step03_analyze/scores.parquet
|
|
192
|
+
│ │ └── seed42/…
|
|
193
|
+
│ └── 01_method=spearman/…
|
|
194
|
+
└── summary/ ← steps that compare across conditions
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The directory structure *is* the experiment structure, so finding an artifact never requires reading code. Nothing here is ever overwritten: a second run creates a new `run_<id>/` beside this one.
|
|
198
|
+
|
|
199
|
+
### And then
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
run.yaml ──► publishable reproduce ──► a checkout anyone can run
|
|
203
|
+
└─► publishable study add ──► a bundle beside your manuscript
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Full vocabulary: [Ontology](https://github.com/seouri/publishable/blob/main/docs/design-principles.md#ontology).
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## What you actually write
|
|
211
|
+
|
|
212
|
+
One config, generated fully populated by `publishable generate experiment` (or its shorter alias `publishable init`), so you edit rather than author from scratch:
|
|
213
|
+
|
|
214
|
+
```yaml
|
|
215
|
+
parameters:
|
|
216
|
+
analysis:
|
|
217
|
+
method: pearson # choices: pearson | spearman | kendall
|
|
218
|
+
min_samples: 30 # integer >= 2
|
|
219
|
+
|
|
220
|
+
sweep:
|
|
221
|
+
baseline: {analysis.method: pearson}
|
|
222
|
+
grid:
|
|
223
|
+
analysis.method: [spearman, kendall]
|
|
224
|
+
|
|
225
|
+
replication:
|
|
226
|
+
repeats:
|
|
227
|
+
- {kind: seed, n: 5} # seed | batch | fold — what a re-execution varies
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
And steps that never mention sweeps — each condition is resolved before your code runs:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
class Step(BaseStep):
|
|
234
|
+
scope = "repeat" # run once per repeat, per condition
|
|
235
|
+
|
|
236
|
+
def run(self, cfg, io):
|
|
237
|
+
# io.units — your patients, samples, trials: whatever you're measuring
|
|
238
|
+
# cfg.parameters — already resolved to THIS condition's values
|
|
239
|
+
result = analyze(io.units, method=cfg.parameters.analysis.method)
|
|
240
|
+
|
|
241
|
+
for unit, pred in zip(io.units, result.per_unit):
|
|
242
|
+
io.record(unit.key, {"pred": pred, "truth": unit.label}) # the per-unit table —
|
|
243
|
+
# what every interval is over
|
|
244
|
+
io.write("scores.parquet", result.rows) # rows in, rows back out — this condition +
|
|
245
|
+
# repeat's own folder
|
|
246
|
+
return {"r": result.r} # recorded per repeat, without an interval
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Write it once for a single condition; adding a sweep later changes nothing here.
|
|
250
|
+
|
|
251
|
+
Statistics come back computed, next to the hashes that make them checkable:
|
|
252
|
+
|
|
253
|
+
```yaml
|
|
254
|
+
results:
|
|
255
|
+
conditions:
|
|
256
|
+
- label: method=spearman
|
|
257
|
+
aggregated:
|
|
258
|
+
step03_analyze: {r: {value: 0.607, basis: units, n: {completed: 228},
|
|
259
|
+
ci95: [0.517, 0.683], repeat_spread: {std: 0.014}}}
|
|
260
|
+
vs_baseline:
|
|
261
|
+
step03_analyze: {r: {delta: 0.026, paired: true, ci95: [-0.007, 0.059]}}
|
|
262
|
+
provenance:
|
|
263
|
+
code_hash: sha256:8e21… # your src/** + templates/**, from a clean tree
|
|
264
|
+
parameters_hash: sha256:1a2b… # this exact parameter set
|
|
265
|
+
input_manifest_hash: sha256:3d8a… # the data it actually read
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The `r` carrying an interval is the *derived* one: template `generic`'s `aggregate` recomputes it from the per-unit table the step recorded, and being recomputable on a resampled table is what earns it a `ci95`. The `r` the step returned sits beside it under `per_repeat`, without one. That is the whole rule — [an interval is over units, never over executions](https://github.com/seouri/publishable/blob/main/docs/reference.md#the-unit-table-is-the-inference-base).
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Start your own
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
# 1. Scaffold a repo. Runs `git init`; writes README, LICENSE, CITATION.cff.
|
|
276
|
+
publishable new my-study && cd my-study
|
|
277
|
+
|
|
278
|
+
# 2. Create an experiment. Data paths must live outside the repo.
|
|
279
|
+
publishable generate experiment cohort-pilot \
|
|
280
|
+
--template generic \
|
|
281
|
+
--input-dir ~/data/cohort-2026 \
|
|
282
|
+
--output-dir ~/results/cohort-pilot
|
|
283
|
+
|
|
284
|
+
# 3. Fill in the two fields `init` leaves for you — metadata.description and
|
|
285
|
+
# metadata.authors — and put your data where the config points.
|
|
286
|
+
# `validate` names anything still missing, and costs nothing.
|
|
287
|
+
publishable validate configs/cohort-pilot/config.yaml
|
|
288
|
+
|
|
289
|
+
# 4. Run it. The scaffold's starter step records one number per unit, so this
|
|
290
|
+
# produces a real estimate with an interval — replace it with your own.
|
|
291
|
+
git add -A && git commit -m "Scaffold cohort-pilot"
|
|
292
|
+
publishable run configs/cohort-pilot/config.yaml
|
|
293
|
+
|
|
294
|
+
# 5. Now make it yours: add steps, implement them, commit.
|
|
295
|
+
publishable generate step cohort-pilot analyze
|
|
296
|
+
git add src/ && git commit -m "Implement cohort analysis"
|
|
297
|
+
publishable validate configs/cohort-pilot/config.yaml
|
|
298
|
+
publishable dry-run configs/cohort-pilot/config.yaml
|
|
299
|
+
publishable run configs/cohort-pilot/config.yaml
|
|
300
|
+
|
|
301
|
+
# 6. When you publish, collect the runs you're reporting.
|
|
302
|
+
publishable study new ~/papers/triage/study --title "Cohort triage pilot"
|
|
303
|
+
publishable study add ~/papers/triage/study ~/results/cohort-pilot/latest/run.yaml --as main
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Step 4 works before you've written any code — the starter step records a number, so a first run produces a real estimate with an interval — so you can see the whole loop before committing to it.
|
|
307
|
+
|
|
308
|
+
### The loop you'll actually live in
|
|
309
|
+
|
|
310
|
+
Once the code is committed, changing an experiment means editing one file. No commit, no flags, no bookkeeping:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
# edit configs/cohort-pilot/config.yaml — say, min_samples: 30 → 50
|
|
314
|
+
publishable run configs/cohort-pilot/config.yaml
|
|
315
|
+
|
|
316
|
+
publishable diff ~/results/cohort-pilot/run_A/run.yaml \
|
|
317
|
+
~/results/cohort-pilot/run_B/run.yaml
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
A run record run_A completed
|
|
322
|
+
B run record run_B completed
|
|
323
|
+
code_hash identical sha256:8e21…
|
|
324
|
+
input_manifest identical sha256:3d8a…
|
|
325
|
+
uv.lock identical sha256:6b1f…
|
|
326
|
+
parameters_hash DIFFERS
|
|
327
|
+
parameters.analysis.min_samples 30 → 50
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
That's the payoff of hashing code and parameters separately: you get to *prove* only one thing changed, which is the claim a comparison rests on.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## What you get
|
|
335
|
+
|
|
336
|
+
- **One file, no flags.** No *operation* command takes an argument other than a path; creation commands take what's needed to bring something into existence, and they're the only exception. A selector flag would live in a shell history that nobody archives.
|
|
337
|
+
- **Code and parameters hashed separately.** `code_hash` covers your code trees only, so "same code, different parameters" is a *provable* claim — even across commits weeks apart.
|
|
338
|
+
- **Code, environment, and data all pinned — and the apparatus too, when you measure through one.** A content hash over your code trees, with the commit recorded so a reproduction can fetch them; `uv.lock` for the environment; a content manifest for the input data, which is the one most tools leave open. And when measurement goes through an apparatus core can't install — a hosted model deployment, an instrument — a plugin probe records its revision per condition and a change fails the run. `uv.lock` pins the client; that record pins the server.
|
|
339
|
+
- **Artifacts are append-only and atomic.** Nothing is ever overwritten or deleted, and a crash mid-write leaves nothing behind rather than a half-file that blocks the retry.
|
|
340
|
+
- **Code and data never share a repo.** Data paths are structurally forbidden inside the git repo — code is shareable, governed data isn't, and they need different protocols.
|
|
341
|
+
- **Intervals over units, not over executions.** `n` counts the things your claim generalizes over. Repeats are reported as pipeline stability, separately and labelled, because an interval across five seeds narrows as you add seeds and says nothing about your cohort. Where core can't compute an interval honestly, it reports the estimate and omits the interval.
|
|
342
|
+
- **Statistics that match your design.** Declaring how units are allocated and how repeats are structured determines the analysis: paired or unpaired, t-based or percentile, clustered or not. A t-interval over bootstrap resamples is wrong, and core won't compute one — nor will it count technical replicates as `n`.
|
|
343
|
+
- **Pre-registration for free.** The config is written and hashed *before* the run, so declared hypotheses can be checked against results — and after-the-fact additions don't match the hash.
|
|
344
|
+
- **Stated limits.** Core documents what it [does not promise](https://github.com/seouri/publishable/blob/main/docs/design-principles.md#what-core-does-not-promise) — bit-identical reruns against external services, verification of your Python, or scientific validity.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Commands
|
|
349
|
+
|
|
350
|
+
Creation commands take a name and what's needed to create it. **Everything else takes paths only.**
|
|
351
|
+
|
|
352
|
+
| Command | Does |
|
|
353
|
+
|---|---|
|
|
354
|
+
| `demo` | Build a complete worked example, then walk through running it command by command |
|
|
355
|
+
| `new` · `plugin new` · `generate` · `init` | Scaffold a project, a plugin, an experiment, a step |
|
|
356
|
+
| `validate` · `dry-run` | Check values, ranges, and the full execution plan before spending a run |
|
|
357
|
+
| `run` · `draft` · `resume` | Execute; `draft` permits a dirty tree, `resume` continues an interrupted run |
|
|
358
|
+
| `report` · `diff` · `freeze` | Render results, compare two runs hash by hash, re-check the apparatus mid-run |
|
|
359
|
+
| `reproduce` | Clone the recorded commit and prepare it to run — no git commands typed |
|
|
360
|
+
| `study new` · `study add` | Assemble the runs a paper reports, outside the repo |
|
|
361
|
+
| `docs` · `list-templates` | Rewrite the managed README regions; list every template this build knows, with its parameters where it can read them |
|
|
362
|
+
|
|
363
|
+
Full details: [CLI reference](https://github.com/seouri/publishable/blob/main/docs/reference.md#cli-reference) — which also carries the `Status` column saying which of these the current build executes.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Extending it
|
|
368
|
+
|
|
369
|
+
Core knows nothing about LLMs, cohorts, instruments, or solvers. Domain knowledge lives in **plugins**, and a plugin doesn't need a PR here:
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
publishable generate experiment triage-pilot \
|
|
373
|
+
--plugin someuser/publishable-llm \
|
|
374
|
+
--template llm_diagnostic \
|
|
375
|
+
--input-dir ~/data/xray-2026 --output-dir ~/results/triage-pilot
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
`--plugin owner/repo` is `uv add git+…` and nothing more — so the plugin lands in your lockfile and gets captured in provenance like any other dependency. Write your own with `publishable plugin new`, which scaffolds an installable package whose docs generate from its parameter spec.
|
|
379
|
+
|
|
380
|
+
The reference LLM plugin is [`publishable-llm`](https://github.com/someuser/publishable-llm).
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Documentation
|
|
385
|
+
|
|
386
|
+
- **[Experimental designs](https://github.com/seouri/publishable/blob/main/docs/experimental-designs.md)** — how to express each design, and the mistakes core prevents
|
|
387
|
+
- **[Reference](https://github.com/seouri/publishable/blob/main/docs/reference.md)** — config schema, CLI, `io` API, templates, sweeps, artifact layout
|
|
388
|
+
- **[Design principles](https://github.com/seouri/publishable/blob/main/docs/design-principles.md)** — why the rules are what they are; read before proposing a change
|
|
389
|
+
- **[Plugin guide](https://github.com/seouri/publishable/blob/main/docs/reference.md#plugins-where-domain-knowledge-lives)** — building and sharing templates
|
|
390
|
+
- **[The development record](https://github.com/seouri/publishable/blob/main/docs/superpowers/README.md)** — non-normative: how each slice was designed, measured and reviewed, kept because a tool arguing the record should be publishable owes its own
|
|
391
|
+
|
|
392
|
+
## Contributing
|
|
393
|
+
|
|
394
|
+
Most new templates should be a [plugin](https://github.com/seouri/publishable/blob/main/docs/reference.md#plugins-where-domain-knowledge-lives), not a PR here — that's what `--plugin owner/repo` is for. Upstream contributions are for core mechanisms: the config envelope, provenance capture, the artifact model, the validation engine, the CLI.
|
|
395
|
+
|
|
396
|
+
A good test of whether something belongs in core: **would it be identical for a wet-lab assay, a simulation sweep, and an LLM benchmark?** If not, it's a plugin.
|
|
397
|
+
|
|
398
|
+
Design disagreements are welcome as issues. If a rule seems arbitrary, [design-principles.md](https://github.com/seouri/publishable/blob/main/docs/design-principles.md) probably explains it — and if it doesn't, that's a documentation bug worth filing.
|
|
399
|
+
|
|
400
|
+
## License
|
|
401
|
+
|
|
402
|
+
MIT — see [LICENSE](https://github.com/seouri/publishable/blob/main/LICENSE).
|