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.
Files changed (41) hide show
  1. consequa_x/__init__.py +22 -0
  2. consequa_x/__main__.py +3 -0
  3. consequa_x/adapters/__init__.py +5 -0
  4. consequa_x/adapters/ollama.py +350 -0
  5. consequa_x/attribution.py +407 -0
  6. consequa_x/benchmark/__init__.py +1 -0
  7. consequa_x/benchmark/assessment_metrics.py +54 -0
  8. consequa_x/benchmark/calibration.py +787 -0
  9. consequa_x/benchmark/calibration_export.py +139 -0
  10. consequa_x/benchmark/calibration_mechanisms.py +266 -0
  11. consequa_x/benchmark/decision_coalition.py +254 -0
  12. consequa_x/benchmark/decision_contrastive.py +448 -0
  13. consequa_x/benchmark/decision_replay.py +329 -0
  14. consequa_x/benchmark/decision_shapley.py +628 -0
  15. consequa_x/benchmark/evaluation.py +187 -0
  16. consequa_x/benchmark/interaction_export.py +220 -0
  17. consequa_x/benchmark/interaction_mechanisms.py +192 -0
  18. consequa_x/benchmark/interval_comparison.py +276 -0
  19. consequa_x/benchmark/intervals.py +164 -0
  20. consequa_x/benchmark/payments.py +426 -0
  21. consequa_x/benchmark/policy_experiment.py +298 -0
  22. consequa_x/benchmark/policy_runner.py +234 -0
  23. consequa_x/benchmark/runner.py +241 -0
  24. consequa_x/benchmark/study.py +183 -0
  25. consequa_x/benchmark/targets.py +149 -0
  26. consequa_x/benchmark/templates.py +102 -0
  27. consequa_x/cli.py +1191 -0
  28. consequa_x/explanation.py +130 -0
  29. consequa_x/graph.py +142 -0
  30. consequa_x/models.py +532 -0
  31. consequa_x/policies.py +248 -0
  32. consequa_x/py.typed +0 -0
  33. consequa_x/schema.py +159 -0
  34. consequa_x/schemas/trace-v0.1.json +543 -0
  35. consequa_x/verification.py +192 -0
  36. consequa_x-0.1.0a2.dist-info/METADATA +303 -0
  37. consequa_x-0.1.0a2.dist-info/RECORD +41 -0
  38. consequa_x-0.1.0a2.dist-info/WHEEL +5 -0
  39. consequa_x-0.1.0a2.dist-info/entry_points.txt +2 -0
  40. consequa_x-0.1.0a2.dist-info/licenses/LICENSE +21 -0
  41. 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
+ }