Skip to content

Commit 640361b

Browse files
author
OpenCode
committed
feat: Phase Z Z1 family blindness challenge harness -- FBH- schema tracks per-family AUROC and panel representation, flags weak AMP classes excluded from selected panel (#xxxx)
1 parent ed3c77e commit 640361b

4 files changed

Lines changed: 595 additions & 1 deletion

File tree

‎docs/research/NEXT_100_PR_MAP.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,11 @@ Track and publish structured comparisons between pipeline selections and cheap b
305305
| 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 |
306306
| Y4 | Add pipeline maturity certificate schema (PMC-) (complete). — src/openamp_foundry/evidence/pipeline_maturity_certificate.py: VALID_PMC_GRADES (A-D), VALID_PMC_VERDICTS (4: pipeline_validated/pipeline_provisional/pipeline_unvalidated/insufficient_evidence), REQUIRED_PMC_COMPONENTS=(CBR,FIA,SDA); PMCComponentCheck helper; grade A (all 3 superior), B (2), C (1), D (0/none assessed); contributes_to_grade auto-derived from verdict; dry_lab_only=True; 54 tests. | Aggregates CBR/FIA/SDA results into A/B/C/D maturity grade; anchors pre-registration; prevents retroactive interpretation. | C |
307307
| Y5 | Add Phase Y accountability gate (YAG-) (complete). — src/openamp_foundry/evidence/phase_y_accountability_gate.py: REQUIRED_Y_COMPONENTS=(CBR,FIA,SDA,PMC), VALID_YAG_VERDICTS (3: accountability_verified/accountability_partial/accountability_not_established); YComponentCheck helper; verdict: accountability_verified (all 4), accountability_partial (2-3), accountability_not_established (0-1); artifact_id prefix-validated per component; dry_lab_only=True; 54 tests. Closes Phase Y. | Top-level gate asserting CBR+FIA+SDA+PMC all present; closes Phase Y; no external pilot claim is credible without passing this gate. | C |
308+
309+
## Phase Z — Per-family benchmark accountability
310+
311+
Make per-family performance gaps visible and machine-checkable, so the pipeline cannot hide weak AMP class coverage behind aggregate metrics.
312+
313+
| PR | Task | Why it matters | Review class |
314+
|---:|---|---|---|
315+
| Z1 | Add family blindness challenge harness schema (FBH-). | Per-family AUROC + panel representation check; flags when weak AMP classes (AUROC&lt;0.55) are excluded from selected panel; prevents aggregate-metric hiding of family blind spots. | C |
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
"""FBH- family blindness challenge harness schema.
2+
3+
Per-family benchmark performance record: flags when the pipeline
4+
underperforms on specific AMP families. Prevents aggregate-metric
5+
hiding of family-blind spots.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from dataclasses import dataclass
11+
12+
VALID_FBH_VERDICTS: frozenset[str] = frozenset({
13+
"all_families_represented",
14+
"weak_family_excluded",
15+
"insufficient_data",
16+
})
17+
18+
VALID_AUROC_GRADES: frozenset[str] = frozenset({
19+
"strong",
20+
"adequate",
21+
"weak",
22+
"not_evaluated",
23+
})
24+
25+
WEAK_FAMILY_AUROC_THRESHOLD: float = 0.55
26+
WEAK_FAMILY_PANEL_FLOOR: float = 0.10
27+
28+
29+
@dataclass
30+
class FamilyPerformanceEntry:
31+
family_name: str
32+
auroc: float
33+
n_candidates: int
34+
panel_count: int
35+
panel_fraction: float
36+
auroc_grade: str
37+
is_weak_class: bool
38+
39+
40+
@dataclass
41+
class FamilyBlingnessChallenge:
42+
fbh_id: str
43+
pipeline_version: str
44+
family_entries: list[FamilyPerformanceEntry]
45+
n_families_total: int
46+
n_weak_families: int
47+
n_excluded_weak_families: int
48+
excluded_weak_family_names: list[str]
49+
verdict: str
50+
dry_lab_only: bool
51+
limitations: list[str]
52+
created_at: str
53+
54+
55+
def _compute_auroc_grade(auroc: float) -> str:
56+
if auroc == -1.0:
57+
return "not_evaluated"
58+
if auroc >= 0.70:
59+
return "strong"
60+
if auroc >= 0.55:
61+
return "adequate"
62+
return "weak"
63+
64+
65+
def _compute_is_weak_class(auroc: float, n_candidates: int) -> bool:
66+
return auroc < WEAK_FAMILY_AUROC_THRESHOLD and n_candidates >= 3
67+
68+
69+
def _compute_verdict(family_entries: list[FamilyPerformanceEntry]) -> str:
70+
if len(family_entries) == 0:
71+
return "insufficient_data"
72+
for entry in family_entries:
73+
if entry.is_weak_class and entry.panel_fraction < WEAK_FAMILY_PANEL_FLOOR:
74+
return "weak_family_excluded"
75+
return "all_families_represented"
76+
77+
78+
def validate_family_blindness_challenge(fbh: FamilyBlingnessChallenge) -> None:
79+
if not fbh.fbh_id.startswith("FBH-"):
80+
raise ValueError(f"fbh_id must start with 'FBH-': {fbh.fbh_id!r}")
81+
if not fbh.pipeline_version:
82+
raise ValueError("pipeline_version must be non-empty")
83+
for entry in fbh.family_entries:
84+
if not (-1.0 <= entry.auroc <= 1.0):
85+
raise ValueError(f"auroc must be in [-1.0, 1.0]: {entry.auroc}")
86+
if entry.n_candidates < 1:
87+
raise ValueError(f"n_candidates must be >= 1: {entry.n_candidates}")
88+
if entry.panel_count < 0:
89+
raise ValueError(f"panel_count must be >= 0: {entry.panel_count}")
90+
if entry.panel_count > entry.n_candidates:
91+
raise ValueError(
92+
f"panel_count ({entry.panel_count}) cannot exceed "
93+
f"n_candidates ({entry.n_candidates})"
94+
)
95+
if fbh.n_families_total != len(fbh.family_entries):
96+
raise ValueError(
97+
f"n_families_total {fbh.n_families_total} != "
98+
f"len(family_entries) {len(fbh.family_entries)}"
99+
)
100+
expected_n_weak = sum(1 for e in fbh.family_entries if e.is_weak_class)
101+
if fbh.n_weak_families != expected_n_weak:
102+
raise ValueError(
103+
f"n_weak_families {fbh.n_weak_families} != computed {expected_n_weak}"
104+
)
105+
expected_excluded = [
106+
e.family_name
107+
for e in fbh.family_entries
108+
if e.is_weak_class and e.panel_fraction < WEAK_FAMILY_PANEL_FLOOR
109+
]
110+
if fbh.n_excluded_weak_families != len(expected_excluded):
111+
raise ValueError(
112+
f"n_excluded_weak_families {fbh.n_excluded_weak_families} != "
113+
f"computed {len(expected_excluded)}"
114+
)
115+
if fbh.excluded_weak_family_names != expected_excluded:
116+
raise ValueError("excluded_weak_family_names mismatch")
117+
if fbh.verdict not in VALID_FBH_VERDICTS:
118+
raise ValueError(f"verdict {fbh.verdict!r} not in VALID_FBH_VERDICTS")
119+
if fbh.verdict == "weak_family_excluded" and fbh.n_excluded_weak_families < 1:
120+
raise ValueError(
121+
"verdict weak_family_excluded but n_excluded_weak_families < 1"
122+
)
123+
if fbh.verdict == "all_families_represented" and fbh.n_excluded_weak_families != 0:
124+
raise ValueError(
125+
"verdict all_families_represented but n_excluded_weak_families != 0"
126+
)
127+
if not fbh.dry_lab_only:
128+
raise ValueError("dry_lab_only must be True")
129+
if not fbh.limitations:
130+
raise ValueError("limitations must be non-empty")
131+
if not fbh.created_at:
132+
raise ValueError("created_at must be non-empty")
133+
134+
135+
def build_family_blindness_challenge(
136+
*,
137+
fbh_id: str,
138+
pipeline_version: str,
139+
family_entry_dicts: list[dict],
140+
limitations: list[str],
141+
created_at: str,
142+
) -> FamilyBlingnessChallenge:
143+
"""Build a FamilyBlingnessChallenge.
144+
145+
family_entry_dicts: list of dicts with keys:
146+
family_name (str), auroc (float),
147+
n_candidates (int), panel_count (int)
148+
"""
149+
family_entries = []
150+
for d in family_entry_dicts:
151+
auroc = float(d["auroc"])
152+
n_candidates = int(d["n_candidates"])
153+
panel_count = int(d["panel_count"])
154+
panel_fraction = round(panel_count / n_candidates, 6) if n_candidates > 0 else 0.0
155+
auroc_grade = _compute_auroc_grade(auroc)
156+
is_weak_class = _compute_is_weak_class(auroc, n_candidates)
157+
family_entries.append(
158+
FamilyPerformanceEntry(
159+
family_name=d["family_name"],
160+
auroc=auroc,
161+
n_candidates=n_candidates,
162+
panel_count=panel_count,
163+
panel_fraction=panel_fraction,
164+
auroc_grade=auroc_grade,
165+
is_weak_class=is_weak_class,
166+
)
167+
)
168+
n_families_total = len(family_entries)
169+
n_weak_families = sum(1 for e in family_entries if e.is_weak_class)
170+
excluded_names = [
171+
e.family_name
172+
for e in family_entries
173+
if e.is_weak_class and e.panel_fraction < WEAK_FAMILY_PANEL_FLOOR
174+
]
175+
n_excluded_weak_families = len(excluded_names)
176+
verdict = _compute_verdict(family_entries)
177+
fbh = FamilyBlingnessChallenge(
178+
fbh_id=fbh_id,
179+
pipeline_version=pipeline_version,
180+
family_entries=family_entries,
181+
n_families_total=n_families_total,
182+
n_weak_families=n_weak_families,
183+
n_excluded_weak_families=n_excluded_weak_families,
184+
excluded_weak_family_names=excluded_names,
185+
verdict=verdict,
186+
dry_lab_only=True,
187+
limitations=limitations,
188+
created_at=created_at,
189+
)
190+
validate_family_blindness_challenge(fbh)
191+
return fbh
192+
193+
194+
def format_family_blindness_challenge(fbh: FamilyBlingnessChallenge) -> str:
195+
lines = [
196+
f"Family Blindness Challenge — {fbh.fbh_id}",
197+
f"Pipeline: {fbh.pipeline_version}",
198+
f"Verdict: {fbh.verdict}",
199+
f"Families: {fbh.n_families_total} total, {fbh.n_weak_families} weak, "
200+
f"{fbh.n_excluded_weak_families} excluded",
201+
]
202+
if fbh.excluded_weak_family_names:
203+
lines.append(
204+
"Excluded weak families: "
205+
f"{', '.join(fbh.excluded_weak_family_names)}"
206+
)
207+
if fbh.family_entries:
208+
lines.append("Per-family performance:")
209+
for entry in fbh.family_entries:
210+
lines.append(
211+
f" {entry.family_name}: AUROC={entry.auroc:.3f} "
212+
f"(grade={entry.auroc_grade}), "
213+
f"panel={entry.panel_count}/{entry.n_candidates} "
214+
f"({entry.panel_fraction:.1%}), weak={entry.is_weak_class}"
215+
)
216+
lines.append(f"Created: {fbh.created_at}")
217+
lines.append(f"Limitations: {'; '.join(fbh.limitations)}")
218+
lines.append(f"dry_lab_only: {fbh.dry_lab_only}")
219+
return "\n".join(lines)

0 commit comments

Comments
 (0)