eval-toolkit 0.33.1__tar.gz → 0.35.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 (156) hide show
  1. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/CHANGELOG.md +136 -0
  2. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/PKG-INFO +1 -1
  3. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/pyproject.toml +1 -1
  4. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/__init__.py +4 -0
  5. eval_toolkit-0.35.0/src/eval_toolkit/_parallel.py +129 -0
  6. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/_version.py +1 -1
  7. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/bootstrap.py +406 -120
  8. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/calibration.py +97 -0
  9. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/manifest.py +32 -1
  10. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/protocols.py +10 -0
  11. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/thresholds.py +129 -0
  12. eval_toolkit-0.35.0/tests/golden/data/dedup_holdout.jsonl +51 -0
  13. eval_toolkit-0.35.0/tests/golden/data/dedup_holdout_expected.json +44 -0
  14. eval_toolkit-0.35.0/tests/golden/data/dedup_holdout_provenance.md +89 -0
  15. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/golden/public_api/snapshot.json +36 -9
  16. eval_toolkit-0.35.0/tests/golden/test_dedup_holdout_calibration.py +212 -0
  17. eval_toolkit-0.35.0/tests/test_block_bootstrap_on_folds.py +114 -0
  18. eval_toolkit-0.35.0/tests/test_bootstrap_njobs.py +165 -0
  19. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_bootstrap_unit.py +67 -0
  20. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_calibration_unit.py +126 -0
  21. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_manifest.py +50 -0
  22. eval_toolkit-0.35.0/tests/test_parallel.py +150 -0
  23. eval_toolkit-0.35.0/tests/test_recall_at_fpr.py +97 -0
  24. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/.gitignore +0 -0
  25. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/LICENSE +0 -0
  26. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/README.md +0 -0
  27. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/STYLE.md +0 -0
  28. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/docs/archive/README.md +0 -0
  29. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/docs/research/README.md +0 -0
  30. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/docs/research/datasets/README.md +0 -0
  31. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/docs/research/papers/data-integrity/README.md +0 -0
  32. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/docs/research/papers/eval-ecosystem/README.md +0 -0
  33. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/docs/research/papers/inference/README.md +0 -0
  34. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/docs/research/papers/prompt-injection/README.md +0 -0
  35. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/docs/source/methodology/README.md +0 -0
  36. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/__main__.py +0 -0
  37. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/_deprecated.py +0 -0
  38. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/analysis.py +0 -0
  39. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/artifacts.py +0 -0
  40. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/claims.py +0 -0
  41. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/config.py +0 -0
  42. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/docs.py +0 -0
  43. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/embeddings.py +0 -0
  44. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/evidence.py +0 -0
  45. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/harness.py +0 -0
  46. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/leakage.py +0 -0
  47. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/loaders.py +0 -0
  48. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/metrics.py +0 -0
  49. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/operating_points.py +0 -0
  50. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/paths.py +0 -0
  51. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/plotting.py +0 -0
  52. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/provenance.py +0 -0
  53. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/py.typed +0 -0
  54. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/schemas/manifest.v1.json +0 -0
  55. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/schemas/manifest.v2.json +0 -0
  56. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/schemas/manifest.v3.json +0 -0
  57. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/schemas/results.v1.json +0 -0
  58. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/schemas/results_full.v1.json +0 -0
  59. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/seeds.py +0 -0
  60. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/splits.py +0 -0
  61. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/src/eval_toolkit/text_dedup.py +0 -0
  62. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_bootstrap_distribution.png +0 -0
  63. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_confusion_matrix_grid.png +0 -0
  64. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_lift_ci.png +0 -0
  65. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_metric_bars.png +0 -0
  66. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_pareto_frontier.png +0 -0
  67. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_pr_curve.png +0 -0
  68. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_reliability_diagram.png +0 -0
  69. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_roc_curve.png +0 -0
  70. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_score_histograms.png +0 -0
  71. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/baseline/test_plotting_visual/plot_slice_metric_heatmap.png +0 -0
  72. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/benchmarks/__init__.py +0 -0
  73. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/benchmarks/test_kernel_benchmarks.py +0 -0
  74. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/conftest.py +0 -0
  75. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/golden/bootstrap_ci/cases.json +0 -0
  76. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/golden/docs/expected.md +0 -0
  77. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/golden/docs/input.md +0 -0
  78. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/golden/docs/metrics.json +0 -0
  79. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/strategies.py +0 -0
  80. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_analysis.py +0 -0
  81. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_artifacts.py +0 -0
  82. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_bootstrap_calibration_mc.py +0 -0
  83. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_bootstrap_edge_cases.py +0 -0
  84. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_bootstrap_golden.py +0 -0
  85. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_bootstrap_props.py +0 -0
  86. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_bootstrap_research_grounded.py +0 -0
  87. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_calibration_bootstrap_chain.py +0 -0
  88. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_calibration_determinism.py +0 -0
  89. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_calibration_optimization_failures.py +0 -0
  90. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_calibration_props.py +0 -0
  91. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_calibration_research_grounded.py +0 -0
  92. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_claims.py +0 -0
  93. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_claims_coverage.py +0 -0
  94. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_claims_props.py +0 -0
  95. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_cli.py +0 -0
  96. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_config.py +0 -0
  97. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_coverage_bootstrap.py +0 -0
  98. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_coverage_calibration.py +0 -0
  99. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_coverage_harness.py +0 -0
  100. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_coverage_metrics.py +0 -0
  101. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_coverage_plotting.py +0 -0
  102. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_dedup_split_leakage_chain.py +0 -0
  103. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_deprecations.py +0 -0
  104. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_docs_golden.py +0 -0
  105. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_docs_props.py +0 -0
  106. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_embeddings.py +0 -0
  107. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_evidence_validators.py +0 -0
  108. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_harness_edge_cases.py +0 -0
  109. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_harness_fault_injection.py +0 -0
  110. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_harness_folded.py +0 -0
  111. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_harness_internals.py +0 -0
  112. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_harness_metric_options.py +0 -0
  113. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_harness_smoke.py +0 -0
  114. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_import_boundaries.py +0 -0
  115. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_leakage.py +0 -0
  116. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_leakage_error_paths.py +0 -0
  117. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_leakage_props.py +0 -0
  118. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_loaders.py +0 -0
  119. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_loaders_coverage.py +0 -0
  120. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_loaders_props.py +0 -0
  121. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_logging.py +0 -0
  122. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_manifest_contamination_round_trip.py +0 -0
  123. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_manifest_props.py +0 -0
  124. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_manifest_validation.py +0 -0
  125. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_metrics_props.py +0 -0
  126. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_metrics_stratified_subsets.py +0 -0
  127. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_metrics_unit.py +0 -0
  128. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_misc_coverage.py +0 -0
  129. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_numeric_edge_cases.py +0 -0
  130. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_operating_points.py +0 -0
  131. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_operating_points_props.py +0 -0
  132. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_paths.py +0 -0
  133. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_pipeline_e2e.py +0 -0
  134. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_plotting_edge.py +0 -0
  135. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_plotting_smoke.py +0 -0
  136. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_plotting_visual.py +0 -0
  137. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_protocol_conformance.py +0 -0
  138. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_provenance.py +0 -0
  139. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_public_api.py +0 -0
  140. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_reference_equivalence.py +0 -0
  141. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_reproducibility_integration.py +0 -0
  142. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_schemas.py +0 -0
  143. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_seeds.py +0 -0
  144. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_splits.py +0 -0
  145. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_splits_leakage_integration.py +0 -0
  146. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_splits_props.py +0 -0
  147. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_text_dedup.py +0 -0
  148. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_text_dedup_coverage.py +0 -0
  149. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_text_dedup_props.py +0 -0
  150. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_text_dedup_strategies.py +0 -0
  151. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_thresholds.py +0 -0
  152. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_thresholds_constant_score.py +0 -0
  153. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_thresholds_coverage.py +0 -0
  154. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_thresholds_props.py +0 -0
  155. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_thresholds_research_grounded.py +0 -0
  156. {eval_toolkit-0.33.1 → eval_toolkit-0.35.0}/tests/test_v09_contracts.py +0 -0
@@ -7,6 +7,142 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.35.0] — 2026-05-18 — `fit_temperature_binary` + Scorer picklability ADR
11
+
12
+ Small, additive release. Adds a binary-classification calibration helper
13
+ that lets consumers drop the ~50 LOC scalar-proba adapter many were
14
+ carrying, plus a design ADR that unblocks the v0.36 harness / operating-
15
+ point parallelization work (#29, #30) without re-litigating picklability.
16
+
17
+ ### Added
18
+
19
+ - `eval_toolkit.fit_temperature_binary(y_true, y_score)` — scalar-proba
20
+ adapter for the multi-class `fit_temperature` fitter. Converts `(n,)`
21
+ probabilities of class 1 to a 2-column logit array via clipped logit
22
+ (`[0, logit(p)]` so softmax row 1 reproduces `p`), delegates to the
23
+ deployment-quality fitter, and returns `(T_opt, apply)` where
24
+ `apply: (n,) -> (n,)` does scalar-in / scalar-out T-scaling. Unlike
25
+ `fit_temperature_oracle`, no warning — the contract assumes val / test
26
+ separation (deployment-quality calibration, not fit-on-test). Closes
27
+ #28.
28
+
29
+ ### Documentation
30
+
31
+ - `docs/source/methodology/parallelism.md` — new `## Scorer picklability`
32
+ sub-section documenting the Scorer protocol's picklability contract
33
+ for `n_jobs > 1` usage. Includes worked picklable / broken-closure /
34
+ fix examples plus a list of common non-picklable patterns to watch for
35
+ in user-supplied Scorers (closures, lambdas on instances, local-scope
36
+ classes, attributes holding live sockets / file handles). Anchors on
37
+ the existing v0.34.0 `parallel_map` pickle sniff + `TypeError`
38
+ channel — no new exception class. Unblocks v0.36 implementation of
39
+ #29 and #30.
40
+ - `eval_toolkit.protocols.Scorer` docstring — Notes block pointing at
41
+ the new methodology section.
42
+
43
+ ## [0.34.0] — 2026-05-17 — Phase 4 stats unblockers + unified parallelism + cookbook (BREAKING)
44
+
45
+ Closes all 7 open backlog issues in one consumer-closing release. Also
46
+ lands the toolkit's first unified parallelism story: a shared internal
47
+ `parallel_map` helper + `n_jobs` kwarg on all 5 public bootstrap
48
+ functions. Future iterations will mechanically extend the same helper to
49
+ harness + operating-points (follow-up issues filed).
50
+
51
+ ### Breaking changes
52
+
53
+ - **`eval_toolkit.bootstrap.mde_from_ci`**: parameter renamed from
54
+ `paired` to `ci` and type widened to `BootstrapCI | PairedBootstrapCI`
55
+ (was `PairedBootstrapCI`-only). Positional callers unaffected; keyword
56
+ callers must update:
57
+ ```python
58
+ mde_from_ci(paired=x) # v0.33.x and earlier
59
+ mde_from_ci(ci=x) # v0.34.0+
60
+ mde_from_ci(x) # positional form, unchanged
61
+ ```
62
+ This is a one-time exception to the repo's 2-minor-version deprecation
63
+ warning policy (justification + criteria recorded in
64
+ [`docs/source/DEPRECATION.md`](docs/source/DEPRECATION.md#one-time-exceptions-to-the-2-minor-version-warning-policy)).
65
+ Notification issues filed on the 2 known toolkit consumers
66
+ (`prompt-injection-detection-submission`, `post-transformers`). Audit
67
+ confirms both use positional form — zero actual breakage in practice.
68
+
69
+ ### Added
70
+
71
+ - `eval_toolkit.bootstrap.block_bootstrap_on_folds` — CV-aware sibling
72
+ to `cv_clt_ci`; resamples K folds with replacement; returns
73
+ `BootstrapCI(method="block_bootstrap")`. The A-008 sensitivity-check
74
+ pattern (block-bootstrap halfwidth / cv_clt halfwidth > 1.5 flags
75
+ LODO non-exchangeability) is the prototypical use. Closes #21.
76
+ - `eval_toolkit.RecallAtFprResult` (frozen dataclass) +
77
+ `eval_toolkit.recall_at_fpr(y_true, y_score, target_fpr)` — one-shot
78
+ recall + actual_fpr + FP/TN at the smallest threshold meeting FPR ≤
79
+ target. Use `.to_dict()` for JSON / pandas-row integration. Closes #9.
80
+ - New optional `n_jobs: int = 1` kwarg on 5 bootstrap functions:
81
+ `bootstrap_ci`, `paired_bootstrap_diff`, `paired_bootstrap_ece_diff`,
82
+ `paired_bootstrap_op_point_diff`, `paired_mde` (via `_bootstrap_t_ci`
83
+ internal helper). Backed by a new internal `_parallel.parallel_map`
84
+ helper (joblib loky; not exported). `n_jobs > 1` reproduces `n_jobs=1`
85
+ result bit-for-bit for the same seed (via
86
+ `np.random.SeedSequence.spawn`). Lambda metrics rejected at call time
87
+ with helpful `TypeError`. `n_jobs > os.cpu_count()` is auto-capped
88
+ with WARNING log; `n_jobs=0` raises `ValueError`. Closes #17.
89
+ - 6 new pages in `docs/source/examples/`:
90
+ - **Cookbook** (closes #19): `nested_seed_split.md`,
91
+ `callable_embedder_dedup.md`, `cross_corpus_contamination_scan.md`.
92
+ - **Plotting walkthroughs**: `plot_roc_curve_walkthrough.md`,
93
+ `plot_pareto_frontier_walkthrough.md`,
94
+ `plot_slice_metric_heatmap_walkthrough.md` (backfills the v0.33.0
95
+ docs gap).
96
+ - `docs/source/methodology/parallelism.md` — design rationale + caller
97
+ contract for the toolkit-wide parallelism story. Documents the 6
98
+ design principles (single backend, single helper, opt-in per-fn,
99
+ default sequential, reproducibility via SeedSequence, picklability
100
+ surface) and the checklist for adding `n_jobs` to a new function.
101
+
102
+ ### Changed
103
+
104
+ - `eval_toolkit.bootstrap.mde_from_ci` now accepts
105
+ `BootstrapCI | PairedBootstrapCI` (was paired-only). See **Breaking
106
+ changes** above. Closes #20.
107
+ - `eval_toolkit.build_manifest` gains `config_path: Path | str | None`
108
+ kwarg; when supplied, `config_hash` is computed as
109
+ `sha256(Path(config_path).read_bytes()).hexdigest()` — capturing the
110
+ exact YAML file bytes including comments + key ordering (which the
111
+ default canonical-JSON path strips during parse). Default behavior
112
+ preserved when `config_path is None`. Closes #10.
113
+
114
+ ### Internal
115
+
116
+ - New `src/eval_toolkit/_parallel.py` (internal; not exported) — single
117
+ source of truth for parallelism. Future per-function `n_jobs`
118
+ additions will reuse this helper. The toolkit's first INFO-level
119
+ log site is here (once-per-process guidance log when `n_jobs=1` AND
120
+ iteration count ≥ 1000). New `tests/test_parallel.py` covers smart-
121
+ default semantics + reproducibility contract.
122
+ - New golden test `tests/golden/test_dedup_holdout_calibration.py`
123
+ exercising 3 deterministic `SimilarityStrategy` variants against a
124
+ migrated 50-pair adversarial fixture at thresholds {0.75, 0.80, 0.85}
125
+ (strict snapshot at `tests/golden/data/dedup_holdout_expected.json`)
126
+ plus an `EmbeddingCosineStrategy` soft-bound check (FPR < 0.5,
127
+ FNR < 0.5 at threshold 0.80) gated by `pytest.importorskip` +
128
+ `@pytest.mark.slow`. Refresh helper at
129
+ `scripts/refresh_dedup_holdout.py`. Closes #18.
130
+ - `CONTRIBUTING.md` + `docs/source/repo-strategy.md` updated with
131
+ explicit "Parallelism" section codifying the new pattern (was an
132
+ implicit anti-pattern before; v0.34.0 codifies the new opt-in design).
133
+ - `docs/source/DEPRECATION.md` extended with a "One-time exceptions"
134
+ section documenting the `mde_from_ci` rename + criteria future
135
+ exceptions must satisfy.
136
+ - RNG-stream note: the 5 wired bootstrap fns now derive per-resample
137
+ seeds via `np.random.SeedSequence(seed).spawn(n_resamples)` instead
138
+ of sequential calls on a single `Generator`. The bootstrap output is
139
+ *statistically equivalent* (both are valid bootstraps) but the exact
140
+ numerical CI bounds for the same caller-supplied `seed` will differ
141
+ slightly from v0.33.x. Existing tests use behavioral assertions
142
+ (`overlaps_zero`, `delta`, etc.) that are robust to the RNG-stream
143
+ change; tests that pin exact CI bounds (e.g., consumer golden tests)
144
+ may need regen on upgrade.
145
+
10
146
  ## [0.33.1] — 2026-05-17 — MiniLM convenience embedder
11
147
 
12
148
  Closes the last open item in the v0.33 milestone (deferred from v0.33.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eval-toolkit
3
- Version: 0.33.1
3
+ Version: 0.35.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/
@@ -164,7 +164,7 @@ warn_no_return = true
164
164
  strict_equality = true
165
165
 
166
166
  [[tool.mypy.overrides]]
167
- module = ["scipy.*", "sklearn.*", "matplotlib.*", "pandas.*", "yaml.*", "sentence_transformers.*"]
167
+ module = ["scipy.*", "sklearn.*", "matplotlib.*", "pandas.*", "yaml.*", "sentence_transformers.*", "joblib.*"]
168
168
  ignore_missing_imports = true
169
169
 
170
170
  [tool.pytest.ini_options]
@@ -62,6 +62,7 @@ _EXPORTS: dict[str, str] = {
62
62
  "PairedBootstrapCI": "eval_toolkit.bootstrap",
63
63
  "ThresholdedMetricFn": "eval_toolkit.bootstrap",
64
64
  "ThresholdFn": "eval_toolkit.bootstrap",
65
+ "block_bootstrap_on_folds": "eval_toolkit.bootstrap",
65
66
  "bonferroni_correct": "eval_toolkit.bootstrap",
66
67
  "bootstrap_ci": "eval_toolkit.bootstrap",
67
68
  "correct_p_values": "eval_toolkit.bootstrap",
@@ -86,6 +87,7 @@ _EXPORTS: dict[str, str] = {
86
87
  "fit_isotonic_calibrator": "eval_toolkit.calibration",
87
88
  "fit_platt_calibrator": "eval_toolkit.calibration",
88
89
  "fit_temperature": "eval_toolkit.calibration",
90
+ "fit_temperature_binary": "eval_toolkit.calibration",
89
91
  "fit_temperature_oracle": "eval_toolkit.calibration",
90
92
  "reliability_curve": "eval_toolkit.calibration",
91
93
  "reliability_diagram_data": "eval_toolkit.calibration",
@@ -252,6 +254,7 @@ _EXPORTS: dict[str, str] = {
252
254
  "CISafeThresholdSelector": "eval_toolkit.thresholds",
253
255
  "CostSensitiveSelector": "eval_toolkit.thresholds",
254
256
  "MaxF1Selector": "eval_toolkit.thresholds",
257
+ "RecallAtFprResult": "eval_toolkit.thresholds",
255
258
  "TargetFPRSelector": "eval_toolkit.thresholds",
256
259
  "TargetPrecisionSelector": "eval_toolkit.thresholds",
257
260
  "TargetRecallSelector": "eval_toolkit.thresholds",
@@ -259,6 +262,7 @@ _EXPORTS: dict[str, str] = {
259
262
  "ThresholdSelector": "eval_toolkit.thresholds",
260
263
  "WilsonInterval": "eval_toolkit.thresholds",
261
264
  "YoudenJSelector": "eval_toolkit.thresholds",
265
+ "recall_at_fpr": "eval_toolkit.thresholds",
262
266
  "select_threshold": "eval_toolkit.thresholds",
263
267
  "wilson_interval": "eval_toolkit.thresholds",
264
268
  }
@@ -0,0 +1,129 @@
1
+ """Toolkit-internal parallel-map helper. Single source of truth for parallelism.
2
+
3
+ This module is *internal* (not exported via ``__all__`` or
4
+ ``__init__._EXPORTS``); future per-function ``n_jobs`` additions across the
5
+ toolkit call into this helper rather than each inventing their own
6
+ parallelism backend.
7
+
8
+ See ``docs/source/methodology/parallelism.md`` for the design rationale +
9
+ caller contract (reproducibility, picklability, smart defaults).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import os
16
+ import pickle
17
+ from collections.abc import Callable, Iterable, Sized
18
+
19
+ _logger = logging.getLogger(__name__)
20
+
21
+ _GUIDANCE_THRESHOLD = 1000
22
+
23
+ _GUIDANCE_EMITTED = False
24
+
25
+
26
+ def parallel_map[T, R](
27
+ fn: Callable[[T], R],
28
+ items: Iterable[T],
29
+ *,
30
+ n_jobs: int = 1,
31
+ description: str = "work",
32
+ ) -> list[R]:
33
+ """Map ``fn`` over ``items``; parallel when ``n_jobs != 1``.
34
+
35
+ Design contract (see ``docs/source/methodology/parallelism.md``):
36
+
37
+ - ``n_jobs == 1`` (default) — pure-Python serial; preserves tracebacks.
38
+ If ``len(items) >= 1000``, emits an INFO log on the first qualifying
39
+ call per Python process suggesting ``n_jobs > 1`` (silent thereafter).
40
+ - ``n_jobs == -1`` — joblib loky with all cores.
41
+ - ``n_jobs > 1`` — joblib loky; values exceeding ``os.cpu_count()``
42
+ are silently capped (with a WARNING log) to avoid CPU-frying.
43
+ - ``n_jobs == 0`` — raises ``ValueError`` (likely a typo for 1 or -1).
44
+ - ``fn`` MUST be picklable when ``n_jobs != 1`` (lambdas and closures
45
+ over local state are rejected at call time with a helpful
46
+ ``TypeError``).
47
+ - Reproducibility: caller is responsible for deterministic per-item
48
+ state (use ``np.random.SeedSequence(seed).spawn(n)`` for resample
49
+ loops so ``n_jobs > 1`` produces identical results to ``n_jobs == 1``
50
+ for the same seed).
51
+
52
+ Parameters
53
+ ----------
54
+ fn : Callable[[T], R]
55
+ Picklable callable to apply to each item. Lambdas and closures
56
+ over local state are rejected when ``n_jobs != 1``.
57
+ items : Iterable[T]
58
+ Work items. Materialised internally; pass any iterable.
59
+ n_jobs : int, optional
60
+ Default 1 (sequential). Set to -1 for all cores, or a positive int.
61
+ ``n_jobs=0`` is rejected (use 1 or -1).
62
+ description : str, optional
63
+ Used in log messages and error messages for context (e.g.,
64
+ ``"paired bootstrap"``).
65
+
66
+ Returns
67
+ -------
68
+ list[R]
69
+ Results in the order of ``items``.
70
+
71
+ Raises
72
+ ------
73
+ ValueError
74
+ If ``n_jobs == 0``.
75
+ TypeError
76
+ If ``n_jobs != 1`` and ``fn`` is not picklable.
77
+
78
+ Examples
79
+ --------
80
+ >>> def square(x): return x * x
81
+ >>> parallel_map(square, [1, 2, 3], n_jobs=1)
82
+ [1, 4, 9]
83
+ """
84
+ if n_jobs == 0:
85
+ raise ValueError(
86
+ f"n_jobs=0 is not allowed for {description}; use 1 (sequential), "
87
+ "-1 (all cores), or a positive integer."
88
+ )
89
+
90
+ items_list = items if isinstance(items, list) else list(items)
91
+ n_items = len(items_list) if isinstance(items_list, Sized) else 0
92
+
93
+ if n_jobs == 1:
94
+ global _GUIDANCE_EMITTED
95
+ if n_items >= _GUIDANCE_THRESHOLD and not _GUIDANCE_EMITTED:
96
+ _logger.info(
97
+ "%s: running %d items sequentially (n_jobs=1). For parallel "
98
+ "speedup set n_jobs > 1 (typical wall-clock 3-5x on 8 cores). "
99
+ "(Shown once per process.)",
100
+ description,
101
+ n_items,
102
+ )
103
+ _GUIDANCE_EMITTED = True
104
+ return [fn(item) for item in items_list]
105
+
106
+ if n_jobs > 0:
107
+ cpu_count = os.cpu_count() or 1
108
+ if n_jobs > cpu_count:
109
+ _logger.warning(
110
+ "%s: capping n_jobs from %d to %d (os.cpu_count()).",
111
+ description,
112
+ n_jobs,
113
+ cpu_count,
114
+ )
115
+ n_jobs = cpu_count
116
+
117
+ try:
118
+ pickle.dumps(fn)
119
+ except (pickle.PicklingError, AttributeError, TypeError) as e:
120
+ raise TypeError(
121
+ f"parallel_map of {description}: callable is not picklable "
122
+ f"(lambdas and closures over local state are not supported "
123
+ f"with n_jobs != 1). Define a named top-level function. "
124
+ f"Underlying error: {e}"
125
+ ) from e
126
+
127
+ from joblib import Parallel, delayed # noqa: PLC0415
128
+
129
+ return list(Parallel(n_jobs=n_jobs, backend="loky")(delayed(fn)(item) for item in items_list))
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.33.1"
5
+ __version__ = "0.35.0"