Skip to content

Analytics: Analytics Dashboard APIs#888

Merged
Ayush8923 merged 11 commits into
mainfrom
feat/analytics-dashboard-api
Jun 3, 2026
Merged

Analytics: Analytics Dashboard APIs#888
Ayush8923 merged 11 commits into
mainfrom
feat/analytics-dashboard-api

Conversation

@Ayush8923
Copy link
Copy Markdown
Collaborator

@Ayush8923 Ayush8923 commented May 26, 2026

Issue: #851

Summary

  • Add two new analytics endpoints (GET /analytics/monthly and GET /analytics/monthly/chart) that compute usage metrics live from llm_call, llm_chain, and evaluation_run.
  • Support four metrics (requests, cost, eval_runs, eval_cost) with filters for from_month/to_month, modality, provider, and project_id. Auto-scopes to the caller's current project (or whole org when none is selected) and requires REQUIRE_ORGANIZATION permission.
  • Add/Update the Swagger documentation for these two new chart APIs

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • ready-for-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 599e34ba-e0ed-446e-9596-c17cbdad2406

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds monthly analytics aggregation for the organization, exposing two new API endpoints that compute request counts, costs, and evaluation metrics across LLM calls, chains, and evaluation runs, grouped by month, modality, and provider with optional filtering and time-window selection.

Changes

Monthly Analytics Feature

Layer / File(s) Summary
Analytics Type Contracts
backend/app/models/analytics.py, backend/app/models/__init__.py
Defines Modality enum (T_FS_T, S_FS_S, STT, TTS, OTHER), AnalyticsMetric enum (REQUESTS, COST, EVAL_RUNS, EVAL_COST), AnalyticsMonthlyMetricPoint for month/modality/provider values with token fields, and chart types (AnalyticsChartGroupBy, AnalyticsChartSeries, AnalyticsChartResponse).
Provider Configuration Setup
backend/app/crud/model_config.py
Adds KNOWN_PROVIDERS frozenset derived from the Provider Literal type, and NATIVE_PROVIDER_SUFFIX constant for normalizing native provider names in _normalize_provider.
Monthly Metrics Aggregation Service
backend/app/services/analytics/aggregation.py, backend/app/services/analytics/__init__.py
Implements aggregate_monthly_metrics that queries three sources in sequence: LLM calls (derives modality from input/output types, computes cost via estimate_model_cost for known providers), chains (attributes to first block's modality/provider via left-join), and evaluations (normalizes provider from config, maps eval type to modality, sums costs). All three aggregations merge into shared in-memory buckets keyed by (date, Modality, provider).
Analytics API Endpoints
backend/app/api/routes/analytics.py
Adds GET /analytics/monthly returning AnalyticsMonthlyMetricPoint rows and GET /analytics/monthly/chart returning AnalyticsChartResponse with labeled month series. Both support query parameters for metric selection, time-window bounds (snapped to month boundaries), modality/provider filtering, and optional project scoping. Helpers validate unknown providers (400 error), compute metric values, and generate series names based on group_by.
API Router Registration
backend/app/api/main.py
Imports analytics module and registers its router under the main api_router.
Endpoint Documentation
backend/app/api/docs/analytics/monthly.md, backend/app/api/docs/analytics/monthly_chart.md
Documents both endpoints: authentication, default scoping, full parameter/enum definitions, response shapes with token aggregation rules, sorting behavior, example requests, and frontend integration tips for Recharts and Chart.js.
Analytics Endpoint Tests
backend/app/tests/api/routes/test_analytics.py
Pytest fixtures for seeding LLM jobs, evaluation datasets, and model pricing. Tests validate authentication, monthly metric aggregation (requests/costs by modality/provider), modality derivation from I/O types, evaluation metrics, chain attribution to first block, time-window filtering, project scoping, soft-delete exclusion, and chart group_by variants with token totals.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ProjectTech4DevAI/kaapi-backend#477: The aggregation layer's evaluation analytics resolver depends on this PR's switch to storing config_id/config_version in evaluation runs instead of inline config dicts.
  • ProjectTech4DevAI/kaapi-backend#746: The cost aggregation calls estimate_model_cost, which this PR changes regarding total_cost rounding, directly impacting USD cost values exposed by the new analytics endpoints.

Suggested labels

enhancement

Suggested reviewers

  • vprashrex
  • AkhileshNegi
  • Prajna1999

Poem

🐰 Hops through monthly buckets bright,
Modality and provider in sight,
LLM calls and evals too,
Cost and tokens counted true.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.27% 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 'Analytics: Analytics Dashboard APIs' is directly related to the changeset, which implements two new analytics endpoints for a dashboard with live monthly and chart APIs.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/analytics-dashboard-api

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.

@Ayush8923 Ayush8923 assigned Ayush8923 and unassigned Ayush8923 May 26, 2026
@sentry
Copy link
Copy Markdown

sentry Bot commented May 26, 2026

Codecov Report

❌ Patch coverage is 98.97959% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
backend/app/services/analytics/aggregation.py 97.27% 3 Missing ⚠️
backend/app/tests/api/routes/test_analytics.py 99.37% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Ayush8923 Ayush8923 linked an issue May 26, 2026 that may be closed by this pull request
Comment on lines +127 to +151
stmt = (
select(
month_col,
modality_col,
provider_col,
LlmCall.model,
count_col,
input_tokens_col,
output_tokens_col,
)
.where(
LlmCall.deleted_at.is_(None),
LlmCall.organization_id == organization_id,
)
.group_by(month_col, modality_col, provider_col, LlmCall.model)
)
if from_month is not None:
stmt = stmt.where(LlmCall.inserted_at >= from_month)
if end_date is not None:
stmt = stmt.where(LlmCall.inserted_at < end_date)
if project_id is not None:
stmt = stmt.where(LlmCall.project_id == project_id)
if provider_filter is not None:
stmt = stmt.where(LlmCall.provider == provider_filter)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Please review these types of queries thoroughly, as I am fetching the data using queries like these.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 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 `@backend/app/api/docs/analytics/monthly_chart.md`:
- Around line 111-113: The fenced example HTTP request blocks in
monthly_chart.md lack language identifiers; update each triple-backtick fence
that wraps the GET examples (e.g., the blocks showing "GET
/api/analytics/monthly/chart?metric=...") to use ```http so the markdown linter
recognizes them as HTTP examples—apply this change to the blocks at the shown
ranges (including the examples for metric=requests, metric=eval_cost,
metric=cost, and the other occurrences at 117-119, 123-125, 129-131).

In `@backend/app/api/docs/analytics/monthly.md`:
- Around line 131-133: The fenced code blocks showing HTTP request examples
(e.g., the lines starting "GET
/api/analytics/monthly?metric=cost&from_month=2026-01-01&to_month=2026-05-01",
"GET /api/analytics/monthly?metric=requests&modality=T-FS-T&provider=openai",
and "GET
/api/analytics/monthly?metric=eval_cost&modality=STT&from_month=2026-01-01") are
missing a language tag; update each triple-backtick fence in
backend/app/api/docs/analytics/monthly.md that wraps these GET examples to
include the language identifier http (i.e., change ``` to ```http) so markdown
linting passes.

In `@backend/app/api/routes/analytics.py`:
- Around line 160-167: After snapping dates with _snap_to_first_of_month, add an
explicit validation that if both from_month and to_month are present (or
effective_from_month computed) and the from_month > to_month (i.e., inverted
window) you return a 400 error instead of proceeding to
aggregate_monthly_metrics; locate the logic around from_month, to_month,
effective_from_month in the analytics route handler and raise/return a
BadRequest (HTTP 400) with a clear message like "from_month must be <= to_month"
before calling aggregate_monthly_metrics.
- Around line 103-145: Add explicit return type annotations to both endpoint
handlers: update get_monthly_analytics and the other analytics endpoint defined
later (the function around the 200-252 block) to declare return types like ->
APIResponse[Any] (or a more specific APIResponse[YourResponseModel] if a
response model exists), and ensure APIResponse and Any (or the specific model)
are imported; this satisfies the typing guideline by annotating the function
return values with APIResponse[...] for both endpoints.

In `@backend/app/services/analytics/aggregation.py`:
- Around line 164-170: The aggregation loop is performing a DB lookup per
grouped row via estimate_model_cost; instead, preload the model/pricing metadata
once and compute costs in-memory. Before the loop that calls
estimate_model_cost, query and build a dict cache keyed by (row.provider,
row.model) containing the required model_config/pricing info, then change the
loop to fetch from that cache and call a new helper or an updated
estimate_model_cost that accepts the preloaded config (refer to
estimate_model_cost and the aggregation loop variable names) so no DB access
happens inside the loop.
- Around line 269-273: The provider_expr currently uses mc_lookup.c.provider
first which can misattribute providers; change the coalesce order so
cv_provider_normalized (the normalized provider from ConfigVersion) is the first
choice, then fall back to sa.cast(mc_lookup.c.provider, sa.String), then
"unknown"; ensure you still nullify empty strings via
sa.func.nullif(cv_provider_normalized, "") and keep use of sa.func.coalesce,
sa.cast, mc_lookup.c.provider, cv_provider_normalized, and provider_expr when
making the replacement.
- Around line 163-167: The membership check against KNOWN_PROVIDERS happens
before normalizing provider names so rows like row.provider == "openai-native"
skip cost lookup; normalize the provider string first (e.g., map or strip
"-native" to "openai") before the if-check that uses KNOWN_PROVIDERS and pass
the normalized provider into estimate_model_cost (call sites: the if using
input_tokens/output_tokens and the estimate_model_cost invocation). Update any
local variable (e.g., create normalized_provider) and use that variable in both
the membership check and the provider argument to estimate_model_cost.
- Line 65: The function _llm_modality_case currently has an untyped parameter
`call`; add a proper type annotation that accepts either the LlmCall ORM class
or an aliased variant (e.g., typing.Union[LlmCall, sa.orm.util.AliasedClass] or
the project's preferred alias type) so callers using LlmCall and
aliased(LlmCall) type-check correctly; update the signature to annotate `call`
accordingly and run type checks to ensure compatibility with code that passes
aliased(LlmCall).

In `@backend/app/tests/api/routes/test_analytics.py`:
- Around line 136-140: Annotate the _ok helper with explicit type hints: add a
parameter annotation (e.g., response: Response) and a return annotation (e.g.,
-> Dict[str, Any] or -> Any) and import the referenced types; specifically
update the _ok function in test_analytics.py (function name _ok) to accept
response: Response and return a typed dict (or Any) and add the needed imports
(from requests import Response or from typing import Any, Dict) at the top of
the file.
🪄 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

Run ID: 65fd5fe5-1f4e-4d94-a2ca-47bdb6064d84

📥 Commits

Reviewing files that changed from the base of the PR and between 597dfc9 and 07059e6.

📒 Files selected for processing (10)
  • backend/app/api/docs/analytics/monthly.md
  • backend/app/api/docs/analytics/monthly_chart.md
  • backend/app/api/main.py
  • backend/app/api/routes/analytics.py
  • backend/app/crud/model_config.py
  • backend/app/models/__init__.py
  • backend/app/models/analytics.py
  • backend/app/services/analytics/__init__.py
  • backend/app/services/analytics/aggregation.py
  • backend/app/tests/api/routes/test_analytics.py

Comment thread backend/app/api/docs/analytics/monthly_chart.md
Comment thread backend/app/api/docs/analytics/monthly.md
Comment thread backend/app/api/routes/analytics.py
Comment thread backend/app/api/routes/analytics.py
Comment thread backend/app/services/analytics/aggregation.py
Comment thread backend/app/services/analytics/aggregation.py
Comment thread backend/app/services/analytics/aggregation.py
Comment thread backend/app/services/analytics/aggregation.py
Comment thread backend/app/tests/api/routes/test_analytics.py
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

OpenAPI changes   🟢 2 non-breaking changes

Tip

Safe to merge from an API-contract perspective.

Full changelog  ·  2
Method Path Change
🟢 GET /api/v1/analytics/monthly endpoint added
🟢 GET /api/v1/analytics/monthly/chart endpoint added

main257af80a · generated by oasdiff

@Ayush8923 Ayush8923 merged commit 901517d into main Jun 3, 2026
4 of 5 checks passed
@Ayush8923 Ayush8923 deleted the feat/analytics-dashboard-api branch June 3, 2026 05:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Analytics: APIs for Analytics Dashboard

3 participants