eval-toolkit 0.38.0__tar.gz → 0.40.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.
Files changed (160) hide show
  1. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/CHANGELOG.md +98 -0
  2. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/PKG-INFO +1 -1
  3. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/__init__.py +4 -0
  4. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/_parallel.py +13 -0
  5. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/_version.py +1 -1
  6. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/calibration.py +159 -0
  7. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/leakage.py +8 -1
  8. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/metrics.py +85 -1
  9. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/public_api/snapshot.json +26 -1
  10. eval_toolkit-0.40.0/tests/test_calibration_binary_adapters.py +203 -0
  11. eval_toolkit-0.40.0/tests/test_is_metric_defined_for_slice.py +109 -0
  12. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/.gitignore +0 -0
  13. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/LICENSE +0 -0
  14. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/README.md +0 -0
  15. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/STYLE.md +0 -0
  16. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/archive/README.md +0 -0
  17. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/README.md +0 -0
  18. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/datasets/README.md +0 -0
  19. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/papers/data-integrity/README.md +0 -0
  20. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/papers/eval-ecosystem/README.md +0 -0
  21. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/papers/inference/README.md +0 -0
  22. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/papers/prompt-injection/README.md +0 -0
  23. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/source/methodology/README.md +0 -0
  24. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/pyproject.toml +0 -0
  25. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/__main__.py +0 -0
  26. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/_deprecated.py +0 -0
  27. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/analysis.py +0 -0
  28. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/artifacts.py +0 -0
  29. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/bootstrap.py +0 -0
  30. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/claims.py +0 -0
  31. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/config.py +0 -0
  32. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/docs.py +0 -0
  33. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/embeddings.py +0 -0
  34. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/evidence.py +0 -0
  35. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/harness.py +0 -0
  36. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/loaders.py +0 -0
  37. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/manifest.py +0 -0
  38. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/operating_points.py +0 -0
  39. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/paths.py +0 -0
  40. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/plotting.py +0 -0
  41. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/protocols.py +0 -0
  42. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/provenance.py +0 -0
  43. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/py.typed +0 -0
  44. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/manifest.v1.json +0 -0
  45. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/manifest.v2.json +0 -0
  46. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/manifest.v3.json +0 -0
  47. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/results.v1.json +0 -0
  48. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/results_full.v1.json +0 -0
  49. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/seeds.py +0 -0
  50. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/splits.py +0 -0
  51. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/text_dedup.py +0 -0
  52. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/thresholds.py +0 -0
  53. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_bootstrap_distribution.png +0 -0
  54. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_confusion_matrix_grid.png +0 -0
  55. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_lift_ci.png +0 -0
  56. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_metric_bars.png +0 -0
  57. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_pareto_frontier.png +0 -0
  58. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_pr_curve.png +0 -0
  59. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_reliability_diagram.png +0 -0
  60. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_roc_curve.png +0 -0
  61. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_score_histograms.png +0 -0
  62. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_slice_metric_heatmap.png +0 -0
  63. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/benchmarks/__init__.py +0 -0
  64. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/benchmarks/test_kernel_benchmarks.py +0 -0
  65. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/conftest.py +0 -0
  66. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/bootstrap_ci/cases.json +0 -0
  67. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/data/dedup_holdout.jsonl +0 -0
  68. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/data/dedup_holdout_expected.json +0 -0
  69. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/data/dedup_holdout_provenance.md +0 -0
  70. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/docs/expected.md +0 -0
  71. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/docs/input.md +0 -0
  72. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/docs/metrics.json +0 -0
  73. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/test_dedup_holdout_calibration.py +0 -0
  74. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/strategies.py +0 -0
  75. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_analysis.py +0 -0
  76. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_artifacts.py +0 -0
  77. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_block_bootstrap_on_folds.py +0 -0
  78. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_calibration_mc.py +0 -0
  79. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_edge_cases.py +0 -0
  80. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_golden.py +0 -0
  81. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_njobs.py +0 -0
  82. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_props.py +0 -0
  83. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_research_grounded.py +0 -0
  84. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_unit.py +0 -0
  85. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_bootstrap_chain.py +0 -0
  86. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_determinism.py +0 -0
  87. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_optimization_failures.py +0 -0
  88. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_props.py +0 -0
  89. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_research_grounded.py +0 -0
  90. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_unit.py +0 -0
  91. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_claims.py +0 -0
  92. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_claims_coverage.py +0 -0
  93. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_claims_props.py +0 -0
  94. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_cli.py +0 -0
  95. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_config.py +0 -0
  96. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_bootstrap.py +0 -0
  97. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_calibration.py +0 -0
  98. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_harness.py +0 -0
  99. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_metrics.py +0 -0
  100. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_plotting.py +0 -0
  101. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_dedup_split_leakage_chain.py +0 -0
  102. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_deprecations.py +0 -0
  103. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_docs_golden.py +0 -0
  104. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_docs_props.py +0 -0
  105. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_embeddings.py +0 -0
  106. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_evidence_validators.py +0 -0
  107. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_edge_cases.py +0 -0
  108. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_fault_injection.py +0 -0
  109. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_folded.py +0 -0
  110. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_internals.py +0 -0
  111. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_metric_options.py +0 -0
  112. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_parallelism.py +0 -0
  113. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_smoke.py +0 -0
  114. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_import_boundaries.py +0 -0
  115. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_leakage.py +0 -0
  116. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_leakage_error_paths.py +0 -0
  117. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_leakage_props.py +0 -0
  118. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_loaders.py +0 -0
  119. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_loaders_coverage.py +0 -0
  120. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_loaders_props.py +0 -0
  121. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_logging.py +0 -0
  122. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_manifest.py +0 -0
  123. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_manifest_contamination_round_trip.py +0 -0
  124. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_manifest_props.py +0 -0
  125. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_manifest_validation.py +0 -0
  126. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_metrics_props.py +0 -0
  127. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_metrics_stratified_subsets.py +0 -0
  128. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_metrics_unit.py +0 -0
  129. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_misc_coverage.py +0 -0
  130. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_numeric_edge_cases.py +0 -0
  131. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_operating_points.py +0 -0
  132. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_operating_points_props.py +0 -0
  133. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_parallel.py +0 -0
  134. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_paths.py +0 -0
  135. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_pipeline_e2e.py +0 -0
  136. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_plotting_edge.py +0 -0
  137. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_plotting_smoke.py +0 -0
  138. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_plotting_visual.py +0 -0
  139. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_protocol_conformance.py +0 -0
  140. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_provenance.py +0 -0
  141. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_public_api.py +0 -0
  142. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_recall_at_fpr.py +0 -0
  143. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_reference_equivalence.py +0 -0
  144. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_reproducibility_integration.py +0 -0
  145. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_schemas.py +0 -0
  146. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_seeds.py +0 -0
  147. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_splits.py +0 -0
  148. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_splits_leakage_integration.py +0 -0
  149. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_splits_props.py +0 -0
  150. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_text_dedup.py +0 -0
  151. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_text_dedup_coverage.py +0 -0
  152. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_text_dedup_props.py +0 -0
  153. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_text_dedup_strategies.py +0 -0
  154. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds.py +0 -0
  155. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds_constant_score.py +0 -0
  156. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds_coverage.py +0 -0
  157. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds_props.py +0 -0
  158. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds_research_grounded.py +0 -0
  159. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_tokenization_leakage_check.py +0 -0
  160. {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_v09_contracts.py +0 -0
@@ -7,6 +7,104 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.40.0] — 2026-05-18 — fit_platt_binary + fit_beta_binary (closes #43)
11
+
12
+ Completes the binary scalar-prob calibrator family started in v0.35.0
13
+ with `fit_temperature_binary` (#28). Consumers running the canonical
14
+ 4-calibrator audit battery (temperature + isotonic + Platt + Beta) now
15
+ have library-first primitives for all four, eliminating ~100 LOC of
16
+ hand-roll across the calibration battery pattern.
17
+
18
+ ### Added
19
+
20
+ - **`eval_toolkit.fit_platt_binary(y_true, y_score)`** — binary-
21
+ probability adapter for `fit_platt_calibrator`. Returns
22
+ `((a, b), apply)` where `(a, b)` are the fitted Platt slope and
23
+ intercept and `apply: (n,) -> (n,)` is the scalar-in / scalar-out
24
+ callable that maps probabilities through `σ(a·s + b)`. Trivially
25
+ wraps `fit_platt_calibrator`; exposes `(a, b)` explicitly so
26
+ consumers can log the parameters in a `RunManifest` without
27
+ introspecting the `PlattFit` dataclass.
28
+ - **`eval_toolkit.fit_beta_binary(y_true, y_score)`** — 3-parameter
29
+ Beta calibration per Kull et al. 2017. Returns `((a, b, c), apply)`
30
+ where `(a, b, c)` are the coefficients on `log(s)`, `log(1-s)`, and
31
+ the intercept respectively. Implemented directly (rather than
32
+ wrapping `fit_beta_calibrator` which returns only the callable) so
33
+ the fitted coefficients are captured alongside the apply function.
34
+ - Both functions: 5 doctest examples each (run in CI via
35
+ `.doctest-modules`) + 15 unit tests covering shape contracts, param
36
+ exposure, parity with the underlying calibrators, single-class
37
+ rejection, and a family-consistency test verifying all 3 binary
38
+ adapters (temperature, Platt, Beta) satisfy the same
39
+ `(params_tuple, apply)` shape.
40
+
41
+ ### Why mirror `fit_temperature_binary`'s tuple shape
42
+
43
+ #43's body proposed `Callable[[NDArray], NDArray]` as the return type
44
+ (just the apply). We landed on `tuple[params, apply]` instead —
45
+ matches the existing v0.35.0 contract and gives consumers the params
46
+ they need for audit-battery `RunManifest` entries. Consumers wanting
47
+ just the apply can use `_, apply = fit_platt_binary(...)`.
48
+
49
+ ### Protocol stability
50
+
51
+ No Protocol shape changes. Per v0.39.0's roadmap Gate 2 update, v1.0
52
+ requires ≥2 minors without Protocol edits — v0.40.0 is minor 1 of 2
53
+ toward that target.
54
+
55
+ ## [0.39.0] — 2026-05-18 — consumer-feedback batch (closes #39, #40, #41)
56
+
57
+ Three issues lifted from `prompt-injection-detection-prototype v1.0.0`
58
+ consumer-side workarounds into upstream toolkit primitives + docs.
59
+ All small scope; the consumer had filed each with a "Rationale for
60
+ upstream landing" section per the *"Working around a library
61
+ limitation without filing an upstream issue"* anti-pattern discipline.
62
+
63
+ ### Added
64
+
65
+ - **`eval_toolkit.metrics.is_metric_defined_for_slice`** + module-level
66
+ constant **`SINGLE_CLASS_INCOMPATIBLE_METRICS`** (`frozenset({"auroc",
67
+ "auprc"})`). AUROC and AUPRC are mathematically undefined on
68
+ single-class slices — ranking metrics require both classes. The
69
+ primitive takes ``is_single_class: bool`` (caller computes from the
70
+ slice's class distribution) and returns whether the metric is
71
+ defined. Use at the per-cell layer to filter `(metric, slice)`
72
+ combinations BEFORE bootstrap so degenerate values never pollute
73
+ downstream artifacts. Default incompatible set overridable per call.
74
+ Closes #39.
75
+
76
+ ### Changed
77
+
78
+ - **`LeakageCheck.name` Protocol member redeclared as `@property`**
79
+ (was `name: str` class-level annotation). Strictly contract-
80
+ tightening: existing consumers who *read* `check.name` keep working;
81
+ this fixes the `mypy --strict` rejection of
82
+ `list[LeakageCheck] = [CrossSplitLeakageCheck()]` that surfaced when
83
+ consumers built lists of frozen-dataclass leakage checks. Runtime
84
+ `isinstance(check, LeakageCheck)` was always fine — only mypy strict
85
+ was unhappy with read-only-vs-settable Protocol semantics (PEP 544).
86
+ Closes #40.
87
+
88
+ ### Documentation
89
+
90
+ - **`docs/source/methodology/parallelism.md` §"Memory model:
91
+ worker-copy semantics"** — new section documenting joblib loky's
92
+ pickle-based worker IPC and its memory implications for DataFrame-
93
+ bearing specs (`n_jobs × spec_size` resident memory; the bug that
94
+ OOM-killed a 128-core BCa bootstrap sweep on prompt-injection-
95
+ detection-prototype's marginal-CI regen). Worked example of the
96
+ file-path-shared-state pattern + recommended ceiling formula
97
+ (`min(N, available_RAM_GB / spec_size_GB)`). Cross-referenced from
98
+ the `parallel_map` docstring in `_parallel.py`. Closes #41.
99
+
100
+ ### Why bundled
101
+
102
+ Three small consumer-feedback items from the same downstream project;
103
+ ship as one minor rather than three patches. Pattern matches the
104
+ v0.37.0 "TokenizationLeakageCheck + per-module floors" bundling —
105
+ small consumer-driven fixes consolidated to keep release-prep
106
+ overhead per item low.
107
+
10
108
  ## [0.38.0] — 2026-05-18 — executable examples (myst-nb migration)
11
109
 
12
110
  Docs-only minor. Migrates the 14 walkthrough pages in
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eval-toolkit
3
- Version: 0.38.0
3
+ Version: 0.40.0
4
4
  Summary: Reusable evaluation contracts for binary classification: metrics, bootstrap CIs, calibration, artifacts, and evidence gates.
5
5
  Project-URL: Homepage, https://github.com/brandon-behring/eval-toolkit
6
6
  Project-URL: Documentation, https://brandon-behring.github.io/eval-toolkit/
@@ -83,8 +83,10 @@ _EXPORTS: dict[str, str] = {
83
83
  "DEFAULT_STRATEGY": "eval_toolkit.calibration",
84
84
  "CostMatrix": "eval_toolkit.calibration",
85
85
  "bayes_optimal_threshold": "eval_toolkit.calibration",
86
+ "fit_beta_binary": "eval_toolkit.calibration",
86
87
  "fit_beta_calibrator": "eval_toolkit.calibration",
87
88
  "fit_isotonic_calibrator": "eval_toolkit.calibration",
89
+ "fit_platt_binary": "eval_toolkit.calibration",
88
90
  "fit_platt_calibrator": "eval_toolkit.calibration",
89
91
  "fit_temperature": "eval_toolkit.calibration",
90
92
  "fit_temperature_binary": "eval_toolkit.calibration",
@@ -164,6 +166,7 @@ _EXPORTS: dict[str, str] = {
164
166
  "write_manifest": "eval_toolkit.manifest",
165
167
  # --- metrics ---
166
168
  "DEFAULT_ASSUMED_PRIORS": "eval_toolkit.metrics",
169
+ "SINGLE_CLASS_INCOMPATIBLE_METRICS": "eval_toolkit.metrics",
167
170
  "ThresholdResult": "eval_toolkit.metrics",
168
171
  "brier_decomposition": "eval_toolkit.metrics",
169
172
  "brier_score": "eval_toolkit.metrics",
@@ -173,6 +176,7 @@ _EXPORTS: dict[str, str] = {
173
176
  "expected_calibration_error_l2": "eval_toolkit.metrics",
174
177
  "expected_calibration_error_l2_debiased": "eval_toolkit.metrics",
175
178
  "headline_metrics": "eval_toolkit.metrics",
179
+ "is_metric_defined_for_slice": "eval_toolkit.metrics",
176
180
  "metrics_at_threshold": "eval_toolkit.metrics",
177
181
  "pr_auc": "eval_toolkit.metrics",
178
182
  "precision_at_prior": "eval_toolkit.metrics",
@@ -48,6 +48,19 @@ def parallel_map[T, R](
48
48
  state (use ``np.random.SeedSequence(seed).spawn(n)`` for resample
49
49
  loops so ``n_jobs > 1`` produces identical results to ``n_jobs == 1``
50
50
  for the same seed).
51
+ - **Worker-copy memory model.** Each loky worker receives a full copy
52
+ of every argument bound at ``delayed(fn)(item)`` call time —
53
+ loky's worker IPC is pickle-based, so caller objects duplicate
54
+ rather than alias. For DataFrame-bearing specs this means
55
+ ``n_jobs × spec_size`` resident memory; a 128-core sweep over a
56
+ 30 GB-per-cell BCa bootstrap will OOM-kill before any cell
57
+ finishes. Pass DataFrames via file-path indirection (parquet
58
+ path + reload inside ``fn``) for memory-bounded parallelism, or
59
+ cap ``n_jobs`` at ``min(N, available_RAM_GB / spec_size_GB)``.
60
+ NumPy arrays >~1 MB transparently use joblib's memmap fast path
61
+ and do NOT duplicate. See
62
+ ``methodology/parallelism.md#memory-model-worker-copy-semantics``
63
+ for the shared-state pattern with a worked example.
51
64
 
52
65
  Parameters
53
66
  ----------
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.38.0"
5
+ __version__ = "0.40.0"
@@ -53,8 +53,10 @@ __all__ = [
53
53
  "CostMatrix",
54
54
  "PlattFit",
55
55
  "bayes_optimal_threshold",
56
+ "fit_beta_binary",
56
57
  "fit_beta_calibrator",
57
58
  "fit_isotonic_calibrator",
59
+ "fit_platt_binary",
58
60
  "fit_platt_calibrator",
59
61
  "fit_temperature",
60
62
  "fit_temperature_binary",
@@ -1135,6 +1137,163 @@ def fit_temperature_binary(
1135
1137
  return t_optimal, apply
1136
1138
 
1137
1139
 
1140
+ def fit_platt_binary(
1141
+ y_true: np.ndarray, y_score: np.ndarray
1142
+ ) -> tuple[tuple[float, float], Callable[[np.ndarray], np.ndarray]]:
1143
+ r"""Binary-probability adapter for :func:`fit_platt_calibrator`.
1144
+
1145
+ Mirror of :func:`fit_temperature_binary`: returns ``((a, b), apply)``
1146
+ where ``(a, b)`` are the fitted Platt slope and intercept and
1147
+ ``apply: (n,) -> (n,)`` is a scalar-in / scalar-out callable that
1148
+ maps any input probability array through :math:`\sigma(a \cdot s + b)`.
1149
+
1150
+ Trivially wraps :func:`fit_platt_calibrator` — added in v0.40.0 to
1151
+ provide naming consistency with :func:`fit_temperature_binary` and
1152
+ :func:`fit_beta_binary` for the 4-calibrator audit-battery pattern
1153
+ (temperature, isotonic, Platt, Beta). The ``(a, b)`` tuple is exposed
1154
+ explicitly so consumers can log it in a :class:`RunManifest` without
1155
+ introspecting the :class:`PlattFit` dataclass.
1156
+
1157
+ Parameters
1158
+ ----------
1159
+ y_true : np.ndarray, shape (n,)
1160
+ Binary validation labels in {0, 1}.
1161
+ y_score : np.ndarray, shape (n,)
1162
+ Validation predicted probabilities of class 1, in [0, 1]. Values
1163
+ at the extremes are clipped to ``[1e-7, 1 - 1e-7]`` by the
1164
+ underlying Platt fitter.
1165
+
1166
+ Returns
1167
+ -------
1168
+ tuple
1169
+ ``((a, b), apply)`` — ``a`` is the Platt slope, ``b`` is the
1170
+ intercept, ``apply`` maps probabilities through the fitted
1171
+ sigmoid.
1172
+
1173
+ Raises
1174
+ ------
1175
+ ValueError
1176
+ On shape mismatch, empty input, non-finite scores, or single-class
1177
+ ``y_true`` (propagated from :func:`fit_platt_calibrator`).
1178
+ RuntimeError
1179
+ If the L-BFGS-B optimizer fails to converge.
1180
+
1181
+ Examples
1182
+ --------
1183
+ >>> import numpy as np
1184
+ >>> rng = np.random.default_rng(0)
1185
+ >>> n = 500
1186
+ >>> y_val = rng.binomial(1, 0.3, size=n).astype(int)
1187
+ >>> p_val = np.clip(y_val * 0.6 + rng.normal(0, 0.2, n), 0.01, 0.99)
1188
+ >>> (a, b), apply = fit_platt_binary(y_val, p_val)
1189
+ >>> isinstance(a, float) and isinstance(b, float)
1190
+ True
1191
+ >>> apply(np.array([0.1, 0.5, 0.9])).shape == (3,)
1192
+ True
1193
+
1194
+ See Also
1195
+ --------
1196
+ fit_platt_calibrator : underlying canonical Platt fitter (returns ``PlattFit``).
1197
+ fit_temperature_binary : 1-parameter sibling.
1198
+ fit_beta_binary : 3-parameter sibling.
1199
+ """
1200
+ fit = fit_platt_calibrator(y_true, y_score)
1201
+ return (fit.a, fit.b), fit.transform
1202
+
1203
+
1204
+ def fit_beta_binary(
1205
+ y_true: np.ndarray, y_score: np.ndarray
1206
+ ) -> tuple[tuple[float, float, float], Callable[[np.ndarray], np.ndarray]]:
1207
+ r"""Binary-probability adapter for :func:`fit_beta_calibrator` per Kull et al. 2017.
1208
+
1209
+ Mirror of :func:`fit_temperature_binary`: returns
1210
+ ``((a, b, c), apply)`` where ``(a, b, c)`` are the 3-parameter Beta
1211
+ calibration coefficients (``a`` on :math:`\log s`, ``b`` on
1212
+ :math:`\log(1-s)`, ``c`` intercept) and ``apply`` is the scalar-in /
1213
+ scalar-out callable.
1214
+
1215
+ Added in v0.40.0 for naming consistency with
1216
+ :func:`fit_temperature_binary` and :func:`fit_platt_binary` in the
1217
+ 4-calibrator audit-battery pattern. Implemented directly (rather
1218
+ than wrapping :func:`fit_beta_calibrator`) so the fitted ``(a, b, c)``
1219
+ parameters can be exposed alongside the apply callable — the
1220
+ existing :func:`fit_beta_calibrator` returns only the callable.
1221
+
1222
+ Beta calibration is a strict generalization of Platt scaling
1223
+ (Kull 2017 §3); it empirically dominates Platt on most real-world
1224
+ classifiers at the cost of one extra parameter. Unlike Platt and
1225
+ isotonic, the Beta sigmoid is *not* monotone in the raw score.
1226
+
1227
+ Parameters
1228
+ ----------
1229
+ y_true : np.ndarray, shape (n,)
1230
+ Binary validation labels in {0, 1}.
1231
+ y_score : np.ndarray, shape (n,)
1232
+ Validation predicted probabilities of class 1, in (0, 1). Values
1233
+ at the extremes are clipped to ``[1e-7, 1 - 1e-7]`` so the
1234
+ log-link is finite.
1235
+
1236
+ Returns
1237
+ -------
1238
+ tuple
1239
+ ``((a, b, c), apply)`` — ``(a, b, c)`` are the Beta calibration
1240
+ coefficients; ``apply`` maps probabilities through
1241
+ :math:`\sigma(a \log s + b \log(1-s) + c)`.
1242
+
1243
+ Raises
1244
+ ------
1245
+ ValueError
1246
+ On shape mismatch, empty input, non-finite scores, or single-class
1247
+ ``y_true``.
1248
+
1249
+ Examples
1250
+ --------
1251
+ >>> import numpy as np
1252
+ >>> rng = np.random.default_rng(0)
1253
+ >>> n = 500
1254
+ >>> y_val = rng.binomial(1, 0.3, size=n).astype(int)
1255
+ >>> p_val = np.clip(y_val * 0.6 + rng.normal(0, 0.2, n), 0.01, 0.99)
1256
+ >>> (a, b, c), apply = fit_beta_binary(y_val, p_val)
1257
+ >>> all(isinstance(x, float) for x in (a, b, c))
1258
+ True
1259
+ >>> apply(np.array([0.1, 0.5, 0.9])).shape == (3,)
1260
+ True
1261
+
1262
+ See Also
1263
+ --------
1264
+ fit_beta_calibrator : underlying Beta fitter (returns only the callable).
1265
+ fit_temperature_binary : 1-parameter sibling.
1266
+ fit_platt_binary : 2-parameter sibling.
1267
+
1268
+ References
1269
+ ----------
1270
+ Kull, M., Filho, T. S., & Flach, P. "Beta calibration: a well-founded
1271
+ and easily implemented improvement on logistic calibration for binary
1272
+ classifiers." AISTATS 2017. arXiv:1607.06770.
1273
+ """
1274
+ from sklearn.linear_model import LogisticRegression # noqa: PLC0415
1275
+
1276
+ y_true_arr, y_score_arr = _validate_calibrator_inputs(y_true, y_score)
1277
+ s_clipped = np.clip(y_score_arr, _SCORE_CLIP_LO, _SCORE_CLIP_HI)
1278
+ features = np.column_stack([np.log(s_clipped), np.log(1.0 - s_clipped)])
1279
+ lr = LogisticRegression(C=1e9, solver="lbfgs", max_iter=2000)
1280
+ lr.fit(features, y_true_arr)
1281
+ a = float(lr.coef_[0][0])
1282
+ b = float(lr.coef_[0][1])
1283
+ c = float(lr.intercept_[0])
1284
+
1285
+ def apply(scores: np.ndarray) -> np.ndarray:
1286
+ arr = np.asarray(scores, dtype=float).ravel()
1287
+ if not np.isfinite(arr).all():
1288
+ raise ValueError("scores contains NaN or inf")
1289
+ clipped = np.clip(arr, _SCORE_CLIP_LO, _SCORE_CLIP_HI)
1290
+ z = a * np.log(clipped) + b * np.log(1.0 - clipped) + c
1291
+ out: np.ndarray = (1.0 / (1.0 + np.exp(-z))).astype(float)
1292
+ return out
1293
+
1294
+ return (a, b, c), apply
1295
+
1296
+
1138
1297
  def fit_temperature_oracle(
1139
1298
  y_true: np.ndarray, y_score: np.ndarray
1140
1299
  ) -> tuple[float, Callable[[np.ndarray], np.ndarray]]:
@@ -218,9 +218,16 @@ class LeakageCheck(Protocol):
218
218
  name : str
219
219
  Stable identifier for the check, written into the
220
220
  :class:`LeakageFinding` and used for downstream filtering.
221
+ Read-only (a ``@property`` on the Protocol) so the natural
222
+ ``@dataclass(frozen=True)`` implementation pattern is
223
+ compatible with ``mypy --strict``: a frozen attribute satisfies
224
+ a read-only Protocol member but not a settable one (PEP 544).
221
225
  """
222
226
 
223
- name: str
227
+ @property
228
+ def name(self) -> str: # pragma: no cover
229
+ """Stable check identifier (read-only)."""
230
+ ...
224
231
 
225
232
  def validate(self, splits: Mapping[str, EvalSlice]) -> LeakageFinding: # pragma: no cover
226
233
  """Validate the splits and return one :class:`LeakageFinding`."""
@@ -8,7 +8,7 @@ JSON-serializable values.
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
- from collections.abc import Mapping
11
+ from collections.abc import Collection, Mapping
12
12
  from dataclasses import dataclass
13
13
  from typing import TYPE_CHECKING, Final, Literal, overload
14
14
 
@@ -95,6 +95,7 @@ def _empty_strategy_guard(
95
95
 
96
96
  __all__ = [
97
97
  "DEFAULT_ASSUMED_PRIORS",
98
+ "SINGLE_CLASS_INCOMPATIBLE_METRICS",
98
99
  "ThresholdResult",
99
100
  "brier_decomposition",
100
101
  "brier_score",
@@ -104,6 +105,7 @@ __all__ = [
104
105
  "expected_calibration_error_l2",
105
106
  "expected_calibration_error_l2_debiased",
106
107
  "headline_metrics",
108
+ "is_metric_defined_for_slice",
107
109
  "metrics_at_threshold",
108
110
  "pr_auc",
109
111
  "precision_at_prior",
@@ -115,6 +117,88 @@ __all__ = [
115
117
  "stratified_recall",
116
118
  ]
117
119
 
120
+ SINGLE_CLASS_INCOMPATIBLE_METRICS: Final[frozenset[str]] = frozenset({"auroc", "auprc"})
121
+ """Metrics that are mathematically undefined on single-class slices.
122
+
123
+ AUROC and AUPRC are *ranking* metrics — both require at least one
124
+ positive AND one negative example to produce a well-defined score.
125
+ sklearn's implementations return degenerate ``1.0`` or ``0.0`` (or
126
+ warn) when called on single-class input; those values entering
127
+ bootstrap/CI artifacts produce misleading downstream evidence.
128
+
129
+ Overridable per call to :func:`is_metric_defined_for_slice` via the
130
+ ``incompatible_metrics`` kwarg. Surfaced by v0.39.0 / closes #39.
131
+ """
132
+
133
+
134
+ def is_metric_defined_for_slice(
135
+ metric_name: str,
136
+ *,
137
+ is_single_class: bool,
138
+ incompatible_metrics: Collection[str] = SINGLE_CLASS_INCOMPATIBLE_METRICS,
139
+ ) -> bool:
140
+ """Return ``True`` iff a metric is well-defined for the given class distribution.
141
+
142
+ Use at the per-cell layer to filter ``(metric, slice)`` combinations
143
+ BEFORE bootstrap so degenerate values never pollute the downstream
144
+ artifacts (CI bands, MDE estimates, marginal-effect summaries).
145
+
146
+ Parameters
147
+ ----------
148
+ metric_name : str
149
+ Metric identifier. Case-insensitive. Common values:
150
+ ``"auroc"``, ``"auprc"``, ``"ece"``, ``"brier"``, ``"recall"``,
151
+ ``"precision"``, ``"recall_at_fpr"``.
152
+ is_single_class : bool
153
+ Whether the slice has only one observed class (all-positive
154
+ or all-negative ``y_true``). Caller computes this from the
155
+ slice's label vector — the function does not inspect data.
156
+ incompatible_metrics : Collection[str], optional
157
+ Metric names that are undefined on single-class slices. Default
158
+ :data:`SINGLE_CLASS_INCOMPATIBLE_METRICS` (``{"auroc", "auprc"}``).
159
+ Override to widen (e.g., add ``"recall_at_fpr"`` if your threshold
160
+ selector also requires both classes) or narrow per call. Compared
161
+ case-insensitively.
162
+
163
+ Returns
164
+ -------
165
+ bool
166
+ ``True`` if the metric is well-defined for the slice's class
167
+ distribution; ``False`` if it would be degenerate.
168
+
169
+ Examples
170
+ --------
171
+ >>> is_metric_defined_for_slice("auroc", is_single_class=True)
172
+ False
173
+ >>> is_metric_defined_for_slice("auroc", is_single_class=False)
174
+ True
175
+ >>> is_metric_defined_for_slice("ece", is_single_class=True)
176
+ True
177
+ >>> is_metric_defined_for_slice("AUROC", is_single_class=True)
178
+ False
179
+ >>> is_metric_defined_for_slice(
180
+ ... "recall_at_fpr",
181
+ ... is_single_class=True,
182
+ ... incompatible_metrics=frozenset({"auroc", "auprc", "recall_at_fpr"}),
183
+ ... )
184
+ False
185
+
186
+ Notes
187
+ -----
188
+ The function takes ``is_single_class`` rather than inspecting
189
+ ``y_true`` directly to keep the primitive cheap (no array
190
+ materialisation) and composable with caller-side slice taxonomies
191
+ where the single-class property is known a priori from slice
192
+ semantics (e.g., a benign-only or attack-only split).
193
+
194
+ See Also
195
+ --------
196
+ SINGLE_CLASS_INCOMPATIBLE_METRICS : the default incompatibility set.
197
+ """
198
+ if not is_single_class:
199
+ return True
200
+ return metric_name.lower() not in {m.lower() for m in incompatible_metrics}
201
+
118
202
 
119
203
  @dataclass(frozen=True, slots=True)
120
204
  class ThresholdResult:
@@ -75,6 +75,7 @@
75
75
  "RecallAtFprResult",
76
76
  "RunManifest",
77
77
  "RunResult",
78
+ "SINGLE_CLASS_INCOMPATIBLE_METRICS",
78
79
  "Scorer",
79
80
  "SimilarityAuditFinding",
80
81
  "SimilarityAuditReport",
@@ -133,9 +134,11 @@
133
134
  "fdr_bh_correct",
134
135
  "figure_metadata",
135
136
  "file_sha256",
137
+ "fit_beta_binary",
136
138
  "fit_beta_calibrator",
137
139
  "fit_isotonic_calibrator",
138
140
  "fit_operating_points",
141
+ "fit_platt_binary",
139
142
  "fit_platt_calibrator",
140
143
  "fit_temperature",
141
144
  "fit_temperature_binary",
@@ -144,6 +147,7 @@
144
147
  "frozen_config",
145
148
  "headline_metrics",
146
149
  "headline_present_gate",
150
+ "is_metric_defined_for_slice",
147
151
  "iter_folds_with_pool",
148
152
  "load_prediction_arrays",
149
153
  "low_fpr_feasibility_gate",
@@ -827,6 +831,12 @@
827
831
  "kind": "class",
828
832
  "signature": "(run_id: 'str', git_sha: 'str | None', config: 'dict[str, object]', by_slice: 'dict[str, dict[str, object]]' = <factory>, by_fold: 'dict[str, RunResult]' = <factory>, fold_summary: 'dict[str, dict[str, object]]' = <factory>, claim_report: 'dict[str, object]' = <factory>, prediction_artifacts: 'list[dict[str, object]]' = <factory>, evidence_axes: 'list[dict[str, object]]' = <factory>, pairing_metadata: 'dict[str, object]' = <factory>, aggregate_evidence: 'dict[str, object]' = <factory>, threshold_policy: 'dict[str, object]' = <factory>, schema_version: 'str' = 'v1') -> None"
829
833
  },
834
+ "SINGLE_CLASS_INCOMPATIBLE_METRICS": {
835
+ "doc_first_line": "Build an immutable unordered collection of unique elements.",
836
+ "kind": "value",
837
+ "type": "frozenset",
838
+ "value": "['auprc', 'auroc']"
839
+ },
830
840
  "Scorer": {
831
841
  "bases": [
832
842
  "Protocol"
@@ -1026,7 +1036,7 @@
1026
1036
  "doc_first_line": "str(object='') -> str",
1027
1037
  "kind": "value",
1028
1038
  "type": "str",
1029
- "value": "'0.38.0'"
1039
+ "value": "'0.40.0'"
1030
1040
  },
1031
1041
  "apply_operating_points": {
1032
1042
  "doc_first_line": "Apply fitted thresholds to a mixed-class or single-class target slice.",
@@ -1188,6 +1198,11 @@
1188
1198
  "kind": "function",
1189
1199
  "signature": "(path: 'Path | str', *, strict: 'bool' = False) -> 'str | None'"
1190
1200
  },
1201
+ "fit_beta_binary": {
1202
+ "doc_first_line": "Binary-probability adapter for :func:`fit_beta_calibrator` per Kull et al. 2017.",
1203
+ "kind": "function",
1204
+ "signature": "(y_true: 'np.ndarray', y_score: 'np.ndarray') -> 'tuple[tuple[float, float, float], Callable[[np.ndarray], np.ndarray]]'"
1205
+ },
1191
1206
  "fit_beta_calibrator": {
1192
1207
  "doc_first_line": "Beta calibration per Kull et al. 2017 [#kull]_.",
1193
1208
  "kind": "function",
@@ -1203,6 +1218,11 @@
1203
1218
  "kind": "function",
1204
1219
  "signature": "(y_true: 'np.ndarray', y_score: 'np.ndarray', selectors: 'Sequence[ThresholdSelector]', *, fitted_on_slice: 'str' = '', scorer_name: 'str' = '') -> 'dict[str, FittedOperatingPoint]'"
1205
1220
  },
1221
+ "fit_platt_binary": {
1222
+ "doc_first_line": "Binary-probability adapter for :func:`fit_platt_calibrator`.",
1223
+ "kind": "function",
1224
+ "signature": "(y_true: 'np.ndarray', y_score: 'np.ndarray') -> 'tuple[tuple[float, float], Callable[[np.ndarray], np.ndarray]]'"
1225
+ },
1206
1226
  "fit_platt_calibrator": {
1207
1227
  "doc_first_line": "Platt 1999 [#platt]_ sigmoid scaling with Lin 2007 [#lin]_ Laplace-smoothed targets.",
1208
1228
  "kind": "function",
@@ -1243,6 +1263,11 @@
1243
1263
  "kind": "function",
1244
1264
  "signature": "(path: 'str' = 'headline', *, severity: 'GateSeverity' = 'error') -> 'EvidenceGate'"
1245
1265
  },
1266
+ "is_metric_defined_for_slice": {
1267
+ "doc_first_line": "Return ``True`` iff a metric is well-defined for the given class distribution.",
1268
+ "kind": "function",
1269
+ "signature": "(metric_name: 'str', *, is_single_class: 'bool', incompatible_metrics: 'Collection[str]' = frozenset({'auprc', 'auroc'})) -> 'bool'"
1270
+ },
1246
1271
  "iter_folds_with_pool": {
1247
1272
  "doc_first_line": "Compose a :class:`Splitter` with a :class:`PoolBuilder`.",
1248
1273
  "kind": "function",