transport-runtime 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ """Transport Runtime: zero-label deployment for any fitted classifier."""
2
+ from .core import TransportLayer, DeployedModel, DeployedConformal, moment_delta
3
+ from .ot_gap import (cdf_l1_gap, label_free_gap, aux_minmax, aux_uniform,
4
+ aux_model, total_coverage_gap)
5
+ from .channels import discover_score_channels, quantile_channel
6
+ from .clinical import channel_clinical_audit, clinical_threshold_audit, compare_decision_curves, decision_curve
7
+ from .selection import select_transport_channel, select_transport_channel_stability
8
+ from .slope import (
9
+ allocate_labels_for_slope,
10
+ apply_channel_affine,
11
+ channel_affine_repair,
12
+ channel_affine_margin_interval,
13
+ bootstrap_channel_affine,
14
+ ece_from_p,
15
+ evaluate_affine_repair,
16
+ fit_channel_affine,
17
+ margins_from_model,
18
+ slope_threshold_sensitivity,
19
+ summarize_slope_risk,
20
+ within_channel_score_shift,
21
+ )
22
+ from .certify import CertificateRecommendation, evaluate_certificate, recommend_certificate, recommend_certificate_from_audit_pack, summarize_channel_clinical_risk, summarize_slope_uncertainty
23
+ from .audit import audit_pack, save_audit_pack
24
+ from .diff import contract_diff
25
+ from .verify import CheckResult, VerificationReport, verify_contract
26
+ from .compiler import (
27
+ CertificateSpec,
28
+ ChannelSpec,
29
+ DeploymentContract,
30
+ PrevalenceSpec,
31
+ TransportCompiler,
32
+ )
33
+ from .robust import (
34
+ PrevalenceIntervalSet,
35
+ choose_repair,
36
+ deploy_eb,
37
+ deploy_interval,
38
+ empirical_bayes_target,
39
+ estimate_repair_gain,
40
+ interval_regret_bound,
41
+ interval_uniform_variance,
42
+ minimax_target,
43
+ worst_case_channel_bias,
44
+ )
45
+ from .identify import (
46
+ IdentificationResult,
47
+ STATUS_INCONCLUSIVE,
48
+ STATUS_NOT_TRANSPORTABLE_BY_ADJUSTMENT,
49
+ STATUS_TRANSPORTABLE,
50
+ ancestors,
51
+ d_connected,
52
+ descendants,
53
+ intervened_graph,
54
+ is_dag,
55
+ s_admissible,
56
+ transportability_id,
57
+ valid_backdoor_set,
58
+ )
59
+
60
+ __all__ = [
61
+ 'TransportLayer', 'DeployedModel', 'DeployedConformal', 'moment_delta',
62
+ 'ChannelSpec', 'PrevalenceSpec', 'CertificateSpec', 'TransportCompiler', 'DeploymentContract',
63
+ 'quantile_channel', 'discover_score_channels', 'select_transport_channel', 'select_transport_channel_stability',
64
+ 'decision_curve', 'compare_decision_curves', 'clinical_threshold_audit', 'channel_clinical_audit',
65
+ 'margins_from_model', 'within_channel_score_shift', 'summarize_slope_risk', 'slope_threshold_sensitivity',
66
+ 'fit_channel_affine', 'apply_channel_affine', 'channel_affine_repair',
67
+ 'bootstrap_channel_affine', 'channel_affine_margin_interval',
68
+ 'allocate_labels_for_slope',
69
+ 'ece_from_p', 'evaluate_affine_repair',
70
+ 'verify_contract', 'VerificationReport', 'CheckResult',
71
+ 'recommend_certificate', 'recommend_certificate_from_audit_pack', 'CertificateRecommendation', 'evaluate_certificate', 'summarize_slope_uncertainty', 'summarize_channel_clinical_risk',
72
+ 'audit_pack', 'save_audit_pack', 'contract_diff',
73
+ 'cdf_l1_gap', 'label_free_gap', 'aux_minmax', 'aux_uniform',
74
+ 'aux_model', 'total_coverage_gap',
75
+ 'PrevalenceIntervalSet', 'deploy_interval', 'interval_regret_bound',
76
+ 'deploy_eb', 'empirical_bayes_target', 'estimate_repair_gain', 'choose_repair', 'interval_uniform_variance',
77
+ 'minimax_target', 'worst_case_channel_bias',
78
+ 'IdentificationResult', 'transportability_id', 'valid_backdoor_set',
79
+ 's_admissible', 'd_connected', 'intervened_graph', 'is_dag',
80
+ 'ancestors', 'descendants', 'STATUS_TRANSPORTABLE', 'STATUS_INCONCLUSIVE',
81
+ 'STATUS_NOT_TRANSPORTABLE_BY_ADJUSTMENT',
82
+ ]
83
+ __version__ = '0.6.0'
84
+
85
+ from transport_runtime.pipeline import TransportPipeline # noqa: E402,F401
@@ -0,0 +1,210 @@
1
+ """Standardized audit pack for deployment contracts.
2
+
3
+ An audit pack is the single JSON artifact that combines:
4
+ - contract manifest;
5
+ - slope-risk summary;
6
+ - contract verification;
7
+ - labeled-target certificate replay, when labels are available.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+
13
+ import numpy as np
14
+ from scipy.special import expit
15
+
16
+ from .certify import evaluate_certificate
17
+ from .clinical import clinical_threshold_audit, decision_curve
18
+ from .compiler import DeploymentContract
19
+ from .slope import bootstrap_channel_affine, channel_affine_margin_interval, margins_from_model
20
+ from .verify import verify_contract
21
+
22
+
23
+ def _tolist(value):
24
+ return np.asarray(value, dtype=float).tolist()
25
+
26
+
27
+ def _slope_repair_certificate(contract, X, channels, y, boot_kwargs):
28
+ levels = getattr(contract.deployed, "levels_", None)
29
+ if levels is None:
30
+ return None
31
+ f_repaired = margins_from_model(contract.layer.model, X) + np.asarray(levels, dtype=float)[np.asarray(channels, dtype=int)]
32
+ boot = bootstrap_channel_affine(f_repaired, y, channels, **boot_kwargs)
33
+ interval = channel_affine_margin_interval(f_repaired, channels, boot)
34
+ width = interval["upper"] - interval["lower"]
35
+ return {
36
+ "note": "finite-label uncertainty for affine slope repair fitted after level repair; "
37
+ "this is fitting uncertainty, not proof of target calibration",
38
+ "alpha": float(boot["alpha"]),
39
+ "ci_level": float(boot["ci_level"]),
40
+ "n_boot": int(boot["n_boot"]),
41
+ "channel_n": _tolist(boot["channel_n"]),
42
+ "a": _tolist(boot["a"]),
43
+ "b": _tolist(boot["b"]),
44
+ "a_mean": _tolist(boot["a_mean"]),
45
+ "b_mean": _tolist(boot["b_mean"]),
46
+ "a_std": _tolist(boot["a_std"]),
47
+ "b_std": _tolist(boot["b_std"]),
48
+ "a_ci_lower": _tolist(boot["a_ci_lower"]),
49
+ "a_ci_upper": _tolist(boot["a_ci_upper"]),
50
+ "b_ci_lower": _tolist(boot["b_ci_lower"]),
51
+ "b_ci_upper": _tolist(boot["b_ci_upper"]),
52
+ "bootstrap_success_rate": _tolist(boot["bootstrap_success_rate"]),
53
+ "margin_interval_summary": {
54
+ "n": int(len(width)),
55
+ "mean_width": float(np.mean(width)),
56
+ "median_width": float(np.median(width)),
57
+ "q95_width": float(np.quantile(width, 0.95)),
58
+ "max_width": float(np.max(width)),
59
+ },
60
+ }
61
+
62
+
63
+ def _clinical_threshold_audit(contract, X, channels, y, thresholds):
64
+ base_p = expit(margins_from_model(contract.layer.model, X))
65
+ repaired_p = contract.predict_proba(X, channels)
66
+ audit = clinical_threshold_audit(y, base_p, repaired_p, thresholds=thresholds)
67
+ return {
68
+ "note": "threshold-specific net-benefit audit comparing repaired contract output with unrepaired model output; "
69
+ "synthetic or labeled-target audit evidence does not by itself prove clinical effectiveness",
70
+ "thresholds": audit["thresholds"],
71
+ "base_net_benefit": audit["base_net_benefit"],
72
+ "repaired_net_benefit": audit["repaired_net_benefit"],
73
+ "repaired_minus_base": audit["repaired_minus_base"],
74
+ "summary": audit["summary"],
75
+ }
76
+
77
+
78
+ def _channel_clinical_summary(contract, X, channels, y, thresholds):
79
+ thresholds = np.asarray(
80
+ thresholds if thresholds is not None else np.linspace(0.01, 0.99, 99),
81
+ dtype=float,
82
+ )
83
+ base_p = expit(margins_from_model(contract.layer.model, X))
84
+ repaired_p = contract.predict_proba(X, channels)
85
+ y = np.asarray(y, dtype=float)
86
+ channels = np.asarray(channels, dtype=int)
87
+ summaries = []
88
+ for g in np.unique(channels):
89
+ sel = channels == g
90
+ base = decision_curve(y[sel], base_p[sel], thresholds=thresholds)
91
+ repaired = decision_curve(y[sel], repaired_p[sel], thresholds=thresholds)
92
+ gain = np.asarray(repaired["net_benefit"], dtype=float) - np.asarray(base["net_benefit"], dtype=float)
93
+ j = int(np.argmax(gain))
94
+ summaries.append(
95
+ {
96
+ "channel": int(g),
97
+ "channel_n": int(sel.sum()),
98
+ "channel_prevalence": float(np.mean(y[sel])),
99
+ "integrated_gain": float(np.trapz(gain, thresholds)),
100
+ "max_gain": float(gain[j]),
101
+ "threshold_at_max_gain": float(thresholds[j]),
102
+ "worst_gain": float(np.min(gain)),
103
+ "fraction_improved": float(np.mean(gain > 0.0)),
104
+ }
105
+ )
106
+ integrated = np.array([row["integrated_gain"] for row in summaries], dtype=float)
107
+ fragile = summaries[int(np.argmin(integrated))]
108
+ return {
109
+ "note": "channel-level threshold-specific net-benefit summary; negative channel gains can be masked by aggregate averages",
110
+ "thresholds": thresholds.tolist(),
111
+ "channel_summaries": summaries,
112
+ "overall": {
113
+ "n_channels": int(len(summaries)),
114
+ "mean_integrated_gain": float(np.mean(integrated)),
115
+ "min_integrated_gain": float(np.min(integrated)),
116
+ "n_channels_negative_integrated_gain": int(np.sum(integrated < 0.0)),
117
+ "most_fragile_channel": fragile["channel"],
118
+ "most_fragile_integrated_gain": fragile["integrated_gain"],
119
+ "most_fragile_worst_gain": fragile["worst_gain"],
120
+ },
121
+ }
122
+
123
+
124
+ def audit_pack(
125
+ contract: DeploymentContract,
126
+ X=None,
127
+ channels=None,
128
+ y=None,
129
+ slope_boot=False,
130
+ slope_boot_kwargs=None,
131
+ clinical=False,
132
+ clinical_thresholds=None,
133
+ channel_clinical=False,
134
+ channel_clinical_thresholds=None,
135
+ ) -> dict:
136
+ """Build a standardized audit pack for a deployment contract.
137
+
138
+ Set `slope_boot=True` to add a finite-label affine slope-repair uncertainty
139
+ certificate when labeled target data are supplied. The option is disabled by
140
+ default to keep routine audit packs lightweight.
141
+ """
142
+ pack = contract.manifest()
143
+ pack["slope_risk"] = contract.slope_risk
144
+
145
+ if X is not None and channels is not None:
146
+ pack["verification"] = verify_contract(contract, X=X, channels=channels).to_dict()
147
+ else:
148
+ pack["verification"] = None
149
+
150
+ if contract.certificate is not None and X is not None and y is not None and channels is not None:
151
+ pack["certificate_evaluation"] = evaluate_certificate(contract, X, y, channels)
152
+ else:
153
+ pack["certificate_evaluation"] = None
154
+
155
+ if slope_boot:
156
+ if X is None or y is None or channels is None:
157
+ raise ValueError("slope_boot=True requires X, channels, and y")
158
+ kwargs = dict(slope_boot_kwargs or {})
159
+ pack["slope_repair_certificate"] = _slope_repair_certificate(contract, X, channels, y, kwargs)
160
+ else:
161
+ pack["slope_repair_certificate"] = None
162
+
163
+ if clinical:
164
+ if X is None or y is None or channels is None:
165
+ raise ValueError("clinical=True requires X, channels, and y")
166
+ pack["clinical_threshold_audit"] = _clinical_threshold_audit(contract, X, channels, y, clinical_thresholds)
167
+ else:
168
+ pack["clinical_threshold_audit"] = None
169
+
170
+ if channel_clinical:
171
+ if X is None or y is None or channels is None:
172
+ raise ValueError("channel_clinical=True requires X, channels, and y")
173
+ pack["channel_clinical_summary"] = _channel_clinical_summary(
174
+ contract, X, channels, y, channel_clinical_thresholds
175
+ )
176
+ else:
177
+ pack["channel_clinical_summary"] = None
178
+
179
+ return pack
180
+
181
+
182
+ def save_audit_pack(
183
+ contract: DeploymentContract,
184
+ path,
185
+ X=None,
186
+ channels=None,
187
+ y=None,
188
+ slope_boot=False,
189
+ slope_boot_kwargs=None,
190
+ clinical=False,
191
+ clinical_thresholds=None,
192
+ channel_clinical=False,
193
+ channel_clinical_thresholds=None,
194
+ ) -> dict:
195
+ """Save an audit pack JSON and return it."""
196
+ pack = audit_pack(
197
+ contract,
198
+ X=X,
199
+ channels=channels,
200
+ y=y,
201
+ slope_boot=slope_boot,
202
+ slope_boot_kwargs=slope_boot_kwargs,
203
+ clinical=clinical,
204
+ clinical_thresholds=clinical_thresholds,
205
+ channel_clinical=channel_clinical,
206
+ channel_clinical_thresholds=channel_clinical_thresholds,
207
+ )
208
+ with open(str(path), "w", encoding="utf-8") as f:
209
+ json.dump(pack, f, indent=2, sort_keys=True)
210
+ return pack
@@ -0,0 +1,115 @@
1
+ """Three-tier certificate strategy (B2 closure, v0.6.84).
2
+
3
+ Empirical basis: docs/theory_impossibility.md rounds 66-68 and
4
+ scripts/ncs_conformal_transport_v01.py - under transport, no single conformal
5
+ certificate is universally safe (plain split-conformal collapses
6
+ conditionally; Mondrian trades marginal for conditional coverage; label-free
7
+ regime features only partially separate regimes). This module operationalises
8
+ the resulting three-tier architecture:
9
+
10
+ Tier 1 - label_free_default: conservative certificate choice from deployment
11
+ quantities only (target logit-spread ratio + public prevalence).
12
+ Always deployable; matches 2/4 regimes in validation (the two
13
+ extremes), conservative elsewhere.
14
+ Tier 2 - label_pool_audit: definitive regime classification + certificate
15
+ selection from a small label pool (n~50-100, the runtime's existing
16
+ DCA protocol). Estimates class-conditional coverage of BOTH
17
+ candidate certificates on the pool.
18
+ Tier 3 - audit output: regardless of tier, both certificates' marginal and
19
+ conditional coverage are emitted - 'ship the audit that chooses',
20
+ not a bare certificate.
21
+
22
+ Reproduce the validation: PYTHONPATH=. python scripts/ncs_conformal_transport_v01.py
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import numpy as np
27
+
28
+
29
+ def logit(p):
30
+ p = np.clip(np.asarray(p, dtype=float), 1e-6, 1 - 1e-6)
31
+ return np.log(p / (1 - p))
32
+
33
+
34
+ def spread_ratio(p_source_calibrated, p_target_naive):
35
+ """logit-spread ratio (label-free regime feature; see module docstring for
36
+ the documented limitation - binomial variance depends on the mean, so this
37
+ does NOT fully separate regimes)."""
38
+ ls, lt = logit(p_source_calibrated), logit(p_target_naive)
39
+ return float(np.std(lt) / max(np.std(ls), 1e-9))
40
+
41
+
42
+ def label_free_default(ratio, pi_public, tau=0.10, pi_lo=0.05, pi_hi=0.50):
43
+ """Tier 1: conservative label-free certificate choice.
44
+
45
+ Returns 'mondrian+repair' for extreme-prevalence or spread-deviation
46
+ regimes, else 'plain+repair'."""
47
+ if pi_public < pi_lo or pi_public > pi_hi:
48
+ return "mondrian+repair"
49
+ if abs(ratio - 1.0) > tau:
50
+ return "mondrian+repair"
51
+ return "plain+repair"
52
+
53
+
54
+ def _class_coverage(p, y, c, q):
55
+ p = np.asarray(p, dtype=float)
56
+ y = np.asarray(y, dtype=int)
57
+ m = y == c
58
+ if m.sum() == 0:
59
+ return np.nan
60
+ return float(np.mean(p[m] >= q))
61
+
62
+
63
+ def label_pool_audit(p_pool, y_pool, p_deploy_pool, q_plain, qs_mondrian,
64
+ pi_public, tau=0.10, pi_lo=0.05, pi_hi=0.50):
65
+ """Tier 2: definitive selection from a small label pool.
66
+
67
+ Estimates class-conditional coverage of both candidate certificates on the
68
+ label pool and selects the one with the better worst-class coverage
69
+ (maximin). Falls back to the label-free default when the pool has too few
70
+ positives to estimate conditional coverage.
71
+ """
72
+ p_pool = np.asarray(p_pool, dtype=float)
73
+ y_pool = np.asarray(y_pool, dtype=int)
74
+ ratio = spread_ratio(p_deploy_pool, p_pool)
75
+ default = label_free_default(ratio, pi_public, tau, pi_lo, pi_hi)
76
+ n_pos = int((y_pool == 1).sum())
77
+ if n_pos < 10:
78
+ return {"certificate": default, "tier": 1, "reason": "insufficient pool positives",
79
+ "n_pool": len(y_pool), "n_pos": n_pos}
80
+
81
+ cov_p = {"overall": float(np.mean(p_pool >= 1 - q_plain)),
82
+ "y1": _class_coverage(p_pool, y_pool, 1, 1 - q_plain),
83
+ "y0": _class_coverage(p_pool, y_pool, 0, q_plain)}
84
+ # Mondrian on 2-class with deploy probabilities: case threshold from qs[1],
85
+ # control threshold from qs[0]; coverage uses the appropriate threshold.
86
+ cov_m = {"overall": float(np.mean(
87
+ (p_pool >= 1 - qs_mondrian[1]) | (1 - p_pool >= 1 - qs_mondrian[0]))),
88
+ "y1": _class_coverage(p_pool, y_pool, 1, 1 - qs_mondrian[1]),
89
+ "y0": _class_coverage(p_pool, y_pool, 0, 1 - qs_mondrian[0])}
90
+ worst = {"plain+repair": min(cov_p["y0"], cov_p["y1"]),
91
+ "mondrian+repair": min(cov_m["y0"], cov_m["y1"])}
92
+ chosen = max(worst, key=worst.get)
93
+ return {"certificate": chosen, "tier": 2, "reason": "maximin on label pool",
94
+ "n_pool": len(y_pool), "n_pos": n_pos,
95
+ "coverage_plain": cov_p, "coverage_mondrian": cov_m,
96
+ "worst_class_coverage": worst}
97
+
98
+
99
+ def three_tier_strategy(p_source_calibrated, p_target_naive, pi_public,
100
+ p_pool=None, y_pool=None, q_plain=None, qs_mondrian=None):
101
+ """Tier 3 orchestrator: emits the audit that chooses.
102
+
103
+ Returns a dict with the selected certificate, the tier used, the label-free
104
+ default (always reported), and - when a pool is provided - both
105
+ certificates' conditional coverage estimates."""
106
+ ratio = spread_ratio(p_source_calibrated, p_target_naive)
107
+ default = label_free_default(ratio, pi_public)
108
+ out = {"spread_ratio": ratio, "label_free_default": default,
109
+ "tier": 1, "certificate": default}
110
+ if p_pool is not None and y_pool is not None:
111
+ audit = label_pool_audit(p_pool, y_pool, p_target_naive[:len(p_pool)],
112
+ q_plain, qs_mondrian, pi_public)
113
+ out.update(audit)
114
+ out["audit"] = {k: v for k, v in audit.items() if k.startswith("coverage")}
115
+ return out
@@ -0,0 +1,236 @@
1
+ """Certificate recommendation for Transport Runtime deployment contracts.
2
+
3
+ Innovation layer: a certificate is not always equally appropriate. Small channels,
4
+ post-repair slope-risk flags, and high public-prevalence uncertainty should change
5
+ the certificate policy.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+
11
+ import numpy as np
12
+
13
+ from .compiler import CertificateSpec, DeploymentContract, _certificate_to_dict
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class CertificateRecommendation:
18
+ spec: CertificateSpec
19
+ reasons: tuple[str, ...]
20
+
21
+ def to_dict(self) -> dict:
22
+ return {
23
+ "spec": {
24
+ "alpha": self.spec.alpha,
25
+ "per_channel": self.spec.per_channel,
26
+ "weighted": self.spec.weighted,
27
+ },
28
+ "reasons": list(self.reasons),
29
+ }
30
+
31
+
32
+ def summarize_slope_uncertainty(slope_uncertainty) -> dict | None:
33
+ """Summarize optional slope-repair uncertainty for certificate policy.
34
+
35
+ Accepts either a scalar width or an audit-pack `slope_repair_certificate`
36
+ dictionary containing `a_ci_lower`, `a_ci_upper`, `b_ci_lower`, and
37
+ `b_ci_upper`. Returns `None` when no uncertainty evidence is supplied.
38
+ """
39
+ if slope_uncertainty is None:
40
+ return None
41
+ if isinstance(slope_uncertainty, dict):
42
+ required = ("a_ci_lower", "a_ci_upper", "b_ci_lower", "b_ci_upper")
43
+ if all(k in slope_uncertainty for k in required):
44
+ a_width = np.asarray(slope_uncertainty["a_ci_upper"], dtype=float) - np.asarray(slope_uncertainty["a_ci_lower"], dtype=float)
45
+ b_width = np.asarray(slope_uncertainty["b_ci_upper"], dtype=float) - np.asarray(slope_uncertainty["b_ci_lower"], dtype=float)
46
+ width = a_width + b_width
47
+ if np.any(~np.isfinite(width)):
48
+ raise ValueError("slope uncertainty intervals must be finite")
49
+ return {
50
+ "max_width": float(np.max(width)),
51
+ "mean_width": float(np.mean(width)),
52
+ "n_channels": int(len(width)),
53
+ "source": "slope_repair_certificate",
54
+ }
55
+ if "margin_interval_summary" in slope_uncertainty:
56
+ q95 = float(slope_uncertainty["margin_interval_summary"].get("q95_width", np.nan))
57
+ if not np.isfinite(q95):
58
+ raise ValueError("margin interval summary must contain a finite q95_width")
59
+ return {
60
+ "max_width": q95,
61
+ "mean_width": q95,
62
+ "n_channels": len(np.asarray(slope_uncertainty.get("channel_n", [0]))),
63
+ "source": "margin_interval_summary",
64
+ }
65
+ raise ValueError("unsupported slope_uncertainty dictionary")
66
+ width = float(slope_uncertainty)
67
+ if not np.isfinite(width):
68
+ raise ValueError("slope uncertainty width must be finite")
69
+ return {"max_width": width, "mean_width": width, "n_channels": None, "source": "scalar"}
70
+
71
+
72
+ def summarize_channel_clinical_risk(channel_clinical, threshold: float = 0.0) -> dict | None:
73
+ """Summarize channel-level clinical evidence for certificate policy."""
74
+ if channel_clinical is None:
75
+ return None
76
+ if not isinstance(channel_clinical, dict) or "overall" not in channel_clinical:
77
+ raise ValueError("channel_clinical must be an audit-pack channel_clinical_summary dictionary")
78
+ overall = channel_clinical["overall"]
79
+ return {
80
+ "n_channels": int(overall.get("n_channels", 0)),
81
+ "n_negative_channels": int(overall.get("n_channels_negative_integrated_gain", 0)),
82
+ "most_fragile_channel": int(overall.get("most_fragile_channel", -1)),
83
+ "most_fragile_integrated_gain": float(overall.get("most_fragile_integrated_gain", np.nan)),
84
+ "most_fragile_worst_gain": float(overall.get("most_fragile_worst_gain", np.nan)),
85
+ "threshold": float(threshold),
86
+ "source": "channel_clinical_summary",
87
+ }
88
+
89
+
90
+ def recommend_certificate(
91
+ channel_counts,
92
+ slope_risk: dict | None = None,
93
+ prevalence_variance=None,
94
+ slope_uncertainty=None,
95
+ channel_clinical=None,
96
+ alpha: float = 0.1,
97
+ min_channel_n: int = 200,
98
+ high_variance_threshold: float = 0.02**2,
99
+ slope_uncertainty_threshold: float = 0.20,
100
+ channel_clinical_threshold: float = 0.0,
101
+ ) -> CertificateRecommendation:
102
+ """Recommend a certificate policy from deployment risk features.
103
+
104
+ Rules
105
+ -----
106
+ - small channels, slope-risk flags, high finite-label slope uncertainty, or
107
+ channel-level clinical losses -> weighted per-channel certificate;
108
+ - otherwise high prevalence uncertainty -> weighted marginal certificate;
109
+ - otherwise -> plain split-conformal certificate.
110
+ """
111
+ counts = np.asarray(channel_counts, dtype=float)
112
+ if counts.ndim != 1 or len(counts) == 0:
113
+ raise ValueError("channel_counts must be a non-empty vector")
114
+ reasons: list[str] = []
115
+
116
+ small = bool(np.any(counts < min_channel_n))
117
+ if small:
118
+ reasons.append(f"at least one channel has n < {min_channel_n}")
119
+
120
+ flagged = False
121
+ if slope_risk is not None:
122
+ flagged = int(slope_risk.get("n_flagged", 0)) > 0
123
+ if flagged:
124
+ reasons.append(f"slope-risk flagged channels: {slope_risk.get('flagged_channels', [])}")
125
+
126
+ slope_summary = summarize_slope_uncertainty(slope_uncertainty)
127
+ high_slope_uncertainty = False
128
+ if slope_summary is not None:
129
+ high_slope_uncertainty = bool(slope_summary["max_width"] > slope_uncertainty_threshold)
130
+ if high_slope_uncertainty:
131
+ reasons.append(
132
+ "high finite-label slope uncertainty: "
133
+ f"max width {slope_summary['max_width']:.4f} > {slope_uncertainty_threshold:.4f}"
134
+ )
135
+
136
+ channel_summary = summarize_channel_clinical_risk(channel_clinical, threshold=channel_clinical_threshold)
137
+ high_channel_clinical_risk = False
138
+ if channel_summary is not None:
139
+ has_negative_channel = channel_summary["n_negative_channels"] > 0
140
+ fragile_below_threshold = (
141
+ np.isfinite(channel_summary["most_fragile_integrated_gain"])
142
+ and channel_summary["most_fragile_integrated_gain"] < channel_clinical_threshold
143
+ )
144
+ high_channel_clinical_risk = bool(has_negative_channel or fragile_below_threshold)
145
+ if high_channel_clinical_risk:
146
+ reasons.append(
147
+ "channel-level clinical loss: "
148
+ f"negative channels={channel_summary['n_negative_channels']}, "
149
+ f"most fragile ch{channel_summary['most_fragile_channel']} "
150
+ f"integrated gain={channel_summary['most_fragile_integrated_gain']:.4f}"
151
+ )
152
+
153
+ high_var = False
154
+ if prevalence_variance is not None:
155
+ var = np.asarray(prevalence_variance, dtype=float)
156
+ high_var = bool(np.max(var) > high_variance_threshold)
157
+ if high_var:
158
+ reasons.append("high public-prevalence uncertainty")
159
+
160
+ if small or flagged or high_slope_uncertainty or high_channel_clinical_risk:
161
+ return CertificateRecommendation(
162
+ spec=CertificateSpec(alpha=alpha, per_channel=True, weighted=True),
163
+ reasons=tuple(reasons) or ("small channels or slope-risk flags",),
164
+ )
165
+ if high_var:
166
+ return CertificateRecommendation(
167
+ spec=CertificateSpec(alpha=alpha, per_channel=False, weighted=True),
168
+ reasons=tuple(reasons),
169
+ )
170
+ return CertificateRecommendation(
171
+ spec=CertificateSpec(alpha=alpha, per_channel=False, weighted=False),
172
+ reasons=tuple(reasons) or ("large channels, no slope-risk flags, low prevalence uncertainty",),
173
+ )
174
+
175
+
176
+ def recommend_certificate_from_audit_pack(
177
+ pack: dict,
178
+ alpha: float = 0.1,
179
+ min_channel_n: int = 200,
180
+ high_variance_threshold: float = 0.02**2,
181
+ slope_uncertainty_threshold: float = 0.20,
182
+ channel_clinical_threshold: float = 0.0,
183
+ ) -> CertificateRecommendation:
184
+ """Recommend a certificate policy directly from an audit pack.
185
+
186
+ Requires `audit_pack(..., slope_boot=True)` so that `channel_n` and the
187
+ finite-label slope-repair uncertainty certificate are available.
188
+ """
189
+ if not isinstance(pack, dict):
190
+ raise ValueError("pack must be an audit-pack dictionary")
191
+ slope_cert = pack.get("slope_repair_certificate")
192
+ if not isinstance(slope_cert, dict) or "channel_n" not in slope_cert:
193
+ raise ValueError("audit pack lacks slope_repair_certificate.channel_n; run audit_pack(..., slope_boot=True)")
194
+ counts = np.asarray(slope_cert["channel_n"], dtype=int)
195
+ return recommend_certificate(
196
+ counts,
197
+ slope_risk=pack.get("slope_risk"),
198
+ prevalence_variance=None,
199
+ slope_uncertainty=slope_cert,
200
+ channel_clinical=pack.get("channel_clinical_summary"),
201
+ alpha=alpha,
202
+ min_channel_n=min_channel_n,
203
+ high_variance_threshold=high_variance_threshold,
204
+ slope_uncertainty_threshold=slope_uncertainty_threshold,
205
+ channel_clinical_threshold=channel_clinical_threshold,
206
+ )
207
+
208
+
209
+ def evaluate_certificate(contract: DeploymentContract, X, y, channels) -> dict:
210
+ """Replay a compiled certificate on labeled target data.
211
+
212
+ Returns overall empirical coverage and per-channel coverage, including the
213
+ minimum channel coverage. This closes the loop between certificate policy
214
+ recommendation and post-deployment evaluation.
215
+ """
216
+ if contract.certificate is None:
217
+ raise ValueError("contract has no certificate; compile with CertificateSpec")
218
+ X = np.asarray(X)
219
+ y = np.asarray(y, dtype=int)
220
+ channels = np.asarray(channels, dtype=int)
221
+ if not (len(X) == len(y) == len(channels)):
222
+ raise ValueError("X, y, and channels must have matching lengths")
223
+
224
+ pred_sets = contract.certificate.predict_set(X, channels)
225
+ covered = np.array([int(yi in s) for yi, s in zip(y, pred_sets)], dtype=float)
226
+ per_channel = {}
227
+ for g in sorted(set(channels)):
228
+ sel = channels == g
229
+ per_channel[int(g)] = float(np.mean(covered[sel])) if sel.any() else np.nan
230
+ return {
231
+ "overall_coverage": float(np.mean(covered)),
232
+ "per_channel_coverage": per_channel,
233
+ "min_channel_coverage": float(np.nanmin(list(per_channel.values()))),
234
+ "n": int(len(y)),
235
+ "certificate_spec": _certificate_to_dict(contract.certificate_spec),
236
+ }