panelcast 0.14.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.
- panelcast-0.14.0/LICENSE +21 -0
- panelcast-0.14.0/PKG-INFO +316 -0
- panelcast-0.14.0/README.md +261 -0
- panelcast-0.14.0/pyproject.toml +163 -0
- panelcast-0.14.0/setup.cfg +4 -0
- panelcast-0.14.0/src/panelcast/__init__.py +73 -0
- panelcast-0.14.0/src/panelcast/_data/datasets/aero.yaml +82 -0
- panelcast-0.14.0/src/panelcast/_data/datasets/aoty_full.yaml +37 -0
- panelcast-0.14.0/src/panelcast/_data/examples/aerospace/descriptor.yaml +82 -0
- panelcast-0.14.0/src/panelcast/_data/examples/aerospace/flights.csv +40 -0
- panelcast-0.14.0/src/panelcast/cli/__init__.py +51 -0
- panelcast-0.14.0/src/panelcast/cli/backtest_cmd.py +94 -0
- panelcast-0.14.0/src/panelcast/cli/commands.py +814 -0
- panelcast-0.14.0/src/panelcast/cli/doctor_cmd.py +61 -0
- panelcast-0.14.0/src/panelcast/cli/main.py +66 -0
- panelcast-0.14.0/src/panelcast/cli/preflight_cmd.py +101 -0
- panelcast-0.14.0/src/panelcast/cli/run.py +1000 -0
- panelcast-0.14.0/src/panelcast/cli/runs_cmd.py +528 -0
- panelcast-0.14.0/src/panelcast/cli/select_cmd.py +238 -0
- panelcast-0.14.0/src/panelcast/cli/stack_cmd.py +62 -0
- panelcast-0.14.0/src/panelcast/cli/stages.py +350 -0
- panelcast-0.14.0/src/panelcast/config/descriptor.py +404 -0
- panelcast-0.14.0/src/panelcast/config/gates.py +47 -0
- panelcast-0.14.0/src/panelcast/config/loader.py +70 -0
- panelcast-0.14.0/src/panelcast/config/pipeline_yaml.py +328 -0
- panelcast-0.14.0/src/panelcast/data/alignment.py +95 -0
- panelcast-0.14.0/src/panelcast/data/chronology.py +87 -0
- panelcast-0.14.0/src/panelcast/data/cleaning.py +541 -0
- panelcast-0.14.0/src/panelcast/data/imputation.py +82 -0
- panelcast-0.14.0/src/panelcast/data/ingest.py +196 -0
- panelcast-0.14.0/src/panelcast/data/lineage.py +210 -0
- panelcast-0.14.0/src/panelcast/data/manifests.py +205 -0
- panelcast-0.14.0/src/panelcast/data/split.py +308 -0
- panelcast-0.14.0/src/panelcast/data/split_types.py +99 -0
- panelcast-0.14.0/src/panelcast/data/validation.py +244 -0
- panelcast-0.14.0/src/panelcast/doctor.py +178 -0
- panelcast-0.14.0/src/panelcast/evaluation/__init__.py +62 -0
- panelcast-0.14.0/src/panelcast/evaluation/calibration.py +619 -0
- panelcast-0.14.0/src/panelcast/evaluation/conformal.py +137 -0
- panelcast-0.14.0/src/panelcast/evaluation/cv.py +394 -0
- panelcast-0.14.0/src/panelcast/evaluation/decomposition.py +112 -0
- panelcast-0.14.0/src/panelcast/evaluation/metrics.py +355 -0
- panelcast-0.14.0/src/panelcast/evaluation/ppc.py +205 -0
- panelcast-0.14.0/src/panelcast/evaluation/prior_predictive.py +384 -0
- panelcast-0.14.0/src/panelcast/evaluation/ranking.py +132 -0
- panelcast-0.14.0/src/panelcast/evaluation/slices.py +223 -0
- panelcast-0.14.0/src/panelcast/features/__init__.py +24 -0
- panelcast-0.14.0/src/panelcast/features/album_type.py +140 -0
- panelcast-0.14.0/src/panelcast/features/artist.py +56 -0
- panelcast-0.14.0/src/panelcast/features/base.py +208 -0
- panelcast-0.14.0/src/panelcast/features/basis.py +141 -0
- panelcast-0.14.0/src/panelcast/features/collaboration.py +158 -0
- panelcast-0.14.0/src/panelcast/features/core.py +115 -0
- panelcast-0.14.0/src/panelcast/features/errors.py +107 -0
- panelcast-0.14.0/src/panelcast/features/gbm_offset.py +324 -0
- panelcast-0.14.0/src/panelcast/features/genre.py +211 -0
- panelcast-0.14.0/src/panelcast/features/history.py +181 -0
- panelcast-0.14.0/src/panelcast/features/packs/__init__.py +7 -0
- panelcast-0.14.0/src/panelcast/features/packs/aoty.py +26 -0
- panelcast-0.14.0/src/panelcast/features/pca.py +5 -0
- panelcast-0.14.0/src/panelcast/features/pipeline.py +185 -0
- panelcast-0.14.0/src/panelcast/features/registry.py +125 -0
- panelcast-0.14.0/src/panelcast/features/temporal.py +172 -0
- panelcast-0.14.0/src/panelcast/gpu_memory/__init__.py +48 -0
- panelcast-0.14.0/src/panelcast/gpu_memory/admission.py +83 -0
- panelcast-0.14.0/src/panelcast/gpu_memory/calibration_store.py +262 -0
- panelcast-0.14.0/src/panelcast/gpu_memory/estimate.py +248 -0
- panelcast-0.14.0/src/panelcast/gpu_memory/measure.py +129 -0
- panelcast-0.14.0/src/panelcast/gpu_memory/platform.py +134 -0
- panelcast-0.14.0/src/panelcast/gpu_memory/query.py +194 -0
- panelcast-0.14.0/src/panelcast/gpu_memory/runtime_predictor.py +160 -0
- panelcast-0.14.0/src/panelcast/io/paths.py +27 -0
- panelcast-0.14.0/src/panelcast/io/readers.py +20 -0
- panelcast-0.14.0/src/panelcast/io/writers.py +9 -0
- panelcast-0.14.0/src/panelcast/model_preflight.py +385 -0
- panelcast-0.14.0/src/panelcast/model_preflight_data.py +125 -0
- panelcast-0.14.0/src/panelcast/models/__init__.py +1 -0
- panelcast-0.14.0/src/panelcast/models/baselines/__init__.py +37 -0
- panelcast-0.14.0/src/panelcast/models/baselines/core.py +480 -0
- panelcast-0.14.0/src/panelcast/models/bayes/__init__.py +72 -0
- panelcast-0.14.0/src/panelcast/models/bayes/diagnostics.py +450 -0
- panelcast-0.14.0/src/panelcast/models/bayes/fit.py +827 -0
- panelcast-0.14.0/src/panelcast/models/bayes/io.py +382 -0
- panelcast-0.14.0/src/panelcast/models/bayes/likelihoods.py +835 -0
- panelcast-0.14.0/src/panelcast/models/bayes/model.py +1035 -0
- panelcast-0.14.0/src/panelcast/models/bayes/model_math.py +43 -0
- panelcast-0.14.0/src/panelcast/models/bayes/predict.py +615 -0
- panelcast-0.14.0/src/panelcast/models/bayes/priors.py +350 -0
- panelcast-0.14.0/src/panelcast/models/bayes/rollout.py +214 -0
- panelcast-0.14.0/src/panelcast/models/bayes/transforms.py +126 -0
- panelcast-0.14.0/src/panelcast/paths.py +107 -0
- panelcast-0.14.0/src/panelcast/pipelines/__init__.py +67 -0
- panelcast-0.14.0/src/panelcast/pipelines/backtest.py +340 -0
- panelcast-0.14.0/src/panelcast/pipelines/build_features.py +496 -0
- panelcast-0.14.0/src/panelcast/pipelines/compare_baselines.py +390 -0
- panelcast-0.14.0/src/panelcast/pipelines/create_splits.py +425 -0
- panelcast-0.14.0/src/panelcast/pipelines/diagnose.py +257 -0
- panelcast-0.14.0/src/panelcast/pipelines/errors.py +194 -0
- panelcast-0.14.0/src/panelcast/pipelines/evaluate.py +2087 -0
- panelcast-0.14.0/src/panelcast/pipelines/manifest.py +367 -0
- panelcast-0.14.0/src/panelcast/pipelines/orchestrator.py +1958 -0
- panelcast-0.14.0/src/panelcast/pipelines/predict_next.py +929 -0
- panelcast-0.14.0/src/panelcast/pipelines/prepare_dataset.py +287 -0
- panelcast-0.14.0/src/panelcast/pipelines/publication.py +1296 -0
- panelcast-0.14.0/src/panelcast/pipelines/sensitivity.py +1340 -0
- panelcast-0.14.0/src/panelcast/pipelines/stages.py +807 -0
- panelcast-0.14.0/src/panelcast/pipelines/stamps.py +84 -0
- panelcast-0.14.0/src/panelcast/pipelines/train_bayes.py +1876 -0
- panelcast-0.14.0/src/panelcast/pipelines/training_summary.py +198 -0
- panelcast-0.14.0/src/panelcast/preflight/__init__.py +236 -0
- panelcast-0.14.0/src/panelcast/preflight/cache.py +177 -0
- panelcast-0.14.0/src/panelcast/preflight/calibrate.py +249 -0
- panelcast-0.14.0/src/panelcast/preflight/check.py +250 -0
- panelcast-0.14.0/src/panelcast/preflight/full_check.py +737 -0
- panelcast-0.14.0/src/panelcast/preflight/mini_run.py +407 -0
- panelcast-0.14.0/src/panelcast/preflight/output.py +240 -0
- panelcast-0.14.0/src/panelcast/py.typed +0 -0
- panelcast-0.14.0/src/panelcast/reporting/__init__.py +57 -0
- panelcast-0.14.0/src/panelcast/reporting/curves.py +166 -0
- panelcast-0.14.0/src/panelcast/reporting/figures.py +1181 -0
- panelcast-0.14.0/src/panelcast/reporting/html_report.py +219 -0
- panelcast-0.14.0/src/panelcast/reporting/model_card.py +1144 -0
- panelcast-0.14.0/src/panelcast/reporting/tables.py +667 -0
- panelcast-0.14.0/src/panelcast/select/__init__.py +34 -0
- panelcast-0.14.0/src/panelcast/select/confirmation.py +373 -0
- panelcast-0.14.0/src/panelcast/select/orchestrate.py +510 -0
- panelcast-0.14.0/src/panelcast/select/prior_screen.py +198 -0
- panelcast-0.14.0/src/panelcast/select/rules.py +200 -0
- panelcast-0.14.0/src/panelcast/select/runner.py +1130 -0
- panelcast-0.14.0/src/panelcast/select/scoring.py +424 -0
- panelcast-0.14.0/src/panelcast/select/space.py +453 -0
- panelcast-0.14.0/src/panelcast/select/stacking.py +499 -0
- panelcast-0.14.0/src/panelcast/select/tiers.py +219 -0
- panelcast-0.14.0/src/panelcast/utils/environment.py +150 -0
- panelcast-0.14.0/src/panelcast/utils/git_state.py +177 -0
- panelcast-0.14.0/src/panelcast/utils/hashing.py +93 -0
- panelcast-0.14.0/src/panelcast/utils/jax_cache.py +57 -0
- panelcast-0.14.0/src/panelcast/utils/logging.py +152 -0
- panelcast-0.14.0/src/panelcast/utils/random.py +69 -0
- panelcast-0.14.0/src/panelcast/visualization/__init__.py +68 -0
- panelcast-0.14.0/src/panelcast/visualization/charts.py +535 -0
- panelcast-0.14.0/src/panelcast/visualization/dashboard.py +612 -0
- panelcast-0.14.0/src/panelcast/visualization/export.py +306 -0
- panelcast-0.14.0/src/panelcast/visualization/theme.py +101 -0
- panelcast-0.14.0/src/panelcast.egg-info/PKG-INFO +316 -0
- panelcast-0.14.0/src/panelcast.egg-info/SOURCES.txt +148 -0
- panelcast-0.14.0/src/panelcast.egg-info/dependency_links.txt +1 -0
- panelcast-0.14.0/src/panelcast.egg-info/entry_points.txt +2 -0
- panelcast-0.14.0/src/panelcast.egg-info/requires.txt +33 -0
- panelcast-0.14.0/src/panelcast.egg-info/top_level.txt +1 -0
panelcast-0.14.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jack Wenenn
|
|
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,316 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: panelcast
|
|
3
|
+
Version: 0.14.0
|
|
4
|
+
Summary: Hierarchical Bayesian prediction for bounded scores of events nested in entities over time, configured by a YAML descriptor
|
|
5
|
+
Author-email: Jack Wenenn <jcwenenn@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cupidthatbtc/panelcast
|
|
8
|
+
Project-URL: Repository, https://github.com/cupidthatbtc/panelcast
|
|
9
|
+
Project-URL: Issues, https://github.com/cupidthatbtc/panelcast/issues
|
|
10
|
+
Keywords: bayesian,numpyro,jax,hierarchical,forecasting,panel-data
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy<3,>=1.26
|
|
25
|
+
Requires-Dist: pandas<3,>=2.2
|
|
26
|
+
Requires-Dist: pandera<1,>=0.18
|
|
27
|
+
Requires-Dist: pyarrow>=15.0
|
|
28
|
+
Requires-Dist: pydantic<3,>=2.6
|
|
29
|
+
Requires-Dist: pyyaml>=6.0
|
|
30
|
+
Requires-Dist: scikit-learn<2,>=1.4
|
|
31
|
+
Requires-Dist: scipy<2,>=1.11
|
|
32
|
+
Requires-Dist: statsmodels<1,>=0.14
|
|
33
|
+
Requires-Dist: structlog>=24.1
|
|
34
|
+
Requires-Dist: arviz<1,>=0.18
|
|
35
|
+
Requires-Dist: xarray>=2024.1
|
|
36
|
+
Requires-Dist: typer>=0.12
|
|
37
|
+
Requires-Dist: rich>=13.7
|
|
38
|
+
Requires-Dist: matplotlib<4,>=3.8
|
|
39
|
+
Requires-Dist: seaborn>=0.13
|
|
40
|
+
Requires-Dist: uncertainties>=3.1
|
|
41
|
+
Requires-Dist: jax>=0.8.2
|
|
42
|
+
Requires-Dist: numpyro>=0.19
|
|
43
|
+
Requires-Dist: jinja2>=3.1
|
|
44
|
+
Requires-Dist: plotly>=6.1
|
|
45
|
+
Provides-Extra: gpu
|
|
46
|
+
Requires-Dist: nvidia-ml-py>=13.580.65; extra == "gpu"
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
50
|
+
Requires-Dist: pytest-timeout>=2.4.0; extra == "dev"
|
|
51
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
52
|
+
Provides-Extra: comparison
|
|
53
|
+
Requires-Dist: pymc>=5.13; extra == "comparison"
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
# panelcast
|
|
57
|
+
|
|
58
|
+
[](https://github.com/cupidthatbtc/panelcast/actions/workflows/ci.yml)
|
|
59
|
+
[](https://github.com/cupidthatbtc/panelcast/actions/workflows/nightly.yml)
|
|
60
|
+
[](https://codecov.io/gh/cupidthatbtc/panelcast)
|
|
61
|
+

|
|
62
|
+
[](LICENSE)
|
|
63
|
+
[](https://pixi.sh)
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
> **Scope — finished software; open domain-model research.**
|
|
67
|
+
>
|
|
68
|
+
> The *software* claim is complete: panelcast is feature-complete
|
|
69
|
+
> infrastructure for specifying, fitting, evaluating, and auditing
|
|
70
|
+
> hierarchical panel models — reproducibility, leakage controls, diagnostics,
|
|
71
|
+
> and domain portability are the finished, tested deliverable. The
|
|
72
|
+
> *statistical* claim for the flagship AOTY domain is partially established:
|
|
73
|
+
> on a representative ~800-artist / ~5,182-album AOTY subset (skewness −2.08),
|
|
74
|
+
> the published fit **passes the convergence gate** at the amended publication
|
|
75
|
+
> configuration with the 0.13.0 entity-obs default (R-hat 1.00, bulk ESS
|
|
76
|
+
> 1,119, 0 divergences), and the baseline benchmark runs on the same real
|
|
77
|
+
> splits. Two items remain open **as domain-model research, not
|
|
78
|
+
> package-completeness prerequisites**: (1) the **skewness and max**
|
|
79
|
+
> posterior-predictive p-values stay pinned by a bounded-skew mismatch — six
|
|
80
|
+
> likelihood families plus a dequantization toggle were tried with **none
|
|
81
|
+
> resolving them**, though the entity-obs default cleared q10 and q90; (2)
|
|
82
|
+
> the numbers come from the validated subset, not the full eligible corpus
|
|
83
|
+
> (~62k albums with ≥10 ratings; #15). See [`MODEL_CARD.md`](MODEL_CARD.md)
|
|
84
|
+
> and [`docs/LIKELIHOOD_CANDIDATES.md`](docs/LIKELIHOOD_CANDIDATES.md). Treat
|
|
85
|
+
> the subset numbers as real but not final. Canonical numbers:
|
|
86
|
+
> [`.audit/release_results.json`](.audit/release_results.json).
|
|
87
|
+
|
|
88
|
+
**Hierarchical Bayesian prediction for bounded scores of events nested in entities over time — configured by one YAML descriptor.**
|
|
89
|
+
|
|
90
|
+
Lots of forecasting problems share a shape: *entities* accumulate a history of
|
|
91
|
+
*events*, each event carries a *bounded score* and a noisy *observation count*,
|
|
92
|
+
and you want to predict the next score. Musicians release albums rated 0–100.
|
|
93
|
+
Airframes fly test flights scored 0–10. Candidates contest elections with a
|
|
94
|
+
vote share in [0, 1]. panelcast models that shape once — partial pooling across
|
|
95
|
+
entities, a time-varying entity effect, album-to-album (event-to-event)
|
|
96
|
+
dependence, and review-count-scaled noise — and lets you point it at a new
|
|
97
|
+
domain with a single descriptor file and **zero source changes**.
|
|
98
|
+
|
|
99
|
+
The emphasis is the infrastructure *around* the model as much as the model
|
|
100
|
+
itself: leakage controls, data lineage, preflight gates, and
|
|
101
|
+
convergence/calibration diagnostics as first-class, gating checks.
|
|
102
|
+
|
|
103
|
+
## Domains
|
|
104
|
+
|
|
105
|
+
Every dataset-specific name (columns, target bounds, date formats, posterior
|
|
106
|
+
prefixes, feature blocks) flows through a single `DatasetDescriptor`. Each field
|
|
107
|
+
**defaults to its AOTY value**, so a new domain only states what differs.
|
|
108
|
+
|
|
109
|
+
| Domain | Entity → Event | Bounded score | Status |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| **Album of the Year** (flagship) | Artist → Album | `User_Score` ∈ [0, 100] | Built-in defaults + the `aoty` feature pack (genre, album-type, collaboration). Run with no `--dataset` flag. |
|
|
112
|
+
| **Aerospace** (worked example) | Airframe → Test flight | `Perf_Score` ∈ [0, 10] | Bundled descriptor `configs/datasets/aero.yaml` + end-to-end portability test. One YAML, no music-specific code. |
|
|
113
|
+
| **US elections** | Candidate/seat → Contest | Vote share ∈ [0, 1] | Sibling project (`elections_pred`) that retargets this pipeline — lives in its own repo, not bundled here. |
|
|
114
|
+
|
|
115
|
+
The contract that `--dataset aoty_full` is byte-identical to running with no flag
|
|
116
|
+
at all is enforced by `tests/e2e/test_domain_portability.py`. See
|
|
117
|
+
[`docs/PORTING.md`](docs/PORTING.md) for the full walkthrough.
|
|
118
|
+
|
|
119
|
+
**Replications.** [panelcast-replications](https://github.com/cupidthatbtc/panelcast-replications)
|
|
120
|
+
re-analyses published panel studies through this pipeline — one descriptor YAML
|
|
121
|
+
per paper, zero source changes: Berry–Reese–Larkey 1999 (baseball aging/ability)
|
|
122
|
+
and Strittmatter–Sunde–Zegners 2020 (chess cognitive life cycle), with the
|
|
123
|
+
diagnostic ladder and identification caveats written up in full.
|
|
124
|
+
|
|
125
|
+
## Model structure
|
|
126
|
+
|
|
127
|
+
Hierarchical partial pooling across entities; a time-varying entity effect via a
|
|
128
|
+
Gaussian random walk; AR(1) event-to-event dependence; heteroscedastic
|
|
129
|
+
observation noise scaled by observation count; non-centered parameterization
|
|
130
|
+
(`LocScaleReparam`) plus a sigma-ref reparameterization to break the
|
|
131
|
+
multiplicative funnel; Student-t likelihood with a soft-clip to the target
|
|
132
|
+
bounds. The default Student-t is one of nine selectable observation families
|
|
133
|
+
(`--likelihood-family`: also `normal`, `skew_studentt`, `skew_normal`,
|
|
134
|
+
`split_normal`, `beta`, `mixture`, `beta_binomial`, `beta_ceiling`), with an optional
|
|
135
|
+
integer-aware dequantization toggle. Optional per-entity overdispersion with a
|
|
136
|
+
lognormal variance prior is available behind a gate. Built on
|
|
137
|
+
[NumPyro](https://num.pyro.ai/) / JAX.
|
|
138
|
+
|
|
139
|
+
## How it compares — and what it's for
|
|
140
|
+
|
|
141
|
+
On the ~5,000-album AOTY subset, against baselines fit on the same real splits
|
|
142
|
+
(within-entity temporal holdout, N = 653):
|
|
143
|
+
|
|
144
|
+
| | MAE | R² | 80% cov | 95% cov |
|
|
145
|
+
|---|---:|---:|---:|---:|
|
|
146
|
+
| **panelcast** | **5.28** | **0.498** | 0.830 | 0.968 |
|
|
147
|
+
| ridge | 5.38 | 0.498 | 0.879 | 0.965 |
|
|
148
|
+
| gradient boosting | 5.58 | 0.471 | 0.763 | 0.888 |
|
|
149
|
+
| entity mean | 6.11 | 0.322 | 0.818 | 0.925 |
|
|
150
|
+
|
|
151
|
+
The model **leads on MAE** and ties ridge on R² (0.498 each), while carrying the
|
|
152
|
+
only *modeled* intervals, near-nominal at 0.83/0.97. The MAE margin over ridge is
|
|
153
|
+
modest (5.28 vs 5.38); the decisive gaps are CRPS and calibration — the
|
|
154
|
+
gradient-boosted regressor lands close on raw error but **under-covers** badly
|
|
155
|
+
(0.76/0.89), its intervals a bolt-on rather than a modeled quantity. On the cold-start
|
|
156
|
+
(never-seen entity) split it leads outright (MAE 6.82, R² 0.117, 95% coverage 0.965).
|
|
157
|
+
Point accuracy was never the deliverable, though — *calibrated uncertainty* is:
|
|
158
|
+
intervals as a modeled quantity, an interpretable between-entity vs residual variance
|
|
159
|
+
decomposition, and a generative model you can interrogate — and the model now wins on
|
|
160
|
+
accuracy too. Full table, cold-start behaviour, and the R²-by-history gradient:
|
|
161
|
+
[`docs/BASELINES.md`](docs/BASELINES.md).
|
|
162
|
+
|
|
163
|
+
## Example output
|
|
164
|
+
|
|
165
|
+
The flagship AOTY model, fit on a ~5,000-album subset (within-artist temporal
|
|
166
|
+
holdout). The pipeline's `report` stage renders these automatically.
|
|
167
|
+
|
|
168
|
+
Predicted vs. actual on held-out next albums (95% interval), and interval
|
|
169
|
+
calibration (predicted vs. empirical coverage, ~650 albums/bin):
|
|
170
|
+
|
|
171
|
+
<img src="docs/images/aoty_predictions.png" height="300" alt="Predicted vs. actual scores on held-out next albums with 95% intervals"> <img src="docs/images/aoty_reliability.png" height="300" alt="Interval calibration: predicted vs. empirical coverage by bin">
|
|
172
|
+
|
|
173
|
+
What the model learned — posterior densities of the headline parameters (94% HDI):
|
|
174
|
+
the average album sits near 71/100, and album-to-album dependence (`rho`) is weak
|
|
175
|
+
once the artist level is centered out:
|
|
176
|
+
|
|
177
|
+
<img src="docs/images/aoty_posterior.png" width="85%" alt="Posterior densities of the headline model parameters (94% HDI)">
|
|
178
|
+
|
|
179
|
+
Convergence and posterior geometry — per-chain traces and densities for the
|
|
180
|
+
headline parameters (4 chains, well-mixed), and their pairwise joint posterior
|
|
181
|
+
(round contours, no funnels — the non-centered parameterization doing its job):
|
|
182
|
+
|
|
183
|
+
<img src="docs/images/aoty_trace.png" height="320" alt="MCMC trace and per-chain density for the headline parameters, showing well-mixed chains"> <img src="docs/images/aoty_pairplot.png" height="320" alt="Pairwise joint posterior of the hyperparameters with no funnel geometry">
|
|
184
|
+
|
|
185
|
+
## Install
|
|
186
|
+
|
|
187
|
+
**Prerequisite:** Python ≥ 3.11.
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pip install panelcast
|
|
191
|
+
panelcast --help
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
For the exact tested environment or repository development, use
|
|
195
|
+
[pixi](https://pixi.sh):
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
git clone https://github.com/cupidthatbtc/panelcast.git
|
|
199
|
+
cd panelcast
|
|
200
|
+
pixi install
|
|
201
|
+
pixi run panelcast --help
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
> `pixi.lock` is the reproducible environment: it pins the full stack, notably
|
|
205
|
+
> the tightly coupled JAX/NumPyro pair. A plain pip installation obeys the tested
|
|
206
|
+
> dependency bounds but does not promise an identical solver result. Use pixi
|
|
207
|
+
> for publication or reproduction work.
|
|
208
|
+
>
|
|
209
|
+
> **Tested platforms:** the wheel-install CI matrix installs the built wheel
|
|
210
|
+
> into a fresh environment and runs the CPU demo on Linux, macOS, and Windows
|
|
211
|
+
> for Python 3.11–3.13. Python 3.14 is expected to work (the pixi environment
|
|
212
|
+
> runs it) but is not yet part of the pip matrix.
|
|
213
|
+
|
|
214
|
+
## 60-second quickstart (aerospace example)
|
|
215
|
+
|
|
216
|
+
Retarget the whole pipeline to a non-music domain with no code changes, using
|
|
217
|
+
the bundled synthetic aerospace dataset (committed under `examples/aerospace/`:
|
|
218
|
+
8 airframes flying ~39 sequential test flights scored 0–10):
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# Run the entire pipeline end-to-end on the example, at tiny scale
|
|
222
|
+
panelcast demo
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`demo` reads the bundled aerospace descriptor and CSV from the installed wheel
|
|
226
|
+
(the checkout copies live under `examples/aerospace/`). The descriptor remaps the
|
|
227
|
+
columns, switches the score bounds to [0, 10], drops the music-specific feature
|
|
228
|
+
packs, and adds the domain's own numeric covariates. It runs data → splits →
|
|
229
|
+
features → train → evaluate → predict → report, finishing with a generated
|
|
230
|
+
model card under `outputs/<run_id>/reports/`. The model code is untouched.
|
|
231
|
+
|
|
232
|
+
The committed CSV is regenerated from the shared synthetic generator with
|
|
233
|
+
`python scripts/generate_aero_example.py`. To benchmark the model against simple
|
|
234
|
+
baselines on the splits it just produced:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
panelcast compare --baselines --dataset aero
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
To run the flagship AOTY domain instead, point at your data and omit `--dataset`:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
export AOTY_DATASET_PATH="/path/to/aoty_data.csv"
|
|
244
|
+
panelcast run --preflight-only # GPU-memory / schema / calibration gate
|
|
245
|
+
panelcast run # full pipeline
|
|
246
|
+
panelcast stage train --verbose # or run a single stage
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
See [`docs/CLI.md`](docs/CLI.md) for the complete command reference.
|
|
250
|
+
|
|
251
|
+
## Features
|
|
252
|
+
|
|
253
|
+
- Leak-safe data pipeline and evaluation (within-entity temporal split + an
|
|
254
|
+
entity-disjoint secondary check)
|
|
255
|
+
- Explicit data contract and lineage from raw CSV to final artifacts
|
|
256
|
+
- Preflight gates (GPU memory, schema validation, calibration) before expensive runs
|
|
257
|
+
- Convergence + PPC + coverage diagnostics as first-class, gating checks
|
|
258
|
+
- Sensitivity matrix over priors, splits, and feature ablations
|
|
259
|
+
- Publication-ready artifacts: tables, figures, model card, citations
|
|
260
|
+
- Domain portability proven by an end-to-end test, not just asserted — the
|
|
261
|
+
*apparatus* (descriptor → pipeline) runs on a new domain with zero source
|
|
262
|
+
changes; predictive accuracy off the flagship domain is untested by construction
|
|
263
|
+
|
|
264
|
+
## Documentation
|
|
265
|
+
|
|
266
|
+
- [`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md) — step-by-step startup guide (start here)
|
|
267
|
+
- [`docs/PORTING.md`](docs/PORTING.md) — retarget to a new domain (the aerospace walkthrough)
|
|
268
|
+
- [`docs/EXTENSIBILITY.md`](docs/EXTENSIBILITY.md) — adding features safely
|
|
269
|
+
- [`docs/CLI.md`](docs/CLI.md) — complete CLI reference
|
|
270
|
+
- [`docs/API.md`](docs/API.md) — supported Python import surface and its semver guarantee
|
|
271
|
+
- [`docs/LEAKAGE_CONTROLS.md`](docs/LEAKAGE_CONTROLS.md) — guardrails and leakage prevention
|
|
272
|
+
- [`docs/EVALUATION_PROTOCOL.md`](docs/EVALUATION_PROTOCOL.md) — metrics, diagnostics, and thresholds
|
|
273
|
+
- [`docs/PROJECT_STRUCTURE.md`](docs/PROJECT_STRUCTURE.md) — directory and file layout
|
|
274
|
+
- [`docs/DATA_CONTRACT.md`](docs/DATA_CONTRACT.md) — raw schema and cleaned artifacts
|
|
275
|
+
- [`docs/LINEAGE.md`](docs/LINEAGE.md) — repository lineage (the private predecessor, the 2026-06-20 migration) and JOSS submission timing
|
|
276
|
+
- [`MODEL_CARD.md`](MODEL_CARD.md) — intended use, results, and limitations
|
|
277
|
+
|
|
278
|
+
A note on results: at the amended publication configuration the published fit
|
|
279
|
+
**passes the convergence gate** on a real ~800-artist / ~5,182-album AOTY
|
|
280
|
+
subset (R-hat 1.00, bulk ESS 1,119, 0 divergences) under the default
|
|
281
|
+
**Student-t** likelihood on the `offset_logit` transformed scale with the
|
|
282
|
+
0.13.0 entity-obs default:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
panelcast run --preset publication # 4 chains × 5000, Student-t likelihood
|
|
286
|
+
panelcast diagnose # convergence + PPC of that run
|
|
287
|
+
panelcast compare --baselines # the model vs. simple baselines
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
What the subset validates: leak-safe splits with role-based names, an honest
|
|
291
|
+
baseline comparison (`panelcast compare`) on the same real splits, and a
|
|
292
|
+
convergent publication-scale fit on real, strongly left-skewed data — the
|
|
293
|
+
software behaves end-to-end under production settings. What convergence does
|
|
294
|
+
*not* establish: that the likelihood is correctly specified (the PPC pins say
|
|
295
|
+
it is not), or that subset results transfer to the full corpus. Both remain
|
|
296
|
+
open as domain-model research: the **skewness and max** posterior-predictive
|
|
297
|
+
p-values stay pinned at the extremes from a symmetric-likelihood /
|
|
298
|
+
left-skewed-target mismatch — six likelihood families (`beta`,
|
|
299
|
+
`skew_studentt`, `skew_normal`, `split_normal`, `beta_binomial`, `mixture`)
|
|
300
|
+
plus a dequantization toggle were tried and **none resolves them**, though the
|
|
301
|
+
entity-obs default cleared the q10 and q90 pins (see
|
|
302
|
+
[`docs/LIKELIHOOD_CANDIDATES.md`](docs/LIKELIHOOD_CANDIDATES.md)) — and this is
|
|
303
|
+
the validated subset, not the full eligible corpus (~62k albums with ≥10 user
|
|
304
|
+
ratings), which needs the full dataset and a GPU (#15).
|
|
305
|
+
|
|
306
|
+
Intentionally out of scope for the package claim: a resolved AOTY likelihood
|
|
307
|
+
(that is the open research above), full-corpus results (#15), and predictive
|
|
308
|
+
accuracy on non-flagship domains (portability is structural — the pipeline
|
|
309
|
+
runs — not a transferred accuracy claim). The code, the diagnostics, and the
|
|
310
|
+
honest naming of what is and isn't resolved are the point. Every headline
|
|
311
|
+
number here derives from the canonical release-result manifest,
|
|
312
|
+
[`.audit/release_results.json`](.audit/release_results.json), and drift fails CI.
|
|
313
|
+
|
|
314
|
+
## License
|
|
315
|
+
|
|
316
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# panelcast
|
|
2
|
+
|
|
3
|
+
[](https://github.com/cupidthatbtc/panelcast/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/cupidthatbtc/panelcast/actions/workflows/nightly.yml)
|
|
5
|
+
[](https://codecov.io/gh/cupidthatbtc/panelcast)
|
|
6
|
+

|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://pixi.sh)
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
> **Scope — finished software; open domain-model research.**
|
|
12
|
+
>
|
|
13
|
+
> The *software* claim is complete: panelcast is feature-complete
|
|
14
|
+
> infrastructure for specifying, fitting, evaluating, and auditing
|
|
15
|
+
> hierarchical panel models — reproducibility, leakage controls, diagnostics,
|
|
16
|
+
> and domain portability are the finished, tested deliverable. The
|
|
17
|
+
> *statistical* claim for the flagship AOTY domain is partially established:
|
|
18
|
+
> on a representative ~800-artist / ~5,182-album AOTY subset (skewness −2.08),
|
|
19
|
+
> the published fit **passes the convergence gate** at the amended publication
|
|
20
|
+
> configuration with the 0.13.0 entity-obs default (R-hat 1.00, bulk ESS
|
|
21
|
+
> 1,119, 0 divergences), and the baseline benchmark runs on the same real
|
|
22
|
+
> splits. Two items remain open **as domain-model research, not
|
|
23
|
+
> package-completeness prerequisites**: (1) the **skewness and max**
|
|
24
|
+
> posterior-predictive p-values stay pinned by a bounded-skew mismatch — six
|
|
25
|
+
> likelihood families plus a dequantization toggle were tried with **none
|
|
26
|
+
> resolving them**, though the entity-obs default cleared q10 and q90; (2)
|
|
27
|
+
> the numbers come from the validated subset, not the full eligible corpus
|
|
28
|
+
> (~62k albums with ≥10 ratings; #15). See [`MODEL_CARD.md`](MODEL_CARD.md)
|
|
29
|
+
> and [`docs/LIKELIHOOD_CANDIDATES.md`](docs/LIKELIHOOD_CANDIDATES.md). Treat
|
|
30
|
+
> the subset numbers as real but not final. Canonical numbers:
|
|
31
|
+
> [`.audit/release_results.json`](.audit/release_results.json).
|
|
32
|
+
|
|
33
|
+
**Hierarchical Bayesian prediction for bounded scores of events nested in entities over time — configured by one YAML descriptor.**
|
|
34
|
+
|
|
35
|
+
Lots of forecasting problems share a shape: *entities* accumulate a history of
|
|
36
|
+
*events*, each event carries a *bounded score* and a noisy *observation count*,
|
|
37
|
+
and you want to predict the next score. Musicians release albums rated 0–100.
|
|
38
|
+
Airframes fly test flights scored 0–10. Candidates contest elections with a
|
|
39
|
+
vote share in [0, 1]. panelcast models that shape once — partial pooling across
|
|
40
|
+
entities, a time-varying entity effect, album-to-album (event-to-event)
|
|
41
|
+
dependence, and review-count-scaled noise — and lets you point it at a new
|
|
42
|
+
domain with a single descriptor file and **zero source changes**.
|
|
43
|
+
|
|
44
|
+
The emphasis is the infrastructure *around* the model as much as the model
|
|
45
|
+
itself: leakage controls, data lineage, preflight gates, and
|
|
46
|
+
convergence/calibration diagnostics as first-class, gating checks.
|
|
47
|
+
|
|
48
|
+
## Domains
|
|
49
|
+
|
|
50
|
+
Every dataset-specific name (columns, target bounds, date formats, posterior
|
|
51
|
+
prefixes, feature blocks) flows through a single `DatasetDescriptor`. Each field
|
|
52
|
+
**defaults to its AOTY value**, so a new domain only states what differs.
|
|
53
|
+
|
|
54
|
+
| Domain | Entity → Event | Bounded score | Status |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| **Album of the Year** (flagship) | Artist → Album | `User_Score` ∈ [0, 100] | Built-in defaults + the `aoty` feature pack (genre, album-type, collaboration). Run with no `--dataset` flag. |
|
|
57
|
+
| **Aerospace** (worked example) | Airframe → Test flight | `Perf_Score` ∈ [0, 10] | Bundled descriptor `configs/datasets/aero.yaml` + end-to-end portability test. One YAML, no music-specific code. |
|
|
58
|
+
| **US elections** | Candidate/seat → Contest | Vote share ∈ [0, 1] | Sibling project (`elections_pred`) that retargets this pipeline — lives in its own repo, not bundled here. |
|
|
59
|
+
|
|
60
|
+
The contract that `--dataset aoty_full` is byte-identical to running with no flag
|
|
61
|
+
at all is enforced by `tests/e2e/test_domain_portability.py`. See
|
|
62
|
+
[`docs/PORTING.md`](docs/PORTING.md) for the full walkthrough.
|
|
63
|
+
|
|
64
|
+
**Replications.** [panelcast-replications](https://github.com/cupidthatbtc/panelcast-replications)
|
|
65
|
+
re-analyses published panel studies through this pipeline — one descriptor YAML
|
|
66
|
+
per paper, zero source changes: Berry–Reese–Larkey 1999 (baseball aging/ability)
|
|
67
|
+
and Strittmatter–Sunde–Zegners 2020 (chess cognitive life cycle), with the
|
|
68
|
+
diagnostic ladder and identification caveats written up in full.
|
|
69
|
+
|
|
70
|
+
## Model structure
|
|
71
|
+
|
|
72
|
+
Hierarchical partial pooling across entities; a time-varying entity effect via a
|
|
73
|
+
Gaussian random walk; AR(1) event-to-event dependence; heteroscedastic
|
|
74
|
+
observation noise scaled by observation count; non-centered parameterization
|
|
75
|
+
(`LocScaleReparam`) plus a sigma-ref reparameterization to break the
|
|
76
|
+
multiplicative funnel; Student-t likelihood with a soft-clip to the target
|
|
77
|
+
bounds. The default Student-t is one of nine selectable observation families
|
|
78
|
+
(`--likelihood-family`: also `normal`, `skew_studentt`, `skew_normal`,
|
|
79
|
+
`split_normal`, `beta`, `mixture`, `beta_binomial`, `beta_ceiling`), with an optional
|
|
80
|
+
integer-aware dequantization toggle. Optional per-entity overdispersion with a
|
|
81
|
+
lognormal variance prior is available behind a gate. Built on
|
|
82
|
+
[NumPyro](https://num.pyro.ai/) / JAX.
|
|
83
|
+
|
|
84
|
+
## How it compares — and what it's for
|
|
85
|
+
|
|
86
|
+
On the ~5,000-album AOTY subset, against baselines fit on the same real splits
|
|
87
|
+
(within-entity temporal holdout, N = 653):
|
|
88
|
+
|
|
89
|
+
| | MAE | R² | 80% cov | 95% cov |
|
|
90
|
+
|---|---:|---:|---:|---:|
|
|
91
|
+
| **panelcast** | **5.28** | **0.498** | 0.830 | 0.968 |
|
|
92
|
+
| ridge | 5.38 | 0.498 | 0.879 | 0.965 |
|
|
93
|
+
| gradient boosting | 5.58 | 0.471 | 0.763 | 0.888 |
|
|
94
|
+
| entity mean | 6.11 | 0.322 | 0.818 | 0.925 |
|
|
95
|
+
|
|
96
|
+
The model **leads on MAE** and ties ridge on R² (0.498 each), while carrying the
|
|
97
|
+
only *modeled* intervals, near-nominal at 0.83/0.97. The MAE margin over ridge is
|
|
98
|
+
modest (5.28 vs 5.38); the decisive gaps are CRPS and calibration — the
|
|
99
|
+
gradient-boosted regressor lands close on raw error but **under-covers** badly
|
|
100
|
+
(0.76/0.89), its intervals a bolt-on rather than a modeled quantity. On the cold-start
|
|
101
|
+
(never-seen entity) split it leads outright (MAE 6.82, R² 0.117, 95% coverage 0.965).
|
|
102
|
+
Point accuracy was never the deliverable, though — *calibrated uncertainty* is:
|
|
103
|
+
intervals as a modeled quantity, an interpretable between-entity vs residual variance
|
|
104
|
+
decomposition, and a generative model you can interrogate — and the model now wins on
|
|
105
|
+
accuracy too. Full table, cold-start behaviour, and the R²-by-history gradient:
|
|
106
|
+
[`docs/BASELINES.md`](docs/BASELINES.md).
|
|
107
|
+
|
|
108
|
+
## Example output
|
|
109
|
+
|
|
110
|
+
The flagship AOTY model, fit on a ~5,000-album subset (within-artist temporal
|
|
111
|
+
holdout). The pipeline's `report` stage renders these automatically.
|
|
112
|
+
|
|
113
|
+
Predicted vs. actual on held-out next albums (95% interval), and interval
|
|
114
|
+
calibration (predicted vs. empirical coverage, ~650 albums/bin):
|
|
115
|
+
|
|
116
|
+
<img src="docs/images/aoty_predictions.png" height="300" alt="Predicted vs. actual scores on held-out next albums with 95% intervals"> <img src="docs/images/aoty_reliability.png" height="300" alt="Interval calibration: predicted vs. empirical coverage by bin">
|
|
117
|
+
|
|
118
|
+
What the model learned — posterior densities of the headline parameters (94% HDI):
|
|
119
|
+
the average album sits near 71/100, and album-to-album dependence (`rho`) is weak
|
|
120
|
+
once the artist level is centered out:
|
|
121
|
+
|
|
122
|
+
<img src="docs/images/aoty_posterior.png" width="85%" alt="Posterior densities of the headline model parameters (94% HDI)">
|
|
123
|
+
|
|
124
|
+
Convergence and posterior geometry — per-chain traces and densities for the
|
|
125
|
+
headline parameters (4 chains, well-mixed), and their pairwise joint posterior
|
|
126
|
+
(round contours, no funnels — the non-centered parameterization doing its job):
|
|
127
|
+
|
|
128
|
+
<img src="docs/images/aoty_trace.png" height="320" alt="MCMC trace and per-chain density for the headline parameters, showing well-mixed chains"> <img src="docs/images/aoty_pairplot.png" height="320" alt="Pairwise joint posterior of the hyperparameters with no funnel geometry">
|
|
129
|
+
|
|
130
|
+
## Install
|
|
131
|
+
|
|
132
|
+
**Prerequisite:** Python ≥ 3.11.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install panelcast
|
|
136
|
+
panelcast --help
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
For the exact tested environment or repository development, use
|
|
140
|
+
[pixi](https://pixi.sh):
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
git clone https://github.com/cupidthatbtc/panelcast.git
|
|
144
|
+
cd panelcast
|
|
145
|
+
pixi install
|
|
146
|
+
pixi run panelcast --help
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> `pixi.lock` is the reproducible environment: it pins the full stack, notably
|
|
150
|
+
> the tightly coupled JAX/NumPyro pair. A plain pip installation obeys the tested
|
|
151
|
+
> dependency bounds but does not promise an identical solver result. Use pixi
|
|
152
|
+
> for publication or reproduction work.
|
|
153
|
+
>
|
|
154
|
+
> **Tested platforms:** the wheel-install CI matrix installs the built wheel
|
|
155
|
+
> into a fresh environment and runs the CPU demo on Linux, macOS, and Windows
|
|
156
|
+
> for Python 3.11–3.13. Python 3.14 is expected to work (the pixi environment
|
|
157
|
+
> runs it) but is not yet part of the pip matrix.
|
|
158
|
+
|
|
159
|
+
## 60-second quickstart (aerospace example)
|
|
160
|
+
|
|
161
|
+
Retarget the whole pipeline to a non-music domain with no code changes, using
|
|
162
|
+
the bundled synthetic aerospace dataset (committed under `examples/aerospace/`:
|
|
163
|
+
8 airframes flying ~39 sequential test flights scored 0–10):
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Run the entire pipeline end-to-end on the example, at tiny scale
|
|
167
|
+
panelcast demo
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`demo` reads the bundled aerospace descriptor and CSV from the installed wheel
|
|
171
|
+
(the checkout copies live under `examples/aerospace/`). The descriptor remaps the
|
|
172
|
+
columns, switches the score bounds to [0, 10], drops the music-specific feature
|
|
173
|
+
packs, and adds the domain's own numeric covariates. It runs data → splits →
|
|
174
|
+
features → train → evaluate → predict → report, finishing with a generated
|
|
175
|
+
model card under `outputs/<run_id>/reports/`. The model code is untouched.
|
|
176
|
+
|
|
177
|
+
The committed CSV is regenerated from the shared synthetic generator with
|
|
178
|
+
`python scripts/generate_aero_example.py`. To benchmark the model against simple
|
|
179
|
+
baselines on the splits it just produced:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
panelcast compare --baselines --dataset aero
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
To run the flagship AOTY domain instead, point at your data and omit `--dataset`:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
export AOTY_DATASET_PATH="/path/to/aoty_data.csv"
|
|
189
|
+
panelcast run --preflight-only # GPU-memory / schema / calibration gate
|
|
190
|
+
panelcast run # full pipeline
|
|
191
|
+
panelcast stage train --verbose # or run a single stage
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
See [`docs/CLI.md`](docs/CLI.md) for the complete command reference.
|
|
195
|
+
|
|
196
|
+
## Features
|
|
197
|
+
|
|
198
|
+
- Leak-safe data pipeline and evaluation (within-entity temporal split + an
|
|
199
|
+
entity-disjoint secondary check)
|
|
200
|
+
- Explicit data contract and lineage from raw CSV to final artifacts
|
|
201
|
+
- Preflight gates (GPU memory, schema validation, calibration) before expensive runs
|
|
202
|
+
- Convergence + PPC + coverage diagnostics as first-class, gating checks
|
|
203
|
+
- Sensitivity matrix over priors, splits, and feature ablations
|
|
204
|
+
- Publication-ready artifacts: tables, figures, model card, citations
|
|
205
|
+
- Domain portability proven by an end-to-end test, not just asserted — the
|
|
206
|
+
*apparatus* (descriptor → pipeline) runs on a new domain with zero source
|
|
207
|
+
changes; predictive accuracy off the flagship domain is untested by construction
|
|
208
|
+
|
|
209
|
+
## Documentation
|
|
210
|
+
|
|
211
|
+
- [`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md) — step-by-step startup guide (start here)
|
|
212
|
+
- [`docs/PORTING.md`](docs/PORTING.md) — retarget to a new domain (the aerospace walkthrough)
|
|
213
|
+
- [`docs/EXTENSIBILITY.md`](docs/EXTENSIBILITY.md) — adding features safely
|
|
214
|
+
- [`docs/CLI.md`](docs/CLI.md) — complete CLI reference
|
|
215
|
+
- [`docs/API.md`](docs/API.md) — supported Python import surface and its semver guarantee
|
|
216
|
+
- [`docs/LEAKAGE_CONTROLS.md`](docs/LEAKAGE_CONTROLS.md) — guardrails and leakage prevention
|
|
217
|
+
- [`docs/EVALUATION_PROTOCOL.md`](docs/EVALUATION_PROTOCOL.md) — metrics, diagnostics, and thresholds
|
|
218
|
+
- [`docs/PROJECT_STRUCTURE.md`](docs/PROJECT_STRUCTURE.md) — directory and file layout
|
|
219
|
+
- [`docs/DATA_CONTRACT.md`](docs/DATA_CONTRACT.md) — raw schema and cleaned artifacts
|
|
220
|
+
- [`docs/LINEAGE.md`](docs/LINEAGE.md) — repository lineage (the private predecessor, the 2026-06-20 migration) and JOSS submission timing
|
|
221
|
+
- [`MODEL_CARD.md`](MODEL_CARD.md) — intended use, results, and limitations
|
|
222
|
+
|
|
223
|
+
A note on results: at the amended publication configuration the published fit
|
|
224
|
+
**passes the convergence gate** on a real ~800-artist / ~5,182-album AOTY
|
|
225
|
+
subset (R-hat 1.00, bulk ESS 1,119, 0 divergences) under the default
|
|
226
|
+
**Student-t** likelihood on the `offset_logit` transformed scale with the
|
|
227
|
+
0.13.0 entity-obs default:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
panelcast run --preset publication # 4 chains × 5000, Student-t likelihood
|
|
231
|
+
panelcast diagnose # convergence + PPC of that run
|
|
232
|
+
panelcast compare --baselines # the model vs. simple baselines
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
What the subset validates: leak-safe splits with role-based names, an honest
|
|
236
|
+
baseline comparison (`panelcast compare`) on the same real splits, and a
|
|
237
|
+
convergent publication-scale fit on real, strongly left-skewed data — the
|
|
238
|
+
software behaves end-to-end under production settings. What convergence does
|
|
239
|
+
*not* establish: that the likelihood is correctly specified (the PPC pins say
|
|
240
|
+
it is not), or that subset results transfer to the full corpus. Both remain
|
|
241
|
+
open as domain-model research: the **skewness and max** posterior-predictive
|
|
242
|
+
p-values stay pinned at the extremes from a symmetric-likelihood /
|
|
243
|
+
left-skewed-target mismatch — six likelihood families (`beta`,
|
|
244
|
+
`skew_studentt`, `skew_normal`, `split_normal`, `beta_binomial`, `mixture`)
|
|
245
|
+
plus a dequantization toggle were tried and **none resolves them**, though the
|
|
246
|
+
entity-obs default cleared the q10 and q90 pins (see
|
|
247
|
+
[`docs/LIKELIHOOD_CANDIDATES.md`](docs/LIKELIHOOD_CANDIDATES.md)) — and this is
|
|
248
|
+
the validated subset, not the full eligible corpus (~62k albums with ≥10 user
|
|
249
|
+
ratings), which needs the full dataset and a GPU (#15).
|
|
250
|
+
|
|
251
|
+
Intentionally out of scope for the package claim: a resolved AOTY likelihood
|
|
252
|
+
(that is the open research above), full-corpus results (#15), and predictive
|
|
253
|
+
accuracy on non-flagship domains (portability is structural — the pipeline
|
|
254
|
+
runs — not a transferred accuracy claim). The code, the diagnostics, and the
|
|
255
|
+
honest naming of what is and isn't resolved are the point. Every headline
|
|
256
|
+
number here derives from the canonical release-result manifest,
|
|
257
|
+
[`.audit/release_results.json`](.audit/release_results.json), and drift fails CI.
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
MIT License. See [LICENSE](LICENSE) for details.
|