panelcast 0.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. panelcast/__init__.py +73 -0
  2. panelcast/_data/datasets/aero.yaml +82 -0
  3. panelcast/_data/datasets/aoty_full.yaml +37 -0
  4. panelcast/_data/examples/aerospace/descriptor.yaml +82 -0
  5. panelcast/_data/examples/aerospace/flights.csv +40 -0
  6. panelcast/cli/__init__.py +51 -0
  7. panelcast/cli/backtest_cmd.py +94 -0
  8. panelcast/cli/commands.py +814 -0
  9. panelcast/cli/doctor_cmd.py +61 -0
  10. panelcast/cli/main.py +66 -0
  11. panelcast/cli/preflight_cmd.py +101 -0
  12. panelcast/cli/run.py +1000 -0
  13. panelcast/cli/runs_cmd.py +528 -0
  14. panelcast/cli/select_cmd.py +238 -0
  15. panelcast/cli/stack_cmd.py +62 -0
  16. panelcast/cli/stages.py +350 -0
  17. panelcast/config/descriptor.py +404 -0
  18. panelcast/config/gates.py +47 -0
  19. panelcast/config/loader.py +70 -0
  20. panelcast/config/pipeline_yaml.py +328 -0
  21. panelcast/data/alignment.py +95 -0
  22. panelcast/data/chronology.py +87 -0
  23. panelcast/data/cleaning.py +541 -0
  24. panelcast/data/imputation.py +82 -0
  25. panelcast/data/ingest.py +196 -0
  26. panelcast/data/lineage.py +210 -0
  27. panelcast/data/manifests.py +205 -0
  28. panelcast/data/split.py +308 -0
  29. panelcast/data/split_types.py +99 -0
  30. panelcast/data/validation.py +244 -0
  31. panelcast/doctor.py +178 -0
  32. panelcast/evaluation/__init__.py +62 -0
  33. panelcast/evaluation/calibration.py +619 -0
  34. panelcast/evaluation/conformal.py +137 -0
  35. panelcast/evaluation/cv.py +394 -0
  36. panelcast/evaluation/decomposition.py +112 -0
  37. panelcast/evaluation/metrics.py +355 -0
  38. panelcast/evaluation/ppc.py +205 -0
  39. panelcast/evaluation/prior_predictive.py +384 -0
  40. panelcast/evaluation/ranking.py +132 -0
  41. panelcast/evaluation/slices.py +223 -0
  42. panelcast/features/__init__.py +24 -0
  43. panelcast/features/album_type.py +140 -0
  44. panelcast/features/artist.py +56 -0
  45. panelcast/features/base.py +208 -0
  46. panelcast/features/basis.py +141 -0
  47. panelcast/features/collaboration.py +158 -0
  48. panelcast/features/core.py +115 -0
  49. panelcast/features/errors.py +107 -0
  50. panelcast/features/gbm_offset.py +324 -0
  51. panelcast/features/genre.py +211 -0
  52. panelcast/features/history.py +181 -0
  53. panelcast/features/packs/__init__.py +7 -0
  54. panelcast/features/packs/aoty.py +26 -0
  55. panelcast/features/pca.py +5 -0
  56. panelcast/features/pipeline.py +185 -0
  57. panelcast/features/registry.py +125 -0
  58. panelcast/features/temporal.py +172 -0
  59. panelcast/gpu_memory/__init__.py +48 -0
  60. panelcast/gpu_memory/admission.py +83 -0
  61. panelcast/gpu_memory/calibration_store.py +262 -0
  62. panelcast/gpu_memory/estimate.py +248 -0
  63. panelcast/gpu_memory/measure.py +129 -0
  64. panelcast/gpu_memory/platform.py +134 -0
  65. panelcast/gpu_memory/query.py +194 -0
  66. panelcast/gpu_memory/runtime_predictor.py +160 -0
  67. panelcast/io/paths.py +27 -0
  68. panelcast/io/readers.py +20 -0
  69. panelcast/io/writers.py +9 -0
  70. panelcast/model_preflight.py +385 -0
  71. panelcast/model_preflight_data.py +125 -0
  72. panelcast/models/__init__.py +1 -0
  73. panelcast/models/baselines/__init__.py +37 -0
  74. panelcast/models/baselines/core.py +480 -0
  75. panelcast/models/bayes/__init__.py +72 -0
  76. panelcast/models/bayes/diagnostics.py +450 -0
  77. panelcast/models/bayes/fit.py +827 -0
  78. panelcast/models/bayes/io.py +382 -0
  79. panelcast/models/bayes/likelihoods.py +835 -0
  80. panelcast/models/bayes/model.py +1035 -0
  81. panelcast/models/bayes/model_math.py +43 -0
  82. panelcast/models/bayes/predict.py +615 -0
  83. panelcast/models/bayes/priors.py +350 -0
  84. panelcast/models/bayes/rollout.py +214 -0
  85. panelcast/models/bayes/transforms.py +126 -0
  86. panelcast/paths.py +107 -0
  87. panelcast/pipelines/__init__.py +67 -0
  88. panelcast/pipelines/backtest.py +340 -0
  89. panelcast/pipelines/build_features.py +496 -0
  90. panelcast/pipelines/compare_baselines.py +390 -0
  91. panelcast/pipelines/create_splits.py +425 -0
  92. panelcast/pipelines/diagnose.py +257 -0
  93. panelcast/pipelines/errors.py +194 -0
  94. panelcast/pipelines/evaluate.py +2087 -0
  95. panelcast/pipelines/manifest.py +367 -0
  96. panelcast/pipelines/orchestrator.py +1958 -0
  97. panelcast/pipelines/predict_next.py +929 -0
  98. panelcast/pipelines/prepare_dataset.py +287 -0
  99. panelcast/pipelines/publication.py +1296 -0
  100. panelcast/pipelines/sensitivity.py +1340 -0
  101. panelcast/pipelines/stages.py +807 -0
  102. panelcast/pipelines/stamps.py +84 -0
  103. panelcast/pipelines/train_bayes.py +1876 -0
  104. panelcast/pipelines/training_summary.py +198 -0
  105. panelcast/preflight/__init__.py +236 -0
  106. panelcast/preflight/cache.py +177 -0
  107. panelcast/preflight/calibrate.py +249 -0
  108. panelcast/preflight/check.py +250 -0
  109. panelcast/preflight/full_check.py +737 -0
  110. panelcast/preflight/mini_run.py +407 -0
  111. panelcast/preflight/output.py +240 -0
  112. panelcast/py.typed +0 -0
  113. panelcast/reporting/__init__.py +57 -0
  114. panelcast/reporting/curves.py +166 -0
  115. panelcast/reporting/figures.py +1181 -0
  116. panelcast/reporting/html_report.py +219 -0
  117. panelcast/reporting/model_card.py +1144 -0
  118. panelcast/reporting/tables.py +667 -0
  119. panelcast/select/__init__.py +34 -0
  120. panelcast/select/confirmation.py +373 -0
  121. panelcast/select/orchestrate.py +510 -0
  122. panelcast/select/prior_screen.py +198 -0
  123. panelcast/select/rules.py +200 -0
  124. panelcast/select/runner.py +1130 -0
  125. panelcast/select/scoring.py +424 -0
  126. panelcast/select/space.py +453 -0
  127. panelcast/select/stacking.py +499 -0
  128. panelcast/select/tiers.py +219 -0
  129. panelcast/utils/environment.py +150 -0
  130. panelcast/utils/git_state.py +177 -0
  131. panelcast/utils/hashing.py +93 -0
  132. panelcast/utils/jax_cache.py +57 -0
  133. panelcast/utils/logging.py +152 -0
  134. panelcast/utils/random.py +69 -0
  135. panelcast/visualization/__init__.py +68 -0
  136. panelcast/visualization/charts.py +535 -0
  137. panelcast/visualization/dashboard.py +612 -0
  138. panelcast/visualization/export.py +306 -0
  139. panelcast/visualization/theme.py +101 -0
  140. panelcast-0.14.0.dist-info/METADATA +316 -0
  141. panelcast-0.14.0.dist-info/RECORD +145 -0
  142. panelcast-0.14.0.dist-info/WHEEL +5 -0
  143. panelcast-0.14.0.dist-info/entry_points.txt +2 -0
  144. panelcast-0.14.0.dist-info/licenses/LICENSE +21 -0
  145. panelcast-0.14.0.dist-info/top_level.txt +1 -0
panelcast/__init__.py ADDED
@@ -0,0 +1,73 @@
1
+ """panelcast — hierarchical Bayesian prediction for bounded scores of events
2
+ nested in entities over time, configured by a YAML descriptor.
3
+
4
+ The names re-exported here are the supported public API and follow semantic
5
+ versioning from the next minor release; see ``docs/API.md`` for the guarantee.
6
+ Everything reached through ``panelcast.*`` submodules is internal and may change
7
+ without notice. Attribute access is lazy (PEP 562), so ``import panelcast`` stays
8
+ cheap and does not eagerly import jax.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from importlib import import_module
14
+ from importlib.metadata import PackageNotFoundError, version
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ try:
18
+ __version__ = version("panelcast")
19
+ except PackageNotFoundError: # running from a source tree without an install
20
+ __version__ = "0.0.0+unknown"
21
+
22
+ # Each public name mapped to the submodule it is lazily imported from. Keep this
23
+ # minimal — every entry is a semver promise (see docs/API.md), and importing a
24
+ # submodule here would defeat the point of the lazy seam.
25
+ _LAZY_EXPORTS = {
26
+ "DatasetDescriptor": "panelcast.config.descriptor",
27
+ "load_descriptor": "panelcast.config.descriptor",
28
+ "PipelineConfig": "panelcast.pipelines.orchestrator",
29
+ "PipelineOrchestrator": "panelcast.pipelines.orchestrator",
30
+ "run_pipeline": "panelcast.pipelines.orchestrator",
31
+ "FeatureRegistry": "panelcast.features",
32
+ "FeatureBlock": "panelcast.features.base",
33
+ "build_default_registry": "panelcast.features",
34
+ "LikelihoodSpec": "panelcast.models.bayes.likelihoods",
35
+ }
36
+
37
+ if TYPE_CHECKING: # let type checkers resolve the names without importing jax
38
+ from panelcast.config.descriptor import DatasetDescriptor, load_descriptor
39
+ from panelcast.features import FeatureRegistry, build_default_registry
40
+ from panelcast.features.base import FeatureBlock
41
+ from panelcast.models.bayes.likelihoods import LikelihoodSpec
42
+ from panelcast.pipelines.orchestrator import (
43
+ PipelineConfig,
44
+ PipelineOrchestrator,
45
+ run_pipeline,
46
+ )
47
+
48
+
49
+ def __getattr__(name: str) -> Any:
50
+ module = _LAZY_EXPORTS.get(name)
51
+ if module is None:
52
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
53
+ return getattr(import_module(module), name)
54
+
55
+
56
+ def __dir__() -> list[str]:
57
+ # Keep the default module surface (dunders, __all__, eager imports,
58
+ # submodules) discoverable alongside the lazily exported public names.
59
+ return sorted({*globals(), *_LAZY_EXPORTS})
60
+
61
+
62
+ __all__ = [
63
+ "__version__",
64
+ "DatasetDescriptor",
65
+ "load_descriptor",
66
+ "PipelineConfig",
67
+ "PipelineOrchestrator",
68
+ "run_pipeline",
69
+ "FeatureRegistry",
70
+ "FeatureBlock",
71
+ "build_default_registry",
72
+ "LikelihoodSpec",
73
+ ]
@@ -0,0 +1,82 @@
1
+ # Aerospace worked example — a self-contained, non-music domain.
2
+ #
3
+ # Airframes fly sequential test flights, each scored 0-10. Identical copies of
4
+ # this descriptor ship in the checkout and wheel; the raw path is anchored at
5
+ # the checkout or package-data root so `panelcast demo` runs with no external
6
+ # data.
7
+ #
8
+ # Every key omitted here keeps its AOTY default — see
9
+ # src/panelcast/config/descriptor.py. Walkthrough: docs/PORTING.md.
10
+
11
+ name: aero
12
+
13
+ # --- raw source ----------------------------------------------------------
14
+ raw_path_env: AERO_DATASET_PATH
15
+ raw_path_default: examples/aerospace/flights.csv
16
+ encoding: utf-8
17
+ raw_column_map:
18
+ "Flight Date": Flight_Date
19
+ "Perf Score": Perf_Score
20
+ "Sensor Samples": Sensor_Samples
21
+ "Test Crew": Test_Crew
22
+ "Flight ID": Flight_ID
23
+ "Campaign Year": Year
24
+ "Thrust Margin": Thrust_Margin
25
+ "Payload Fraction": Payload_Fraction
26
+ required_raw_columns:
27
+ - Airframe
28
+ - "Flight ID"
29
+ - "Campaign Year"
30
+ - "Flight Date"
31
+ - "Perf Score"
32
+ - "Sensor Samples"
33
+ - "Test Crew"
34
+ - "Thrust Margin"
35
+ - "Payload Fraction"
36
+ optional_raw_columns: []
37
+
38
+ # --- identity / sequencing ------------------------------------------------
39
+ entity_col: Airframe
40
+ event_col: Flight_ID
41
+ # No per-event group column in this domain — the entity_group_pooling gate is
42
+ # structurally unusable (inheriting AOTY's primary_genre would misdescribe it).
43
+ entity_group_col: null
44
+ date_col: Flight_Date
45
+ parsed_date_col: Flight_Date_Parsed
46
+ year_col: Year
47
+ date_format: "%Y-%m-%d" # ISO, unlike AOTY's "April 10, 2018"
48
+
49
+ # --- targets ----------------------------------------------------------------
50
+ target_col: Perf_Score
51
+ target_bounds: [0.0, 10.0]
52
+ model_prefix: perf
53
+ n_obs_col: Sensor_Samples
54
+ # Sensor samples count telemetry points, not independent raters whose mean is the
55
+ # score, so the beta_binomial likelihood is not meaningful here.
56
+ n_obs_is_aggregation_count: false
57
+ secondary_target_col: null
58
+ secondary_prefix: null
59
+ secondary_n_obs_col: null
60
+
61
+ # --- cleaning semantics ----------------------------------------------------
62
+ multi_entity_col: Test_Crew
63
+ multi_entity_separator: " + "
64
+ unknown_entity_sentinel: null
65
+ min_year: 2015
66
+
67
+ # --- dataset preparation -----------------------------------------------------
68
+ min_obs_thresholds: [5, 10, 25]
69
+ primary_min_obs: 5
70
+ processed_name_template: "perf_minobs_{min_ratings}"
71
+
72
+ # --- features -----------------------------------------------------------------
73
+ feature_packs: []
74
+ feature_blocks:
75
+ - name: temporal
76
+ - name: entity_history
77
+ - name: core_numeric
78
+ params:
79
+ columns: [Thrust_Margin, Payload_Fraction]
80
+ ablation_groups:
81
+ temporal: [temporal]
82
+ artist: [entity_history]
@@ -0,0 +1,37 @@
1
+ # AOTY dataset descriptor.
2
+ #
3
+ # Every DatasetDescriptor field defaults to the AOTY literal it replaced, so
4
+ # this file only needs to (re)state the name: `--dataset aoty_full` must be
5
+ # byte-identical to running with no --dataset flag at all (verified by
6
+ # tests/e2e/test_domain_portability.py).
7
+ #
8
+ # Full field reference: src/panelcast/config/descriptor.py.
9
+ # Worked non-music example: configs/datasets/aero.yaml + docs/PORTING.md.
10
+ #
11
+ # Operational note: processed data, splits, features and models all live under
12
+ # relative paths (data/, models/, outputs/) — run each domain from its own
13
+ # working directory to keep artifacts isolated. A --data-root option is a
14
+ # post-publication stretch item.
15
+
16
+ name: aoty
17
+
18
+ # Commented examples of the AOTY defaults this file inherits (do not uncomment
19
+ # unless you mean to override):
20
+ #
21
+ # raw_path_env: AOTY_DATASET_PATH
22
+ # raw_path_default: data/raw/all_albums_full.csv
23
+ # encoding: utf-8-sig
24
+ # entity_col: Artist
25
+ # event_col: Album
26
+ # target_col: User_Score
27
+ # target_bounds: [0.0, 100.0]
28
+ # model_prefix: user
29
+ # n_obs_col: User_Ratings
30
+ # secondary_target_col: Critic_Score
31
+ # secondary_prefix: critic
32
+ # secondary_n_obs_col: Critic_Reviews
33
+ # date_format: "%B %d, %Y"
34
+ # min_obs_thresholds: [5, 10, 25]
35
+ # primary_min_obs: 10
36
+ # processed_name_template: "user_score_minratings_{min_ratings}"
37
+ # feature_packs: [aoty]
@@ -0,0 +1,82 @@
1
+ # Aerospace worked example — a self-contained, non-music domain.
2
+ #
3
+ # Airframes fly sequential test flights, each scored 0-10. Identical copies of
4
+ # this descriptor ship in the checkout and wheel; the raw path is anchored at
5
+ # the checkout or package-data root so `panelcast demo` runs with no external
6
+ # data.
7
+ #
8
+ # Every key omitted here keeps its AOTY default — see
9
+ # src/panelcast/config/descriptor.py. Walkthrough: docs/PORTING.md.
10
+
11
+ name: aero
12
+
13
+ # --- raw source ----------------------------------------------------------
14
+ raw_path_env: AERO_DATASET_PATH
15
+ raw_path_default: examples/aerospace/flights.csv
16
+ encoding: utf-8
17
+ raw_column_map:
18
+ "Flight Date": Flight_Date
19
+ "Perf Score": Perf_Score
20
+ "Sensor Samples": Sensor_Samples
21
+ "Test Crew": Test_Crew
22
+ "Flight ID": Flight_ID
23
+ "Campaign Year": Year
24
+ "Thrust Margin": Thrust_Margin
25
+ "Payload Fraction": Payload_Fraction
26
+ required_raw_columns:
27
+ - Airframe
28
+ - "Flight ID"
29
+ - "Campaign Year"
30
+ - "Flight Date"
31
+ - "Perf Score"
32
+ - "Sensor Samples"
33
+ - "Test Crew"
34
+ - "Thrust Margin"
35
+ - "Payload Fraction"
36
+ optional_raw_columns: []
37
+
38
+ # --- identity / sequencing ------------------------------------------------
39
+ entity_col: Airframe
40
+ event_col: Flight_ID
41
+ # No per-event group column in this domain — the entity_group_pooling gate is
42
+ # structurally unusable (inheriting AOTY's primary_genre would misdescribe it).
43
+ entity_group_col: null
44
+ date_col: Flight_Date
45
+ parsed_date_col: Flight_Date_Parsed
46
+ year_col: Year
47
+ date_format: "%Y-%m-%d" # ISO, unlike AOTY's "April 10, 2018"
48
+
49
+ # --- targets ----------------------------------------------------------------
50
+ target_col: Perf_Score
51
+ target_bounds: [0.0, 10.0]
52
+ model_prefix: perf
53
+ n_obs_col: Sensor_Samples
54
+ # Sensor samples count telemetry points, not independent raters whose mean is the
55
+ # score, so the beta_binomial likelihood is not meaningful here.
56
+ n_obs_is_aggregation_count: false
57
+ secondary_target_col: null
58
+ secondary_prefix: null
59
+ secondary_n_obs_col: null
60
+
61
+ # --- cleaning semantics ----------------------------------------------------
62
+ multi_entity_col: Test_Crew
63
+ multi_entity_separator: " + "
64
+ unknown_entity_sentinel: null
65
+ min_year: 2015
66
+
67
+ # --- dataset preparation -----------------------------------------------------
68
+ min_obs_thresholds: [5, 10, 25]
69
+ primary_min_obs: 5
70
+ processed_name_template: "perf_minobs_{min_ratings}"
71
+
72
+ # --- features -----------------------------------------------------------------
73
+ feature_packs: []
74
+ feature_blocks:
75
+ - name: temporal
76
+ - name: entity_history
77
+ - name: core_numeric
78
+ params:
79
+ columns: [Thrust_Margin, Payload_Fraction]
80
+ ablation_groups:
81
+ temporal: [temporal]
82
+ artist: [entity_history]
@@ -0,0 +1,40 @@
1
+ Airframe,Flight ID,Flight Date,Campaign Year,Perf Score,Sensor Samples,Test Crew,Thrust Margin,Payload Fraction
2
+ Falcon-X1,Falcon-X1-F01,2021-04-16,2021,4.42,116,Falcon-X1 + Chase-3,-1.951,0.883
3
+ Falcon-X1,Falcon-X1-F02,2021-05-10,2021,5.05,102,Falcon-X1 + Chase-2,0.879,0.849
4
+ Falcon-X1,Falcon-X1-F03,2021-05-30,2021,4.27,156,Falcon-X1 + Chase-2,-0.959,0.779
5
+ Falcon-X1,Falcon-X1-F04,2021-07-15,2021,4.72,113,Falcon-X1,-0.155,0.745
6
+ Condor-7,Condor-7-F01,2021-04-28,2021,6.54,176,Condor-7,-0.406,0.428
7
+ Condor-7,Condor-7-F02,2021-05-09,2021,7.0,42,Condor-7,-0.84,0.359
8
+ Condor-7,Condor-7-F03,2021-07-20,2021,6.74,198,Condor-7,0.543,0.783
9
+ Condor-7,Condor-7-F04,2021-07-08,2021,7.02,148,Condor-7 + Chase-3,0.224,0.34
10
+ Raptor-M2,Raptor-M2-F01,2021-04-20,2021,5.24,128,Raptor-M2,-0.275,0.28
11
+ Raptor-M2,Raptor-M2-F02,2021-04-30,2021,5.43,170,Raptor-M2,0.163,0.588
12
+ Raptor-M2,Raptor-M2-F03,2021-06-11,2021,4.81,100,Raptor-M2,0.858,0.486
13
+ Raptor-M2,Raptor-M2-F04,2021-08-30,2021,4.62,219,Raptor-M2 + Chase-1,0.142,0.663
14
+ Raptor-M2,Raptor-M2-F05,2021-09-15,2021,4.83,171,Raptor-M2,0.457,0.317
15
+ Albatross-3,Albatross-3-F01,2021-04-13,2021,6.81,58,Albatross-3,0.481,0.687
16
+ Albatross-3,Albatross-3-F02,2021-05-13,2021,5.55,200,Albatross-3 + Chase-2,-1.687,0.261
17
+ Albatross-3,Albatross-3-F03,2021-06-21,2021,6.81,64,Albatross-3,1.299,0.745
18
+ Albatross-3,Albatross-3-F04,2021-06-12,2021,5.39,28,Albatross-3,-0.339,0.519
19
+ Albatross-3,Albatross-3-F05,2021-09-01,2021,5.49,45,Albatross-3,-1.446,0.731
20
+ Albatross-3,Albatross-3-F06,2021-08-05,2021,6.38,131,Albatross-3,-0.239,0.259
21
+ Kestrel-V,Kestrel-V-F01,2021-04-05,2021,6.29,29,Kestrel-V,-1.189,0.319
22
+ Kestrel-V,Kestrel-V-F02,2021-05-09,2021,5.92,21,Kestrel-V + Chase-2,-0.222,0.871
23
+ Kestrel-V,Kestrel-V-F03,2021-05-05,2021,7.38,179,Kestrel-V,2.914,0.856
24
+ Kestrel-V,Kestrel-V-F04,2021-06-12,2021,6.69,197,Kestrel-V + Chase-1,-0.415,0.507
25
+ Harrier-9,Harrier-9-F01,2021-03-04,2021,4.48,150,Harrier-9,-0.054,0.709
26
+ Harrier-9,Harrier-9-F02,2021-04-25,2021,4.94,105,Harrier-9 + Chase-1,-0.965,0.588
27
+ Harrier-9,Harrier-9-F03,2021-05-27,2021,4.46,218,Harrier-9,0.932,0.404
28
+ Harrier-9,Harrier-9-F04,2021-07-18,2021,3.66,166,Harrier-9 + Chase-1,-0.455,0.505
29
+ Harrier-9,Harrier-9-F05,2021-06-01,2021,4.56,197,Harrier-9,0.286,0.421
30
+ Harrier-9,Harrier-9-F06,2021-10-06,2021,4.65,28,Harrier-9,1.536,0.384
31
+ Osprey-T4,Osprey-T4-F01,2021-03-05,2021,8.52,46,Osprey-T4,1.461,0.812
32
+ Osprey-T4,Osprey-T4-F02,2021-03-23,2021,8.07,214,Osprey-T4,-0.005,0.301
33
+ Osprey-T4,Osprey-T4-F03,2021-03-27,2021,6.88,151,Osprey-T4,-2.05,0.554
34
+ Osprey-T4,Osprey-T4-F04,2021-07-09,2021,7.82,178,Osprey-T4,0.916,0.717
35
+ Swift-E2,Swift-E2-F01,2021-05-01,2021,5.35,113,Swift-E2 + Chase-2,0.538,0.869
36
+ Swift-E2,Swift-E2-F02,2021-05-18,2021,5.7,9,Swift-E2,0.177,0.274
37
+ Swift-E2,Swift-E2-F03,2021-08-18,2021,4.99,11,Swift-E2 + Chase-3,-0.853,0.763
38
+ Swift-E2,Swift-E2-F04,2021-07-25,2021,6.07,183,Swift-E2,-0.654,0.363
39
+ Swift-E2,Swift-E2-F05,2021-07-27,2021,5.69,87,Swift-E2,1.124,0.656
40
+ Swift-E2,Swift-E2-F06,2021-09-11,2021,7.36,58,Swift-E2,-1.097,0.772
@@ -0,0 +1,51 @@
1
+ """Command-line interface for the panelcast prediction pipeline.
2
+
3
+ This module provides CLI entry points for running the full pipeline or
4
+ individual stages. The primary entry point is `panelcast run`, which
5
+ executes all stages in dependency order with progress tracking.
6
+
7
+ Usage:
8
+ panelcast run --seed 42
9
+ panelcast run --dry-run --verbose
10
+ panelcast stage data --verbose
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import typer
16
+
17
+ app = typer.Typer(
18
+ add_completion=True,
19
+ help="panelcast - hierarchical Bayesian prediction for bounded scores over entity histories.",
20
+ invoke_without_command=True,
21
+ )
22
+
23
+ # Stage subcommand group
24
+ stage_app = typer.Typer(help="Run individual pipeline stages")
25
+ app.add_typer(stage_app, name="stage")
26
+
27
+ # Runs subcommand group
28
+ runs_app = typer.Typer(help="Inspect pipeline run directories")
29
+ app.add_typer(runs_app, name="runs")
30
+
31
+ # Import the command submodules for their decorator side effects: importing each
32
+ # one runs the @app.command / @stage_app.command decorators that register the
33
+ # subcommands onto the shared app / stage_app created above. Order matches the
34
+ # original definition order so ``--help`` lists the commands unchanged.
35
+ # isort: off
36
+ from panelcast.cli import main as _main # noqa: E402
37
+ from panelcast.cli import run as _run # noqa: E402, F401
38
+ from panelcast.cli import stages as _stages # noqa: E402, F401
39
+ from panelcast.cli import commands as _commands # noqa: E402, F401
40
+ from panelcast.cli import runs_cmd as _runs_cmd # noqa: E402, F401
41
+ from panelcast.cli import doctor_cmd as _doctor_cmd # noqa: E402, F401
42
+ from panelcast.cli import preflight_cmd as _preflight_cmd # noqa: E402, F401
43
+ from panelcast.cli import select_cmd as _select_cmd # noqa: E402, F401
44
+ from panelcast.cli import stack_cmd as _stack_cmd # noqa: E402, F401
45
+ from panelcast.cli import backtest_cmd as _backtest_cmd # noqa: E402, F401
46
+ # isort: on
47
+
48
+ __version__ = _main.__version__
49
+ main = _main.main
50
+
51
+ __all__ = ["__version__", "app", "main", "runs_app", "stage_app"]
@@ -0,0 +1,94 @@
1
+ """The `panelcast backtest` command (#179).
2
+
3
+ Rolling-origin backtest: runs the full leakage-safe stage chain once per
4
+ origin and reports every headline metric as mean ± SE across origins. A
5
+ killed backtest resumes at the next unfinished origin via the JSON ledger.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ import typer
13
+
14
+ from panelcast.cli import app
15
+
16
+
17
+ @app.command("backtest")
18
+ def backtest(
19
+ origins: int = typer.Option(
20
+ 3,
21
+ "--origins",
22
+ min=1,
23
+ help="Number of rolling origins K: origin k holds out each entity's (last-k)-th event.",
24
+ ),
25
+ backtest_id: str = typer.Option(
26
+ "default",
27
+ "--backtest-id",
28
+ help="Ledger/report directory name under outputs/backtest/ (reuse to resume).",
29
+ ),
30
+ dataset: str | None = typer.Option(
31
+ None,
32
+ "--dataset",
33
+ help="Dataset descriptor (bare name or YAML path; omit for AOTY defaults).",
34
+ ),
35
+ num_chains: int | None = typer.Option(
36
+ None, "--num-chains", min=1, help="Chains per origin fit (default: pipeline default)."
37
+ ),
38
+ num_samples: int | None = typer.Option(
39
+ None, "--num-samples", min=1, help="Draws per origin fit (default: pipeline default)."
40
+ ),
41
+ num_warmup: int | None = typer.Option(
42
+ None, "--num-warmup", min=1, help="Warmup per origin fit (default: pipeline default)."
43
+ ),
44
+ origin_timeout: float | None = typer.Option(
45
+ None,
46
+ "--origin-timeout",
47
+ min=1.0,
48
+ help="Per-origin wall-clock timeout in seconds (default: none).",
49
+ ),
50
+ output_root: str = typer.Option(
51
+ "outputs/backtest", "--output-root", help="Root directory for backtest ledgers/reports."
52
+ ),
53
+ ) -> None:
54
+ """Run (or resume) a rolling-origin backtest and print the aggregate table.
55
+
56
+ Each origin regenerates splits/features with fresh stamps, so the leakage
57
+ controls hold unchanged; every origin's split content hash is recorded in
58
+ the ledger. Deeper origins shrink the eligible entity set — the aggregate
59
+ table reports n_test and n_entities per origin so cross-origin variation
60
+ is framed honestly.
61
+
62
+ Examples:
63
+ panelcast backtest --origins 3
64
+ panelcast backtest --origins 5 --num-chains 2 --num-samples 500
65
+ panelcast backtest --backtest-id nightly # rerun to resume
66
+ """
67
+ from panelcast.pipelines.backtest import BacktestConfig, run_backtest
68
+
69
+ cfg = BacktestConfig(
70
+ origins=origins,
71
+ backtest_id=backtest_id,
72
+ output_root=Path(output_root),
73
+ dataset=dataset,
74
+ num_chains=num_chains,
75
+ num_samples=num_samples,
76
+ num_warmup=num_warmup,
77
+ origin_timeout_seconds=origin_timeout,
78
+ )
79
+ aggregate = run_backtest(cfg)
80
+
81
+ typer.echo(
82
+ f"\nBacktest '{backtest_id}': {aggregate['n_origins_completed']}"
83
+ f"/{aggregate['n_origins_requested']} origins completed."
84
+ )
85
+ for name, block in aggregate["metrics"].items():
86
+ if block is None:
87
+ continue
88
+ se = f" ± {block['se']:.4f}" if block["se"] is not None else ""
89
+ typer.echo(f" {name}: {block['mean']:.4f}{se} [{block['min']:.4f}, {block['max']:.4f}]")
90
+ typer.echo(f"\n wrote {cfg.backtest_dir / 'backtest_metrics.json'}")
91
+ typer.echo(f" wrote {cfg.backtest_dir / 'backtest_report.md'}")
92
+ if aggregate["n_origins_completed"] < aggregate["n_origins_requested"]:
93
+ typer.echo(" incomplete origins remain — rerun the same command to resume.")
94
+ raise typer.Exit(code=1)