Skip to content

fix: optimize evaluation overview reads#168

Merged
yyiilluu merged 3 commits into
mainfrom
codex/optimize-evaluation-overview
Jun 17, 2026
Merged

fix: optimize evaluation overview reads#168
yyiilluu merged 3 commits into
mainfrom
codex/optimize-evaluation-overview

Conversation

@yyiilluu

@yyiilluu yyiilluu commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Make evaluation overview reads scale with bulk, indexed storage calls instead of per-session request and interaction loops.
  • Add storage contracts for windowed evaluation results, first-request lookup, citation extraction, targeted eval-result IDs, and recent shadow verdicts.
  • Patch related evaluation paths that had the same scan-on-demand pattern while preserving public API response shapes.

Changes

  • Evaluation overview now loads only the requested/baseline evaluation windows and reuses in-memory data for attribution, source cohorts, Braintrust, and shadow aggregation.
  • SQLite storage implements the new bulk methods with indexed query paths and migration-compatible fallbacks.
  • Regeneration candidate discovery, forced group evaluation, grade-on-demand, and recent shadow comparisons use targeted reads.
  • Added regression and scale-oriented tests for the new storage contracts and overview service call shape.

Test Plan

  • uv run ruff check on touched OSS files
  • uv run pyright on touched OSS files
  • uv run pytest --no-cov tests/server/services/evaluation_overview/test_service_integration.py tests/server/services/evaluation_overview/test_service_grouping_integration.py tests/server/services/storage/test_session_window_contract.py tests/server/services/storage/test_sqlite_storage_bc_extras.py tests/server/services/storage/test_shadow_verdicts_contract_integration.py tests/server/services/agent_success_evaluation/test_regen_jobs_sampling_integration.py
  • Local self-host Supabase smoke with 1000 seeded evaluation sessions: /api/get_evaluation_overview returned 200 in ~0.28-0.33s after optimization.

Summary by CodeRabbit

  • Performance Improvements
    • Improved evaluation overview and dashboard loading with more efficient bulk data retrieval.
    • Added database indexes to speed up session, evaluation-result, and shadow-verdict queries.
  • Bug Fixes
    • Corrected “recent” shadow verdict retrieval to consistently return newest-first results with proper time-window filtering and enforced limits.
  • Improvements
    • Streamlined attribution data by bulk-loading session earliest-request metadata and citations.
    • Improved resilience for regeneration jobs when session metadata lookups fail, avoiding job error states.

Replace per-session evaluation overview lookups with bulk storage reads and targeted evaluation-result/shadow verdict helpers. This keeps the public response shape unchanged while avoiding N+1 request and citation scans for self-hosted deployments.
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 708be17b-cc0b-4e06-ae73-5ea6780b600f

📥 Commits

Reviewing files that changed from the base of the PR and between 4c568f6 and 18b80d2.

📒 Files selected for processing (5)
  • reflexio/server/services/storage/sqlite_storage/_extras.py
  • reflexio/server/services/storage/storage_base/_shadow_verdicts.py
  • tests/server/services/evaluation_overview/test_service_integration.py
  • tests/server/services/storage/test_shadow_verdicts_contract_integration.py
  • tests/server/services/storage/test_sqlite_storage_bc_extras.py
💤 Files with no reviewable changes (1)
  • tests/server/services/evaluation_overview/test_service_integration.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • reflexio/server/services/storage/storage_base/_shadow_verdicts.py
  • reflexio/server/services/storage/sqlite_storage/_extras.py
  • tests/server/services/storage/test_sqlite_storage_bc_extras.py

📝 Walkthrough

Walkthrough

Introduces SessionFirstRequest and SessionCitation NamedTuple types, then adds bulk storage methods (get_first_requests_by_session_ids, get_agent_success_evaluation_result_ids, get_agent_success_evaluation_results_in_window, get_citations_by_session_ids, get_recent_shadow_comparison_verdicts) with SQLite-optimized implementations and supporting indexes. Callers in the evaluation overview service, regen jobs, group evaluation runner, and API endpoints are refactored to use these bulk methods, replacing per-session fetches and in-process filtering.

Changes

Bulk Storage API and Service Refactor

Layer / File(s) Summary
New session data types
reflexio/models/api_schema/internal_schema.py
Adds SessionFirstRequest and SessionCitation NamedTuple types used as return shapes by all new bulk storage methods.
Storage base bulk method defaults
reflexio/server/services/storage/storage_base/_requests.py, reflexio/server/services/storage/storage_base/_playbook.py, reflexio/server/services/storage/storage_base/_extras.py, reflexio/server/services/storage/storage_base/_shadow_verdicts.py
Adds default (in-process filtering) implementations of get_first_requests_by_session_ids, get_agent_success_evaluation_results_in_window, get_agent_success_evaluation_result_ids, get_citations_by_session_ids, and get_recent_shadow_comparison_verdicts across the four storage base mixins.
SQLite implementations and indexes
reflexio/server/services/storage/sqlite_storage/_base.py, reflexio/server/services/storage/sqlite_storage/_requests.py, reflexio/server/services/storage/sqlite_storage/_playbook.py, reflexio/server/services/storage/sqlite_storage/_extras.py, reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py
Adds SQLite-optimized overrides using chunked IN queries, window functions, and server-side LIMIT/ORDER BY; adds new compound and descending indexes on requests, agent_success_evaluation_result, and shadow_comparison_verdicts tables.
Evaluation overview service bulk-load refactor
reflexio/server/services/evaluation_overview/service.py
Rewrites EvaluationOverviewService.run to bulk-load results, citations, Braintrust scores, and (conditionally) shadow verdicts once per request; passes preloaded data into _build_attribution, _build_source_set_comparison, and tile-building helpers; adds _log_phase structured logging.
Regen jobs, group runner, and API endpoint updates
reflexio/server/services/agent_success_evaluation/regen_jobs.py, reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py, reflexio/server/api.py
Replaces per-session first-request lookups in _build_sample_candidates with a single bulk call; updates force_regenerate to use get_agent_success_evaluation_result_ids; rewires _resolve_session_user_id, _find_fresh_result_id, grade_on_demand, and get_recent_shadow_comparisons in api.py to use the new targeted storage methods.
Contract and integration tests
tests/server/services/storage/test_session_window_contract.py, tests/server/services/storage/test_shadow_verdicts_contract_integration.py, tests/server/services/storage/test_sqlite_storage_bc_extras.py, tests/server/services/agent_success_evaluation/test_regen_jobs_sampling_integration.py, tests/server/services/evaluation_overview/test_service_integration.py
Adds contract tests for all new bulk storage methods; adds regen-job resiliency tests for bulk-fetch failure fallback; refactors and extends evaluation overview integration tests to verify bulk-call shapes and conditional shadow skipping.

Sequence Diagram(s)

sequenceDiagram
  participant APIEndpoint as API / Service Caller
  participant EvalOverviewService as EvaluationOverviewService
  participant Storage
  participant Braintrust

  APIEndpoint->>EvalOverviewService: run(request)
  EvalOverviewService->>Storage: get_agent_success_evaluation_results_in_window(extended_from, to_ts)
  Storage-->>EvalOverviewService: bulk AgentSuccessEvaluationResult list
  EvalOverviewService->>Storage: get_first_requests_by_session_ids(session_ids)
  Storage-->>EvalOverviewService: dict[session_id, SessionFirstRequest]
  EvalOverviewService->>Storage: get_citations_by_session_ids(session_ids)
  Storage-->>EvalOverviewService: list[SessionCitation]
  EvalOverviewService->>Braintrust: get_imported_scores(current 7d window)
  EvalOverviewService->>Braintrust: get_imported_scores(prior 7d window)
  alt include_shadow = True
    EvalOverviewService->>Storage: get_recent_shadow_comparison_verdicts(from_ts, to_ts, judge_prompt_version, limit)
    Storage-->>EvalOverviewService: list[ShadowComparisonVerdict]
  end
  EvalOverviewService-->>APIEndpoint: GetEvaluationOverviewResponse
Loading
sequenceDiagram
  participant RegenJob as _build_sample_candidates
  participant Storage

  RegenJob->>Storage: get_first_requests_by_session_ids(all_session_ids)
  alt bulk fetch succeeds
    Storage-->>RegenJob: dict[session_id, SessionFirstRequest]
    RegenJob->>RegenJob: populate SampleCandidate.created_at / first_request_source per session
  else bulk fetch fails
    Storage-->>RegenJob: exception
    RegenJob->>RegenJob: warn + fallback to created_at=0, source=""
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ReflexioAI/reflexio#39: This PR's get_citations_by_session_ids extracts SessionCitation from interactions.citations, directly building on the Citation type and Interaction.citations field schema introduced in that PR.
  • ReflexioAI/reflexio#148: This PR's get_first_requests_by_session_ids and evaluation overview source-set attribution rewiring align directly with that PR's changes to group/compare sessions by request source across regen_jobs.py and evaluation_overview/service.py.

Poem

🐇 Hopping through sessions, one call for them all,
No more per-session fetches, no per-session sprawl.
Bulk requests, bulk citations, descending by time,
A window of results — oh, isn't it fine!
The indexes descend, the queries are fast,
This bunny's bulk APIs are built to last! 🗂️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: optimize evaluation overview reads' directly and clearly describes the main change—optimizing evaluation overview reads through bulk storage calls and indexed queries to address N+1 patterns.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/optimize-evaluation-overview

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (1)
tests/server/services/storage/test_session_window_contract.py (1)

296-300: ⚡ Quick win

Use next(...) instead of building a list then indexing [0].

This avoids an unnecessary list allocation and matches the project’s static-analysis recommendation.

Proposed change
-    row = [
-        r
-        for r in storage.get_agent_success_evaluation_results(limit=100)
-        if r.result_id == ids[0]
-    ][0]
+    row = next(
+        r
+        for r in storage.get_agent_success_evaluation_results(limit=100)
+        if r.result_id == ids[0]
+    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/storage/test_session_window_contract.py` around lines
296 - 300, Replace the list comprehension with indexing `[0]` in the row
variable assignment with the `next()` function. The current code builds a full
list from the filtered results of storage.get_agent_success_evaluation_results
and then takes the first element. Instead, wrap the generator expression
(without the list brackets) in a next() call to efficiently retrieve just the
first matching item where r.result_id equals ids[0], avoiding unnecessary list
allocation.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/server/services/storage/sqlite_storage/_extras.py`:
- Around line 500-507: The SQL query in the method filters out NULL and empty
array citations using the conditions `AND i.citations IS NOT NULL AND
i.citations != '[]'`, but it does not filter out empty string citations which
can break the _json_loads parsing call. Add an additional WHERE clause condition
`AND i.citations != ''` to exclude empty string values from the query results,
ensuring that only valid non-empty citation strings are processed by the
_json_loads function in the subsequent loop.

In `@reflexio/server/services/storage/sqlite_storage/_playbook.py`:
- Around line 1359-1364: The SQL SELECT query on the
agent_success_evaluation_result table orders results only by created_at DESC,
which produces non-deterministic ordering when multiple rows share the same
timestamp. Fix this by adding result_id DESC as a secondary tie-breaker in the
ORDER BY clause after created_at DESC, so that when timestamps are equal, the
results are ordered consistently by result_id in descending order.

In `@reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py`:
- Around line 184-195: The limit parameter is passed directly to the SQLite
LIMIT clause without defensive validation, and negative values in SQLite LIMIT
can remove the cap entirely and trigger large reads. Before using limit in the
SQL query parameters tuple passed to _fetchall, clamp it to a safe minimum value
(such as 0 or a reasonable maximum) using similar max and min logic already
applied to from_ts and to_ts with _MAX_SAFE_EPOCH_TS, ensuring the storage layer
enforces safe boundaries regardless of what callers provide.

In `@reflexio/server/services/storage/storage_base/_playbook.py`:
- Around line 650-666: The methods in the base fallback implementation are using
hard-coded limits of 10_000 rows when calling
get_agent_success_evaluation_results, which silently truncates results and
violates the storage contract. In both the method filtering by time range and
get_agent_success_evaluation_result_ids, remove or modify the hard-coded limit
parameter to either paginate exhaustively through all available rows until no
more results are returned, or explicitly raise NotImplementedError to indicate
that the default fallback implementation cannot guarantee complete results for
backends that do not override these methods.

In `@reflexio/server/services/storage/storage_base/_requests.py`:
- Around line 168-174: The hardcoded top_k=1000 parameter in the get_sessions
method call can miss the true earliest request when a session contains more than
1,000 rows, causing SessionFirstRequest to return incorrect data. Replace the
fixed top_k=1000 with either a sufficiently large limit that captures all
possible rows in a session, or remove the top_k constraint entirely if the
get_sessions method supports retrieving all rows. Alternatively, if the method
supports sorting, request results sorted by created_at in ascending order and
take only the first row instead of filtering locally.

In `@reflexio/server/services/storage/storage_base/_shadow_verdicts.py`:
- Around line 116-124: The fetch_newest_shadow_comparison_verdicts method does
not validate the limit parameter before slicing. When limit is zero or negative,
Python's slicing behavior produces unexpected results instead of returning an
empty list. Add a guard clause at the beginning of the return statement that
checks if limit is less than or equal to zero and returns an empty list in that
case, otherwise proceed with the current slicing logic on the reversed verdicts.

In `@tests/server/services/evaluation_overview/test_service_integration.py`:
- Around line 371-375: Remove the strict wall-clock time assertion `assert
elapsed < 2.0` from the integration test as it is environment-sensitive and can
cause flakiness in CI environments. The existing assertions like `assert
response.hero.regular_success_rate_pp == 50.0` and
`storage.get_agent_success_evaluation_results_in_window.assert_called_once()`
are sufficient for verifying correct behavior in integration tests. Optionally
remove the `elapsed` variable calculation if it is no longer used elsewhere in
the test.
- Around line 42-60: The helper function _storage_with_results is mocking the
outdated method name get_shadow_comparison_verdicts, but the new storage
contract uses get_recent_shadow_comparison_verdicts. Update the mocked method
name in the storage mock within _storage_with_results from
get_shadow_comparison_verdicts to get_recent_shadow_comparison_verdicts to align
with the new storage contract. Additionally, apply the same fix to the
assertions at lines 328-345 that also reference the old method name to prevent
call-shape regressions.

---

Nitpick comments:
In `@tests/server/services/storage/test_session_window_contract.py`:
- Around line 296-300: Replace the list comprehension with indexing `[0]` in the
row variable assignment with the `next()` function. The current code builds a
full list from the filtered results of
storage.get_agent_success_evaluation_results and then takes the first element.
Instead, wrap the generator expression (without the list brackets) in a next()
call to efficiently retrieve just the first matching item where r.result_id
equals ids[0], avoiding unnecessary list allocation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dca05c92-3e96-408d-973a-331f218a7405

📥 Commits

Reviewing files that changed from the base of the PR and between b3f20fe and bbb97fa.

📒 Files selected for processing (19)
  • reflexio/models/api_schema/internal_schema.py
  • reflexio/server/api.py
  • reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py
  • reflexio/server/services/agent_success_evaluation/regen_jobs.py
  • reflexio/server/services/evaluation_overview/service.py
  • reflexio/server/services/storage/sqlite_storage/_base.py
  • reflexio/server/services/storage/sqlite_storage/_extras.py
  • reflexio/server/services/storage/sqlite_storage/_playbook.py
  • reflexio/server/services/storage/sqlite_storage/_requests.py
  • reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py
  • reflexio/server/services/storage/storage_base/_extras.py
  • reflexio/server/services/storage/storage_base/_playbook.py
  • reflexio/server/services/storage/storage_base/_requests.py
  • reflexio/server/services/storage/storage_base/_shadow_verdicts.py
  • tests/server/services/agent_success_evaluation/test_regen_jobs_sampling_integration.py
  • tests/server/services/evaluation_overview/test_service_integration.py
  • tests/server/services/storage/test_session_window_contract.py
  • tests/server/services/storage/test_shadow_verdicts_contract_integration.py
  • tests/server/services/storage/test_sqlite_storage_bc_extras.py

Comment thread reflexio/server/services/storage/sqlite_storage/_extras.py
Comment on lines +1359 to +1364
"""SELECT result_id FROM agent_success_evaluation_result
WHERE session_id = ?
AND evaluation_name = ?
AND agent_version = ?
ORDER BY created_at DESC""",
(session_id, evaluation_name, agent_version),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make targeted result-id ordering deterministic on timestamp ties.

Ordering only by created_at DESC is unstable when multiple rows share the same timestamp. Add result_id DESC as a tie-breaker so “latest” resolution is deterministic.

Proposed fix
-               ORDER BY created_at DESC""",
+               ORDER BY created_at DESC, result_id DESC""",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/storage/sqlite_storage/_playbook.py` around lines
1359 - 1364, The SQL SELECT query on the agent_success_evaluation_result table
orders results only by created_at DESC, which produces non-deterministic
ordering when multiple rows share the same timestamp. Fix this by adding
result_id DESC as a secondary tie-breaker in the ORDER BY clause after
created_at DESC, so that when timestamps are equal, the results are ordered
consistently by result_id in descending order.

Comment thread reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py
Comment on lines +650 to +666
rows = self.get_agent_success_evaluation_results(
limit=limit or 10_000,
agent_version=agent_version,
)
return [r for r in rows if from_ts <= r.created_at <= to_ts]

def get_agent_success_evaluation_result_ids(
self,
session_id: str,
evaluation_name: str,
agent_version: str,
) -> list[int]:
"""Return result ids for one eval identity tuple."""
rows = self.get_agent_success_evaluation_results(
limit=10_000,
agent_version=agent_version,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid silent truncation in base fallback bulk reads.

The new default implementations hard-cap reads at 10_000, which can silently miss valid rows for both window queries and targeted ID lookups. That breaks the storage contract semantics on non-overridden backends and can produce incorrect overview/regeneration behavior.

Suggested direction
-        rows = self.get_agent_success_evaluation_results(
-            limit=limit or 10_000,
-            agent_version=agent_version,
-        )
+        # Fallback must be complete, not capped; backends should override
+        # for efficient server-side filtering.
+        rows = self.get_agent_success_evaluation_results(
+            limit=limit if limit is not None else 1_000_000_000,
+            agent_version=agent_version,
+        )

Consider either paginating exhaustively in the fallback or explicitly raising NotImplementedError for backends that cannot guarantee completeness.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rows = self.get_agent_success_evaluation_results(
limit=limit or 10_000,
agent_version=agent_version,
)
return [r for r in rows if from_ts <= r.created_at <= to_ts]
def get_agent_success_evaluation_result_ids(
self,
session_id: str,
evaluation_name: str,
agent_version: str,
) -> list[int]:
"""Return result ids for one eval identity tuple."""
rows = self.get_agent_success_evaluation_results(
limit=10_000,
agent_version=agent_version,
)
# Fallback must be complete, not capped; backends should override
# for efficient server-side filtering.
rows = self.get_agent_success_evaluation_results(
limit=limit if limit is not None else 1_000_000_000,
agent_version=agent_version,
)
return [r for r in rows if from_ts <= r.created_at <= to_ts]
def get_agent_success_evaluation_result_ids(
self,
session_id: str,
evaluation_name: str,
agent_version: str,
) -> list[int]:
"""Return result ids for one eval identity tuple."""
rows = self.get_agent_success_evaluation_results(
limit=10_000,
agent_version=agent_version,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/storage/storage_base/_playbook.py` around lines 650
- 666, The methods in the base fallback implementation are using hard-coded
limits of 10_000 rows when calling get_agent_success_evaluation_results, which
silently truncates results and violates the storage contract. In both the method
filtering by time range and get_agent_success_evaluation_result_ids, remove or
modify the hard-coded limit parameter to either paginate exhaustively through
all available rows until no more results are returned, or explicitly raise
NotImplementedError to indicate that the default fallback implementation cannot
guarantee complete results for backends that do not override these methods.

Comment thread reflexio/server/services/storage/storage_base/_requests.py Outdated
Comment thread reflexio/server/services/storage/storage_base/_shadow_verdicts.py
Comment on lines +42 to +60
def _storage_with_results(
results: list[AgentSuccessEvaluationResult],
) -> MagicMock:
storage = MagicMock()
storage.get_agent_success_evaluation_results.return_value = [
_eval_result(result_id=1, session_id="s1", is_success=True, corrections=0),
_eval_result(result_id=2, session_id="s2", is_success=True, corrections=1),
_eval_result(result_id=3, session_id="s3", is_success=False, corrections=3),
]
storage.get_playbook_application_stats.return_value = []
storage.get_interactions_by_session.return_value = []
storage.org_id = ""
storage.get_agent_success_evaluation_results_in_window.return_value = results
storage.get_first_requests_by_session_ids.return_value = {
r.session_id: SessionFirstRequest(
session_id=r.session_id,
user_id="u1",
source="api",
created_at=r.created_at,
)
for r in results
}
storage.get_citations_by_session_ids.return_value = []
storage.get_imported_scores.return_value = []
storage.get_shadow_comparison_verdicts.return_value = []
return storage

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Align mocked/asserted shadow API with the new targeted storage contract.

The helper and skip-shadow assertion still reference get_shadow_comparison_verdicts, but this PR’s storage contract introduces get_recent_shadow_comparison_verdicts. Keeping the old method in these assertions can let call-shape regressions slip through.

Proposed change
 def _storage_with_results(
     results: list[AgentSuccessEvaluationResult],
 ) -> MagicMock:
     storage = MagicMock()
@@
-    storage.get_shadow_comparison_verdicts.return_value = []
+    storage.get_recent_shadow_comparison_verdicts.return_value = []
     return storage
@@
 def test_service_skips_shadow_storage_when_shadow_excluded() -> None:
@@
-    storage.get_shadow_comparison_verdicts.assert_not_called()
+    storage.get_recent_shadow_comparison_verdicts.assert_not_called()

Also applies to: 328-345

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/evaluation_overview/test_service_integration.py` around
lines 42 - 60, The helper function _storage_with_results is mocking the outdated
method name get_shadow_comparison_verdicts, but the new storage contract uses
get_recent_shadow_comparison_verdicts. Update the mocked method name in the
storage mock within _storage_with_results from get_shadow_comparison_verdicts to
get_recent_shadow_comparison_verdicts to align with the new storage contract.
Additionally, apply the same fix to the assertions at lines 328-345 that also
reference the old method name to prevent call-shape regressions.

Comment thread tests/server/services/evaluation_overview/test_service_integration.py Outdated
yyiilluu added 2 commits June 17, 2026 14:30
Paginate the base first-request fallback so sessions with more than 1000 requests still find the true earliest request. Clamp negative SQLite recent-verdict limits to avoid uncapped reads.
Ignore blank SQLite citation payloads before JSON parsing, guard base recent-shadow reads for non-positive limits, and remove an environment-sensitive wall-clock assertion from the overview integration test.
@yyiilluu yyiilluu merged commit 54f1dd1 into main Jun 17, 2026
1 check passed
yilu331 added a commit that referenced this pull request Jun 18, 2026
…172)

## Summary
Found by `/check-and-test` (nightly CI). Two related fixes surfaced by
the new evaluation-overview query added in #168.

**Application fix — clamp `_epoch_to_iso` to a representable range:**
- `_epoch_to_iso` raised `ValueError` when callers passed sentinel
"open" window bounds (e.g. `to_ts=10**12`, used by the
evaluation-overview API as a far-future upper bound) because
`datetime.fromtimestamp` cannot represent year > 9999.
- The new `get_agent_success_evaluation_results_in_window` query (#168)
hit this and **500'd the evaluation overview endpoint on a fresh app**.
- Clamp the input to `[0, 253402300799]` centrally in `_epoch_to_iso` so
all current and future callers are protected. `_shadow_verdicts.py` had
already worked around this at two call sites with a local
`_MAX_SAFE_EPOCH_TS` clamp; that constant now lives in `_base.py` and
the redundant per-call clamps are removed.

**Test fix — stale drift from #168:**
- The `force_regenerate` runner now captures prior result ids via the
targeted `get_agent_success_evaluation_result_ids` lookup instead of
reading full result rows. `test_group_evaluation_runner_regen.py` still
mocked the old method, so the by-id delete asserted the wrong argument
(MagicMock tests) or bound a `MagicMock` into SQL (real-sqlite tests).
Seed the new lookup and patch `get_extractor_name` so the capture
query's `evaluation_name` matches the seeded rows.

## Changes
- `reflexio/services/storage/sqlite_storage/_base.py` — central clamp in
`_epoch_to_iso`; host `_MAX_SAFE_EPOCH_TS`.
- `reflexio/services/storage/sqlite_storage/_shadow_verdicts.py` — drop
the now-redundant local clamps.
- `tests/.../test_group_evaluation_runner_regen.py` — align mocks with
the by-id lookup path.

## Test Plan
- `test_group_evaluation_runner_regen.py` passes under both MagicMock
and real-sqlite paths.
- Evaluation-overview endpoint no longer 500s with an open far-future
`to_ts` on a fresh app.

## Provenance
Generated by the nightly AI-driven CI run on 2026-06-18. The
enterprise-side submodule bump + companion test changes are in
ReflexioAI/reflexio-enterprise#263 — that PR's `open_source/reflexio`
pointer should be repointed to this PR's **merged** `main` commit once
it lands.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **Bug Fixes**
* Fixed query failures when requesting results with extreme or boundary
timestamp values by safely handling out-of-range epochs in SQLite-backed
storage.

* **Tests**
* Improved coverage for agent success evaluation “force regenerate”
flows, including correct identity matching and deletion/preservation
behavior across both mocked and real SQLite scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

1 participant