consequa-x 0.1.0a2__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.
- consequa_x/__init__.py +22 -0
- consequa_x/__main__.py +3 -0
- consequa_x/adapters/__init__.py +5 -0
- consequa_x/adapters/ollama.py +350 -0
- consequa_x/attribution.py +407 -0
- consequa_x/benchmark/__init__.py +1 -0
- consequa_x/benchmark/assessment_metrics.py +54 -0
- consequa_x/benchmark/calibration.py +787 -0
- consequa_x/benchmark/calibration_export.py +139 -0
- consequa_x/benchmark/calibration_mechanisms.py +266 -0
- consequa_x/benchmark/decision_coalition.py +254 -0
- consequa_x/benchmark/decision_contrastive.py +448 -0
- consequa_x/benchmark/decision_replay.py +329 -0
- consequa_x/benchmark/decision_shapley.py +628 -0
- consequa_x/benchmark/evaluation.py +187 -0
- consequa_x/benchmark/interaction_export.py +220 -0
- consequa_x/benchmark/interaction_mechanisms.py +192 -0
- consequa_x/benchmark/interval_comparison.py +276 -0
- consequa_x/benchmark/intervals.py +164 -0
- consequa_x/benchmark/payments.py +426 -0
- consequa_x/benchmark/policy_experiment.py +298 -0
- consequa_x/benchmark/policy_runner.py +234 -0
- consequa_x/benchmark/runner.py +241 -0
- consequa_x/benchmark/study.py +183 -0
- consequa_x/benchmark/targets.py +149 -0
- consequa_x/benchmark/templates.py +102 -0
- consequa_x/cli.py +1191 -0
- consequa_x/explanation.py +130 -0
- consequa_x/graph.py +142 -0
- consequa_x/models.py +532 -0
- consequa_x/policies.py +248 -0
- consequa_x/py.typed +0 -0
- consequa_x/schema.py +159 -0
- consequa_x/schemas/trace-v0.1.json +543 -0
- consequa_x/verification.py +192 -0
- consequa_x-0.1.0a2.dist-info/METADATA +303 -0
- consequa_x-0.1.0a2.dist-info/RECORD +41 -0
- consequa_x-0.1.0a2.dist-info/WHEEL +5 -0
- consequa_x-0.1.0a2.dist-info/entry_points.txt +2 -0
- consequa_x-0.1.0a2.dist-info/licenses/LICENSE +21 -0
- consequa_x-0.1.0a2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
"""Predeclared repeated synthetic calibration through the production replayers.
|
|
2
|
+
|
|
3
|
+
Each replicate captures a deliberately planted factual trajectory in a fresh
|
|
4
|
+
refund world. Both estimators receive the same capture, with separate budgets
|
|
5
|
+
and disjoint continuation seeds. Analytic target labels enter only the scoring
|
|
6
|
+
stage; refusals remain in the planned-trial denominator and are never redrawn.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from copy import deepcopy
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import math
|
|
16
|
+
from statistics import NormalDist
|
|
17
|
+
from typing import Callable
|
|
18
|
+
|
|
19
|
+
from ..attribution import ReplayTarget
|
|
20
|
+
from ..models import EventType, to_dict
|
|
21
|
+
from ..policies import PolicyError
|
|
22
|
+
from .calibration_mechanisms import (
|
|
23
|
+
CALIBRATION_MECHANISMS, CalibrationMechanism, get_calibration_mechanism,
|
|
24
|
+
)
|
|
25
|
+
from .decision_contrastive import (
|
|
26
|
+
DecisionContrastiveResult, _interval_method, _selected_targets, contrastive_decisions,
|
|
27
|
+
)
|
|
28
|
+
from .decision_shapley import DecisionShapleyResult, _interval_methods, shapley_decisions
|
|
29
|
+
from .evaluation import BudgetedPolicy
|
|
30
|
+
from .policy_runner import MAX_TRIAL_SEED, run_policy_scenario
|
|
31
|
+
from .runner import Episode
|
|
32
|
+
from .targets import episode_outcome
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
CALIBRATION_CASES = tuple(CALIBRATION_MECHANISMS) + ("budget-stop-q50",)
|
|
36
|
+
INTERVAL_COMPARISON_CASES = CALIBRATION_CASES + ("protective-stop-q0", "protective-stop-q100")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _mechanism(name):
|
|
40
|
+
if name in ("protective-stop-q0", "protective-stop-q100"):
|
|
41
|
+
return CalibrationMechanism(name, 0. if name.endswith("q0") else 1.)
|
|
42
|
+
return get_calibration_mechanism("protective-stop-q50" if name == "budget-stop-q50" else name)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class CalibrationPlan:
|
|
47
|
+
"""Research-sized defaults; use an explicit small repeat count for smoke checks.
|
|
48
|
+
|
|
49
|
+
``max_calls`` applies independently to each method's factual recording plus
|
|
50
|
+
continuation calls. The budget control uses an explicit limit of three.
|
|
51
|
+
The two methods cannot consume one another's call allowance.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
mechanisms: tuple[str, ...] = CALIBRATION_CASES
|
|
55
|
+
repeats: int = 32
|
|
56
|
+
samples: int = 16
|
|
57
|
+
pairs: int = 16
|
|
58
|
+
samples_per_eval: int = 1
|
|
59
|
+
endpoint_samples: int = 16
|
|
60
|
+
bootstrap_samples: int = 2000
|
|
61
|
+
confidence: float = 0.95
|
|
62
|
+
seed: int = 100
|
|
63
|
+
max_calls: int = 400
|
|
64
|
+
targets: tuple[ReplayTarget, ...] = ("action", "consequence", "action_linked_consequence")
|
|
65
|
+
contrastive_interval_method: str = "percentile_bootstrap"
|
|
66
|
+
shapley_interval_method: str = "normal"
|
|
67
|
+
endpoint_interval_method: str = "normal"
|
|
68
|
+
|
|
69
|
+
def __post_init__(self):
|
|
70
|
+
if (not isinstance(self.mechanisms, tuple) or not self.mechanisms
|
|
71
|
+
or any(not isinstance(name, str) or name not in INTERVAL_COMPARISON_CASES
|
|
72
|
+
for name in self.mechanisms)
|
|
73
|
+
or len(set(self.mechanisms)) != len(self.mechanisms)):
|
|
74
|
+
raise ValueError("mechanisms must be a nonempty tuple of distinct registered names")
|
|
75
|
+
for name, minimum in (("repeats", 1), ("samples", 2), ("pairs", 2),
|
|
76
|
+
("samples_per_eval", 1), ("endpoint_samples", 2),
|
|
77
|
+
("bootstrap_samples", 2), ("max_calls", 1)):
|
|
78
|
+
if type(getattr(self, name)) is not int or getattr(self, name) < minimum:
|
|
79
|
+
raise ValueError(f"{name} must be an integer of at least {minimum}")
|
|
80
|
+
if type(self.seed) is not int or not 0 <= self.seed <= MAX_TRIAL_SEED:
|
|
81
|
+
raise ValueError(f"seed must be an integer in 0..{MAX_TRIAL_SEED}")
|
|
82
|
+
try:
|
|
83
|
+
valid = (type(self.confidence) in {int, float} and math.isfinite(self.confidence)
|
|
84
|
+
and 0 < self.confidence < 1)
|
|
85
|
+
except OverflowError:
|
|
86
|
+
valid = False
|
|
87
|
+
if not valid:
|
|
88
|
+
raise ValueError("confidence must be finite and strictly between 0 and 1")
|
|
89
|
+
if not isinstance(self.targets, tuple):
|
|
90
|
+
raise ValueError("targets must be a tuple of distinct replay targets")
|
|
91
|
+
_selected_targets(self.targets)
|
|
92
|
+
_interval_method(self.contrastive_interval_method)
|
|
93
|
+
_interval_methods(self.shapley_interval_method, self.endpoint_interval_method)
|
|
94
|
+
# Bound the entire declared experiment before allocating its schedule.
|
|
95
|
+
total = sum(_trial_seed_count(self, _mechanism(name).decision_count)
|
|
96
|
+
for name in self.mechanisms) * self.repeats
|
|
97
|
+
if self.seed + total - 1 > MAX_TRIAL_SEED:
|
|
98
|
+
raise ValueError("Complete calibration seed schedule exceeds the model seed range")
|
|
99
|
+
last_inference = (6 * (self.seed + total - 1) + 6
|
|
100
|
+
+ 2 * len(self.mechanisms) * self.repeats - 1)
|
|
101
|
+
if last_inference > 2**31 - 1:
|
|
102
|
+
raise ValueError("Complete calibration inference seed schedule exceeds its valid range")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True)
|
|
106
|
+
class CalibrationTrialPlan:
|
|
107
|
+
index: int
|
|
108
|
+
mechanism: str
|
|
109
|
+
replicate: int
|
|
110
|
+
decision_count: int
|
|
111
|
+
factual_seed: int
|
|
112
|
+
contrastive_seed: int
|
|
113
|
+
contrastive_branches: int
|
|
114
|
+
shapley_seed: int
|
|
115
|
+
shapley_branches: int
|
|
116
|
+
permutation_seed: int
|
|
117
|
+
bootstrap_seed: int
|
|
118
|
+
max_calls: int
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _trial_seed_count(plan: CalibrationPlan, decisions: int) -> int:
|
|
122
|
+
return (1 + decisions * plan.samples + 2 * plan.endpoint_samples
|
|
123
|
+
+ 2 * plan.pairs * (decisions + 1) * plan.samples_per_eval)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def predeclare_calibration(plan: CalibrationPlan) -> tuple[CalibrationTrialPlan, ...]:
|
|
127
|
+
"""Reserve every capture, continuation and inference RNG key before execution."""
|
|
128
|
+
if not isinstance(plan, CalibrationPlan):
|
|
129
|
+
raise TypeError("plan must be a CalibrationPlan")
|
|
130
|
+
trials = []
|
|
131
|
+
cursor = plan.seed
|
|
132
|
+
total = sum(_trial_seed_count(plan, _mechanism(name).decision_count)
|
|
133
|
+
for name in plan.mechanisms) * plan.repeats
|
|
134
|
+
inference_start = 6 * (plan.seed + total - 1) + 6
|
|
135
|
+
for name in plan.mechanisms:
|
|
136
|
+
decisions = _mechanism(name).decision_count
|
|
137
|
+
contrastive_count = decisions * plan.samples
|
|
138
|
+
shapley_count = (2 * plan.endpoint_samples
|
|
139
|
+
+ 2 * plan.pairs * (decisions + 1) * plan.samples_per_eval)
|
|
140
|
+
for replicate in range(plan.repeats):
|
|
141
|
+
factual_seed = cursor
|
|
142
|
+
contrastive_seed = factual_seed + 1
|
|
143
|
+
shapley_seed = contrastive_seed + contrastive_count
|
|
144
|
+
permutation_seed = inference_start + 2 * len(trials)
|
|
145
|
+
bootstrap_seed = permutation_seed + 1
|
|
146
|
+
trials.append(CalibrationTrialPlan(
|
|
147
|
+
len(trials), name, replicate, decisions, factual_seed,
|
|
148
|
+
contrastive_seed, contrastive_count, shapley_seed, shapley_count,
|
|
149
|
+
permutation_seed, bootstrap_seed, 3 if name == "budget-stop-q50" else plan.max_calls,
|
|
150
|
+
))
|
|
151
|
+
cursor = shapley_seed + shapley_count
|
|
152
|
+
return tuple(trials)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass(frozen=True)
|
|
156
|
+
class CalibrationError:
|
|
157
|
+
operation: str
|
|
158
|
+
error_type: str
|
|
159
|
+
error: str
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass(frozen=True)
|
|
163
|
+
class CalibrationTrial:
|
|
164
|
+
seed_plan: CalibrationTrialPlan
|
|
165
|
+
factual: Episode | None
|
|
166
|
+
shapley_factual: Episode | None
|
|
167
|
+
factual_decisions: tuple[dict, ...]
|
|
168
|
+
shapley_factual_decisions: tuple[dict, ...]
|
|
169
|
+
contrastive: DecisionContrastiveResult | None
|
|
170
|
+
shapley: DecisionShapleyResult | None
|
|
171
|
+
contrastive_factual_calls: int
|
|
172
|
+
shapley_factual_calls: int
|
|
173
|
+
contrastive_calls: int
|
|
174
|
+
shapley_calls: int
|
|
175
|
+
errors: tuple[CalibrationError, ...]
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def factual_calls(self) -> int:
|
|
179
|
+
return self.contrastive_factual_calls + self.shapley_factual_calls
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def policy_calls(self) -> int:
|
|
183
|
+
return self.factual_calls + self.contrastive_calls + self.shapley_calls
|
|
184
|
+
|
|
185
|
+
def summary(self) -> dict:
|
|
186
|
+
return {
|
|
187
|
+
"seed_plan": to_dict(self.seed_plan),
|
|
188
|
+
"factual_status": "completed" if self.factual is not None else "failed",
|
|
189
|
+
"shapley_factual_status": "completed" if self.shapley_factual is not None else "failed",
|
|
190
|
+
"factual_decisions": to_dict(self.factual_decisions),
|
|
191
|
+
"shapley_factual_decisions": to_dict(self.shapley_factual_decisions),
|
|
192
|
+
"calls": {"factual": self.factual_calls, "contrastive": self.contrastive_calls,
|
|
193
|
+
"shapley": self.shapley_calls, "total": self.policy_calls},
|
|
194
|
+
"errors": to_dict(self.errors),
|
|
195
|
+
"contrastive": None if self.contrastive is None else self.contrastive.summary(),
|
|
196
|
+
"shapley": None if self.shapley is None else self.shapley.summary(),
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass(frozen=True)
|
|
201
|
+
class CompactCalibrationTrial:
|
|
202
|
+
"""Portable inference receipts; full episodes are discarded after the callback."""
|
|
203
|
+
|
|
204
|
+
seed_plan: CalibrationTrialPlan
|
|
205
|
+
factual: dict
|
|
206
|
+
contrastive: dict | None
|
|
207
|
+
shapley: dict | None
|
|
208
|
+
contrastive_factual_calls: int
|
|
209
|
+
shapley_factual_calls: int
|
|
210
|
+
contrastive_calls: int
|
|
211
|
+
shapley_calls: int
|
|
212
|
+
errors: tuple[CalibrationError, ...]
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def factual_calls(self) -> int:
|
|
216
|
+
return self.contrastive_factual_calls + self.shapley_factual_calls
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def policy_calls(self) -> int:
|
|
220
|
+
return self.factual_calls + self.contrastive_calls + self.shapley_calls
|
|
221
|
+
|
|
222
|
+
def to_dict(self) -> dict:
|
|
223
|
+
return {"seed_plan": to_dict(self.seed_plan), "factual": deepcopy(self.factual),
|
|
224
|
+
"contrastive": deepcopy(self.contrastive), "shapley": deepcopy(self.shapley),
|
|
225
|
+
"calls": {"contrastive_factual": self.contrastive_factual_calls,
|
|
226
|
+
"shapley_factual": self.shapley_factual_calls,
|
|
227
|
+
"factual": self.factual_calls, "contrastive": self.contrastive_calls,
|
|
228
|
+
"shapley": self.shapley_calls, "total": self.policy_calls},
|
|
229
|
+
"errors": to_dict(self.errors)}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _factual_record(episode, decisions, calls):
|
|
233
|
+
return {"status": "completed" if episode is not None else "failed", "calls": calls,
|
|
234
|
+
"trace_sha256": (hashlib.sha256(json.dumps(
|
|
235
|
+
episode.trace.to_dict(), sort_keys=True, separators=(",", ":"),
|
|
236
|
+
ensure_ascii=True, allow_nan=False,
|
|
237
|
+
).encode("utf-8")).hexdigest()
|
|
238
|
+
if episode is not None else None),
|
|
239
|
+
"trace_hash_encoding": "UTF-8 JSON, sorted keys, compact separators, ensure_ascii=True",
|
|
240
|
+
"targets": (to_dict(episode_outcome(episode)) if episode is not None else None),
|
|
241
|
+
"decisions": to_dict(decisions)}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _branch_record(replay, targets):
|
|
245
|
+
if replay is None:
|
|
246
|
+
return {"status": "unstarted", "policy_calls": 0, "target_values": None,
|
|
247
|
+
"error_type": None, "error": None, "budget_exhausted": False,
|
|
248
|
+
"reached_decisions": [], "unreached_held_indices": None}
|
|
249
|
+
outcome = episode_outcome(replay.episode) if replay.episode is not None else None
|
|
250
|
+
return {"status": (replay.status if hasattr(replay, "status") else
|
|
251
|
+
"completed" if replay.episode is not None else "failed"),
|
|
252
|
+
"policy_calls": replay.policy_calls,
|
|
253
|
+
"target_values": ({target: outcome.target_value(target) for target in targets}
|
|
254
|
+
if outcome is not None else None),
|
|
255
|
+
"error_type": replay.error_type, "error": replay.error,
|
|
256
|
+
"budget_exhausted": replay.budget_exhausted,
|
|
257
|
+
"reached_decisions": [{"index": receipt.index, "source": receipt.source,
|
|
258
|
+
"stage": receipt.stage,
|
|
259
|
+
"continuation_model_seed": receipt.continuation_model_seed,
|
|
260
|
+
"factual_model_seed": receipt.factual_model_seed,
|
|
261
|
+
"factual_event_id": receipt.factual_event_id,
|
|
262
|
+
"policy_calls": receipt.policy_calls,
|
|
263
|
+
"error_type": receipt.error_type, "error": receipt.error}
|
|
264
|
+
for receipt in replay.decisions],
|
|
265
|
+
"unreached_held_indices": (list(replay.unreached_held_indices)
|
|
266
|
+
if hasattr(replay, "unreached_held_indices") else None)}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def compact_calibration_trial(trial: CalibrationTrial) -> CompactCalibrationTrial:
|
|
270
|
+
"""Retain all planned slots and statistical inputs without retaining episodes."""
|
|
271
|
+
contrastive = None
|
|
272
|
+
if trial.contrastive is not None:
|
|
273
|
+
result = trial.contrastive
|
|
274
|
+
reached = {branch.index: branch.replay for branch in result.branches}
|
|
275
|
+
branches = []
|
|
276
|
+
for position, seeds in enumerate(result.planned_seeds):
|
|
277
|
+
for sample, seed in enumerate(seeds):
|
|
278
|
+
index = position * result.samples_per_decision + sample
|
|
279
|
+
branches.append({"index": index, "decision_index": position,
|
|
280
|
+
"continuation_seed": seed,
|
|
281
|
+
**_branch_record(reached.get(index), result.targets)})
|
|
282
|
+
contrastive = {"summary": result.summary(), "method_record": result.method_record(),
|
|
283
|
+
"results": [entry.to_dict() for entry in result.results],
|
|
284
|
+
"loci": to_dict(result.loci), "branches": branches}
|
|
285
|
+
shapley = None
|
|
286
|
+
if trial.shapley is not None:
|
|
287
|
+
result = trial.shapley
|
|
288
|
+
reached = {branch.slot.index: branch.replay for branch in result.branches}
|
|
289
|
+
shapley = {"summary": result.summary(), "method_record": result.method_record(),
|
|
290
|
+
"results": [entry.to_dict() for entry in result.results],
|
|
291
|
+
"endpoint_checks": to_dict(result.endpoint_checks),
|
|
292
|
+
"permutations": to_dict(result.permutations),
|
|
293
|
+
"slots": to_dict(result.slots),
|
|
294
|
+
"branches": [{"slot_index": slot.index,
|
|
295
|
+
**_branch_record(reached.get(slot.index), result.targets)}
|
|
296
|
+
for slot in result.slots]}
|
|
297
|
+
return CompactCalibrationTrial(
|
|
298
|
+
trial.seed_plan,
|
|
299
|
+
{"contrastive": _factual_record(trial.factual, trial.factual_decisions,
|
|
300
|
+
trial.contrastive_factual_calls),
|
|
301
|
+
"shapley": _factual_record(trial.shapley_factual, trial.shapley_factual_decisions,
|
|
302
|
+
trial.shapley_factual_calls)},
|
|
303
|
+
contrastive, shapley, trial.contrastive_factual_calls, trial.shapley_factual_calls,
|
|
304
|
+
trial.contrastive_calls, trial.shapley_calls, trial.errors,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@dataclass(frozen=True)
|
|
309
|
+
class CalibrationResult:
|
|
310
|
+
plan: CalibrationPlan
|
|
311
|
+
trials: tuple[CompactCalibrationTrial, ...]
|
|
312
|
+
|
|
313
|
+
def protocol(self) -> dict:
|
|
314
|
+
schedule = predeclare_calibration(self.plan)
|
|
315
|
+
return {
|
|
316
|
+
"name": "conditional_synthetic_refund_calibration",
|
|
317
|
+
"version": 1,
|
|
318
|
+
"plan": to_dict(self.plan),
|
|
319
|
+
"trial_schedule": to_dict(schedule),
|
|
320
|
+
"planned_trials": len(schedule),
|
|
321
|
+
"planned_contrastive_branches": sum(item.contrastive_branches for item in schedule),
|
|
322
|
+
"planned_shapley_branches": sum(item.shapley_branches for item in schedule),
|
|
323
|
+
"evaluator_analytic_references": {
|
|
324
|
+
name: _mechanism(name).analytic_reference() for name in self.plan.mechanisms
|
|
325
|
+
},
|
|
326
|
+
"factual_capture": "predeclared planted trajectory; no seed search or conditioning by redraw",
|
|
327
|
+
"world_seed": "each trial's factual_seed; held fixed within both estimators",
|
|
328
|
+
"policy_seed_schedule": "globally disjoint factual and continuation phase ranges",
|
|
329
|
+
"inference_seed_schedule": "distinct keys beyond every model phase key; permutation then bootstrap per trial",
|
|
330
|
+
"budget": "each independent method budget includes its own factual recording and continuations",
|
|
331
|
+
"paired_factual": "two actual recordings with the same declared world/policy key; no fabricated counter",
|
|
332
|
+
"missingness": "every declared trial retained; no complete-case substitution or redraw",
|
|
333
|
+
"scoring": "analytic reference enters the scorer only; production replayers use real traces",
|
|
334
|
+
"replicate_uncertainty": "nominal Wilson binomial interval at plan.confidence",
|
|
335
|
+
"scope": "exploratory_synthetic_calibration",
|
|
336
|
+
"repeat_count_interpretation": "larger repeat counts improve metric precision, not study validity",
|
|
337
|
+
"scope_limit": "synthetic conditional mechanisms; no population or live-model accuracy claim",
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
def summary(self) -> dict:
|
|
341
|
+
return analyze_calibration(self)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class _RecordingPolicy:
|
|
345
|
+
def __init__(self, policy):
|
|
346
|
+
self.policy = policy
|
|
347
|
+
self.decisions: list[dict] = []
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def metadata(self):
|
|
351
|
+
return deepcopy(self.policy.metadata)
|
|
352
|
+
|
|
353
|
+
def decide(self, request, *, seed):
|
|
354
|
+
receipt = {"index": len(self.decisions), "seed": seed, "request": to_dict(request),
|
|
355
|
+
"reply": None, "error_type": None, "error": None}
|
|
356
|
+
try:
|
|
357
|
+
reply = self.policy.decide(request, seed=seed)
|
|
358
|
+
receipt["reply"] = to_dict(reply)
|
|
359
|
+
return reply
|
|
360
|
+
except (PolicyError, ValueError, TypeError) as error:
|
|
361
|
+
receipt.update(error_type=type(error).__name__, error=str(error))
|
|
362
|
+
raise
|
|
363
|
+
finally:
|
|
364
|
+
self.decisions.append(receipt)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def run_calibration(plan: CalibrationPlan, *,
|
|
368
|
+
on_trial: Callable[[CalibrationTrial], None] | None = None) -> CalibrationResult:
|
|
369
|
+
"""Execute the immutable schedule without dropping failed or refused trials.
|
|
370
|
+
|
|
371
|
+
``on_trial`` is an optional serial export/progress hook. It receives every
|
|
372
|
+
completed trial record once; callback exceptions propagate, because an
|
|
373
|
+
export failure must not silently claim a complete experiment.
|
|
374
|
+
"""
|
|
375
|
+
schedule = predeclare_calibration(plan)
|
|
376
|
+
if on_trial is not None and not callable(on_trial):
|
|
377
|
+
raise TypeError("on_trial must be callable or None")
|
|
378
|
+
trials = []
|
|
379
|
+
for item in schedule:
|
|
380
|
+
mechanism = _mechanism(item.mechanism)
|
|
381
|
+
factual = None
|
|
382
|
+
shapley_factual = None
|
|
383
|
+
contrastive = None
|
|
384
|
+
shapley = None
|
|
385
|
+
errors = []
|
|
386
|
+
captures = {}
|
|
387
|
+
budgets = {}
|
|
388
|
+
recorders = {}
|
|
389
|
+
factual_calls = {}
|
|
390
|
+
for method in ("contrastive", "shapley"):
|
|
391
|
+
policy = mechanism.make_policy(factual_policy_seed=item.factual_seed)
|
|
392
|
+
recorder = _RecordingPolicy(policy)
|
|
393
|
+
bounded = BudgetedPolicy(recorder, item.max_calls)
|
|
394
|
+
recorders[method] = recorder
|
|
395
|
+
budgets[method] = bounded
|
|
396
|
+
try:
|
|
397
|
+
captured = run_policy_scenario(mechanism.scenario_config(), bounded,
|
|
398
|
+
seed=item.factual_seed, policy_seed=item.factual_seed,
|
|
399
|
+
max_attempts=2)
|
|
400
|
+
count = sum(event.type is EventType.AGENT_DECISION for event in captured.trace.events)
|
|
401
|
+
if count != item.decision_count:
|
|
402
|
+
raise ValueError("Factual decision count differs from the predeclared mechanism")
|
|
403
|
+
captures[method] = captured
|
|
404
|
+
except (PolicyError, ValueError, TypeError) as error:
|
|
405
|
+
errors.append(CalibrationError(f"{method}_factual", type(error).__name__, str(error)))
|
|
406
|
+
factual_calls[method] = bounded.calls
|
|
407
|
+
# The recorder is only for the factual capture. Continuation receipts
|
|
408
|
+
# belong to the production replayer and are retained in its result.
|
|
409
|
+
bounded.policy = policy
|
|
410
|
+
factual = captures.get("contrastive")
|
|
411
|
+
shapley_factual = captures.get("shapley")
|
|
412
|
+
if factual is not None:
|
|
413
|
+
try:
|
|
414
|
+
contrastive = contrastive_decisions(
|
|
415
|
+
factual, budgets["contrastive"], samples=plan.samples,
|
|
416
|
+
seed=item.contrastive_seed, targets=plan.targets, confidence=plan.confidence,
|
|
417
|
+
bootstrap_samples=plan.bootstrap_samples, bootstrap_seed=item.bootstrap_seed,
|
|
418
|
+
interval_method=plan.contrastive_interval_method,
|
|
419
|
+
)
|
|
420
|
+
except (PolicyError, ValueError, TypeError) as error:
|
|
421
|
+
errors.append(CalibrationError("contrastive", type(error).__name__, str(error)))
|
|
422
|
+
if shapley_factual is not None:
|
|
423
|
+
try:
|
|
424
|
+
shapley = shapley_decisions(
|
|
425
|
+
shapley_factual, budgets["shapley"], pairs=plan.pairs,
|
|
426
|
+
samples_per_eval=plan.samples_per_eval,
|
|
427
|
+
endpoint_samples=plan.endpoint_samples, seed=item.shapley_seed,
|
|
428
|
+
permutation_seed=item.permutation_seed, confidence=plan.confidence,
|
|
429
|
+
targets=plan.targets,
|
|
430
|
+
interval_method=plan.shapley_interval_method,
|
|
431
|
+
endpoint_interval_method=plan.endpoint_interval_method,
|
|
432
|
+
)
|
|
433
|
+
except (PolicyError, ValueError, TypeError) as error:
|
|
434
|
+
errors.append(CalibrationError("shapley", type(error).__name__, str(error)))
|
|
435
|
+
trial = CalibrationTrial(
|
|
436
|
+
item, factual, shapley_factual, tuple(recorders["contrastive"].decisions),
|
|
437
|
+
tuple(recorders["shapley"].decisions), contrastive, shapley,
|
|
438
|
+
factual_calls["contrastive"], factual_calls["shapley"],
|
|
439
|
+
budgets["contrastive"].calls - factual_calls["contrastive"],
|
|
440
|
+
budgets["shapley"].calls - factual_calls["shapley"], tuple(errors),
|
|
441
|
+
)
|
|
442
|
+
if on_trial is not None:
|
|
443
|
+
on_trial(trial)
|
|
444
|
+
trials.append(compact_calibration_trial(trial))
|
|
445
|
+
return CalibrationResult(plan, tuple(trials))
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def calibration_protocol(plan: CalibrationPlan) -> dict:
|
|
449
|
+
"""Obtain the complete predeclared protocol before collecting any results."""
|
|
450
|
+
return CalibrationResult(plan, ()).protocol()
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def wilson_rate(successes: int, trials: int, confidence: float = 0.95) -> dict:
|
|
454
|
+
"""Nominal uncertainty for a repeated-replicate binary metric, retaining N=0."""
|
|
455
|
+
if (type(successes) is not int or type(trials) is not int
|
|
456
|
+
or not 0 <= successes <= trials):
|
|
457
|
+
raise ValueError("successes and trials must be integers with 0 <= successes <= trials")
|
|
458
|
+
try:
|
|
459
|
+
valid = (type(confidence) in {int, float} and math.isfinite(confidence)
|
|
460
|
+
and 0 < confidence < 1)
|
|
461
|
+
except OverflowError:
|
|
462
|
+
valid = False
|
|
463
|
+
if not valid:
|
|
464
|
+
raise ValueError("confidence must be finite and strictly between 0 and 1")
|
|
465
|
+
if trials == 0:
|
|
466
|
+
return {"numerator": 0, "denominator": 0, "rate": None, "confidence_interval": None,
|
|
467
|
+
"confidence": confidence, "interval_method": "Wilson score"}
|
|
468
|
+
probability = successes / trials
|
|
469
|
+
z = -NormalDist().inv_cdf((1 - confidence) / 2)
|
|
470
|
+
scale = 1 + z * z / trials
|
|
471
|
+
center = (probability + z * z / (2 * trials)) / scale
|
|
472
|
+
margin = z / scale * math.sqrt(
|
|
473
|
+
probability * (1 - probability) / trials + z * z / (4 * trials * trials)
|
|
474
|
+
)
|
|
475
|
+
interval = (0. if successes == 0 else max(0., center - margin),
|
|
476
|
+
1. if successes == trials else min(1., center + margin))
|
|
477
|
+
return {"numerator": successes, "denominator": trials, "rate": probability,
|
|
478
|
+
"confidence_interval": interval, "confidence": confidence,
|
|
479
|
+
"interval_method": "Wilson score"}
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _numeric_metrics(observations: list[tuple[float, tuple[float, float] | None]],
|
|
483
|
+
reference: float | None, confidence: float) -> dict:
|
|
484
|
+
errors = [] if reference is None else [value - reference for value, _ in observations]
|
|
485
|
+
intervals = [] if reference is None else [interval for _, interval in observations
|
|
486
|
+
if interval is not None]
|
|
487
|
+
covered = sum(lower <= reference <= upper for lower, upper in intervals)
|
|
488
|
+
bias = sum(errors) / len(errors) if errors else None
|
|
489
|
+
rmse = math.sqrt(sum(error * error for error in errors) / len(errors)) if errors else None
|
|
490
|
+
bias_mcse = None
|
|
491
|
+
rmse_mcse = None
|
|
492
|
+
if len(errors) >= 2:
|
|
493
|
+
bias_mcse = math.sqrt(sum((error - bias) ** 2 for error in errors)
|
|
494
|
+
/ ((len(errors) - 1) * len(errors)))
|
|
495
|
+
mean_square = rmse * rmse
|
|
496
|
+
squared_mcse = math.sqrt(sum((error * error - mean_square) ** 2 for error in errors)
|
|
497
|
+
/ ((len(errors) - 1) * len(errors)))
|
|
498
|
+
rmse_mcse = squared_mcse / (2 * rmse) if rmse else None
|
|
499
|
+
all_intervals = [interval for _, interval in observations if interval is not None]
|
|
500
|
+
all_degenerate = sum(lower == upper for lower, upper in all_intervals)
|
|
501
|
+
sign_eligible = reference is not None and reference != 0
|
|
502
|
+
sign_detected = sum((lower > 0 if reference > 0 else upper < 0)
|
|
503
|
+
for lower, upper in all_intervals) if sign_eligible else 0
|
|
504
|
+
return {
|
|
505
|
+
"reference": reference,
|
|
506
|
+
"scored_estimates": len(errors),
|
|
507
|
+
"bias": bias, "bias_mcse": bias_mcse, "rmse": rmse, "rmse_mcse": rmse_mcse,
|
|
508
|
+
"rmse_mcse_method": "delta method over independent replicate squared errors",
|
|
509
|
+
"interval_coverage": wilson_rate(covered, len(intervals), confidence),
|
|
510
|
+
"available_estimates": len(observations),
|
|
511
|
+
"available_intervals": len(all_intervals),
|
|
512
|
+
"mean_interval_width": (sum(upper-lower for lower, upper in all_intervals) / len(all_intervals)
|
|
513
|
+
if all_intervals else None),
|
|
514
|
+
"reference_sign_detection": wilson_rate(sign_detected, len(all_intervals) if sign_eligible else 0,
|
|
515
|
+
confidence),
|
|
516
|
+
"degenerate_intervals": all_degenerate,
|
|
517
|
+
"degenerate_interval_rate": wilson_rate(all_degenerate, len(all_intervals), confidence),
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _refusal_reason(trial, method, estimate):
|
|
522
|
+
if estimate is None:
|
|
523
|
+
operations = {error.operation for error in trial.errors}
|
|
524
|
+
return "factual_failure" if f"{method}_factual" in operations else "method_failure"
|
|
525
|
+
suffix = "samples" if method == "contrastive" else "pairs"
|
|
526
|
+
if method == "contrastive" and estimate["factual_bad"] is None:
|
|
527
|
+
return "unknown_factual"
|
|
528
|
+
for name in ("unstarted", "failed", "invalid", "unknown"):
|
|
529
|
+
if estimate.get(f"{name}_{suffix}", 0):
|
|
530
|
+
return name
|
|
531
|
+
return "unavailable"
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _endpoint_refusal_reason(trial, endpoint):
|
|
535
|
+
if endpoint is None:
|
|
536
|
+
return _refusal_reason(trial, "shapley", None)
|
|
537
|
+
for name in ("unstarted", "failed", "invalid", "unknown"):
|
|
538
|
+
if (endpoint["empty_coverage"].get(name, 0)
|
|
539
|
+
or endpoint["full_coverage"].get(name, 0)):
|
|
540
|
+
return name
|
|
541
|
+
return "unavailable"
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def analyze_calibration(result: CalibrationResult) -> dict:
|
|
545
|
+
"""Score production results against external analytic fixture references.
|
|
546
|
+
|
|
547
|
+
Numerical coverage uses eligible, available intervals, with their explicit
|
|
548
|
+
denominator. Refusal rates use every predeclared replicate. A target with
|
|
549
|
+
unavailable analytic truth is reported as ineligible, never scored as zero.
|
|
550
|
+
Counts are grouped by mechanism/target/position rather than pooling
|
|
551
|
+
correlated decisions or two targets from one reused set of branch draws.
|
|
552
|
+
"""
|
|
553
|
+
if not isinstance(result, CalibrationResult):
|
|
554
|
+
raise TypeError("result must be a CalibrationResult")
|
|
555
|
+
schedule = predeclare_calibration(result.plan)
|
|
556
|
+
if (len(result.trials) != len(schedule)
|
|
557
|
+
or any(trial.seed_plan != planned for trial, planned in zip(result.trials, schedule))):
|
|
558
|
+
raise ValueError("Calibration results must retain every trial in the predeclared order")
|
|
559
|
+
estimate_rows = []
|
|
560
|
+
endpoint_rows = []
|
|
561
|
+
locus_rows = []
|
|
562
|
+
branch_rows = []
|
|
563
|
+
for name in result.plan.mechanisms:
|
|
564
|
+
trials = tuple(trial for trial in result.trials if trial.seed_plan.mechanism == name)
|
|
565
|
+
analytic = _mechanism(name).analytic_reference()
|
|
566
|
+
for method in ("contrastive", "shapley"):
|
|
567
|
+
statuses = {status: 0 for status in ("completed", "invalid", "failed", "unstarted")}
|
|
568
|
+
outcomes = {target: {"known": 0, "unknown": 0} for target in result.plan.targets}
|
|
569
|
+
errors = {}
|
|
570
|
+
for trial in trials:
|
|
571
|
+
receipt = getattr(trial, method)
|
|
572
|
+
if receipt is None:
|
|
573
|
+
statuses["unstarted"] += getattr(trial.seed_plan, f"{method}_branches")
|
|
574
|
+
continue
|
|
575
|
+
for branch in receipt["branches"]:
|
|
576
|
+
statuses[branch["status"]] += 1
|
|
577
|
+
if branch["error_type"] is not None:
|
|
578
|
+
label = branch["error_type"]
|
|
579
|
+
errors[label] = errors.get(label, 0) + 1
|
|
580
|
+
if branch["status"] == "completed":
|
|
581
|
+
for target in result.plan.targets:
|
|
582
|
+
label = ("unknown" if branch["target_values"][target] is None else "known")
|
|
583
|
+
outcomes[target][label] += 1
|
|
584
|
+
branch_rows.append({"mechanism": name, "method": method,
|
|
585
|
+
"planned_branches": sum(statuses.values()),
|
|
586
|
+
"status_counts": statuses, "completed_target_outcomes": outcomes,
|
|
587
|
+
"error_type_counts": errors})
|
|
588
|
+
if method == "shapley":
|
|
589
|
+
mechanism = _mechanism(name)
|
|
590
|
+
q = mechanism.retry_probability
|
|
591
|
+
retry = mechanism.factual_operation == "retry"
|
|
592
|
+
conditional = []
|
|
593
|
+
for trial in trials:
|
|
594
|
+
if trial.shapley is None:
|
|
595
|
+
conditional.append(None)
|
|
596
|
+
else:
|
|
597
|
+
exponent = result.plan.samples_per_eval * sum(
|
|
598
|
+
abs(order.index(1) - order.index(2))
|
|
599
|
+
for order in trial.shapley["permutations"]
|
|
600
|
+
) if retry else 0
|
|
601
|
+
conditional.append(q**exponent)
|
|
602
|
+
marginal = ((2 * q**result.plan.samples_per_eval
|
|
603
|
+
+ q**(2 * result.plan.samples_per_eval)) / 3) ** result.plan.pairs
|
|
604
|
+
branch_rows[-1]["nominal_structural_path_availability"] = {
|
|
605
|
+
"probability": marginal if retry else 1.,
|
|
606
|
+
"given_permutations": conditional,
|
|
607
|
+
"conditions": "ideal independent Bernoulli draws; excludes budget and target unknownness",
|
|
608
|
+
"meaning": "all held positions reached, not an analytically defined Shapley game",
|
|
609
|
+
}
|
|
610
|
+
for target in result.plan.targets:
|
|
611
|
+
truth = analytic["targets"][target]
|
|
612
|
+
for method in ("contrastive", "shapley"):
|
|
613
|
+
references = truth["contrastive_effects" if method == "contrastive" else
|
|
614
|
+
"shapley_values"]
|
|
615
|
+
null_positions = truth[f"{method}_null_positions"]
|
|
616
|
+
for position in range(analytic["decision_count"]):
|
|
617
|
+
reference = references[position] if references is not None else None
|
|
618
|
+
eligible = reference is not None
|
|
619
|
+
if method == "shapley" and not truth["shapley_eligible"]:
|
|
620
|
+
reference = None
|
|
621
|
+
eligible = False
|
|
622
|
+
observations = []
|
|
623
|
+
estimated = 0
|
|
624
|
+
exclusions = 0
|
|
625
|
+
interval_count = 0
|
|
626
|
+
reasons = {}
|
|
627
|
+
for trial in trials:
|
|
628
|
+
method_result = getattr(trial, method)
|
|
629
|
+
estimate = None if method_result is None else next(
|
|
630
|
+
(entry for entry in method_result["results"]
|
|
631
|
+
if entry["target"] == target and entry["decision_index"] == position), None,
|
|
632
|
+
)
|
|
633
|
+
if estimate is not None and estimate["status"] == "estimated":
|
|
634
|
+
estimated += 1
|
|
635
|
+
value = estimate["effect" if method == "contrastive" else "shapley_value"]
|
|
636
|
+
observations.append((value, estimate["confidence_interval"]))
|
|
637
|
+
interval = estimate["confidence_interval"]
|
|
638
|
+
interval_count += interval is not None
|
|
639
|
+
if (interval is not None and reference == 0
|
|
640
|
+
and (interval[0] > 0 or interval[1] < 0)):
|
|
641
|
+
exclusions += 1
|
|
642
|
+
else:
|
|
643
|
+
reason = _refusal_reason(trial, method, estimate)
|
|
644
|
+
reasons[reason] = reasons.get(reason, 0) + 1
|
|
645
|
+
row = {
|
|
646
|
+
"mechanism": name, "method": method, "target": target,
|
|
647
|
+
"decision_index": position, "planned_trials": len(trials),
|
|
648
|
+
"estimated_trials": estimated, "refused_trials": len(trials) - estimated,
|
|
649
|
+
"reference_eligible_trials": len(trials) if eligible else 0,
|
|
650
|
+
"reference_ineligible_trials": 0 if eligible else len(trials),
|
|
651
|
+
"reference_reason": None if eligible else truth["reasons"][method],
|
|
652
|
+
"refusal_reason_counts": reasons,
|
|
653
|
+
"availability_rate": wilson_rate(estimated, len(trials), result.plan.confidence),
|
|
654
|
+
"refusal_rate": wilson_rate(len(trials) - estimated, len(trials),
|
|
655
|
+
result.plan.confidence),
|
|
656
|
+
**_numeric_metrics(observations, reference, result.plan.confidence),
|
|
657
|
+
"analytic_null_position": position in null_positions,
|
|
658
|
+
"zero_reference_interval_exclusion": wilson_rate(
|
|
659
|
+
exclusions, interval_count if reference == 0 else 0,
|
|
660
|
+
result.plan.confidence,
|
|
661
|
+
),
|
|
662
|
+
"false_distractor_flags": exclusions if position in null_positions else None,
|
|
663
|
+
}
|
|
664
|
+
row["eligible_unscored_trials"] = row["reference_eligible_trials"] - row["scored_estimates"]
|
|
665
|
+
row["interval_coverage_including_refusals"] = wilson_rate(
|
|
666
|
+
row["interval_coverage"]["numerator"], len(trials) if eligible else 0,
|
|
667
|
+
result.plan.confidence,
|
|
668
|
+
)
|
|
669
|
+
row["coverage_composite_meaning"] = "available estimate and truth-covered interval / all eligible planned trials"
|
|
670
|
+
estimate_rows.append(row)
|
|
671
|
+
|
|
672
|
+
endpoints = []
|
|
673
|
+
endpoint_estimated = 0
|
|
674
|
+
residuals = []
|
|
675
|
+
coefficient_sums = []
|
|
676
|
+
endpoint_reasons = {}
|
|
677
|
+
for trial in trials:
|
|
678
|
+
if trial.shapley is None:
|
|
679
|
+
reason = _endpoint_refusal_reason(trial, None)
|
|
680
|
+
endpoint_reasons[reason] = endpoint_reasons.get(reason, 0) + 1
|
|
681
|
+
continue
|
|
682
|
+
endpoint = next(entry for entry in trial.shapley["endpoint_checks"]
|
|
683
|
+
if entry["target"] == target)
|
|
684
|
+
if endpoint["status"] == "estimated":
|
|
685
|
+
endpoint_estimated += 1
|
|
686
|
+
endpoints.append((endpoint["endpoint_difference"], endpoint["confidence_interval"]))
|
|
687
|
+
else:
|
|
688
|
+
reason = _endpoint_refusal_reason(trial, endpoint)
|
|
689
|
+
endpoint_reasons[reason] = endpoint_reasons.get(reason, 0) + 1
|
|
690
|
+
if endpoint["coefficient_sum"] is not None:
|
|
691
|
+
coefficient_sums.append((endpoint["coefficient_sum"], None))
|
|
692
|
+
if endpoint["discrepancy"] is not None:
|
|
693
|
+
residuals.append(endpoint["discrepancy"])
|
|
694
|
+
endpoint_eligible = truth["endpoint_eligible"]
|
|
695
|
+
endpoint_reference = truth["endpoint_difference"] if endpoint_eligible else None
|
|
696
|
+
endpoint_row = {
|
|
697
|
+
"mechanism": name, "target": target, "planned_trials": len(trials),
|
|
698
|
+
"estimated_trials": endpoint_estimated,
|
|
699
|
+
"refused_trials": len(trials) - endpoint_estimated,
|
|
700
|
+
"reference_eligible_trials": len(trials) if endpoint_eligible else 0,
|
|
701
|
+
"reference_ineligible_trials": 0 if endpoint_eligible else len(trials),
|
|
702
|
+
"reference_reason": truth["reasons"]["endpoint"],
|
|
703
|
+
"refusal_reason_counts": endpoint_reasons,
|
|
704
|
+
"availability_rate": wilson_rate(endpoint_estimated, len(trials), result.plan.confidence),
|
|
705
|
+
"refusal_rate": wilson_rate(len(trials) - endpoint_estimated, len(trials),
|
|
706
|
+
result.plan.confidence),
|
|
707
|
+
**_numeric_metrics(endpoints, endpoint_reference, result.plan.confidence),
|
|
708
|
+
"coefficient_sum": _numeric_metrics(coefficient_sums,
|
|
709
|
+
endpoint_reference if truth["shapley_eligible"] else None,
|
|
710
|
+
result.plan.confidence),
|
|
711
|
+
"efficiency_residual": {
|
|
712
|
+
"available_trials": len(residuals),
|
|
713
|
+
"mean": sum(residuals) / len(residuals) if residuals else None,
|
|
714
|
+
"root_mean_square": (math.sqrt(sum(value * value for value in residuals)
|
|
715
|
+
/ len(residuals)) if residuals else None),
|
|
716
|
+
"confidence_interval": None,
|
|
717
|
+
},
|
|
718
|
+
"efficiency_residual_inference": "descriptive discrepancy; no calibrated interval or test",
|
|
719
|
+
}
|
|
720
|
+
endpoint_row["eligible_unscored_trials"] = (
|
|
721
|
+
endpoint_row["reference_eligible_trials"] - endpoint_row["scored_estimates"]
|
|
722
|
+
)
|
|
723
|
+
endpoint_row["interval_coverage_including_refusals"] = wilson_rate(
|
|
724
|
+
endpoint_row["interval_coverage"]["numerator"],
|
|
725
|
+
len(trials) if endpoint_eligible else 0, result.plan.confidence,
|
|
726
|
+
)
|
|
727
|
+
endpoint_row["coverage_composite_meaning"] = "available endpoint and truth-covered interval / all eligible planned trials"
|
|
728
|
+
endpoint_rows.append(endpoint_row)
|
|
729
|
+
|
|
730
|
+
selections = {"refused": 0, "no_rescue": 0}
|
|
731
|
+
for position in range(analytic["decision_count"]):
|
|
732
|
+
selections[str(position)] = 0
|
|
733
|
+
matches = 0
|
|
734
|
+
locus_eligible = truth["contrastive_eligible"]
|
|
735
|
+
expected = truth["latest_population_rescue"] if locus_eligible else None
|
|
736
|
+
for trial in trials:
|
|
737
|
+
locus = None if trial.contrastive is None else next(
|
|
738
|
+
entry for entry in trial.contrastive["loci"] if entry["target"] == target
|
|
739
|
+
)
|
|
740
|
+
key = ("refused" if locus is None or locus["status"] == "refused" else
|
|
741
|
+
"no_rescue" if locus["status"] == "no_rescue" else str(locus["decision_index"]))
|
|
742
|
+
selections[key] += 1
|
|
743
|
+
if locus_eligible and key != "refused":
|
|
744
|
+
matches += ((key == "no_rescue" and expected is None)
|
|
745
|
+
or key == str(expected))
|
|
746
|
+
locus_rows.append({
|
|
747
|
+
"mechanism": name, "target": target, "planned_trials": len(trials),
|
|
748
|
+
"reference_eligible_trials": len(trials) if locus_eligible else 0,
|
|
749
|
+
"reference_reason": truth["reasons"]["contrastive"],
|
|
750
|
+
"latest_population_rescue": expected,
|
|
751
|
+
"selection_counts": selections,
|
|
752
|
+
"population_rule_match_including_refusals": wilson_rate(
|
|
753
|
+
matches, len(trials) if locus_eligible else 0, result.plan.confidence,
|
|
754
|
+
),
|
|
755
|
+
"population_rule_match_given_available": wilson_rate(
|
|
756
|
+
matches, len(trials) - selections["refused"] if locus_eligible else 0,
|
|
757
|
+
result.plan.confidence,
|
|
758
|
+
),
|
|
759
|
+
"interpretation": "latest position with strictly negative population effect; nominal selection rule",
|
|
760
|
+
})
|
|
761
|
+
return {
|
|
762
|
+
"planned_trials": len(schedule), "retained_trials": len(result.trials),
|
|
763
|
+
"factual_failures": sum(trial.factual[method]["status"] != "completed"
|
|
764
|
+
for trial in result.trials for method in ("contrastive", "shapley")),
|
|
765
|
+
"actual_policy_calls": {
|
|
766
|
+
"factual": sum(trial.factual_calls for trial in result.trials),
|
|
767
|
+
"contrastive": sum(trial.contrastive_calls for trial in result.trials),
|
|
768
|
+
"shapley": sum(trial.shapley_calls for trial in result.trials),
|
|
769
|
+
"total": sum(trial.policy_calls for trial in result.trials),
|
|
770
|
+
},
|
|
771
|
+
"paired_factual_disagreements": sum(
|
|
772
|
+
trial.factual["contrastive"]["trace_sha256"] is not None
|
|
773
|
+
and trial.factual["shapley"]["trace_sha256"] is not None
|
|
774
|
+
and trial.factual["contrastive"]["trace_sha256"] != trial.factual["shapley"]["trace_sha256"]
|
|
775
|
+
for trial in result.trials),
|
|
776
|
+
"estimates": estimate_rows, "endpoints": endpoint_rows, "loci": locus_rows,
|
|
777
|
+
"branches": branch_rows,
|
|
778
|
+
"limits": [
|
|
779
|
+
"Numerical metrics condition on analytic eligibility and available estimates; every denominator is shown.",
|
|
780
|
+
"Refusal and locus match rates retain all planned replicates rather than treating refusals as successes.",
|
|
781
|
+
"Wilson intervals are nominal binomial uncertainty over replicates, not validation of seed independence.",
|
|
782
|
+
"Positions and reused targets are reported separately; their correlated intervals are not pooled.",
|
|
783
|
+
"The factual trajectory is deliberately planted; calibration is conditional on that capture and mechanism.",
|
|
784
|
+
"The nominal latest rescue rule is not a unique-cause or population accuracy certificate.",
|
|
785
|
+
"Synthetic mechanism coverage does not establish live-model generalization or calibrated LLM uncertainty.",
|
|
786
|
+
],
|
|
787
|
+
}
|