| """Calibration and abstention policy for DataForge repairs.
|
|
|
| This is the product differentiator made measurable: DataForge auto-applies a
|
| repair only when the proposing detector's calibrated confidence clears a
|
| threshold fit to a target precision (default 0.95). Below that, the repair is
|
| *proposed for review*, never silently applied. The tool is therefore broad and
|
| safe at once - coverage rises while auto-apply precision stays high.
|
|
|
| The policy is advisory to the detection/repair stack: a repairer marks a
|
| proposed fix's confidence, and :meth:`AbstentionPolicy.action_for` decides
|
| whether it is eligible for auto-apply. The existing SMT verifier and safety
|
| constitution remain hard gates underneath - abstention only ever makes the
|
| system *more* conservative, never less.
|
|
|
| Thresholds are fit empirically (:func:`fit_thresholds`) against labeled
|
| ``(confidence, was_correct)`` samples from a benchmark run, so the auto-apply
|
| boundary is grounded in measured precision, not guesswork.
|
| """
|
|
|
| from __future__ import annotations
|
|
|
| from typing import Literal
|
|
|
| from pydantic import BaseModel, Field
|
|
|
| __all__ = [
|
| "AbstentionAction",
|
| "AbstentionPolicy",
|
| "corrector_default_policy",
|
| "default_policy",
|
| "fit_thresholds",
|
| "policy_from_corrector_samples",
|
| "severity_for_action",
|
| ]
|
|
|
| AbstentionAction = Literal["auto_apply", "review"]
|
|
|
|
|
|
|
|
|
|
|
| _DEFAULT_THRESHOLDS: dict[str, float] = {
|
| "decimal_shift": 0.70,
|
| "fd_violation": 0.80,
|
| "type_mismatch": 0.80,
|
| "format_violation": 0.90,
|
| "categorical_normalization": 0.90,
|
| "missing_value": 1.01,
|
| "outlier": 1.01,
|
| "duplicate_row": 1.01,
|
| }
|
|
|
|
|
| class AbstentionPolicy(BaseModel):
|
| """Maps an issue's calibrated confidence to an auto-apply / review decision.
|
|
|
| Args:
|
| target_precision: The precision the auto-apply thresholds were fit for.
|
| auto_apply_thresholds: Per-issue-type minimum confidence to auto-apply.
|
| default_threshold: Threshold for issue types not listed.
|
| """
|
|
|
| target_precision: float = Field(default=0.95, ge=0.0, le=1.0)
|
| auto_apply_thresholds: dict[str, float] = Field(default_factory=dict)
|
| default_threshold: float = Field(default=0.90, ge=0.0, le=1.01)
|
|
|
| model_config = {"frozen": True}
|
|
|
| def threshold_for(self, issue_type: str) -> float:
|
| """Return the auto-apply confidence threshold for an issue type."""
|
| return self.auto_apply_thresholds.get(issue_type, self.default_threshold)
|
|
|
| def action_for(self, issue_type: str, confidence: float) -> AbstentionAction:
|
| """Decide whether a proposed fix may auto-apply or must be reviewed.
|
|
|
| Args:
|
| issue_type: The detector issue type.
|
| confidence: The proposing detector's calibrated confidence in [0, 1].
|
|
|
| Returns:
|
| ``"auto_apply"`` if confidence clears the threshold, else ``"review"``.
|
| """
|
| return "auto_apply" if confidence >= self.threshold_for(issue_type) else "review"
|
|
|
|
|
| def default_policy() -> AbstentionPolicy:
|
| """Return the conservative default abstention policy."""
|
| return AbstentionPolicy(
|
| target_precision=0.95,
|
| auto_apply_thresholds=dict(_DEFAULT_THRESHOLDS),
|
| default_threshold=0.90,
|
| )
|
|
|
|
|
| def severity_for_action(action: AbstentionAction) -> str:
|
| """Map an abstention action to the detector severity label."""
|
| return "safe" if action == "auto_apply" else "review"
|
|
|
|
|
| def corrector_default_policy() -> AbstentionPolicy:
|
| """Return the honest default policy for the LLM corrector: propose-not-apply.
|
|
|
| Until per-class thresholds are fit from measured corrector correctness (see
|
| :func:`policy_from_corrector_samples`), every corrector proposal is surfaced
|
| as a human-review suggestion and never auto-applied. The high target
|
| precision is recorded so the intent of the boundary is explicit.
|
| """
|
| return AbstentionPolicy(
|
| target_precision=0.95,
|
| auto_apply_thresholds={},
|
| default_threshold=1.01,
|
| )
|
|
|
|
|
| def policy_from_corrector_samples(
|
| samples_by_class: dict[str, list[tuple[float, bool]]],
|
| *,
|
| target_precision: float = 0.95,
|
| min_support: int = 10,
|
| ) -> AbstentionPolicy:
|
| """Build a corrector abstention policy from labeled correctness samples.
|
|
|
| Fits a per-class auto-apply threshold to the precision floor (reusing
|
| :func:`fit_thresholds`) and keeps the propose-not-apply default for any
|
| class that is unlisted, low-support, or cannot reach the floor. The result
|
| auto-applies only where measured precision justifies it; everything else
|
| becomes a review suggestion.
|
| """
|
| thresholds = fit_thresholds(
|
| samples_by_class,
|
| target_precision=target_precision,
|
| min_support=min_support,
|
| )
|
| return AbstentionPolicy(
|
| target_precision=target_precision,
|
| auto_apply_thresholds=thresholds,
|
| default_threshold=1.01,
|
| )
|
|
|
|
|
| def fit_thresholds(
|
| samples_by_class: dict[str, list[tuple[float, bool]]],
|
| *,
|
| target_precision: float = 0.95,
|
| min_support: int = 10,
|
| ) -> dict[str, float]:
|
| """Fit per-class auto-apply confidence thresholds to a target precision.
|
|
|
| For each class, finds the lowest confidence threshold ``t`` such that the
|
| predictions with ``confidence >= t`` achieve at least ``target_precision``.
|
| This maximizes recall subject to the precision floor. Classes with too few
|
| samples, or that cannot reach the target at any threshold, get ``1.01``
|
| (never auto-apply) - the honest, conservative default.
|
|
|
| Args:
|
| samples_by_class: ``{issue_type: [(confidence, was_correct), ...]}``.
|
| target_precision: Minimum precision the threshold must guarantee.
|
| min_support: Minimum labeled samples required to fit a class.
|
|
|
| Returns:
|
| ``{issue_type: threshold}``. A threshold of 1.01 means detection-only.
|
| """
|
| thresholds: dict[str, float] = {}
|
| for issue_type, samples in samples_by_class.items():
|
| if len(samples) < min_support:
|
| thresholds[issue_type] = 1.01
|
| continue
|
|
|
|
|
|
|
|
|
| ordered = sorted(samples, key=lambda s: s[0], reverse=True)
|
| best_threshold = 1.01
|
| applied = 0
|
| correct = 0
|
| index = 0
|
| n = len(ordered)
|
| while index < n:
|
| confidence = ordered[index][0]
|
| while index < n and ordered[index][0] == confidence:
|
| applied += 1
|
| correct += 1 if ordered[index][1] else 0
|
| index += 1
|
| if correct / applied >= target_precision:
|
| best_threshold = confidence
|
| thresholds[issue_type] = round(best_threshold, 4)
|
| return thresholds
|
|
|