reliability-lab 0.1.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. reliability_lab-0.1.0a1/.gitattributes +2 -0
  2. reliability_lab-0.1.0a1/.github/workflows/ci.yml +50 -0
  3. reliability_lab-0.1.0a1/.github/workflows/release.yml +65 -0
  4. reliability_lab-0.1.0a1/.gitignore +28 -0
  5. reliability_lab-0.1.0a1/.python-version +1 -0
  6. reliability_lab-0.1.0a1/CHANGELOG.md +26 -0
  7. reliability_lab-0.1.0a1/LICENSE +28 -0
  8. reliability_lab-0.1.0a1/PKG-INFO +137 -0
  9. reliability_lab-0.1.0a1/README.md +107 -0
  10. reliability_lab-0.1.0a1/docs/design/framing-v2.md +323 -0
  11. reliability_lab-0.1.0a1/docs/releasing.md +32 -0
  12. reliability_lab-0.1.0a1/examples/quickstart.py +58 -0
  13. reliability_lab-0.1.0a1/examples/splits.py +101 -0
  14. reliability_lab-0.1.0a1/pyproject.toml +107 -0
  15. reliability_lab-0.1.0a1/src/reliability_lab/__init__.py +62 -0
  16. reliability_lab-0.1.0a1/src/reliability_lab/_version.py +1 -0
  17. reliability_lab-0.1.0a1/src/reliability_lab/alpha.py +290 -0
  18. reliability_lab-0.1.0a1/src/reliability_lab/data.py +280 -0
  19. reliability_lab-0.1.0a1/src/reliability_lab/design.py +253 -0
  20. reliability_lab-0.1.0a1/src/reliability_lab/exceptions.py +42 -0
  21. reliability_lab-0.1.0a1/src/reliability_lab/inference.py +724 -0
  22. reliability_lab-0.1.0a1/src/reliability_lab/py.typed +0 -0
  23. reliability_lab-0.1.0a1/src/reliability_lab/report.py +717 -0
  24. reliability_lab-0.1.0a1/src/reliability_lab/split.py +338 -0
  25. reliability_lab-0.1.0a1/src/reliability_lab/study.py +824 -0
  26. reliability_lab-0.1.0a1/tests/fixtures/__init__.py +0 -0
  27. reliability_lab-0.1.0a1/tests/fixtures/krippendorff2011.py +52 -0
  28. reliability_lab-0.1.0a1/tests/helpers.py +57 -0
  29. reliability_lab-0.1.0a1/tests/naive_bootstrap.py +90 -0
  30. reliability_lab-0.1.0a1/tests/reference.py +73 -0
  31. reliability_lab-0.1.0a1/tests/test_alpha.py +146 -0
  32. reliability_lab-0.1.0a1/tests/test_bca.py +83 -0
  33. reliability_lab-0.1.0a1/tests/test_compare.py +136 -0
  34. reliability_lab-0.1.0a1/tests/test_data.py +106 -0
  35. reliability_lab-0.1.0a1/tests/test_design.py +195 -0
  36. reliability_lab-0.1.0a1/tests/test_inference_units.py +227 -0
  37. reliability_lab-0.1.0a1/tests/test_kalpha.py +133 -0
  38. reliability_lab-0.1.0a1/tests/test_parity.py +95 -0
  39. reliability_lab-0.1.0a1/tests/test_properties.py +130 -0
  40. reliability_lab-0.1.0a1/tests/test_report.py +75 -0
  41. reliability_lab-0.1.0a1/tests/test_report_io.py +165 -0
  42. reliability_lab-0.1.0a1/tests/test_smoke.py +6 -0
  43. reliability_lab-0.1.0a1/tests/test_split.py +361 -0
  44. reliability_lab-0.1.0a1/tests/test_status_policies.py +200 -0
  45. reliability_lab-0.1.0a1/uv.lock +1473 -0
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -0,0 +1,50 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["**"]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: ${{ matrix.os }} / py${{ matrix.python-version }}
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, windows-latest, macos-latest]
16
+ python-version: ["3.11", "3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v5
19
+ - uses: astral-sh/setup-uv@v6
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - name: Install
23
+ run: uv sync --all-groups --locked
24
+ - name: Lint
25
+ run: uv run ruff check
26
+ - name: Format
27
+ run: uv run ruff format --check
28
+ - name: Type-check
29
+ run: uv run mypy
30
+ - name: Test
31
+ run: uv run pytest
32
+
33
+ # irrCAC pins scipy==1.12.0, below this package's floor (1.15, for the bootstrap's
34
+ # rng keyword). Its parity test only needs the point estimates, so it runs in a
35
+ # throwaway environment with the package installed without its dependencies.
36
+ parity-irrcac:
37
+ name: irrCAC parity (isolated)
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v5
41
+ - uses: astral-sh/setup-uv@v6
42
+ - name: Build the isolated environment
43
+ run: |
44
+ uv venv .venv-irrcac --python 3.12
45
+ uv pip install --python .venv-irrcac irrcac pytest hypothesis
46
+ uv pip install --python .venv-irrcac --no-deps .
47
+ - name: Run irrCAC parity
48
+ run: >-
49
+ .venv-irrcac/bin/python -m pytest tests/test_parity.py -m parity -k irrcac
50
+ -p no:cacheprovider
@@ -0,0 +1,65 @@
1
+ name: Release
2
+
3
+ # Publishes on a version tag (v0.1.0a1, v0.1.0, ...) with PyPI trusted publishing:
4
+ # no API tokens are stored. See docs/releasing.md for the one-time setup.
5
+ on:
6
+ push:
7
+ tags: ["v*"]
8
+
9
+ jobs:
10
+ build:
11
+ name: Build and check the distributions
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v5
15
+ - uses: astral-sh/setup-uv@v6
16
+ - name: Check the tag matches the package version
17
+ run: |
18
+ version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/reliability_lab/_version.py)
19
+ if [ "v$version" != "$GITHUB_REF_NAME" ]; then
20
+ echo "tag $GITHUB_REF_NAME does not match package version $version" >&2
21
+ exit 1
22
+ fi
23
+ - name: Build
24
+ run: uv build
25
+ - name: Check metadata
26
+ run: uvx twine check --strict dist/*
27
+ - uses: actions/upload-artifact@v4
28
+ with:
29
+ name: dist
30
+ path: dist/
31
+
32
+ publish-testpypi:
33
+ name: Publish to TestPyPI
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+ environment:
37
+ name: testpypi
38
+ url: https://test.pypi.org/p/reliability-lab
39
+ permissions:
40
+ id-token: write
41
+ steps:
42
+ - uses: actions/download-artifact@v4
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+ - uses: pypa/gh-action-pypi-publish@release/v1
47
+ with:
48
+ repository-url: https://test.pypi.org/legacy/
49
+
50
+ publish-pypi:
51
+ name: Publish to PyPI
52
+ needs: publish-testpypi
53
+ runs-on: ubuntu-latest
54
+ # Give this environment a required reviewer to approve each release by hand.
55
+ environment:
56
+ name: pypi
57
+ url: https://pypi.org/p/reliability-lab
58
+ permissions:
59
+ id-token: write
60
+ steps:
61
+ - uses: actions/download-artifact@v4
62
+ with:
63
+ name: dist
64
+ path: dist/
65
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,28 @@
1
+ # Byte-compiled / cache
2
+ __pycache__/
3
+ *.py[cod]
4
+ .mypy_cache/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .hypothesis/
8
+
9
+ # Environments
10
+ .venv/
11
+ venv/
12
+
13
+ # Build artefacts
14
+ build/
15
+ dist/
16
+ *.egg-info/
17
+
18
+ # Coverage
19
+ .coverage
20
+ .coverage.*
21
+ htmlcov/
22
+ coverage.xml
23
+
24
+ # Editors / OS
25
+ .vscode/
26
+ .idea/
27
+ .DS_Store
28
+ Thumbs.db
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.0a1
6
+
7
+ First public release.
8
+
9
+ - Project scaffold: packaging, BSD-3-Clause licence, CI on Linux, Windows and macOS.
10
+ - Core engine: `RatingFrame` long-form schema with rating status, `CoderSpec` and design labels, Krippendorff's alpha (nominal, ordinal, interval, ratio, custom distances) with white-box diagnostics, and missing-data policies (`as_missing`, `as_category`, `sensitivity`).
11
+ - Bootstrap inference: `AlphaResult.bootstrap(scheme=...)` with a required scheme.
12
+ - `units` resamples units, or groups, optionally within strata, and recomputes D_e per replicate.
13
+ - `kalpha` follows Krippendorff's algorithm, resampling pairs of values with D_e fixed.
14
+ - Percentile intervals are the default; BCa is opt-in and flagged experimental.
15
+ - Failed replicates are counted and reported, the seed is always recorded, and an optional `alpha_min` reports how many replicates fall below it.
16
+ - Paired comparisons: `ReliabilityStudy.compare(a=..., b=...)` places the interval on Δα = α_b − α_a, using the same resampled units for both configurations.
17
+ - Group-safe splits:
18
+ - `ReliabilityStudy.holdout()` and `cv()` take any scikit-learn style splitter. The splitter sees one sample per group, so groups and repeated LLM calls never cross partitions.
19
+ - When a group's units span several strata, `group_stratum` must be given.
20
+ - `SplitManifest` saves partition ids, the splitter, realised sizes and fingerprints; `apply_manifest()` reapplies them to a later log.
21
+ - `assert_no_leakage()` checks that few-shot examples come from the dev partition.
22
+ - Reports:
23
+ - versioned JSON (`to_json`) with provenance for every result type: software versions, ratings fingerprint, declarations, split and known gaps;
24
+ - tidy tables (`tables`) and Parquet output (`to_parquet`, with the `parquet` extra).
25
+ - Tag-triggered release workflow using PyPI trusted publishing.
26
+ - New exceptions and warnings: `BootstrapWarning`, `LeakageError`. The scipy floor is now 1.15.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Sathapana Chawananon
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.5
2
+ Name: reliability-lab
3
+ Version: 0.1.0a1
4
+ Summary: Study-design library for the reliability of LLM and human judgments, built on Krippendorff's alpha.
5
+ Project-URL: Repository, https://github.com/Sathapana/reliability-lab
6
+ Project-URL: Issues, https://github.com/Sathapana/reliability-lab/issues
7
+ Author: Sathapana Chawananon
8
+ License-Expression: BSD-3-Clause
9
+ License-File: LICENSE
10
+ Keywords: alpha,annotation,evaluation,inter-rater reliability,krippendorff,llm-as-a-judge
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
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 :: Information Analysis
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: numpy>=1.26
23
+ Requires-Dist: pandas>=2.1
24
+ Requires-Dist: scipy>=1.15
25
+ Provides-Extra: parquet
26
+ Requires-Dist: pyarrow>=15; extra == 'parquet'
27
+ Provides-Extra: sklearn
28
+ Requires-Dist: scikit-learn>=1.4; extra == 'sklearn'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # reliability-lab
32
+
33
+ **Status: alpha (0.1.0a1).** The API may still change between releases.
34
+
35
+ reliability-lab is a study-design library for measuring the reliability of LLM and human judgments. It is built on Krippendorff's alpha. Alpha implementations already exist in Python. This package adds the workflow around the coefficient:
36
+
37
+ - a validated long-form ratings schema in which every rating has an explicit status (`observed`, `abstained`, `inapplicable`, `parse_error`, `provider_error`);
38
+ - declared measurement semantics: level, value domain, coder definition and design label;
39
+ - white-box output: observed and expected disagreement, coincidence matrices and per-unit contributions;
40
+ - explicit missing-data policies, because LLM failures are rarely unrelated to the item;
41
+ - bootstrap intervals by a scheme you name: whole units or groups resampled (`scheme="units"`), or Krippendorff's KALPHA algorithm (`scheme="kalpha"`);
42
+ - paired comparisons of two configurations on the same units, with the interval on the difference;
43
+ - group-safe train/test splits that work with any scikit-learn splitter, with a saved manifest that reproduces the split;
44
+ - versioned JSON and Parquet reports with provenance.
45
+
46
+ LLM-specific workflows (a trace sidecar, repeatability and order-swap checks) are next on the roadmap ([design document](docs/design/framing-v2.md)).
47
+
48
+ > **Reliability is not validity.** Coders can agree consistently and still be wrong in the same way, for example because of a flawed rubric or a bias they share. When results come from different designs (repeatability, inter-model agreement, human panels), they are reported as different quantities and are never pooled.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install --pre "reliability-lab[sklearn,parquet]"
54
+ ```
55
+
56
+ The extras are optional. `sklearn` provides the splitters, although any object with a scikit-learn style `split` method works; `parquet` enables `to_parquet`. For development, run `uv sync` and then `uv run pytest`.
57
+
58
+ ## Example
59
+
60
+ ```python
61
+ import pandas as pd
62
+ from reliability_lab import CoderSpec, RatingFrame, ReliabilityStudy
63
+
64
+ df = pd.DataFrame(
65
+ {
66
+ "item": ["u1", "u1", "u2", "u2", "u3", "u3"],
67
+ "annotator": ["ann", "bob", "ann", "bob", "ann", "bob"],
68
+ "label": ["pos", "pos", "neg", "pos", "neg", "neg"],
69
+ }
70
+ )
71
+
72
+ ratings = RatingFrame.from_long(df, unit="item", rater="annotator", value="label")
73
+ study = ReliabilityStudy(
74
+ ratings,
75
+ measurement="nominal",
76
+ values=["neg", "pos"],
77
+ coders=CoderSpec(by="rater_id"),
78
+ design="human_panel",
79
+ )
80
+ result = study.alpha()
81
+ print(result.summary())
82
+ ```
83
+
84
+ ## Intervals and comparisons
85
+
86
+ Every interval names its scheme, because the two schemes answer different questions. `scheme` is a required argument.
87
+
88
+ ```python
89
+ boot = result.bootstrap(scheme="units", n_resamples=2000, random_state=7)
90
+ print(boot.summary()) # 95% percentile intervals for every missing-data policy
91
+ ```
92
+
93
+ - `scheme="units"` resamples whole units, and whole groups when a `group` column is declared. It can also resample within declared strata (`strata=True`), and it recomputes the expected disagreement in every replicate. `method="bca"` is available as an experimental option.
94
+ - `scheme="kalpha"` reproduces Krippendorff's KALPHA algorithm. It resamples pairs of values and holds the expected disagreement fixed.
95
+
96
+ To compare two configurations of a judge, for example two prompts, select each one's rows. Both must cover the same units. Every replicate resamples the same units for both, and the interval is on the difference:
97
+
98
+ ```python
99
+ diff = study.compare(
100
+ a={"condition_id": "prompt_v1"},
101
+ b={"condition_id": "prompt_v2"},
102
+ n_resamples=2000,
103
+ random_state=7,
104
+ )
105
+ print(diff.summary()) # alpha_b - alpha_a with a paired interval
106
+ ```
107
+
108
+ ## Splits and reports
109
+
110
+ Teams tune the judge (prompt, few-shot examples, rubric) and should report reliability on units the tuning never saw. So split before tuning, freeze the judge, then estimate on the test partition.
111
+
112
+ When groups are declared, the splitter sees one sample per group (a conversation, say), so groups and repeated LLM calls never cross partitions:
113
+
114
+ ```python
115
+ from sklearn.model_selection import StratifiedGroupKFold
116
+
117
+ dev, test = study.holdout(
118
+ StratifiedGroupKFold(n_splits=4, shuffle=True, random_state=7),
119
+ group_stratum="majority", # needed only when a group's units span several strata
120
+ )
121
+ test.save_manifest("split.json") # the ids of every partition, not just the seed
122
+
123
+ # ... tune and freeze the judge on dev ...
124
+
125
+ result = test.alpha()
126
+ result.to_json("alpha_test.json") # strict JSON with provenance
127
+ result.to_parquet("alpha_test/") # tidy tables
128
+
129
+ dev2, test2 = later_study.apply_manifest("split.json") # same units, new judge runs
130
+ test.manifest.assert_no_leakage(few_shot_units) # raises LeakageError
131
+ ```
132
+
133
+ See [`examples/splits.py`](examples/splits.py) for a full run. Releases are described in [docs/releasing.md](docs/releasing.md).
134
+
135
+ ## Licence
136
+
137
+ BSD-3-Clause. Alpha is implemented independently from Krippendorff's papers. The GPL-licensed `krippendorff` package is used only in the test suite, as a parity check.
@@ -0,0 +1,107 @@
1
+ # reliability-lab
2
+
3
+ **Status: alpha (0.1.0a1).** The API may still change between releases.
4
+
5
+ reliability-lab is a study-design library for measuring the reliability of LLM and human judgments. It is built on Krippendorff's alpha. Alpha implementations already exist in Python. This package adds the workflow around the coefficient:
6
+
7
+ - a validated long-form ratings schema in which every rating has an explicit status (`observed`, `abstained`, `inapplicable`, `parse_error`, `provider_error`);
8
+ - declared measurement semantics: level, value domain, coder definition and design label;
9
+ - white-box output: observed and expected disagreement, coincidence matrices and per-unit contributions;
10
+ - explicit missing-data policies, because LLM failures are rarely unrelated to the item;
11
+ - bootstrap intervals by a scheme you name: whole units or groups resampled (`scheme="units"`), or Krippendorff's KALPHA algorithm (`scheme="kalpha"`);
12
+ - paired comparisons of two configurations on the same units, with the interval on the difference;
13
+ - group-safe train/test splits that work with any scikit-learn splitter, with a saved manifest that reproduces the split;
14
+ - versioned JSON and Parquet reports with provenance.
15
+
16
+ LLM-specific workflows (a trace sidecar, repeatability and order-swap checks) are next on the roadmap ([design document](docs/design/framing-v2.md)).
17
+
18
+ > **Reliability is not validity.** Coders can agree consistently and still be wrong in the same way, for example because of a flawed rubric or a bias they share. When results come from different designs (repeatability, inter-model agreement, human panels), they are reported as different quantities and are never pooled.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install --pre "reliability-lab[sklearn,parquet]"
24
+ ```
25
+
26
+ The extras are optional. `sklearn` provides the splitters, although any object with a scikit-learn style `split` method works; `parquet` enables `to_parquet`. For development, run `uv sync` and then `uv run pytest`.
27
+
28
+ ## Example
29
+
30
+ ```python
31
+ import pandas as pd
32
+ from reliability_lab import CoderSpec, RatingFrame, ReliabilityStudy
33
+
34
+ df = pd.DataFrame(
35
+ {
36
+ "item": ["u1", "u1", "u2", "u2", "u3", "u3"],
37
+ "annotator": ["ann", "bob", "ann", "bob", "ann", "bob"],
38
+ "label": ["pos", "pos", "neg", "pos", "neg", "neg"],
39
+ }
40
+ )
41
+
42
+ ratings = RatingFrame.from_long(df, unit="item", rater="annotator", value="label")
43
+ study = ReliabilityStudy(
44
+ ratings,
45
+ measurement="nominal",
46
+ values=["neg", "pos"],
47
+ coders=CoderSpec(by="rater_id"),
48
+ design="human_panel",
49
+ )
50
+ result = study.alpha()
51
+ print(result.summary())
52
+ ```
53
+
54
+ ## Intervals and comparisons
55
+
56
+ Every interval names its scheme, because the two schemes answer different questions. `scheme` is a required argument.
57
+
58
+ ```python
59
+ boot = result.bootstrap(scheme="units", n_resamples=2000, random_state=7)
60
+ print(boot.summary()) # 95% percentile intervals for every missing-data policy
61
+ ```
62
+
63
+ - `scheme="units"` resamples whole units, and whole groups when a `group` column is declared. It can also resample within declared strata (`strata=True`), and it recomputes the expected disagreement in every replicate. `method="bca"` is available as an experimental option.
64
+ - `scheme="kalpha"` reproduces Krippendorff's KALPHA algorithm. It resamples pairs of values and holds the expected disagreement fixed.
65
+
66
+ To compare two configurations of a judge, for example two prompts, select each one's rows. Both must cover the same units. Every replicate resamples the same units for both, and the interval is on the difference:
67
+
68
+ ```python
69
+ diff = study.compare(
70
+ a={"condition_id": "prompt_v1"},
71
+ b={"condition_id": "prompt_v2"},
72
+ n_resamples=2000,
73
+ random_state=7,
74
+ )
75
+ print(diff.summary()) # alpha_b - alpha_a with a paired interval
76
+ ```
77
+
78
+ ## Splits and reports
79
+
80
+ Teams tune the judge (prompt, few-shot examples, rubric) and should report reliability on units the tuning never saw. So split before tuning, freeze the judge, then estimate on the test partition.
81
+
82
+ When groups are declared, the splitter sees one sample per group (a conversation, say), so groups and repeated LLM calls never cross partitions:
83
+
84
+ ```python
85
+ from sklearn.model_selection import StratifiedGroupKFold
86
+
87
+ dev, test = study.holdout(
88
+ StratifiedGroupKFold(n_splits=4, shuffle=True, random_state=7),
89
+ group_stratum="majority", # needed only when a group's units span several strata
90
+ )
91
+ test.save_manifest("split.json") # the ids of every partition, not just the seed
92
+
93
+ # ... tune and freeze the judge on dev ...
94
+
95
+ result = test.alpha()
96
+ result.to_json("alpha_test.json") # strict JSON with provenance
97
+ result.to_parquet("alpha_test/") # tidy tables
98
+
99
+ dev2, test2 = later_study.apply_manifest("split.json") # same units, new judge runs
100
+ test.manifest.assert_no_leakage(few_shot_units) # raises LeakageError
101
+ ```
102
+
103
+ See [`examples/splits.py`](examples/splits.py) for a full run. Releases are described in [docs/releasing.md](docs/releasing.md).
104
+
105
+ ## Licence
106
+
107
+ BSD-3-Clause. Alpha is implemented independently from Krippendorff's papers. The GPL-licensed `krippendorff` package is used only in the test suite, as a parity check.