Skip to content

Feat/datainfra refactor tracker#98

Merged
jerry609 merged 2 commits into
masterfrom
feat/datainfra-refactor-tracker
Feb 12, 2026
Merged

Feat/datainfra refactor tracker#98
jerry609 merged 2 commits into
masterfrom
feat/datainfra-refactor-tracker

Conversation

@jerry609

@jerry609 jerry609 commented Feb 12, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added workflow performance metrics tracking to monitor execution timing, claim counts, and evidence coverage across automated report generation and analysis processes.
    • Added a new API endpoint to retrieve workflow performance summaries and historical metrics data.
  • Tests

    • Added unit tests for workflow metrics functionality and API endpoints.

Copilot AI review requested due to automatic review settings February 12, 2026 12:42
@jerry609
jerry609 merged commit 1767382 into master Feb 12, 2026
4 of 5 checks passed
@coderabbitai

coderabbitai Bot commented Feb 12, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

This pull request introduces comprehensive workflow metrics instrumentation across the codebase. It adds a new WorkflowMetricStore class with persistent database storage, a new WorkflowEvalMetricModel for metrics data, instrumentation points in daily/analyze workflow routes and research context building, and a new API endpoint for retrieving workflow metrics summaries.

Changes

Cohort / File(s) Summary
Workflow Metrics Infrastructure
src/paperbot/infrastructure/stores/models.py, src/paperbot/infrastructure/stores/workflow_metric_store.py
Introduces WorkflowEvalMetricModel ORM table with fields for tracking workflow execution metrics (claim/evidence counts, coverage, elapsed time, status) and WorkflowMetricStore class providing record_metric and summarize methods for persistent metric storage and aggregation.
Paperscool Route Instrumentation
src/paperbot/api/routes/paperscool.py
Adds phase timing and metric recording across daily and analyze workflow pathways; includes helper functions _get_workflow_metric_store() and _count_report_claims_and_evidence() to capture metrics at key milestones (search, build, enrich, persist phases) with try/except wrapping for error tracking.
Research Route Instrumentation
src/paperbot/api/routes/research.py
Adds WorkflowMetricStore dependency, new public endpoint get_workflow_metrics_summary() at /research/metrics/workflows, and WorkflowMetricsResponse model; instruments build_context() to record timing and claim/evidence metrics with success/failure tracking.
Unit Tests
tests/unit/test_workflow_metric_store.py, tests/unit/test_research_workflow_metrics_route.py
Adds tests for WorkflowMetricStore persistence, filtering, and aggregation logic; tests research metrics API endpoint with in-memory SQLite backend and query parameter filtering.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/Caller
    participant Route as API Route<br/>(paperscool/research)
    participant Store as WorkflowMetricStore
    participant DB as Database<br/>(workflow_eval_metrics)

    Client->>Route: Request workflow execution
    activate Route
    Route->>Route: Start timing<br/>(time.perf_counter)
    
    rect rgba(100, 150, 200, 0.5)
    Note over Route: Execute workflow phase<br/>(search, build, enrich, etc.)
    Route->>Route: Phase completion
    end
    
    Route->>Store: record_metric(workflow, stage,<br/>claim_count, evidence_count, elapsed_ms)
    activate Store
    Store->>Store: Compute coverage_rate<br/>Serialize detail JSON
    Store->>DB: INSERT INTO workflow_eval_metrics
    deactivate Store
    DB-->>Store: Metric persisted
    Store-->>Route: Return dict representation
    
    rect rgba(150, 200, 100, 0.5)
    Note over Route: Workflow completes<br/>(success/failure)
    end
    
    Route-->>Client: Return result
    deactivate Route
    
    Client->>Route: GET /metrics/workflows?days=7
    activate Route
    Route->>Store: summarize(days=7, workflow=..., track_id=...)
    activate Store
    Store->>DB: Query metrics within window<br/>with filters
    Store->>Store: Aggregate by:<br/>- total (runs, success, failed)<br/>- per-day buckets<br/>- per-workflow buckets<br/>- per-track buckets
    Store->>Store: Compute averages<br/>and coverage rates
    deactivate Store
    Store-->>Route: Return summary dict
    Route->>Route: Wrap in WorkflowMetricsResponse
    Route-->>Client: Return metrics summary (HTTP 200)
    deactivate Route
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Metrics bloom where workflows run,
Each claim and stage, their counts we sum,
Timing taps and data stored,
Workflow wisdom now recorded!
Coverage tracked, the truth distilled—
One hop closer, work fulfilled. 🎯✨

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/datainfra-refactor-tracker

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.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @jerry609, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's observability by implementing a comprehensive system for tracking and analyzing workflow performance. It provides a structured way to collect and store detailed metrics about the execution of critical processes, enabling better monitoring of their efficiency and effectiveness. The addition of a dedicated API endpoint for these metrics facilitates easy access to aggregated data, supporting data-driven insights into workflow health and areas for improvement.

Highlights

  • Workflow Metric Tracking: Introduced a new WorkflowMetricStore to persist and aggregate workflow quality metrics such as execution time, claim counts, evidence counts, and status for various stages of key workflows.
  • API Integration: Integrated metric recording into the paperscool_daily and research_context workflows, capturing performance and outcome data at different processing stages.
  • New Metrics API Endpoint: Added a new API endpoint /research/metrics/workflows to retrieve summarized workflow metrics, allowing for observability and analysis of workflow performance over time.
  • Database Model: Defined a new SQLAlchemy model, WorkflowEvalMetricModel, to structure and store the collected workflow evaluation metrics in the database.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/paperbot/api/routes/paperscool.py
    • Imported time module for performance measurement.
    • Imported and initialized WorkflowMetricStore.
    • Added _get_workflow_metric_store helper function for lazy initialization.
    • Implemented _count_report_claims_and_evidence to calculate claims and evidence from reports.
    • Integrated metric recording at various stages within _dailypaper_stream (e.g., search, build, enrich, approval pending, result).
    • Added phase_ms tracking to measure elapsed time for different phases of the daily paper stream.
    • Recorded metrics for both successful completion and failures in generate_daily_report's sync path.
    • Added metric recording for approve_daily_session and reject_daily_session actions.
  • src/paperbot/api/routes/research.py
    • Imported time module for performance measurement.
    • Imported and initialized WorkflowMetricStore.
    • Added _get_workflow_metric_store helper function.
    • Introduced a new GET endpoint /research/metrics/workflows to retrieve workflow metric summaries.
    • Integrated metric recording into the build_context function, capturing completion and failure states.
  • src/paperbot/infrastructure/stores/models.py
    • Added WorkflowEvalMetricModel to define the database schema for storing workflow evaluation metrics, including workflow name, stage, status, counts, coverage, and elapsed time.
  • src/paperbot/infrastructure/stores/workflow_metric_store.py
    • Added new file workflow_metric_store.py.
    • Defined the WorkflowMetricStore class for persisting and aggregating workflow metrics.
    • Implemented record_metric method to save individual metric entries to the database.
    • Implemented summarize method to generate aggregated summaries of metrics over a specified period, broken down by day, workflow, and track.
    • Included helper methods for bucket aggregation, coverage calculation, and object serialization.
  • tests/unit/test_research_workflow_metrics_route.py
    • Added new test file test_research_workflow_metrics_route.py.
    • Implemented a unit test to verify the functionality of the /research/metrics/workflows API endpoint, ensuring it returns correct metric summaries.
  • tests/unit/test_workflow_metric_store.py
    • Added new test file test_workflow_metric_store.py.
    • Implemented unit tests to validate the WorkflowMetricStore's ability to record metrics, summarize them accurately, and filter results by workflow and track ID.
Activity
  • New functionality for tracking workflow metrics has been implemented.
  • New API endpoints and database models have been introduced.
  • Unit tests have been added to cover the new metric store and API route.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copilot AI 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.

Pull request overview

Adds a workflow-metrics persistence + aggregation layer (SQLAlchemy-backed) and wires it into the Research context and PapersCool workflows, plus exposes a research API endpoint to query summarized workflow metrics.

Changes:

  • Introduces WorkflowEvalMetricModel + WorkflowMetricStore to record and summarize workflow runs (status/latency/coverage).
  • Adds a new Research route GET /api/research/metrics/workflows to fetch aggregated workflow metrics.
  • Instruments research.build_context and multiple paperscool execution paths to record workflow metrics; adds unit tests for store + route.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/paperbot/infrastructure/stores/models.py Adds WorkflowEvalMetricModel table for workflow metrics persistence.
src/paperbot/infrastructure/stores/workflow_metric_store.py New store for recording metrics and summarizing totals/buckets.
src/paperbot/api/routes/research.py Adds metrics summary endpoint and records metrics for build_context.
src/paperbot/api/routes/paperscool.py Records workflow metrics across daily/analyze/approval flows and adds evidence/claim counting helper.
tests/unit/test_workflow_metric_store.py Unit tests for record + summarize behavior and filtering.
tests/unit/test_research_workflow_metrics_route.py Route test for workflow metrics summary endpoint.
Comments suppressed due to low confidence (1)

src/paperbot/api/routes/research.py:1151

  • When activate_track_id is provided but the track is not found, the handler raises before the try/except that records workflow metrics. This creates a blind spot where an important failure mode is never recorded. Consider recording a failed metric (with a distinct stage/status) just before raising the 404 so the request is still observable.
    if req.activate_track_id is not None:
        Logger.info("Activating research track", file=LogFiles.HARVEST)
        activated = _research_store.activate_track(
            user_id=req.user_id, track_id=req.activate_track_id
        )
        if not activated:
            Logger.error("Research track not found", file=LogFiles.HARVEST)
            raise HTTPException(status_code=404, detail="Track not found")

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +34 to +36
assert summary["totals"]["runs"] >= 1
assert summary["totals"]["claim_count"] >= 12
assert summary["totals"]["evidence_count"] >= 9

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

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

This test uses >= assertions for totals even though it writes to a fresh per-test SQLite file. Using exact expectations (e.g. runs==1, claim_count==12, evidence_count==9) would make the test more precise and better at catching regressions in filtering/aggregation.

Suggested change
assert summary["totals"]["runs"] >= 1
assert summary["totals"]["claim_count"] >= 12
assert summary["totals"]["evidence_count"] >= 9
assert summary["totals"]["runs"] == 1
assert summary["totals"]["claim_count"] == 12
assert summary["totals"]["evidence_count"] == 9

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +100

for row in rows:
totals["runs"] += 1
if row.status == "completed":
totals["success_runs"] += 1
else:

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

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

summarize() currently treats any status other than "completed" as a failure. Call sites record statuses like "pending_approval" and "rejected" (e.g. paperscool), which will be miscounted as failed runs and inflate failure_stages. Consider explicitly classifying statuses (e.g. success={"completed"}, failure={"failed"}, and either ignore/track separately the rest) so pending/rejected don’t show up as runtime failures.

Suggested change
for row in rows:
totals["runs"] += 1
if row.status == "completed":
totals["success_runs"] += 1
else:
success_statuses = {"completed"}
failure_statuses = {"failed"}
for row in rows:
totals["runs"] += 1
status = (row.status or "").lower()
if status in success_statuses:
totals["success_runs"] += 1
elif status in failure_statuses:

Copilot uses AI. Check for mistakes.
bucket["runs"] = int(bucket.get("runs") or 0) + 1
if row.status == "completed":
bucket["success_runs"] = int(bucket.get("success_runs") or 0) + 1
else:

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

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

Same issue as above in per-bucket aggregation: _add_to_bucket() increments failed_runs for any non-"completed" status, so buckets will also misreport pending/rejected runs as failures. Align bucket counting with the status classification used for totals.

Suggested change
else:
elif row.status == "failed":

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +80
stmt = select(WorkflowEvalMetricModel).where(WorkflowEvalMetricModel.ts >= since)
if workflow:
stmt = stmt.where(WorkflowEvalMetricModel.workflow == str(workflow)[:64])
if track_id is not None:
stmt = stmt.where(WorkflowEvalMetricModel.track_id == int(track_id))

with self._provider.session() as session:
rows = session.execute(stmt).scalars().all()

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

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

summarize() loads all matching metric rows into memory and aggregates in Python. Over a 90-day window this can become expensive (memory + latency) as the table grows. Consider pushing aggregation into SQL (COUNT/SUM/GROUP BY for totals, by_day, by_workflow, by_track) and only fetching aggregated rows.

Copilot uses AI. Check for mistakes.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a WorkflowMetricStore to centralize and persist metrics for various workflows, which is a great step towards better observability. The implementation is solid, with a new database model, a store class to handle persistence and aggregation, and integration into the paperscool and research API routes. New tests are also included to cover the new functionality.

My review includes a couple of suggestions for improvement: one regarding code duplication in paperscool.py for better maintainability, and another about optimizing the metric aggregation logic in workflow_metric_store.py for better performance as the number of metrics grows.

session_id=session_id, result=result_payload, status="completed"
)

claims, evidences = _count_report_claims_and_evidence(report)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The calculation of claims and evidences is also performed earlier in the if req.require_approval: block (line 649). To avoid code duplication and improve maintainability, you could calculate these values once after the report is finalized (e.g., after the enrichment phase around line 645) and before the approval check, then reuse the variables in both places.

Comment on lines +62 to +176
def summarize(
self,
*,
days: int = 7,
workflow: Optional[str] = None,
track_id: Optional[int] = None,
) -> Dict[str, Any]:
window_days = max(1, min(int(days), 90))
since = _utcnow() - timedelta(days=window_days)

stmt = select(WorkflowEvalMetricModel).where(WorkflowEvalMetricModel.ts >= since)
if workflow:
stmt = stmt.where(WorkflowEvalMetricModel.workflow == str(workflow)[:64])
if track_id is not None:
stmt = stmt.where(WorkflowEvalMetricModel.track_id == int(track_id))

with self._provider.session() as session:
rows = session.execute(stmt).scalars().all()

totals = {
"runs": 0,
"success_runs": 0,
"failed_runs": 0,
"avg_elapsed_ms": 0.0,
"claim_count": 0,
"evidence_count": 0,
"coverage_rate": 0.0,
}

by_day: Dict[str, Dict[str, Any]] = {}
by_workflow: Dict[str, Dict[str, Any]] = {}
by_track: Dict[str, Dict[str, Any]] = {}
failure_stages: Dict[str, int] = defaultdict(int)

for row in rows:
totals["runs"] += 1
if row.status == "completed":
totals["success_runs"] += 1
else:
totals["failed_runs"] += 1
failure_stages[row.stage or "unknown"] += 1

totals["avg_elapsed_ms"] += float(row.elapsed_ms or 0.0)
totals["claim_count"] += int(row.claim_count or 0)
totals["evidence_count"] += int(row.evidence_count or 0)

date_key = (row.ts or _utcnow()).date().isoformat()
day = by_day.setdefault(
date_key,
{
"date": date_key,
"runs": 0,
"success_runs": 0,
"failed_runs": 0,
"claim_count": 0,
"evidence_count": 0,
"avg_elapsed_ms": 0.0,
"coverage_rate": 0.0,
},
)
self._add_to_bucket(day, row)

wf = by_workflow.setdefault(
row.workflow or "unknown",
{
"workflow": row.workflow or "unknown",
"runs": 0,
"success_runs": 0,
"failed_runs": 0,
"claim_count": 0,
"evidence_count": 0,
"avg_elapsed_ms": 0.0,
"coverage_rate": 0.0,
},
)
self._add_to_bucket(wf, row)

track_key = str(row.track_id) if row.track_id is not None else "none"
track_bucket = by_track.setdefault(
track_key,
{
"track_id": row.track_id,
"runs": 0,
"success_runs": 0,
"failed_runs": 0,
"claim_count": 0,
"evidence_count": 0,
"avg_elapsed_ms": 0.0,
"coverage_rate": 0.0,
},
)
self._add_to_bucket(track_bucket, row)

if totals["runs"] > 0:
totals["avg_elapsed_ms"] = round(totals["avg_elapsed_ms"] / totals["runs"], 2)
totals["coverage_rate"] = self._coverage(totals["claim_count"], totals["evidence_count"])

return {
"window_days": window_days,
"totals": totals,
"by_day": [self._finalize_bucket(by_day[k]) for k in sorted(by_day.keys())],
"by_workflow": sorted(
(self._finalize_bucket(v) for v in by_workflow.values()),
key=lambda x: int(x.get("runs") or 0),
reverse=True,
),
"by_track": sorted(
(self._finalize_bucket(v) for v in by_track.values()),
key=lambda x: int(x.get("runs") or 0),
reverse=True,
),
"failure_stages": dict(
sorted(failure_stages.items(), key=lambda kv: kv[1], reverse=True)
),
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The summarize method fetches all metric rows from the database within the time window and performs aggregation in Python. For large volumes of metrics, this can become inefficient in terms of memory and performance. Consider leveraging the database for aggregation using SQLAlchemy's func module (e.g., func.sum, func.avg, func.count) and group_by. This would offload the aggregation work to the database, which is highly optimized for such tasks, and reduce the amount of data transferred to the application.

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.

2 participants