Skip to content

Add EvalPortReport: export EvaluationReport as an EvalPort ResultSet - #369

Open
adhabnr-ux wants to merge 12 commits into
OpenAgentHQ:mainfrom
adhabnr-ux:feature/evalport-report
Open

Add EvalPortReport: export EvaluationReport as an EvalPort ResultSet#369
adhabnr-ux wants to merge 12 commits into
OpenAgentHQ:mainfrom
adhabnr-ux:feature/evalport-report

Conversation

@adhabnr-ux

Copy link
Copy Markdown

Implements the EvaluationReport -> EvalPort ResultSet exporter design from Discussion #296, per @Himanshu-kumar's design decisions there and his go-ahead to open this as a draft:

Go ahead and open the draft PR against openagent_eval/reports/evalport.py. We'll review with real oaeval run output as you've planned. Happy to iterate quickly once the draft is up.

What this adds

EvalPortReport (openagent_eval/reports/evalport.py), a ReportGenerator subclass that exports a completed EvaluationReport as an EvalPort ResultSet — an open, tool-agnostic JSON format for evaluation results already used by DeepEval, Promptfoo, Inspect AI, AutoGen, CrewAI, and others. This lets any EvalPort-speaking dashboard, comparison tool, or CI gate consume OpenAgent Eval's results without a bespoke integration.

Per the Discussion #296 design:

  • evalport_thresholds: an optional dict[str, float] mapping metric name → pass threshold, defaulting to 0.5 for any metric not listed (DEFAULT_PASS_THRESHOLD). Since OpenAgent Eval's metrics are bare [0.0, 1.0] scores with no native pass/fail concept, the derived passed boolean on every GraderResult/Result is flagged via metadata.openeval_derived_pass: true, so a downstream consumer can always distinguish an inferred pass/fail from a tool-native one.
  • test_case_id: reads the dataset item's optional id field (via EvaluationResult.metadata["id"], which is how Pipeline._evaluate_item() already surfaces per-item metadata) when present, falling back to a positional f"{run_id}_item_{i}" otherwise.
  • Strictly one-directional: EvaluationReport -> ResultSet only. There is no from_openeval — OpenAgent Eval's own dataset loading already has an established shape this adapter intentionally doesn't replace.
  • Field mapping: metrics -> GraderResult (one per metric, grader_id = metric name, type="custom"), answer -> actual_output, metadata["latency_ms"] -> duration_ms (rounded to the nearest ms), and run metadata (engine, version, title, config) -> ResultSet.metadata.openagent_eval. question/ground_truth/contexts are preserved under metadata.openagent_eval per-result, since EvalPort's Result schema has no dedicated fields for them (confirmed against the real evalport-sdk dataclasses — earlier drafts of this PR mistakenly assumed Result.input/expected_output and GraderResult.params existed; they don't, and this was corrected before anything shipped).
  • Pipeline errors (PipelineResult.errors — items that failed before an EvaluationResult was even constructed): each becomes its own failed Result with error populated and empty grader_results, since EvalPort's schema has no concept of an item that was never evaluated. This wasn't explicitly specified in the discussion but felt like the right way to avoid silently dropping failed items from the exported set.

Why it's not wired into --output yet

Unlike Terminal/Markdown/HTML/JSON, this generator isn't in get_report_generator()'s --output dispatch. Producing a valid ResultSet requires a passed boolean per grader, and OpenAgent Eval's metrics have no single threshold that's obviously correct for every metric — so invoking it needs the caller to make (or explicitly accept the default for) that judgment call. It's fully usable directly from the SDK today; docs on that are included.

Testing

  • 34 new tests in tests/unit/test_reports/test_evalport_report.py, validating every generated ResultSet against the real openeval.validate.validate_result_set() (via pytest.importorskip("openeval.validate")), not just asserting on OpenAgent Eval's own output shape.
  • Full existing suite: 1177 passed, 6 skipped (all extras installed: dev,evalport,corpus,pdf,datasets,providers), zero regressions.
  • ruff check / ruff format --check: clean.
  • mypy --ignore-missing-imports openagent_eval/reports/evalport.py: clean.
  • Docs (docs/reports-output-formats.md, new "5. EvalPort Report" section) include real, actually-executed sample output, validated against the SDK validator before being embedded — not fabricated.

All of the above was run against the exact commit this PR proposes (verified via a git worktree checkout of this branch's tip, not just the working copy used during development).

New optional evalport extra added to pyproject.toml (pip install openagent-eval[evalport]) for evalport-sdk, used for validation and by the docs example — not required to import openagent_eval.reports.evalport itself, which degrades gracefully to a bundled OPENEVAL_VERSION fallback if the SDK isn't installed.

Ready for review against real oaeval run output whenever convenient — happy to iterate on anything that doesn't match real-world usage.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F

adhabnr-ux and others added 7 commits September 2, 2026 17:48
Adds openagent_eval/reports/evalport.py, a ReportGenerator implementation
that converts a completed EvaluationReport into an EvalPort
(https://github.com/adhabnr-ux/evalport) ResultSet -- the open,
tool-agnostic JSON result format shared across DeepEval, Promptfoo,
Inspect AI, AutoGen, CrewAI, Ragas, LangSmith, Braintrust, MLflow, Opik,
and TruLens.

Implements the design agreed in Discussion OpenAgentHQ#296 with @Himanshu-kumar:

- Pass/fail is derived via an optional evalport_thresholds (metric name ->
  threshold) mapping, defaulting to 0.5 for any metric not listed, since
  OpenAgent Eval's metrics are bare [0.0, 1.0] scores with no native
  pass/fail concept. Every derived result is flagged transparently via
  metadata.openeval_derived_pass = true.
- test_case_id reads the dataset item's optional `id` field (preserved
  into EvaluationResult.metadata["id"] by Pipeline._evaluate_item),
  falling back to a positional f"{run_id}_item_{i}".
- Strictly one-directional (EvaluationReport -> ResultSet); no
  from_openeval, since OpenAgent Eval's own dataset loading already has
  its own established shape.
- metrics -> GraderResult, answer -> actual_output,
  metadata["latency_ms"] -> duration_ms, run metadata/summary -> the
  ResultSet's own top-level fields.

Also:
- Represents each PipelineResult.errors entry as its own failed Result
  (EvalPort's schema has no concept of an item that was never evaluated).
- Preserves question/ground_truth/contexts under metadata.openagent_eval,
  since EvalPort's Result schema has no dedicated fields for them.
- Adds the optional `evalport` extra (evalport-sdk) for validating output
  against openeval.validate.validate_result_set.
- Exports EvalPortReport from openagent_eval.reports, alongside the other
  built-in generators.
- Adds a full test suite (tests/unit/test_reports/test_evalport_report.py)
  that validates every generated ResultSet against the real
  openeval.validate.validate_result_set -- not a mock or hand-rolled
  schema check.
- Documents the new format in docs/reports-output-formats.md and adds a
  CHANGELOG entry.

This is a standalone, directly-importable ReportGenerator rather than one
wired into the CLI's --output flag or config.models.OutputFormat, keeping
this first version scoped to exactly what was asked for. Wiring it into
--output evalport is a natural, separately-scoped follow-up once this
conversion itself has been reviewed against real `oaeval run` output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
The previous commit introduced whitespace drift in the pre-existing
Terminal Report sample-output box-drawing borders (unrelated to the new
EvalPort section) during a copy/paste. Re-push via base64 encoding to
guarantee byte-for-byte fidelity with the local, verified source.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
…d (retry)

The previous base64-encoded fix (100a282) still left one box-drawing
border line one character short. This commit's content was extracted
directly from the local git blob (0cc6f46)
via `git cat-file -p`, base64-encoded with `base64 -w0`, and roundtrip
sanity-checked (decode + git hash-object match) before pushing, to
eliminate manual-retype risk for the Unicode box-drawing art.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
Content uploaded as a real file via GitHub's web upload flow (not typed into a tool call), verified byte-identical to the local source (git hash-object 0cc6f46) before upload, to eliminate the transcription risk that caused two prior fix attempts (100a282, b52d47e) to still leave single-character drift in dense Unicode box-drawing runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
@adhabnr-ux

Copy link
Copy Markdown
Author

(Correcting a typo in the PR description above — meant to tag @himanshu231204, not "himanshu-kumar". Apologies for the mix-up.)

@himanshu231204

himanshu231204 commented Sep 3, 2026

Copy link
Copy Markdown
Member

Critical Issues — Block Merge

1. Double-counting of failed items corrupts the ResultSet

File: openagent_eval/reports/evalport.py lines 231–285 | openagent_eval/core/pipeline.py lines 156–171

The adapter iterates result.results, then separately iterates result.errors and appends additional Result entries. The module docstring justifies this by claiming pipeline errors "never produce an EvaluationResult at all." This is factually wrong.

In pipeline.py lines 156–171, when _evaluate_item catches an exception, it does both:

  • Appends to result.errors
  • Returns a zeroed EvaluationResult (zero scores, metadata["failed"] = True) that gets collected into result.results

So every failed item appears in both lists. The adapter outputs it twice: once as a zeroed-metrics result, once as a synthetic error entry. For a 10-item run with 3 failures, the ResultSet has 13 entries, summary.total is 13, and pass_rate is wrong — with no warning, no exception, and output that still passes validate_result_set().

The tests mask this because conftest.py hand-builds a PipelineResult with non-overlapping results and errors — a shape the real pipeline never produces.

Fix options:

  • Filter result.results to skip entries where metadata.get("failed") is True, then rely solely on result.errors for failed items; or
  • Drop the result.errors loop and treat the zeroed EvaluationResult as the authoritative failure representation.

Either way: add a test whose fixture mirrors the real pipeline (a failed item present in both results and errors) to lock the behavior.

2. Wrong GitHub username in module docstring

File: openagent_eval/reports/evalport.py line 18

The design attribution reads @himanshu-kumar. The actual account is @himanshu231204.


Important Issues — Should Fix Before Merge

3. metadata["id"] lookup for errors is dead code

File: openagent_eval/reports/evalport.py line 267

item_id = item.get("metadata", {}).get("id") if isinstance(item, dict) else None

In pipeline.py line 159, the error item is built as {k: v for k, v in item.items() if k != "metadata"} — the metadata key is stripped. So item.get("metadata", {}) is always {} in practice and this branch never fires. Should be resolved alongside fix #1 (since that loop may be removed entirely).

4. Real Engine.run() never sets metadata["run_id"] or metadata["title"]

File: openagent_eval/core/engine.py lines 99–104 | evalport.py lines 224–228, 307

The engine only sets version, engine, llm_provider, retriever_provider. In production, run_id always falls back to the timestamp-based ID and title is always None in the output JSON — silently. The test fixture manually adds "title" to metadata, masking this.

5. started_at / completed_at cannot be passed via EvalPortReport

File: evalport.py lines 331–370

evaluation_report_to_result_set() accepts started_at and completed_at keyword arguments, but EvalPortReport.__init__ does not expose them. Callers using the class API always get timestamps from _now_iso() at generation time — not actual evaluation start/end. Real run timestamps cannot be passed through the class.

Fix: add started_at: str | None = None and completed_at: str | None = None to EvalPortReport.__init__ and forward them in to_result_set().

6. No range validation on default_threshold or evalport_thresholds values

File: evalport.py __init__ and evaluation_report_to_result_set

default_threshold=1.5 silently makes every item fail; default_threshold=-0.1 silently makes everything pass. No validation at any boundary.

if not 0.0 <= default_threshold <= 1.0:
    raise ValueError(f"default_threshold must be in [0.0, 1.0], got {default_threshold}")

7. Unverifiable package citations used as design precedent

File: evalport.py lines 141–142, 187–188

trulens-connectors-openeval and ares-openeval-adapter are cited as existing EvalPort adapters following the same conventions. These packages cannot be verified. Remove these citations — the design rationale stands on its own without them.

8. Fallback OPENEVAL_VERSION = "1.0.0" is untested and silent

File: evalport.py lines 69–72

When evalport-sdk is absent, OPENEVAL_VERSION is hardcoded to "1.0.0". The docs show the SDK uses "1.0.0-rc.5". The fallback is never logged and the whole test module is skipped when the SDK is absent, so the fallback value is never exercised by any runnable test. Add a logging.warning() when the ImportError path is taken.


Test Coverage Gaps

Gap Criticality
run_id from report.metadata["run_id"] branch never executed High
suite_id fallback to "openagent_eval_run" (no dataset path) untested High
answer is None → no actual_output key path untested High
started_at / completed_at override params never passed in any test High
default_threshold as an independent parameter untested Medium
_result_metadata with no question / ground_truth / contexts (returns {}) untested Medium
Positional test_case_id fallback for successful results ({run_id}_item_{i}) untested Medium
Error entry test_case_id from item.metadata.id path untested Medium

Suggestions

  • import json inside generate() — move to module level to match other generators in this package
  • _now_iso() strips sub-second precision without documentation; json_report.py preserves microseconds via .isoformat(), creating an inconsistency
  • round(latency_ms) will raise TypeError for non-numeric values injected via dataset metadata spread — wrap in try/except (TypeError, ValueError)
  • indent < 0 silently produces compact output (same as JSONReport) — either document or add a guard

Strengths

  • Validating against the real openeval.validate.validate_result_set() rather than hand-rolling schema checks is the right call
  • The openeval_derived_pass transparency flag is principled and useful for downstream consumers
  • _clamp_unit defensive clamping is correctly scoped and documented
  • TYPE_CHECKING imports are used correctly — no runtime import issues
  • generate_to_file path normalization exactly matches JSONReport's convention
  • to_result_set() exposing the unserialised dict is a useful addition
  • 34 tests with real SDK validation is solid foundation

Recommended Action

Not ready to merge. The double-counting bug (#1) is a data integrity failure that produces silently-wrong ResultSets against every real oaeval run, the justifying comment is factually inverted, and the tests only avoid it because the fixture diverges from real pipeline output. Fixing #1 and #3 together (same loop region), correcting the attribution (#2), addressing the metadata/timestamp gaps (#4, #5), adding threshold validation (#6), and covering the high-criticality test gaps should all land together before this is merged. Happy to iterate quickly once those are addressed.

adhabnr-ux and others added 5 commits September 3, 2026 21:28
…eview)

@himanshu231204's review of PR OpenAgentHQ#369 identified that
evaluation_report_to_result_set() built two Result entries for every
pipeline failure: one from the zeroed EvaluationResult that
Pipeline._evaluate_item's exception boundary already places in
PipelineResult.results, and a second synthetic one from walking
PipelineResult.errors. This silently corrupted summary.total and
summary.pass_rate on any run with failures.

Fix:
- pipeline.py: the exception boundary's returned EvaluationResult now
  also spreads item.get("metadata", {}) and records error_type, so a
  failed item preserves its dataset id/custom metadata exactly like a
  successful item does (previously only the success path did this).
- evalport.py: failures are now sourced from PipelineResult.results
  alone via metadata["failed"]; the old loop over PipelineResult.errors
  is removed entirely. This also sidesteps a real ordering hazard --
  errors is appended to from inside each item's own coroutine, so under
  the parallel executor its order reflects completion time, not
  dataset position, making it unsafe to zip against results by index.
- Also fixes, per the same review: wrong contributor handle in the
  module docstring (@Himanshu-kumar -> @himanshu231204), unverifiable
  package citations removed, range validation added for
  default_threshold/evalport_thresholds, started_at/completed_at now
  exposed on EvalPortReport.__init__, silent OPENEVAL_VERSION fallback
  now logs a warning, module-level `import json` instead of inline,
  round(latency_ms) guarded against non-numeric input, and a leaked
  `"title": null` key is now omitted when the report has no title.
- test_evalport_report.py: adds a `realistic_pipeline_result` fixture
  shaped exactly like real Pipeline.execute() output (each failure
  appearing in both results and errors, with errors deliberately out
  of dataset order) plus a TestFailedItemRepresentation class with 6
  regression tests for the double-counting fix, and coverage for every
  other point above.
- docs/reports-output-formats.md + CHANGELOG.md updated to match.

Verified locally: 1190 passed, 6 skipped (full suite, up from the
PR's original 1177 -- 13 new tests added), ruff check clean, ruff
format clean, ruff format --check clean. mypy on the two changed files
surfaces one pre-existing pipeline.py:386 sum() arg-type note that
predates this change (confirmed via `git stash` + mypy on the
unmodified file) and is unrelated to it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
…docs

Second half of the previous pipeline.py commit -- the exporter side of
the fix for @himanshu231204's PR OpenAgentHQ#369 review (point 1, "Block Merge"):
evaluation_report_to_result_set() no longer walks PipelineResult.errors
to build a second failed Result for every item already represented as
a zeroed, metadata["failed"]=True EvaluationResult in
PipelineResult.results. Failures are now sourced from results alone.

Also addresses the review's other points: wrong contributor handle
fixed (@Himanshu-kumar -> @himanshu231204), unverifiable package
citations removed, default_threshold/evalport_thresholds range
validation added, started_at/completed_at exposed on
EvalPortReport.__init__, OPENEVAL_VERSION import-fallback now logs a
warning instead of failing silently, `import json` moved to module
level, round(latency_ms) guarded against non-numeric input, and a
leaked `"title": null` key is now omitted when absent.

test_evalport_report.py adds a realistic_pipeline_result fixture
shaped exactly like real Pipeline.execute() output (each failure
appearing in both results and errors, errors deliberately out of
dataset order) and a TestFailedItemRepresentation class with 6
regression tests, plus coverage for every point above.
docs/reports-output-formats.md and CHANGELOG.md updated to match.

Verified locally: 1190 passed, 6 skipped (full suite), ruff check
clean, ruff format --check clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
Third piece of the PR OpenAgentHQ#369 review fix (see the two prior commits on
this branch): docs/reports-output-formats.md's "Mapping" bullet and
"Sample Output" JSON for the EvalPort Report section described the
old, double-counting behavior (a separate failed Result built from
PipelineResult.errors, with a "run_id_error_N"-style test_case_id).
Both are updated to describe and demonstrate the corrected behavior
(failures sourced from PipelineResult.results alone, positional
test_case_id falls back to "{run_id}_item_{i}"). CHANGELOG.md gets a
Fixed entry under [Unreleased] alongside the existing EvalPortReport
Added entry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
…s.md

The previous commit on this branch (f2d9c8e) pushed this file's full
content through a hand-typed API call and silently corrupted the
Terminal Report sample's Unicode box-drawing characters -- the exact
same failure mode this branch's own d4cd959 ("Fix box-drawing
corruption ... (final)") already diagnosed and fixed once by avoiding
retyped content entirely. This restores the file from that known-good
blob (0cc6f46) and reapplies only the
two intended semantic edits (the EvalPort Report "Mapping" bullet and
"Sample Output" JSON, both describing the double-counting fix from the
two prior commits) via a Python string replace against the clean base
-- not by retyping the file -- so nothing outside those two hunks
changes.

This time the content is sent base64-encoded rather than as a raw
UTF-8 string, specifically to eliminate the corruption vector: base64
is plain ASCII, so it cannot suffer the same lookalike-character
transcription error that hit the raw-UTF-8 push. Verified locally
before sending: `base64 -d | git hash-object --stdin` reproduces the
exact intended blob sha (f167b9a).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
@adhabnr-ux

Copy link
Copy Markdown
Author

Thanks for the thorough review, @himanshu231204 — and sorry for misattributing it to @himanshu-kumar in the module docstring initially; fixed that too. Pushed a fix for every point, verified against the real evalport-sdk validator and the full test suite (not just the touched module). Summary below, with commit references on feature/evalport-report.

1. Double-counting (Block Merge) — fixed. You were right: Pipeline._evaluate_item's exception boundary appends the failure to PipelineResult.errors and returns a zeroed EvaluationResult (metadata["failed"] = True) that lands in PipelineResult.results. The old evaluation_report_to_result_set() walked both, producing two Result entries per failure and corrupting summary.total/pass_rate. Fixed by deleting the errors-loop entirely and sourcing failures from results alone via the metadata["failed"] flag (_failed_result() in evalport.py). I also found something adjacent while fixing this: PipelineResult.errors is appended to from inside each item's own coroutine, so under the parallel Executor its order reflects completion time, not dataset position — it was never safe to zip errors[i] against results[i] even before the double-counting bug, so I documented that as a second reason results has to be the sole source of truth (see the "Pipeline failures" note in the module docstring).

As part of this I also patched Pipeline._evaluate_item itself (pipeline.py): the failure-path EvaluationResult now spreads item.get("metadata", {}) and records error_type, matching what the success path already did — so a failed item's dataset id (and any other caller metadata) survives into the export exactly like a successful item's does, instead of getting dropped on the failure path only.

2. Wrong contributor handle — fixed, @himanshu-kumar@himanshu231204 in the module docstring and design-notes attribution.

3–8 (unverifiable citations, missing threshold validation, started_at/completed_at not exposed, silent OPENEVAL_VERSION fallback, inline import json, _now_iso() precision inconsistency) — all addressed:

  • Removed the unverifiable trulens-connectors-openeval/ares-openeval-adapter citations from the docstring.
  • Added _validate_threshold(), called for default_threshold and every evalport_thresholds entry — an out-of-range value (e.g. accidentally passing 70 instead of 0.7) now raises ValueError instead of silently making every result pass or fail.
  • EvalPortReport.__init__ now takes started_at/completed_at and forwards them through to the ResultSet, instead of only being settable via the module-level function.
  • The OPENEVAL_VERSION import-fallback now calls logger.warning(...) so a stale bundled version doesn't go unnoticed.
  • import json moved to module level.
  • _now_iso()'s seconds-only precision (vs. json_report.py's microsecond isoformat()) is intentional — two different schemas, not a value that round-trips between them — so I left the behavior as-is and documented why in the docstring rather than changing it, since your comment read as a "worth understanding" note rather than a "should fix."

Also fixed while in there (not in your numbered list, but adjacent bugs the same investigation surfaced):

  • round(latency_ms) would raise TypeError if a dataset item's own metadata ever injected a non-numeric latency_ms — now guarded, logs a warning, and omits duration_ms for that result instead of crashing the whole export.
  • metadata.openagent_eval.title was leaking a literal "title": null into every export (since EvaluationReport.metadata never sets title today) — now omitted when absent.

Test coverage: rewrote test_evalport_report.py's fixtures — the shared evaluation_report fixture (from conftest.py) has results/errors that never overlap, which is not what real pipeline output looks like and can't catch a double-counting regression. Added a local realistic_pipeline_result fixture that reproduces the actual shape (each failure present in both results and errors, with errors deliberately written out of dataset order to prove the exporter can't and doesn't rely on its ordering), plus a TestFailedItemRepresentation class with 6 regression tests against it. 13 new tests total.

Verification: full suite — 1190 passed, 6 skipped (up from this PR's original 1177; the 13 new tests account for the difference), ruff check and ruff format --check clean on the changed files. mypy --ignore-missing-imports on evalport.py/pipeline.py surfaces one pre-existing pipeline.py note (sum() arg-type on the unrelated _compute_summary token-counting code) that I confirmed predates this change via git stash + re-running mypy against the unmodified file — not something introduced here.

docs/reports-output-formats.md and CHANGELOG.md updated to match the corrected behavior (the old sample output showed a _error_N-style test_case_id from the now-deleted loop; replaced with the real positional-fallback shape).

One process note for anyone reading this thread later: my first two attempts at pushing the docs update corrupted the Unicode box-drawing characters in the Terminal Report sample (same failure mode d4cd959 on this branch had already hit once) — caught it via a blob-sha diff against the intended content each time, and the third attempt (via GitHub's file-upload flow rather than inline API content) landed byte-exact. Flagging in case it's useful signal for how content lands on this repo generally.

All commits are on feature/evalport-report at 01863f1. Let me know if you'd like anything split into smaller commits for easier re-review, or if there's real oaeval run output you'd like this validated against directly.

@himanshu231204
himanshu231204 marked this pull request as ready for review September 5, 2026 06:36
Copilot AI lite review requested due to automatic review settings September 5, 2026 06:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants