-
Notifications
You must be signed in to change notification settings - Fork 0
fix: make capability failures explicit and add fidelity delta report #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,23 @@ | |
| JSONDict = dict[str, Any] | ||
|
|
||
|
|
||
| def _failure_note_from_actual(actual: JSONDict) -> str: | ||
| if "error" in actual: | ||
| error_text = str(actual.get("error", "")).lower() | ||
| unsupported_markers = ( | ||
| "notimplemented", | ||
| "not implemented", | ||
| "unsupported", | ||
| "not supported", | ||
| "read-only", | ||
| "write-only", | ||
| ) | ||
| if any(marker in error_text for marker in unsupported_markers): | ||
| return "Not implemented" | ||
| return "Incorrect result" | ||
| return "Incorrect result" | ||
|
|
||
|
|
||
| def _build_exception_diagnostic( | ||
| adapter: ExcelAdapter, | ||
| *, | ||
|
|
@@ -417,6 +434,7 @@ def test_read_case( | |
| passed=passed, | ||
| expected=expected, | ||
| actual=actual, | ||
| notes=None if passed else _failure_note_from_actual(actual), | ||
| diagnostics=( | ||
| [] | ||
| if passed | ||
|
|
@@ -492,6 +510,7 @@ def test_read_case( | |
| passed=passed, | ||
| expected=expected, | ||
| actual=actual, | ||
| notes=None if passed else _failure_note_from_actual(actual), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new note mapping only runs in the non-exception return path, so capability failures raised by the new Tier-3 defaults ( Useful? React with 👍 / 👎. |
||
| diagnostics=( | ||
| [] | ||
| if passed | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -87,6 +87,7 @@ def render_results(results: BenchmarkResults, output_dir: Path) -> None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| render_markdown(results, output_dir / "README.md") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| render_csv(results, output_dir / "matrix.csv") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _append_history(results, output_dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _render_fidelity_deltas(output_dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def render_json(results: BenchmarkResults, path: Path) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -719,6 +720,103 @@ def _append_history(results: BenchmarkResults, output_dir: Path) -> None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f.write(json.dumps(entry) + "\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _render_fidelity_deltas(output_dir: Path) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Render a markdown report comparing the two most recent fidelity runs.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| history_path = output_dir / "history.jsonl" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out_path = output_dir / "FIDELITY_DELTAS.md" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not history_path.exists(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out_path.write_text("# Fidelity Deltas\n\nNo history available.\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| entries: list[dict[str, Any]] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for line in history_path.read_text().splitlines(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| line = line.strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not line: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsed = json.loads(line) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except json.JSONDecodeError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(parsed, dict): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| entries.append(parsed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(entries) < 2: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out_path.write_text("# Fidelity Deltas\n\nNeed at least two runs in history.jsonl.\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| previous = entries[-2] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current = entries[-1] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+731
to
+748
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To improve memory efficiency, especially if
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deltas = _compute_fidelity_deltas(previous, current) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+747
to
+749
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines: list[str] = ["# Fidelity Deltas", ""] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append(f"- Previous run: `{previous.get('run_date', 'unknown')}`") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append(f"- Current run: `{current.get('run_date', 'unknown')}`") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not deltas: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("No score changes detected.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out_path.write_text("\n".join(lines)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| regressions = [d for d in deltas if d["delta"] < 0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| improvements = [d for d in deltas if d["delta"] > 0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("## Summary") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append(f"- Regressions: **{len(regressions)}**") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append(f"- Improvements: **{len(improvements)}**") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append(f"- Net score change: **{sum(d['delta'] for d in deltas):+d}**") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("## Changed Scores") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("| Library | Feature | Mode | Previous | Current | Δ |") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("|---------|---------|------|----------|---------|---|") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for item in sorted(deltas, key=lambda d: (d["delta"], d["library"], d["feature"], d["mode"])): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"| {item['library']} | {item['feature']} | {item['mode']} | " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"{item['previous']} | {item['current']} | {item['delta']:+d} |" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.append("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out_path.write_text("\n".join(lines)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _compute_fidelity_deltas( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| previous: dict[str, Any], current: dict[str, Any] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> list[dict[str, Any]]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Compute score deltas between two history entries.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deltas: list[dict[str, Any]] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prev_scores: dict[str, Any] = previous.get("scores", {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| curr_scores: dict[str, Any] = current.get("scores", {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for library in sorted(set(prev_scores) | set(curr_scores)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prev_lib = prev_scores.get(library, {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| curr_lib = curr_scores.get(library, {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for feature in sorted(set(prev_lib) | set(curr_lib)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prev_feature = prev_lib.get(feature, {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| curr_feature = curr_lib.get(feature, {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for mode in ("read", "write"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prev_value = prev_feature.get(mode) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| curr_value = curr_feature.get(mode) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if prev_value is None or curr_value is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New features/modes silently ignored in deltas When one run has a library/feature/mode entry and the other doesn't (i.e., Prompt To Fix With AIThis is a comment left during a code review.
Path: src/excelbench/results/renderer.py
Line: 803:803
Comment:
**New features/modes silently ignored in deltas**
When one run has a library/feature/mode entry and the other doesn't (i.e., `prev_value is None or curr_value is None`), the delta is silently skipped. This means if a library adds support for a new feature between runs (going from no score to a score), or drops one entirely, it won't appear in the delta report. Depending on intent, this could mask meaningful regressions or improvements — particularly if a library is newly added or removed between runs.
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if prev_value == curr_value: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deltas.append( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "library": library, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "feature": feature, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "mode": mode, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "previous": int(prev_value), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "current": int(curr_value), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "delta": int(curr_value) - int(prev_value), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return deltas | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _diagnostic_to_json(diagnostic: Diagnostic) -> dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "category": diagnostic.category.value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_failure_note_from_actualbypassed for Tier 3 exceptionsWhen Tier 3 base methods (
read_named_ranges,add_named_range,read_tables,add_table) raiseNotImplementedError, the exception propagates up through theread_*_actualhelper and is caught by the genericexcept Exception as ehandler at line 532, which setsnotes=f"Exception: {type(e).__name__}"— i.e.,"Exception: NotImplementedError".This means
_failure_note_from_actualis never invoked for these cases, and the note will be"Exception: NotImplementedError"rather than"Not implemented". The function is only reached when the adapter call succeeds but returns a dict containing an"error"key.This may be intentional (the exception handler note is arguably more descriptive), but it's worth confirming this is the desired behavior — since the PR description mentions wanting to "clearly distinguish" not-implemented from incorrect results, you may want the exception handler to also produce a
"Not implemented"note forNotImplementedErrorspecifically:Prompt To Fix With AI