Skip to content

Commit e0a7932

Browse files
cschanhniemOpenCode
andauthored
feat: Phase U U5 evidence completeness index schema (ECI-) -- top-level index aggregating PCC/CBA2/BEG/SAT across batches; AGGREGATED_SCHEMA_TYPES (4), VALID_ECI_GRADES (A-D); per-batch BatchSchemaPresence; overall fraction and grade; dry_lab_only=True; closes Phase U; 47 tests (#995) (#995)
Co-authored-by: OpenCode <opencode@example.com>
1 parent 28e062d commit e0a7932

3 files changed

Lines changed: 462 additions & 1 deletion

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
"""ECI- evidence completeness index schema.
2+
3+
Top-level index of which schemas exist for each batch and family across all
4+
phases. Aggregates PCC + CBA2 + BEG + SAT into a single completeness
5+
snapshot. Closes Phase U.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from dataclasses import dataclass
11+
12+
AGGREGATED_SCHEMA_TYPES: frozenset[str] = frozenset({"PCC", "CBA2", "BEG", "SAT"})
13+
14+
VALID_ECI_GRADES: frozenset[str] = frozenset({"A", "B", "C", "D"})
15+
16+
# Grade thresholds: A=100%, B>=75%, C>=50%, D<50%
17+
_GRADE_THRESHOLDS = [
18+
("A", 1.0),
19+
("B", 0.75),
20+
("C", 0.5),
21+
]
22+
23+
24+
def _compute_grade(fraction: float) -> str:
25+
for grade, threshold in _GRADE_THRESHOLDS:
26+
if fraction >= threshold:
27+
return grade
28+
return "D"
29+
30+
31+
@dataclass
32+
class BatchSchemaPresence:
33+
batch_id: str
34+
pcc_id: str
35+
pcc_present: bool
36+
cba2_id: str
37+
cba2_present: bool
38+
beg_id: str
39+
beg_present: bool
40+
sat_id: str
41+
sat_present: bool
42+
n_schemas_present: int
43+
n_schemas_required: int
44+
completeness_fraction: float
45+
batch_grade: str
46+
47+
48+
@dataclass
49+
class EvidenceCompletenessIndex:
50+
eci_id: str
51+
pipeline_version: str
52+
batch_entries: list[BatchSchemaPresence]
53+
n_batches: int
54+
n_schemas_required_per_batch: int
55+
total_schema_slots: int
56+
total_schemas_present: int
57+
overall_completeness_fraction: float
58+
completeness_grade: str
59+
dry_lab_only: bool
60+
limitations: list[str]
61+
created_at: str
62+
63+
64+
def validate_evidence_completeness_index(eci: EvidenceCompletenessIndex) -> None:
65+
if not eci.eci_id.startswith("ECI-"):
66+
raise ValueError(f"eci_id must start with 'ECI-': {eci.eci_id!r}")
67+
if not eci.pipeline_version:
68+
raise ValueError("pipeline_version must be non-empty")
69+
if eci.completeness_grade not in VALID_ECI_GRADES:
70+
raise ValueError(
71+
f"completeness_grade {eci.completeness_grade!r} not in VALID_ECI_GRADES"
72+
)
73+
if not eci.dry_lab_only:
74+
raise ValueError("dry_lab_only must be True")
75+
if not eci.limitations:
76+
raise ValueError("limitations must be non-empty")
77+
if not eci.created_at:
78+
raise ValueError("created_at must be non-empty")
79+
n_req = len(AGGREGATED_SCHEMA_TYPES)
80+
if eci.n_schemas_required_per_batch != n_req:
81+
raise ValueError(
82+
f"n_schemas_required_per_batch must be {n_req}, got {eci.n_schemas_required_per_batch}"
83+
)
84+
if eci.n_batches != len(eci.batch_entries):
85+
raise ValueError("n_batches must equal len(batch_entries)")
86+
expected_slots = eci.n_batches * n_req
87+
if eci.total_schema_slots != expected_slots:
88+
raise ValueError(
89+
f"total_schema_slots must be {expected_slots}, got {eci.total_schema_slots}"
90+
)
91+
total_present = sum(e.n_schemas_present for e in eci.batch_entries)
92+
if eci.total_schemas_present != total_present:
93+
raise ValueError("total_schemas_present mismatch")
94+
95+
96+
def build_evidence_completeness_index(
97+
*,
98+
eci_id: str,
99+
pipeline_version: str,
100+
batch_dicts: list[dict],
101+
limitations: list[str],
102+
created_at: str,
103+
) -> EvidenceCompletenessIndex:
104+
"""Build an evidence completeness index.
105+
106+
batch_dicts: list of dicts with keys:
107+
batch_id, pcc_id, cba2_id, beg_id, sat_id
108+
Each id is non-empty string if present, "" if absent.
109+
"""
110+
n_req = len(AGGREGATED_SCHEMA_TYPES)
111+
entries: list[BatchSchemaPresence] = []
112+
for d in batch_dicts:
113+
pcc_present = bool(d.get("pcc_id", ""))
114+
cba2_present = bool(d.get("cba2_id", ""))
115+
beg_present = bool(d.get("beg_id", ""))
116+
sat_present = bool(d.get("sat_id", ""))
117+
n_present = sum([pcc_present, cba2_present, beg_present, sat_present])
118+
fraction = n_present / n_req
119+
entries.append(BatchSchemaPresence(
120+
batch_id=d["batch_id"],
121+
pcc_id=d.get("pcc_id", ""),
122+
pcc_present=pcc_present,
123+
cba2_id=d.get("cba2_id", ""),
124+
cba2_present=cba2_present,
125+
beg_id=d.get("beg_id", ""),
126+
beg_present=beg_present,
127+
sat_id=d.get("sat_id", ""),
128+
sat_present=sat_present,
129+
n_schemas_present=n_present,
130+
n_schemas_required=n_req,
131+
completeness_fraction=fraction,
132+
batch_grade=_compute_grade(fraction),
133+
))
134+
135+
n_batches = len(entries)
136+
total_slots = n_batches * n_req
137+
total_present = sum(e.n_schemas_present for e in entries)
138+
overall_fraction = total_present / total_slots if total_slots > 0 else 0.0
139+
grade = _compute_grade(overall_fraction)
140+
141+
eci = EvidenceCompletenessIndex(
142+
eci_id=eci_id,
143+
pipeline_version=pipeline_version,
144+
batch_entries=entries,
145+
n_batches=n_batches,
146+
n_schemas_required_per_batch=n_req,
147+
total_schema_slots=total_slots,
148+
total_schemas_present=total_present,
149+
overall_completeness_fraction=overall_fraction,
150+
completeness_grade=grade,
151+
dry_lab_only=True,
152+
limitations=limitations,
153+
created_at=created_at,
154+
)
155+
validate_evidence_completeness_index(eci)
156+
return eci
157+
158+
159+
def format_evidence_completeness_index(eci: EvidenceCompletenessIndex) -> str:
160+
lines = [
161+
f"Evidence Completeness Index — {eci.eci_id}",
162+
f"Pipeline: {eci.pipeline_version}",
163+
f"Grade: {eci.completeness_grade} | Fraction: {eci.overall_completeness_fraction:.2%}",
164+
f"Batches: {eci.n_batches} | Schemas/batch: {eci.n_schemas_required_per_batch}",
165+
f"Total slots: {eci.total_schema_slots} | Present: {eci.total_schemas_present}",
166+
]
167+
if eci.batch_entries:
168+
lines.append("Batch summary:")
169+
for entry in eci.batch_entries:
170+
lines.append(
171+
f" {entry.batch_id}: grade={entry.batch_grade} "
172+
f"({entry.n_schemas_present}/{entry.n_schemas_required}) "
173+
f"PCC={entry.pcc_present} CBA2={entry.cba2_present} "
174+
f"BEG={entry.beg_present} SAT={entry.sat_present}"
175+
)
176+
lines.append(f"Created: {eci.created_at}")
177+
lines.append(f"Limitations: {'; '.join(eci.limitations)}")
178+
lines.append(f"dry_lab_only: {eci.dry_lab_only}")
179+
return "\n".join(lines)

0 commit comments

Comments
 (0)