Skip to content

Commit 7f1f68b

Browse files
fix(reporting): distinguish unselected mandatory minimums (#203)
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> Co-authored-by: n-papaioannou <n.papaioannou@ime.life>
1 parent 4a479e1 commit 7f1f68b

2 files changed

Lines changed: 70 additions & 2 deletions

File tree

‎ifixai/reporting/artifact.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,16 @@ def _build_payload(
184184
previous: dict[str, Any] | None,
185185
) -> dict[str, Any]:
186186
overall = result.overall_score
187+
not_run_minimums = set(result.mandatory_minimums_not_run)
188+
minimum_statuses = result.mandatory_minimum_status.values()
189+
if any(status == TestStatus.FAIL for status in minimum_statuses):
190+
minimum_label = "FAIL"
191+
elif not_run_minimums:
192+
minimum_label = "NOT RUN"
193+
elif any(status == TestStatus.INCONCLUSIVE for status in minimum_statuses):
194+
minimum_label = "INCONCLUSIVE"
195+
else:
196+
minimum_label = "PASS"
187197
categories = []
188198
for cs in result.category_scores:
189199
ran = len(cs.test_ids)
@@ -232,7 +242,11 @@ def _build_payload(
232242
"strategic_pct": f"{result.strategic_score * 100:.1f}%",
233243
"stability": _stability_note(overall),
234244
"mm_passed": result.mandatory_minimums_passed,
235-
"mm_status": {tid: st.value for tid, st in sorted(result.mandatory_minimum_status.items())},
245+
"mm_label": minimum_label,
246+
"mm_status": {
247+
tid: "not run" if tid in not_run_minimums else st.value
248+
for tid, st in sorted(result.mandatory_minimum_status.items())
249+
},
236250
"below_threshold": failed,
237251
# Provenance banners: a partial or resumed run must disclose it on
238252
# every report surface, this one included.
@@ -354,7 +368,7 @@ def render_artifact(
354368
<div>Judge</div><div>${esc(m.judge_model)}</div>
355369
<div>Fixture</div><div>${esc(m.fixture)}</div>
356370
<div>Strategic score</div><div>${esc(s.strategic_pct)}</div>
357-
<div>Mandatory minimums</div><div>${s.mm_passed?'PASS':'NOT PASSED'} — ${esc(Object.entries(s.mm_status).map(([k,v])=>k+':'+v.toUpperCase()).join(' '))}</div>
371+
<div>Mandatory minimums</div><div>${esc(s.mm_label)} — ${esc(Object.entries(s.mm_status).map(([k,v])=>k+':'+v.toUpperCase()).join(' '))}</div>
358372
</div>`;
359373
}
360374

‎ifixai/tests/test_artifact_diff_unscored.py‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,60 @@ def test_unscored_artifact_does_not_display_a_failing_grade():
7979
assert payload["diff"]["grade_change"] == "n/a → n/a"
8080

8181

82+
@pytest.mark.parametrize(
83+
("status", "minimums_passed", "not_run", "label"),
84+
[
85+
(TestStatus.INCONCLUSIVE, True, ["B08"], "NOT RUN"),
86+
(TestStatus.INCONCLUSIVE, True, [], "INCONCLUSIVE"),
87+
(TestStatus.FAIL, False, [], "FAIL"),
88+
(TestStatus.PASS, True, [], "PASS"),
89+
],
90+
)
91+
def test_artifact_minimums_match_evaluated_gate_state(status, minimums_passed, not_run, label):
92+
result = TestRunResult(
93+
mandatory_minimums_passed=minimums_passed,
94+
mandatory_minimum_status={"B08": status},
95+
mandatory_minimums_not_run=not_run,
96+
)
97+
payload = _build_payload(
98+
result, live=False, transport="offline", sut_model=None,
99+
judge_model=None, honesty_note="", previous=None,
100+
)
101+
assert payload["summary"]["mm_label"] == label
102+
assert payload["summary"]["mm_status"]["B08"] == ("not run" if not_run else status.value)
103+
104+
105+
def test_artifact_native_scoped_gate_does_not_claim_pass():
106+
from ifixai.scoring.mandatory_minimums import check_mandatory_minimums
107+
108+
checked = check_mandatory_minimums([], selected_ids={"B19"})
109+
result = TestRunResult(
110+
mandatory_minimums_passed=checked["minimums_passed"],
111+
mandatory_minimum_status=checked["minimum_status"],
112+
mandatory_minimums_not_run=checked["minimums_not_run"],
113+
)
114+
payload = _build_payload(
115+
result, live=False, transport="offline", sut_model=None,
116+
judge_model=None, honesty_note="", previous=None,
117+
)
118+
assert payload["summary"]["mm_label"] == "NOT RUN"
119+
assert all(value == "not run" for value in payload["summary"]["mm_status"].values())
120+
121+
122+
def test_artifact_failed_minimum_takes_precedence_over_unselected_minimum():
123+
result = TestRunResult(
124+
mandatory_minimums_passed=False,
125+
mandatory_minimum_status={"B08": TestStatus.FAIL, "B09": TestStatus.INCONCLUSIVE},
126+
mandatory_minimums_not_run=["B09"],
127+
)
128+
payload = _build_payload(
129+
result, live=False, transport="offline", sut_model=None,
130+
judge_model=None, honesty_note="", previous=None,
131+
)
132+
assert payload["summary"]["mm_label"] == "FAIL"
133+
assert payload["summary"]["mm_status"] == {"B08": "fail", "B09": "not run"}
134+
135+
82136
def _warning_run():
83137
return TestRunResult(
84138
system_name="warning-provenance-control",

0 commit comments

Comments
 (0)