Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/research/NEXT_100_PR_MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,6 @@ Track and publish structured comparisons between pipeline selections and cheap b
|----|------|----------------|----------|
| Y1 | Add cheap baseline comparison record schema (CBR-) (complete). — src/openamp_foundry/evidence/cheap_baseline_comparison_record.py: VALID_CBR_VERDICTS (4: pipeline_superior/tied/baseline_superior/insufficient_data), VALID_BASELINE_METHODS (5: charge_only_rank/length_only_rank/random_selection/charge_length_combined/hydrophobicity_only_rank), VALID_CBR_METRICS (4: auroc/hit_rate/top_k_precision/ndcg), SUPERIORITY_THRESHOLD=0.05, MIN_SAMPLE_SIZE=5; metric_delta auto-computed; verdict auto-derived; dry_lab_only=True; 62 tests. | Structured record: pipeline metric vs charge-only/random/length-only baseline; pre-registered threshold; verdict (pipeline_superior/tied/baseline_superior/insufficient_data). Forces every performance claim to cite the baseline it beat. | C |
| Y2 | Add feature importance audit schema (FIA-) (complete). — src/openamp_foundry/evidence/feature_importance_audit.py: VALID_FIA_VERDICTS (5), VALID_FEATURE_IMPORTANCE_LEVELS (4), VALID_AUDIT_FEATURES (8), DOMINATION_THRESHOLD=0.80; importance_level auto-assigned; top_feature/charge_score/length_score auto-extracted; verdict: charge_dominated when charge_explains_fraction>=0.80; dry_lab_only=True; 50 tests. | Documents which features drove selections and whether charge/length alone explains the result; anti-cheap-explanation gate. | C |
| Y3 | Add selection diversity audit schema (SDA-). | Tracks sequence diversity of selected panel vs random draw; detects proximity-driven selection masquerading as discovery; required before any novelty claim. | C |
| Y3 | Add selection diversity audit schema (SDA-) (complete). — src/openamp_foundry/evidence/selection_diversity_audit.py: VALID_SDA_VERDICTS (4: diverse_panel/moderately_diverse/proximity_driven/insufficient_data), VALID_DIVERSITY_METRICS (4), DIVERSE_PANEL_THRESHOLD=0.10, PROXIMITY_DRIVEN_THRESHOLD=-0.05, MIN_PANEL_SIZE=3; diversity_delta auto-computed; verdict: diverse_panel (delta>=0.10), proximity_driven (delta<=-0.05); dry_lab_only=True; 46 tests. | Tracks sequence diversity of selected panel vs random draw; detects proximity-driven selection masquerading as discovery; required before any novelty claim. | C |
| Y4 | Add pipeline maturity certificate schema (PMC-). | Aggregates CBR/FIA/SDA results into A/B/C/D maturity grade; anchors pre-registration; prevents retroactive interpretation of results. | C |
| Y5 | Add Phase Y accountability gate (YAG-). | Top-level gate asserting CBR+FIA+SDA+PMC all present; verdict: accountability_verified/accountability_partial/accountability_not_established; closes Phase Y; no external pilot claim is credible without passing this gate. | C |
142 changes: 142 additions & 0 deletions src/openamp_foundry/evidence/selection_diversity_audit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"""SDA- selection diversity audit schema.

Tracks sequence diversity of selected candidate panel vs random draw from
the same pool. Detects proximity-driven selection masquerading as discovery.
Required before any novelty claim: if selected candidates cluster as tightly
as random, the selection is not adding diversity value.
"""

from __future__ import annotations

from dataclasses import dataclass

VALID_SDA_VERDICTS: frozenset[str] = frozenset({
"diverse_panel",
"moderately_diverse",
"proximity_driven",
"insufficient_data",
})

VALID_DIVERSITY_METRICS: frozenset[str] = frozenset({
"mean_pairwise_identity",
"mean_pairwise_distance",
"clustering_coefficient",
"effective_sequence_count",
})

DIVERSE_PANEL_THRESHOLD: float = 0.10
PROXIMITY_DRIVEN_THRESHOLD: float = -0.05
MIN_PANEL_SIZE: int = 3


@dataclass
class SelectionDiversityAudit:
sda_id: str
pipeline_version: str
diversity_metric: str
n_selected: int
panel_diversity_score: float
random_baseline_diversity_score: float
diversity_delta: float
sda_verdict: str
dry_lab_only: bool
limitations: list[str]
created_at: str


def validate_selection_diversity_audit(sda: SelectionDiversityAudit) -> None:
if not sda.sda_id.startswith("SDA-"):
raise ValueError(f"sda_id must start with 'SDA-': {sda.sda_id!r}")
if not sda.pipeline_version:
raise ValueError("pipeline_version must be non-empty")
if sda.diversity_metric not in VALID_DIVERSITY_METRICS:
raise ValueError(
f"diversity_metric {sda.diversity_metric!r} not in VALID_DIVERSITY_METRICS"
)
if sda.n_selected < 0:
raise ValueError("n_selected must be non-negative")
expected_delta = round(
sda.panel_diversity_score - sda.random_baseline_diversity_score, 6
)
if abs(sda.diversity_delta - expected_delta) > 1e-5:
raise ValueError(
f"diversity_delta mismatch: expected {expected_delta}, got {sda.diversity_delta}"
)
if sda.sda_verdict not in VALID_SDA_VERDICTS:
raise ValueError(
f"sda_verdict {sda.sda_verdict!r} not in VALID_SDA_VERDICTS"
)
if not sda.dry_lab_only:
raise ValueError("dry_lab_only must be True")
if not sda.limitations:
raise ValueError("limitations must be non-empty")
if not sda.created_at:
raise ValueError("created_at must be non-empty")


def _compute_verdict(
n_selected: int,
delta: float,
) -> str:
if n_selected < MIN_PANEL_SIZE:
return "insufficient_data"
if delta >= DIVERSE_PANEL_THRESHOLD:
return "diverse_panel"
if delta <= PROXIMITY_DRIVEN_THRESHOLD:
return "proximity_driven"
return "moderately_diverse"


def build_selection_diversity_audit(
*,
sda_id: str,
pipeline_version: str,
diversity_metric: str,
n_selected: int,
panel_diversity_score: float,
random_baseline_diversity_score: float,
limitations: list[str],
created_at: str,
) -> SelectionDiversityAudit:
"""Build a SelectionDiversityAudit.

diversity_delta = panel_diversity_score - random_baseline_diversity_score (auto-computed).
For mean_pairwise_distance: higher score = more diverse.
For mean_pairwise_identity: lower score = more diverse (so delta>0 means less identity = more diversity).
Verdict: diverse_panel (delta>=0.10), proximity_driven (delta<=-0.05), else moderately_diverse.
"""
delta = round(panel_diversity_score - random_baseline_diversity_score, 6)
verdict = _compute_verdict(n_selected, delta)
sda = SelectionDiversityAudit(
sda_id=sda_id,
pipeline_version=pipeline_version,
diversity_metric=diversity_metric,
n_selected=n_selected,
panel_diversity_score=float(panel_diversity_score),
random_baseline_diversity_score=float(random_baseline_diversity_score),
diversity_delta=delta,
sda_verdict=verdict,
dry_lab_only=True,
limitations=limitations,
created_at=created_at,
)
validate_selection_diversity_audit(sda)
return sda


def format_selection_diversity_audit(sda: SelectionDiversityAudit) -> str:
lines = [
f"Selection Diversity Audit — {sda.sda_id}",
f"Pipeline: {sda.pipeline_version}",
f"Metric: {sda.diversity_metric} | Verdict: {sda.sda_verdict}",
f"Panel diversity: {sda.panel_diversity_score:.4f}",
f"Random baseline diversity: {sda.random_baseline_diversity_score:.4f}",
f"Delta: {sda.diversity_delta:+.4f} "
f"(diverse>=+{DIVERSE_PANEL_THRESHOLD}, "
f"proximity<={PROXIMITY_DRIVEN_THRESHOLD})",
f"Candidates selected: {sda.n_selected}",
f"Created: {sda.created_at}",
f"Limitations: {'; '.join(sda.limitations)}",
f"dry_lab_only: {sda.dry_lab_only}",
]
return "\n".join(lines)
Loading
Loading